diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst --- a/llvm/docs/CommandGuide/llvm-objdump.rst +++ b/llvm/docs/CommandGuide/llvm-objdump.rst @@ -25,6 +25,10 @@ Display the information contained within an archive's headers. +.. option:: --color + + Enable syntax highlighting. + .. option:: -d, --disassemble Disassemble all text sections found in the input files. diff --git a/llvm/include/llvm/Support/WithColor.h b/llvm/include/llvm/Support/WithColor.h --- a/llvm/include/llvm/Support/WithColor.h +++ b/llvm/include/llvm/Support/WithColor.h @@ -94,6 +94,8 @@ static raw_ostream &remark(raw_ostream &OS, StringRef Prefix = "", bool DisableColors = false); + /// Determine whether colors are displayed in the given stream. + static bool colorsEnabled(raw_ostream &OS); /// Determine whether colors are displayed. bool colorsEnabled(); diff --git a/llvm/lib/Support/WithColor.cpp b/llvm/lib/Support/WithColor.cpp --- a/llvm/lib/Support/WithColor.cpp +++ b/llvm/lib/Support/WithColor.cpp @@ -96,14 +96,18 @@ << "remark: "; } -bool WithColor::colorsEnabled() { - if (DisableColors) - return false; +bool WithColor::colorsEnabled(raw_ostream &OS) { if (UseColor == cl::BOU_UNSET) return OS.has_colors(); return UseColor == cl::BOU_TRUE; } +bool WithColor::colorsEnabled() { + if (DisableColors) + return false; + return colorsEnabled(OS); +} + WithColor &WithColor::changeColor(raw_ostream::Colors Color, bool Bold, bool BG) { if (colorsEnabled()) diff --git a/llvm/test/tools/llvm-objdump/X86/disassembly-highlighting.test b/llvm/test/tools/llvm-objdump/X86/disassembly-highlighting.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/X86/disassembly-highlighting.test @@ -0,0 +1,49 @@ +## Show that when disassembling, llvm-objdump highlights symbol names, +## registers, and immediate values if colors are enabled. + +## This test expects ANSI escape sequences. +# UNSUPPORTED: system-windows +# REQUIRES: shell + +# RUN: yaml2obj %s > %t +# RUN: llvm-objdump -d --no-show-raw-insn --color=false %t | FileCheck %s --check-prefix=DISABLED +# RUN: llvm-objdump -d --no-show-raw-insn --color %t | tr -d '\033' \ +# RUN: | FileCheck %s --check-prefix=ENABLED + +# DISABLED: 301: movl 249(%rip), %edx +# ENABLED: 301: movl 249([0;36m%rip[0m)[0m, [0;36m%edx[0m +# DISABLED-NEXT: 307: callq -12 +# ENABLED-NEXT: 307: callq -12[0;1;33m [0m +# DISABLED-NEXT: 30c: movl $0, %eax +# ENABLED-NEXT: 30c: movl [0;31m$0[0m, [0;36m%eax[0m + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [SHF_ALLOC, SHF_EXECINSTR] + Address: 0x300 + Content: 'c38b15f9000000e8f4ffffffb800000000c3' + - Name: .data + Type: SHT_PROGBITS + Flags: [SHF_WRITE, SHF_ALLOC] + Address: 0x400 + Content: '00000000' +Symbols: + - Name: foo + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x0000000000000300 + Size: 0x0000000000000001 + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x0000000000000301 + Size: 0x0000000000000011 diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -342,6 +342,116 @@ typedef std::vector> SectionSymbolsTy; +static bool ColorsEnabled; + +// Token types in the disassembly. When adding a new element, don't forget to +// also update HighlightColors. +enum class ObjdumpColor { + Default, + Immediate, + Register, + SymbolName, + Memory, +}; + +// TODO: Make this configurable (just like $LS_COLORS). +static const std::map> + HighlightColors = { + // {ObjdumpColor, { raw_ostream::Colors, Bold }} + {ObjdumpColor::Default, {raw_ostream::SAVEDCOLOR, false}}, + {ObjdumpColor::Immediate, {raw_ostream::RED, false}}, + {ObjdumpColor::Register, {raw_ostream::CYAN, false}}, + {ObjdumpColor::SymbolName, {raw_ostream::YELLOW, true}}, + {ObjdumpColor::Memory, {raw_ostream::SAVEDCOLOR, false}}, +}; + +/// a context for WithNestedColor which holds saved colors. +class WithNestedColorContext { + std::stack> NestedColors; + +public: + void pushColor(raw_ostream::Colors Color, bool Bold) { + NestedColors.emplace(Color, Bold); + } + + void popColor() { NestedColors.pop(); } + + bool empty() { return NestedColors.empty(); } + + const std::pair ¤tColor() { + return NestedColors.top(); + } +}; + +/// A RAII object which temporarily changes the color in a specific stream just +/// like WithColor, but supports restoring the previous color in the given +/// context. +/// +/// Usage: +/// +/// WithNestedColorContext Ctx; +/// WithNestedColor WNC1(outs(), Ctx, raw_ostream::RED, ...); +/// { +/// WithNestedColor WNC2(outs(), Ctx, raw_ostream::BLUE, ...); +/// outs() << "A blue-colored text."; +/// // WNC2 is destructed and WNC1's color is restored here. +/// } +/// outs() << "A red-colored text."; +/// +class WithNestedColor { + raw_ostream &OS; + WithNestedColorContext &Ctx; + bool DisableColors; + +public: + WithNestedColor(raw_ostream &OS, WithNestedColorContext &Ctx, + raw_ostream::Colors Color, bool Bold, bool DisableColors) + : OS(OS), Ctx(Ctx), DisableColors(DisableColors) { + if (DisableColors) + return; + + Ctx.pushColor(Color, Bold); + if (Color != raw_ostream::SAVEDCOLOR || Bold) + OS.changeColor(Color, Bold); + } + + template WithNestedColor &operator<<(const T &O) { + OS << O; + return *this; + } + + ~WithNestedColor() { + if (DisableColors) + return; + + OS.resetColor(); + Ctx.popColor(); + // Restore the previous color if it exists. + if (!Ctx.empty()) { + std::pair Pair = Ctx.currentColor(); + if (Pair.first != raw_ostream::SAVEDCOLOR || Pair.second) + OS.changeColor(Pair.first, Pair.second); + } + } +}; + +static WithColor withHighlightColor(ObjdumpColor Color) { + auto Pair = HighlightColors.find(Color); + assert(Pair != HighlightColors.end() && + "All token types must be defined in HighlightColors."); + return WithColor(outs(), Pair->second.first, Pair->second.second, false, + !ColorsEnabled); +} + +static WithNestedColor withNestedHighlightColor(WithNestedColorContext &Context, + ObjdumpColor Color) { + auto Pair = HighlightColors.find(Color); + assert(Pair != HighlightColors.end() && + "All token types must be defined in HighlightColors."); + return WithNestedColor(outs(), Context, Pair->second.first, + Pair->second.second, !ColorsEnabled); +} + static bool shouldKeep(object::SectionRef S) { if (FilterSections.empty()) return true; @@ -691,7 +801,9 @@ object::SectionedAddress Address, raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, + size_t &InstructionCol, std::vector *Rels = nullptr) { + uint64_t BeforeOffset = OS.tell(); if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename); @@ -709,6 +821,7 @@ unsigned Column = OS.tell() - Start; OS.indent(Column < TabStop - 1 ? TabStop - 1 - Column : 7 - Column % 8); + InstructionCol = OS.tell() - BeforeOffset; if (MI) IP.printInst(MI, OS, "", STI); else @@ -734,8 +847,9 @@ void printInst(MCInstPrinter &IP, const MCInst *MI, ArrayRef Bytes, object::SectionedAddress Address, raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, - StringRef ObjectFilename, + StringRef ObjectFilename, size_t &InstructionCol, std::vector *Rels) override { + size_t StartOffset = OS.tell(); if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, ""); if (!MI) { @@ -743,6 +857,8 @@ OS << " "; return; } + + InstructionCol = OS.tell() - StartOffset; std::string Buffer; { raw_string_ostream TempStream(Buffer); @@ -804,8 +920,9 @@ void printInst(MCInstPrinter &IP, const MCInst *MI, ArrayRef Bytes, object::SectionedAddress Address, raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, - StringRef ObjectFilename, + StringRef ObjectFilename, size_t &InstructionCol, std::vector *Rels) override { + size_t StartOffset = OS.tell(); if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename); @@ -813,6 +930,7 @@ SmallString<40> InstStr; raw_svector_ostream IS(InstStr); + InstructionCol = OS.tell() - StartOffset; IP.printInst(MI, IS, "", STI); OS << left_justify(IS.str(), 60); @@ -856,8 +974,9 @@ void printInst(MCInstPrinter &IP, const MCInst *MI, ArrayRef Bytes, object::SectionedAddress Address, raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, - StringRef ObjectFilename, + StringRef ObjectFilename, size_t &InstructionCol, std::vector *Rels) override { + size_t StartOffset = OS.tell(); if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename); if (!NoLeadingAddr) @@ -866,6 +985,7 @@ OS << "\t"; dumpBytes(Bytes, OS); } + InstructionCol = OS.tell() - StartOffset; if (MI) IP.printInst(MI, OS, "", STI); else @@ -1103,14 +1223,68 @@ } } +static void printMarkupSpans(StringRef Text, ArrayRef Spans, + size_t &NextPos, size_t InstOffset, + WithNestedColorContext Context) { + for (const MarkupSpan &Span : Spans) { + size_t Pos = Span.Pos - InstOffset; + size_t InnerPos = Span.InnerPos - InstOffset; + outs() << Text.substr(NextPos, Pos - NextPos); + + ObjdumpColor Color; + if (Span.Type == MarkupType::Imm) + Color = ObjdumpColor::Immediate; + else if (Span.Type == MarkupType::Reg) + Color = ObjdumpColor::Register; + else if (Span.Type == MarkupType::Mem) + Color = ObjdumpColor::Memory; + else + Color = ObjdumpColor::Default; + + if (Span.InnerSpans.empty()) { + withNestedHighlightColor(Context, Color) + << Text.substr(InnerPos, Span.InnerLength); + } else { + WithNestedColor WNC = withNestedHighlightColor(Context, Color); + NextPos = InnerPos; + printMarkupSpans(Text, Span.InnerSpans, NextPos, InstOffset, Context); + StringRef AfterText = + Text.substr(NextPos, Span.InnerLength - (NextPos - InnerPos)); + outs() << AfterText; + } + + NextPos = Pos + Span.Length; + } +} + +static void printMarkedUpInst(StringRef Text, + const std::vector &Spans, + size_t InstructionCol, size_t InstOffset) { + if (!ColorsEnabled) { + outs() << Text; + return; + } + + // Print hexdump, etc. + outs() << Text.slice(0, InstructionCol); + + StringRef Disasm = Text.slice(InstructionCol, Text.size()); + size_t NextPos = 0; + WithNestedColorContext Context; + printMarkupSpans(Disasm, Spans, NextPos, InstOffset, Context); + + // Print the remaining part. + outs() << Disasm.substr(NextPos); +} + static void disassembleObject(const Target *TheTarget, const ObjectFile *Obj, MCContext &Ctx, MCDisassembler *PrimaryDisAsm, MCDisassembler *SecondaryDisAsm, const MCInstrAnalysis *MIA, MCInstPrinter *IP, const MCSubtargetInfo *PrimarySTI, const MCSubtargetInfo *SecondarySTI, - PrettyPrinter &PIP, - SourcePrinter &SP, bool InlineRelocs) { + PrettyPrinter &PIP, SourcePrinter &SP, + bool InlineRelocs) { const MCSubtargetInfo *STI = PrimarySTI; MCDisassembler *DisAsm = PrimaryDisAsm; bool PrimaryIsThumb = false; @@ -1251,6 +1425,9 @@ SmallString<40> Comments; raw_svector_ostream CommentStream(Comments); + std::vector MarkupSpans; + IP->setMarkupSpans(MarkupSpans); + ArrayRef Bytes = arrayRefFromStringRef( unwrapOrError(Section.getContents(), Obj->getFileName())); @@ -1408,12 +1585,20 @@ if (Size == 0) Size = 1; + std::string InstText; + raw_string_ostream InstTextStream(InstText); + size_t InstructionCol; + size_t StartOffset = InstTextStream.tell(); PIP.printInst(*IP, Disassembled ? &Inst : nullptr, Bytes.slice(Index, Size), {SectionAddr + Index + VMAAdjustment, Section.getIndex()}, - outs(), "", *STI, &SP, Obj->getFileName(), &Rels); + InstTextStream, "", *STI, &SP, Obj->getFileName(), + InstructionCol, &Rels); + printMarkedUpInst(StringRef(InstTextStream.str()), MarkupSpans, + InstructionCol, StartOffset + InstructionCol); outs() << CommentStream.str(); Comments.clear(); + MarkupSpans.clear(); // Try to resolve the target of a call, tail call, etc. to a specific // symbol. @@ -1463,11 +1648,13 @@ --TargetSym; uint64_t TargetAddress = std::get<0>(*TargetSym); StringRef TargetName = std::get<1>(*TargetSym); - outs() << " <" << TargetName; + WithColor Highlight = + withHighlightColor(ObjdumpColor::SymbolName); + Highlight << " <" << TargetName; uint64_t Disp = Target - TargetAddress; if (Disp) - outs() << "+0x" << Twine::utohexstr(Disp); - outs() << '>'; + Highlight << "+0x" << Twine::utohexstr(Disp); + Highlight << '>'; } } } @@ -1579,6 +1766,7 @@ report_error(Obj->getFileName(), "no instruction printer for target " + TripleName); IP->setPrintImmHex(PrintImmHex); + IP->setUseMarkup(ColorsEnabled); PrettyPrinter &PIP = selectPrettyPrinter(Triple(TripleName)); SourcePrinter SP(Obj, TheTarget->getName()); @@ -2207,7 +2395,8 @@ int main(int argc, char **argv) { using namespace llvm; InitLLVM X(argc, argv); - const cl::OptionCategory *OptionFilters[] = {&ObjdumpCat, &MachOCat}; + const cl::OptionCategory *OptionFilters[] = {&ObjdumpCat, &MachOCat, + &ColorCategory}; cl::HideUnrelatedOptions(OptionFilters); // Initialize targets and assembly printers/parsers. @@ -2223,6 +2412,8 @@ if (StartAddress >= StopAddress) error("start address should be less than stop address"); + ColorsEnabled = WithColor::colorsEnabled(outs()); + ToolName = argv[0]; // Defaults to a.out if no filenames specified.