diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst index b156b212e461f2..db29f6cf970b27 100644 --- a/llvm/docs/CommandGuide/llvm-objdump.rst +++ b/llvm/docs/CommandGuide/llvm-objdump.rst @@ -291,6 +291,36 @@ OPTIONS Target triple to disassemble for, see ``--version`` for available targets. +.. option:: --visualize-jumps= + + Display a control-flow graph which shows the targets of branch instructions to the left of disasembly. ``modes`` is a comma-separated list of options, which configure the character set and used to print the graph. + + If ``modes`` is omitted, the default is ``unicode,auto`` + + .. option:: off + + Disable control-flow graph + + .. option:: ascii + + Render control-flow graph using ASCII characters + + .. option:: unicode + + Render control-flow graph using unicode box-drawing characters + + .. option:: nocolor + + Render control-flow graph without using colors + + .. option:: auto + + Render control-flow graph using colors if supported by the terminal + + .. option:: color + + Render control-flow graph using colors + .. option:: -w, --wide Ignored for compatibility with GNU objdump. diff --git a/llvm/lib/Support/FormattedStream.cpp b/llvm/lib/Support/FormattedStream.cpp index c0d28435099570..48b02889c6d3e5 100644 --- a/llvm/lib/Support/FormattedStream.cpp +++ b/llvm/lib/Support/FormattedStream.cpp @@ -45,6 +45,7 @@ void formatted_raw_ostream::UpdatePosition(const char *Ptr, size_t Size) { break; case '\t': // Assumes tab stop = 8 characters. + Column += 1; Column += (8 - (Column & 0x7)) & 0x7; break; } diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s b/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s new file mode 100644 index 00000000000000..633f2d2918d490 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s @@ -0,0 +1,204 @@ +# RUN: rm -rf %t && split-file %s %t && cd %t + +// RUN: llvm-mc < input.s -triple aarch64 -filetype=obj | \ +// RUN: llvm-objdump --triple aarch64 -d --visualize-jumps=unicode - | \ +// RUN: diff - expected-unicode.txt + +// RUN: llvm-mc < input.s -triple aarch64 -filetype=obj | \ +// RUN: llvm-objdump --triple aarch64 -d --visualize-jumps=ascii - | \ +// RUN: diff - expected-ascii.txt + +// RUN: llvm-mc < input.s -triple aarch64 -filetype=obj | \ +// RUN: llvm-objdump --triple aarch64 -d --visualize-jumps=unicode,color - | \ +// RUN: diff - expected-unicode-color.txt + +// RUN: llvm-mc < input.s -triple aarch64 -filetype=obj | \ +// RUN: llvm-objdump --triple aarch64 -d --visualize-jumps=unicode --reloc - | \ +// RUN: diff - expected-unicode-relocs.txt + +//--- input.s +test_func: + // Relocated instructions don't get control-flow edges. + bl extern_func + b extern_func + + // Two branches to the same label, one forward and one backward. + b .Llabel1 +.Llabel1: + nop + b .Llabel1 + + // Branch to self, no CFG edge shown + b . + + // Conditional branches + b.eq .Llabel2 + cbz x0, .Llabel2 +.Llabel2: + nop + + // Branches are sorted with shorter ones to the right, to reduce number of + // crossings, and keep the lines for short branches short themselves. + b .Llabel5 + b .Llabel4 + b .Llabel3 +.Llabel3: + nop +.Llabel4: + nop +.Llabel5: + nop + + // Sometimes crossings can't be avoided. + b .Llabel6 + b .Llabel7 +.Llabel6: + nop +.Llabel7: + nop + + // TODO If a branch goes to another branch instruction, we don't have a way + // to represent that. Can we improve on this? + b .Llabel8 +.Llabel8: + b .Llabel9 +.Llabel9: + nop + + // Graph lines need to be drawn on the same output line as relocations. + b .Llabel10 + bl extern_func +.Llabel10: + nop + +//--- expected-unicode.txt + +: file format elf64-littleaarch64 + +Disassembly of section .text: + +0000000000000000 : + 0: 94000000 bl 0x0 + 4: 14000000 b 0x4 + 8: ╭── 14000001 b 0xc + c: ├─> d503201f nop + 10: ╰── 17ffffff b 0xc + 14: 14000000 b 0x14 + 18: ╭── 54000040 b.eq 0x20 + 1c: ├── b4000020 cbz x0, 0x20 + 20: ╰─> d503201f nop + 24: ╭────── 14000005 b 0x38 + 28: │ ╭──── 14000003 b 0x34 + 2c: │ │ ╭── 14000001 b 0x30 + 30: │ │ ╰─> d503201f nop + 34: │ ╰───> d503201f nop + 38: ╰─────> d503201f nop + 3c: ╭── 14000002 b 0x44 + 40: ╭─│── 14000002 b 0x48 + 44: │ ╰─> d503201f nop + 48: ╰───> d503201f nop + 4c: ╭── 14000001 b 0x50 + 50: ╭─│── 14000001 b 0x54 + 54: ╰───> d503201f nop + 58: ╭── 14000002 b 0x60 + 5c: │ 94000000 bl 0x5c + 60: ╰─> d503201f nop +//--- expected-ascii.txt + +: file format elf64-littleaarch64 + +Disassembly of section .text: + +0000000000000000 : + 0: 94000000 bl 0x0 + 4: 14000000 b 0x4 + 8: /-- 14000001 b 0xc + c: +-> d503201f nop + 10: \-- 17ffffff b 0xc + 14: 14000000 b 0x14 + 18: /-- 54000040 b.eq 0x20 + 1c: +-- b4000020 cbz x0, 0x20 + 20: \-> d503201f nop + 24: /------ 14000005 b 0x38 + 28: | /---- 14000003 b 0x34 + 2c: | | /-- 14000001 b 0x30 + 30: | | \-> d503201f nop + 34: | \---> d503201f nop + 38: \-----> d503201f nop + 3c: /-- 14000002 b 0x44 + 40: /-|-- 14000002 b 0x48 + 44: | \-> d503201f nop + 48: \---> d503201f nop + 4c: /-- 14000001 b 0x50 + 50: /-|-- 14000001 b 0x54 + 54: \---> d503201f nop + 58: /-- 14000002 b 0x60 + 5c: | 94000000 bl 0x5c + 60: \-> d503201f nop +//--- expected-unicode-color.txt + +: file format elf64-littleaarch64 + +Disassembly of section .text: + +0000000000000000 : + 0:  94000000 bl 0x0 + 4:  14000000 b 0x4 + 8:  ╭── 14000001 b 0xc + c:  ├─> d503201f nop + 10:  ╰── 17ffffff b 0xc + 14:  14000000 b 0x14 + 18:  ╭── 54000040 b.eq 0x20 + 1c:  ├── b4000020 cbz x0, 0x20 + 20:  ╰─> d503201f nop + 24:  ╭────── 14000005 b 0x38 + 28:  │ ╭──── 14000003 b 0x34 + 2c:  │ │ ╭── 14000001 b 0x30 + 30:  │ │ ╰─> d503201f nop + 34:  │ ╰───> d503201f nop + 38:  ╰─────> d503201f nop + 3c:  ╭── 14000002 b 0x44 + 40:  ╭─│── 14000002 b 0x48 + 44:  │ ╰─> d503201f nop + 48:  ╰───> d503201f nop + 4c:  ╭── 14000001 b 0x50 + 50:  ╭─│── 14000001 b 0x54 + 54:  ╰───> d503201f nop + 58:  ╭── 14000002 b 0x60 + 5c:  │  94000000 bl 0x5c + 60:  ╰─> d503201f nop +//--- expected-unicode-relocs.txt + +: file format elf64-littleaarch64 + +Disassembly of section .text: + +0000000000000000 : + 0: 94000000 bl 0x0 + 0000000000000000: R_AARCH64_CALL26 extern_func + 4: 14000000 b 0x4 + 0000000000000004: R_AARCH64_JUMP26 extern_func + 8: ╭── 14000001 b 0xc + c: ├─> d503201f nop + 10: ╰── 17ffffff b 0xc + 14: 14000000 b 0x14 + 18: ╭── 54000040 b.eq 0x20 + 1c: ├── b4000020 cbz x0, 0x20 + 20: ╰─> d503201f nop + 24: ╭────── 14000005 b 0x38 + 28: │ ╭──── 14000003 b 0x34 + 2c: │ │ ╭── 14000001 b 0x30 + 30: │ │ ╰─> d503201f nop + 34: │ ╰───> d503201f nop + 38: ╰─────> d503201f nop + 3c: ╭── 14000002 b 0x44 + 40: ╭─│── 14000002 b 0x48 + 44: │ ╰─> d503201f nop + 48: ╰───> d503201f nop + 4c: ╭── 14000001 b 0x50 + 50: ╭─│── 14000001 b 0x54 + 54: ╰───> d503201f nop + 58: ╭── 14000002 b 0x60 + 5c: │ 94000000 bl 0x5c + │ 000000000000005c: R_AARCH64_CALL26 extern_func + 60: ╰─> d503201f nop diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s b/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s new file mode 100644 index 00000000000000..886dc0dce50082 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s @@ -0,0 +1,114 @@ +// RUN: rm -rf %t && split-file %s %t && cd %t + +// RUN: llvm-mc < input.s -triple armv8a -filetype=obj | \ +// RUN: llvm-objdump --triple armv8a -d --visualize-jumps=unicode - | \ +// RUN: diff - expected-unicode.txt + +// RUN: llvm-mc < input.s -triple armv8a -filetype=obj | \ +// RUN: llvm-objdump --triple armv8a -d --visualize-jumps=ascii - | \ +// RUN: diff - expected-ascii.txt + +//--- input.s +test_func: + // Relocated instructions don't get control-flow edges. + bl extern_func + b extern_func + + // Two branches to the same label, one forward and one backward. + b .Llabel1 +.Llabel1: + nop + b .Llabel1 + + // Branch to self, no CFG edge shown + b . + + // Conditional branches + beq .Llabel2 +.Llabel2: + nop + + // Branches are sorted with shorter ones to the right, to reduce number of + // crossings, and keep the lines for short branches short themselves. + b .Llabel5 + b .Llabel4 + b .Llabel3 +.Llabel3: + nop +.Llabel4: + nop +.Llabel5: + nop + + // Sometimes crossings can't be avoided. + b .Llabel6 + b .Llabel7 +.Llabel6: + nop +.Llabel7: + nop + + // TODO If a branch goes to another branch instruction, we don't have a way + // to represent that. Can we improve on this? + b .Llabel8 +.Llabel8: + b .Llabel9 +.Llabel9: + nop + +//--- expected-unicode.txt + +: file format elf32-littlearm + +Disassembly of section .text: + +00000000 : + 0: ebfffffe bl 0x0 @ imm = #-0x8 + 4: eafffffe b 0x4 @ imm = #-0x8 + 8: ╭── eaffffff b 0xc @ imm = #-0x4 + c: ├─> e320f000 nop + 10: ╰── eafffffd b 0xc @ imm = #-0xc + 14: eafffffe b 0x14 @ imm = #-0x8 + 18: ╭── 0affffff beq 0x1c @ imm = #-0x4 + 1c: ╰─> e320f000 nop + 20: ╭────── ea000003 b 0x34 @ imm = #0xc + 24: │ ╭──── ea000001 b 0x30 @ imm = #0x4 + 28: │ │ ╭── eaffffff b 0x2c @ imm = #-0x4 + 2c: │ │ ╰─> e320f000 nop + 30: │ ╰───> e320f000 nop + 34: ╰─────> e320f000 nop + 38: ╭── ea000000 b 0x40 @ imm = #0x0 + 3c: ╭─│── ea000000 b 0x44 @ imm = #0x0 + 40: │ ╰─> e320f000 nop + 44: ╰───> e320f000 nop + 48: ╭──── eaffffff b 0x4c @ imm = #-0x4 + 4c: ╰─│─> eaffffff b 0x50 @ imm = #-0x4 + 50: ╰─> e320f000 nop +//--- expected-ascii.txt + +: file format elf32-littlearm + +Disassembly of section .text: + +00000000 : + 0: ebfffffe bl 0x0 @ imm = #-0x8 + 4: eafffffe b 0x4 @ imm = #-0x8 + 8: /-- eaffffff b 0xc @ imm = #-0x4 + c: +-> e320f000 nop + 10: \-- eafffffd b 0xc @ imm = #-0xc + 14: eafffffe b 0x14 @ imm = #-0x8 + 18: /-- 0affffff beq 0x1c @ imm = #-0x4 + 1c: \-> e320f000 nop + 20: /------ ea000003 b 0x34 @ imm = #0xc + 24: | /---- ea000001 b 0x30 @ imm = #0x4 + 28: | | /-- eaffffff b 0x2c @ imm = #-0x4 + 2c: | | \-> e320f000 nop + 30: | \---> e320f000 nop + 34: \-----> e320f000 nop + 38: /-- ea000000 b 0x40 @ imm = #0x0 + 3c: /-|-- ea000000 b 0x44 @ imm = #0x0 + 40: | \-> e320f000 nop + 44: \---> e320f000 nop + 48: /---- eaffffff b 0x4c @ imm = #-0x4 + 4c: \-|-> eaffffff b 0x50 @ imm = #-0x4 + 50: \-> e320f000 nop diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s b/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s new file mode 100644 index 00000000000000..ebe2d8663348e9 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s @@ -0,0 +1,136 @@ +// RUN: rm -rf %t && split-file %s %t && cd %t + +// RUN: llvm-mc < input.s -triple thumbv8a -filetype=obj | \ +// RUN: llvm-objdump --triple thumbv8a -d --visualize-jumps=unicode - | \ +// RUN: diff - expected-unicode.txt + +// RUN: llvm-mc < input.s -triple thumbv8a -filetype=obj | \ +// RUN: llvm-objdump --triple thumbv8a -d --visualize-jumps=ascii - | \ +// RUN: diff - expected-ascii.txt + +//--- input.s +test_func: + // Relocated instructions don't get control-flow edges. + bl extern_func + b extern_func + + // Two branches to the same label, one forward and one backward. + b .Llabel1 +.Llabel1: + nop + b .Llabel1 + + // Branch to self, no CFG edge shown + b . + + // Different branch instructions + b.w .Llabel2 + beq .Llabel2 + bne.w .Llabel2 + cbz r0, .Llabel2 + it le + ble .Llabel2 + nop +.Llabel2: + nop +.Llabel2.1: + nop + + // Branches are sorted with shorter ones to the right, to reduce number of + // crossings, and keep the lines for short branches short themselves. + b .Llabel5 + b .Llabel4 + b .Llabel3 +.Llabel3: + nop +.Llabel4: + nop +.Llabel5: + nop + + // Sometimes crossings can't be avoided. + b .Llabel6 + b .Llabel7 +.Llabel6: + nop +.Llabel7: + nop + + // TODO If a branch goes to another branch instruction, we don't have a way + // to represent that. Can we improve on this? + b .Llabel8 +.Llabel8: + b .Llabel9 +.Llabel9: + nop + +//--- expected-unicode.txt + +: file format elf32-littlearm + +Disassembly of section .text: + +00000000 : + 0: f7ff fffe bl 0x0 @ imm = #-0x4 + 4: f7ff bffe b.w 0x4 @ imm = #-0x4 + 8: ╭── e7ff b 0xa @ imm = #-0x2 + a: ├─> bf00 nop + c: ╰── e7fd b 0xa @ imm = #-0x6 + e: e7fe b 0xe @ imm = #-0x4 + 10: ╭── f000 b807 b.w 0x22 @ imm = #0xe + 14: ├── d005 beq 0x22 @ imm = #0xa + 16: ├── f040 8004 bne.w 0x22 @ imm = #0x8 + 1a: ├── b110 cbz r0, 0x22 @ imm = #0x4 + 1c: │ bfd8 it le + 1e: ├── e000 ble 0x22 @ imm = #0x0 + 20: │ bf00 nop + 22: ╰─> bf00 nop + 24: bf00 nop + 26: ╭────── e003 b 0x30 @ imm = #0x6 + 28: │ ╭──── e001 b 0x2e @ imm = #0x2 + 2a: │ │ ╭── e7ff b 0x2c @ imm = #-0x2 + 2c: │ │ ╰─> bf00 nop + 2e: │ ╰───> bf00 nop + 30: ╰─────> bf00 nop + 32: ╭── e000 b 0x36 @ imm = #0x0 + 34: ╭─│── e000 b 0x38 @ imm = #0x0 + 36: │ ╰─> bf00 nop + 38: ╰───> bf00 nop + 3a: ╭── e7ff b 0x3c @ imm = #-0x2 + 3c: ╭─│── e7ff b 0x3e @ imm = #-0x2 + 3e: ╰───> bf00 nop +//--- expected-ascii.txt + +: file format elf32-littlearm + +Disassembly of section .text: + +00000000 : + 0: f7ff fffe bl 0x0 @ imm = #-0x4 + 4: f7ff bffe b.w 0x4 @ imm = #-0x4 + 8: /-- e7ff b 0xa @ imm = #-0x2 + a: +-> bf00 nop + c: \-- e7fd b 0xa @ imm = #-0x6 + e: e7fe b 0xe @ imm = #-0x4 + 10: /-- f000 b807 b.w 0x22 @ imm = #0xe + 14: +-- d005 beq 0x22 @ imm = #0xa + 16: +-- f040 8004 bne.w 0x22 @ imm = #0x8 + 1a: +-- b110 cbz r0, 0x22 @ imm = #0x4 + 1c: | bfd8 it le + 1e: +-- e000 ble 0x22 @ imm = #0x0 + 20: | bf00 nop + 22: \-> bf00 nop + 24: bf00 nop + 26: /------ e003 b 0x30 @ imm = #0x6 + 28: | /---- e001 b 0x2e @ imm = #0x2 + 2a: | | /-- e7ff b 0x2c @ imm = #-0x2 + 2c: | | \-> bf00 nop + 2e: | \---> bf00 nop + 30: \-----> bf00 nop + 32: /-- e000 b 0x36 @ imm = #0x0 + 34: /-|-- e000 b 0x38 @ imm = #0x0 + 36: | \-> bf00 nop + 38: \---> bf00 nop + 3a: /-- e7ff b 0x3c @ imm = #-0x2 + 3c: /-|-- e7ff b 0x3e @ imm = #-0x2 + 3e: \---> bf00 nop diff --git a/llvm/tools/llvm-objdump/ObjdumpOpts.td b/llvm/tools/llvm-objdump/ObjdumpOpts.td index 100a95d3d92542..9a43cdaedd4626 100644 --- a/llvm/tools/llvm-objdump/ObjdumpOpts.td +++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td @@ -92,6 +92,13 @@ def disassembler_color_EQ : Joined<["--"], "disassembler-color=">, HelpText<"Enable or disable disassembler color output. " "Valid options are \"on\", \"off\" and \"terminal\" (default)">; +def visualize_jumps : Flag<["--"], "visualize-jumps">; +def visualize_jumps_EQ : Joined<["--"], "visualize-jumps=">, + MetaVarName<"mode,...">, + HelpText<"Print a control flow graph along side disassembly. " + "Color modes: auto (default), nocolor, color. " + "Character modes: unicode (default), ascii.">; + def dynamic_reloc : Flag<["--"], "dynamic-reloc">, HelpText<"Display the dynamic relocation entries in the file">; def : Flag<["-"], "R">, Alias, diff --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp index 76da86587586ee..7965880be7c0a8 100644 --- a/llvm/tools/llvm-objdump/SourcePrinter.cpp +++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp @@ -95,7 +95,7 @@ void LiveVariablePrinter::addFunction(DWARFDie D) { // Get the column number (in characters) at which the first live variable // line should be printed. unsigned LiveVariablePrinter::getIndentLevel() const { - return DbgIndent + getInstStartColumn(STI); + return GetColumnIndent(STI, DisassemblyColumn::Variables); } // Indent to the first live-range column to the right of the currently @@ -211,19 +211,19 @@ const char *LiveVariablePrinter::getLineChar(LineChar C) const { bool IsASCII = DbgVariables == DVASCII; switch (C) { case LineChar::RangeStart: - return IsASCII ? "^" : (const char *)u8"\u2548"; + return IsASCII ? "^" : u8"\u2548"; case LineChar::RangeMid: - return IsASCII ? "|" : (const char *)u8"\u2503"; + return IsASCII ? "|" : u8"\u2503"; case LineChar::RangeEnd: - return IsASCII ? "v" : (const char *)u8"\u253b"; + return IsASCII ? "v" : u8"\u253b"; case LineChar::LabelVert: - return IsASCII ? "|" : (const char *)u8"\u2502"; + return IsASCII ? "|" : u8"\u2502"; case LineChar::LabelCornerNew: - return IsASCII ? "/" : (const char *)u8"\u250c"; + return IsASCII ? "/" : u8"\u250c"; case LineChar::LabelCornerActive: - return IsASCII ? "|" : (const char *)u8"\u2520"; + return IsASCII ? "|" : u8"\u2520"; case LineChar::LabelHoriz: - return IsASCII ? "-" : (const char *)u8"\u2500"; + return IsASCII ? "-" : u8"\u2500"; } llvm_unreachable("Unhandled LineChar enum"); } @@ -262,12 +262,15 @@ void LiveVariablePrinter::printAfterOtherLine(formatted_raw_ostream &OS, /// already-active live ranges) because something has already been printed /// earlier on this line. void LiveVariablePrinter::printBetweenInsts(formatted_raw_ostream &OS, - bool MustPrint) { + bool MustPrint, uint64_t Addr, + ControlFlowPrinter *CFP) { bool PrintedSomething = false; for (unsigned ColIdx = 0, End = ActiveCols.size(); ColIdx < End; ++ColIdx) { if (ActiveCols[ColIdx].isActive() && ActiveCols[ColIdx].MustDrawLabel) { // First we need to print the live range markers for any active // columns to the left of this one. + if (CFP) + CFP->printOther(OS, Addr); OS.PadToColumn(getIndentLevel()); for (unsigned ColIdx2 = 0; ColIdx2 < ColIdx; ++ColIdx2) { if (ActiveCols[ColIdx2].isActive()) { @@ -503,5 +506,188 @@ SourcePrinter::SourcePrinter(const object::ObjectFile *Obj, Symbolizer.reset(new symbolize::LLVMSymbolizer(SymbolizerOpts)); } +// TODO Light/dark shades? 256-color terminals? +const raw_ostream::Colors LineColors[] = { + raw_ostream::RED, raw_ostream::GREEN, raw_ostream::YELLOW, + raw_ostream::BLUE, raw_ostream::MAGENTA, raw_ostream::CYAN, +}; + +raw_ostream::Colors ControlFlowPrinter::PickColor() { + if (!OutputMode.color_enabled()) + return raw_ostream::RESET; + auto Ret = LineColors[NextColorIdx]; + NextColorIdx = + (NextColorIdx + 1) % (sizeof(LineColors) / sizeof(LineColors[0])); + return Ret; +} + +void ControlFlowPrinter::addEdge(uint64_t From, uint64_t To) { + auto It = Targets.find(To); + if (It == Targets.end()) + It = Targets.insert(std::make_pair(To, ControlFlowTarget(To, PickColor()))) + .first; + It->second.addSource(From); +} + +void ControlFlowPrinter::finalise() { + if (!OutputMode.enabled()) { + setControlFlowColumnWidth(0); + return; + } + + SmallVector SortedTargets; + for (auto &[Addr, Info] : Targets) { + SortedTargets.push_back(&Info); + } + std::sort(SortedTargets.begin(), SortedTargets.end(), + [](ControlFlowTarget *LHS, ControlFlowTarget *RHS) { + return LHS->Length() < RHS->Length(); + }); + + // FIXME This is O(n^3) in the worst case, can we do better? + for (auto &T : SortedTargets) { + int Column = 0; + for (;; ++Column) + if (!std::any_of(SortedTargets.begin(), SortedTargets.end(), + [T, Column](ControlFlowTarget *T2) { + return T != T2 && T2->Column == Column && + T->Overlaps(*T2); + })) + break; + T->Column = Column; + MaxColumn = std::max(MaxColumn, Column); + } + + setControlFlowColumnWidth(MaxColumn * 2 + 4); +} + +const char *ControlFlowPrinter::getLineChar(LineChar C) const { + bool IsASCII = !OutputMode.unicode_enabled(); + switch (C) { + case LineChar::Horiz: + return IsASCII ? "-" : u8"\u2500"; + case LineChar::Vert: + return IsASCII ? "|" : u8"\u2502"; + case LineChar::TopCorner: + return IsASCII ? "/" : u8"\u256d"; + case LineChar::BottomCorner: + return IsASCII ? "\\" : u8"\u2570"; + case LineChar::Tee: + return IsASCII ? "+" : u8"\u251c"; + case LineChar::Arrow: + return ">"; + } + llvm_unreachable("Unhandled LineChar enum"); +} + +#define C(id) getLineChar(LineChar::id) + +void ControlFlowPrinter::printInst(formatted_raw_ostream &OS, + uint64_t Addr) const { + if (!OutputMode.enabled()) + return; + + SmallVector Columns; + Columns.resize(MaxColumn + 1); + const ControlFlowTarget *Horizontal = nullptr; + + IndentToColumn(STI, OS, DisassemblyColumn::ControlFlow); + + // TODO: What happens if an instruction has both incoming and outgoing edges? + + for (auto &[_, Info] : Targets) { + if (Info.ActiveAt(Addr)) { + assert(Columns[Info.Column] == nullptr); + Columns[Info.Column] = &Info; + } + } + + auto Color = [&](raw_ostream &OS, + raw_ostream::Colors Color) -> raw_ostream & { + if (OutputMode.color_enabled()) { + OS << Color; + } + return OS; + }; + + OS.PadToColumn(getIndentLevel()); + for (int ColIdx = MaxColumn; ColIdx >= 0; --ColIdx) { + if (Horizontal) { + if (Columns[ColIdx]) { + // Outgoing horizontal lines are drawn "under" vertical line, because + // that minimises the gap in the "lower" line. + Color(OS, Horizontal->Color) << C(Horiz); + Color(OS, Columns[ColIdx]->Color) << C(Vert); + } else + Color(OS, Horizontal->Color) << C(Horiz) << C(Horiz); + } else if (!Columns[ColIdx]) + OS << " "; + else if (Columns[ColIdx]->HorizontalAt(Addr)) { + Horizontal = Columns[ColIdx]; + if (Columns[ColIdx]->StartsAt(Addr)) + Color(OS, Horizontal->Color) << " " << C(TopCorner); + else if (Columns[ColIdx]->EndsAt(Addr)) + Color(OS, Horizontal->Color) << " " << C(BottomCorner); + else + Color(OS, Horizontal->Color) << " " << C(Tee); + } else if (Columns[ColIdx]->ActiveAt(Addr)) + Color(OS, Columns[ColIdx]->Color) << " " << C(Vert); + else + OS << " "; + } + + if (Horizontal) { + if (Horizontal->TargetAt(Addr)) + Color(OS, Horizontal->Color) << C(Horiz) << C(Arrow); + else + Color(OS, Horizontal->Color) << C(Horiz) << C(Horiz); + } else { + OS << " "; + } + + Color(OS, raw_ostream::RESET); +} + +// TODO boolean params -> enum? +void ControlFlowPrinter::printOther(formatted_raw_ostream &OS, uint64_t Addr, + bool BeforeInst, bool AfterInst) const { + if (!OutputMode.enabled()) + return; + + assert(!(BeforeInst && AfterInst)); + + SmallVector Columns; + Columns.resize(MaxColumn + 1); + + auto Color = [&](raw_ostream &OS, + raw_ostream::Colors Color) -> raw_ostream & { + if (OutputMode.color_enabled()) { + OS << Color; + } + return OS; + }; + + IndentToColumn(STI, OS, DisassemblyColumn::ControlFlow); + + for (auto &[_, Info] : Targets) { + if (Info.ActiveAt(Addr, BeforeInst, AfterInst)) { + assert(Columns[Info.Column] == nullptr); + Columns[Info.Column] = &Info; + } + } + + OS.PadToColumn(getIndentLevel()); + for (int ColIdx = MaxColumn; ColIdx >= 0; --ColIdx) { + if (!Columns[ColIdx]) + OS << " "; + else + Color(OS, Columns[ColIdx]->Color) << " " << C(Vert); + } + + Color(OS, raw_ostream::RESET) << " "; +} + +#undef C + } // namespace objdump } // namespace llvm diff --git a/llvm/tools/llvm-objdump/SourcePrinter.h b/llvm/tools/llvm-objdump/SourcePrinter.h index fc67fc65074441..450d3f46f49e00 100644 --- a/llvm/tools/llvm-objdump/SourcePrinter.h +++ b/llvm/tools/llvm-objdump/SourcePrinter.h @@ -22,6 +22,8 @@ namespace llvm { namespace objdump { +class ControlFlowPrinter; + /// Stores a single expression representing the location of a source-level /// variable, along with the PC range for which that expression is valid. struct LiveVariable { @@ -121,7 +123,8 @@ class LiveVariablePrinter { /// true, we have to print at least one line (with the continuation of any /// already-active live ranges) because something has already been printed /// earlier on this line. - void printBetweenInsts(formatted_raw_ostream &OS, bool MustPrint); + void printBetweenInsts(formatted_raw_ostream &OS, bool MustPrint, + uint64_t Addr = 0, ControlFlowPrinter *CFP = nullptr); /// Print the live variable ranges to the right of a disassembled instruction. void printAfterInst(formatted_raw_ostream &OS); @@ -166,6 +169,115 @@ class SourcePrinter { StringRef Delimiter = "; "); }; +struct VisualizeJumpsMode { + enum Chars_t { Off, ASCII, Unicode }; + enum Colors_t { BlackAndWhite, ThreeBit, Auto }; + + Chars_t Chars; + Colors_t Colors; + + VisualizeJumpsMode() : Chars(Off), Colors(BlackAndWhite) {} + VisualizeJumpsMode(Chars_t Chars, Colors_t Colors) + : Chars(Chars), Colors(Colors) {} + + static VisualizeJumpsMode GetDefault() { + return VisualizeJumpsMode(Unicode, Auto); + } + + bool enabled() const { return Chars != Off; } + bool color_enabled() const { return enabled() && Colors != BlackAndWhite; } + bool unicode_enabled() const { return Chars == Unicode; } + + void ResolveAutoColor(raw_ostream &OS) { + if (Colors == Auto) + Colors = OS.has_colors() ? ThreeBit : BlackAndWhite; + } +}; + +class ControlFlowPrinter { + struct ControlFlowTarget { + uint64_t Target; + SmallVector Sources; + int Column; + raw_ostream::Colors Color; + + ControlFlowTarget(uint64_t Target, raw_ostream::Colors Color) + : Target(Target), Column(~0U), Color(Color), High(Target), Low(Target) { + } + ControlFlowTarget(const ControlFlowTarget &) = delete; + ControlFlowTarget(ControlFlowTarget &&) = default; + + void addSource(uint64_t Source) { + Sources.push_back(Source); + Low = std::min(Low, Source); + High = std::max(High, Source); + } + + uint64_t Length() const { return High - Low; } + + bool Overlaps(ControlFlowTarget &Other) const { + return !(Other.Low > High || Other.High < Low); + } + + bool ActiveAt(uint64_t Addr, bool BeforeInst = false, + bool AfterInst = false) const { + if (BeforeInst) + return Addr > Low && Addr <= High; + else if (AfterInst) + return Addr >= Low && Addr < High; + else + return Addr >= Low && Addr <= High; + } + + bool StartsAt(uint64_t Addr) const { return Addr == Low; } + bool EndsAt(uint64_t Addr) const { return Addr == High; } + bool TargetAt(uint64_t Addr) const { return Addr == Target; } + + bool HorizontalAt(uint64_t Addr) const { + return Addr == Target || + std::any_of(Sources.begin(), Sources.end(), + [Addr](uint64_t Src) { return Src == Addr; }); + } + + private: + uint64_t High, Low; + }; + + VisualizeJumpsMode OutputMode; + DenseMap Targets; + int MaxColumn; + const MCSubtargetInfo &STI; + + int NextColorIdx; + raw_ostream::Colors PickColor(); + + int getIndentLevel() const { return 10; } + + enum class LineChar { + Horiz, + Vert, + TopCorner, + BottomCorner, + Tee, + Arrow, + }; + const char *getLineChar(LineChar C) const; + +public: + ControlFlowPrinter(VisualizeJumpsMode OutputMode, const MCSubtargetInfo &STI) + : OutputMode(OutputMode), MaxColumn(0), STI(STI), NextColorIdx(0) {} + + // Add a control-flow edge from the instruction at address From to the + // instruction at address To. + void addEdge(uint64_t From, uint64_t To); + + void finalise(); + + void printInst(formatted_raw_ostream &OS, uint64_t Addr) const; + void printOther(formatted_raw_ostream &OS, uint64_t Addr, + bool BeforeInst = false, bool AfterInst = false) const; +}; + } // namespace objdump } // namespace llvm diff --git a/llvm/tools/llvm-objdump/XCOFFDump.cpp b/llvm/tools/llvm-objdump/XCOFFDump.cpp index 0f6147924f8a1a..e6266dcd774d68 100644 --- a/llvm/tools/llvm-objdump/XCOFFDump.cpp +++ b/llvm/tools/llvm-objdump/XCOFFDump.cpp @@ -150,7 +150,7 @@ void objdump::dumpTracebackTable(ArrayRef Bytes, uint64_t Address, const MCSubtargetInfo &STI, const XCOFFObjectFile *Obj) { uint64_t Index = 0; - unsigned TabStop = getInstStartColumn(STI) - 1; + unsigned TabStop = GetColumnIndent(STI, DisassemblyColumn::Assembly) - 1; // Print traceback table boundary. printRawData(Bytes.slice(Index, 4), Address, OS, STI); OS << "\t# Traceback table start\n"; diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp index 12bb70d5537d8a..ecbd81e02f3c92 100644 --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -73,6 +73,7 @@ #include "llvm/Support/GraphWriter.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LLVMDriver.h" +#include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/StringSaver.h" @@ -197,6 +198,7 @@ static std::vector DisassembleSymbols; static bool DisassembleZeroes; static std::vector DisassemblerOptions; static ColorOutput DisassemblyColor; +static VisualizeJumpsMode VisualizeJumps; DIDumpType objdump::DwarfDumpType; static bool DynamicRelocations; static bool FaultMapSection; @@ -471,32 +473,92 @@ static bool getHidden(RelocationRef RelRef) { return false; } -/// Get the column at which we want to start printing the instruction -/// disassembly, taking into account anything which appears to the left of it. -unsigned objdump::getInstStartColumn(const MCSubtargetInfo &STI) { - return !ShowRawInsn ? 16 : STI.getTargetTriple().isX86() ? 40 : 24; +static int ControlFlowColumnWidth = 0; +void objdump::setControlFlowColumnWidth(int Width) { + ControlFlowColumnWidth = Width; } -static void AlignToInstStartColumn(size_t Start, const MCSubtargetInfo &STI, - raw_ostream &OS) { - // The output of printInst starts with a tab. Print some spaces so that - // the tab has 1 column and advances to the target tab stop. - unsigned TabStop = getInstStartColumn(STI); - unsigned Column = OS.tell() - Start; - OS.indent(Column < TabStop - 1 ? TabStop - 1 - Column : 7 - Column % 8); +unsigned EncodingColumnWidth(Triple const &Triple) { + switch (Triple.getArch()) { + case Triple::x86: + case Triple::x86_64: + return 30; + default: + return 14; + } +} + +unsigned objdump::GetColumnIndent(MCSubtargetInfo const &STI, + DisassemblyColumn Col) { + unsigned Indent = 0; + + if (Col == DisassemblyColumn::Address) + return Indent; + + // Address: 8 chars of address, followed by ": " + Indent += 10; + + if (Col == DisassemblyColumn::ControlFlow) + return Indent; + + // Control-flow graph: depends on function, disabled by default. + Indent += ControlFlowColumnWidth; + + if (Col == DisassemblyColumn::Encoding) + return Indent; + + // Encoding: depends on architecture + if (ShowRawInsn) + Indent += EncodingColumnWidth(STI.getTargetTriple()); + + // Special case for assembly string: the assembly printer uses tabs, so we + // need to ensure we start the instruction on a tab stop (multiple of 8). + Indent = alignTo(Indent, 8); + if (Col == DisassemblyColumn::Assembly) + return Indent; + + // Assembly width can be configured with --debug-vars-indent= + // FIXME this variable name is confusing. + Indent += DbgIndent; + + if (Col == DisassemblyColumn::Variables) + return Indent; + + llvm_unreachable("Unhandled DisassemblyColumn"); +} + +void objdump::IndentToColumn(MCSubtargetInfo const &STI, + formatted_raw_ostream &OS, DisassemblyColumn Col) { + unsigned TargetIndent = GetColumnIndent(STI, Col); + unsigned CurrentIndent = OS.getColumn(); + + // Special case for assembly string: the output of printInst starts with a + // tab, so we want to start printing one character before a tab stop, so it + // always has a width of one. GetColumnIndent already guarantees that + // TargetIndent will be a multple of 8. + // TODO Add a way to avoid printing the leading tab, to simplify this. + // TODO 7 characters isn't enough for a lot of mnemonics, add an option to + // increase the gap to the first operand. + if (Col == DisassemblyColumn::Assembly) { + TargetIndent -= 1; + if (TargetIndent < CurrentIndent) + TargetIndent = alignTo(CurrentIndent + 1, 8) - 1; + } + + if (TargetIndent > CurrentIndent) + OS.indent(TargetIndent - CurrentIndent); } void objdump::printRawData(ArrayRef Bytes, uint64_t Address, formatted_raw_ostream &OS, MCSubtargetInfo const &STI) { - size_t Start = OS.tell(); if (LeadingAddr) OS << format("%8" PRIx64 ":", Address); if (ShowRawInsn) { OS << ' '; dumpBytes(Bytes, OS); } - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); } namespace { @@ -559,12 +621,22 @@ class PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP); - LVP.printBetweenInsts(OS, false); + LVP.printBetweenInsts(OS, false, Address.Address, &CFP); + + if (LeadingAddr) + OS << format("%8" PRIx64 ":", Address.Address); - printRawData(Bytes, Address.Address, OS, STI); + CFP.printInst(OS, Address.Address); + + if (ShowRawInsn) { + OS << ' '; + dumpBytes(Bytes, OS); + } + + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); if (MI) { // See MCInstPrinter::printInst. On targets where a PC relative immediate @@ -598,7 +670,7 @@ class HexagonPrettyPrinter : public PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) override { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP, ""); if (!MI) { @@ -668,7 +740,7 @@ class AMDGCNPrettyPrinter : public PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) override { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP); @@ -721,7 +793,7 @@ class BPFPrettyPrinter : public PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) override { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP); if (LeadingAddr) @@ -744,14 +816,16 @@ class ARMPrettyPrinter : public PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) override { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP); - LVP.printBetweenInsts(OS, false); + LVP.printBetweenInsts(OS, false, Address.Address, &CFP); - size_t Start = OS.tell(); if (LeadingAddr) OS << format("%8" PRIx64 ":", Address.Address); + + CFP.printInst(OS, Address.Address); + if (ShowRawInsn) { size_t Pos = 0, End = Bytes.size(); if (STI.checkFeatures("+thumb-mode")) { @@ -775,7 +849,7 @@ class ARMPrettyPrinter : public PrettyPrinter { } } - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); if (MI) { IP.printInst(MI, Address.Address, "", STI, OS); @@ -798,14 +872,16 @@ class AArch64PrettyPrinter : public PrettyPrinter { object::SectionedAddress Address, formatted_raw_ostream &OS, StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP, StringRef ObjectFilename, std::vector *Rels, - LiveVariablePrinter &LVP) override { + LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override { if (SP && (PrintSource || PrintLines)) SP->printSourceLine(OS, Address, ObjectFilename, LVP); - LVP.printBetweenInsts(OS, false); + LVP.printBetweenInsts(OS, false, Address.Address, &CFP); - size_t Start = OS.tell(); if (LeadingAddr) OS << format("%8" PRIx64 ":", Address.Address); + + CFP.printInst(OS, Address.Address); + if (ShowRawInsn) { size_t Pos = 0, End = Bytes.size(); for (; Pos + 4 <= End; Pos += 4) @@ -820,7 +896,7 @@ class AArch64PrettyPrinter : public PrettyPrinter { } } - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); if (MI) { IP.printInst(MI, Address.Address, "", STI, OS); @@ -1166,14 +1242,14 @@ static uint64_t dumpARMELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End, const ObjectFile &Obj, ArrayRef Bytes, ArrayRef MappingSymbols, - const MCSubtargetInfo &STI, raw_ostream &OS) { + const MCSubtargetInfo &STI, + formatted_raw_ostream &OS) { llvm::endianness Endian = Obj.isLittleEndian() ? llvm::endianness::little : llvm::endianness::big; - size_t Start = OS.tell(); OS << format("%8" PRIx64 ": ", SectionAddr + Index); if (Index + 4 <= End) { dumpBytes(Bytes.slice(Index, 4), OS); - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); OS << "\t.word\t" << format_hex(support::endian::read32(Bytes.data() + Index, Endian), 10); @@ -1181,19 +1257,19 @@ static uint64_t dumpARMELFData(uint64_t SectionAddr, uint64_t Index, } if (Index + 2 <= End) { dumpBytes(Bytes.slice(Index, 2), OS); - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); OS << "\t.short\t" << format_hex(support::endian::read16(Bytes.data() + Index, Endian), 6); return 2; } dumpBytes(Bytes.slice(Index, 1), OS); - AlignToInstStartColumn(Start, STI, OS); + IndentToColumn(STI, OS, DisassemblyColumn::Assembly); OS << "\t.byte\t" << format_hex(Bytes[Index], 4); return 1; } static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End, - ArrayRef Bytes) { + ArrayRef Bytes, formatted_raw_ostream &OS) { // print out data up to 8 bytes at a time in hex and ascii uint8_t AsciiData[9] = {'\0'}; uint8_t Byte; @@ -1201,9 +1277,9 @@ static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End, for (; Index < End; ++Index) { if (NumBytes == 0) - outs() << format("%8" PRIx64 ":", SectionAddr + Index); + OS << format("%8" PRIx64 ":", SectionAddr + Index); Byte = Bytes.slice(Index)[0]; - outs() << format(" %02x", Byte); + OS << format(" %02x", Byte); AsciiData[NumBytes] = isPrint(Byte) ? Byte : '.'; uint8_t IndentOffset = 0; @@ -1218,9 +1294,9 @@ static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End, } if (NumBytes == 8) { AsciiData[8] = '\0'; - outs() << std::string(IndentOffset, ' ') << " "; - outs() << reinterpret_cast(AsciiData); - outs() << '\n'; + OS << std::string(IndentOffset, ' ') << " "; + OS << reinterpret_cast(AsciiData); + OS << '\n'; NumBytes = 0; } } @@ -1282,16 +1358,12 @@ collectBBAddrMapLabels(const std::unordered_map &AddrToBBAd } } -static void -collectLocalBranchTargets(ArrayRef Bytes, MCInstrAnalysis *MIA, - MCDisassembler *DisAsm, MCInstPrinter *IP, - const MCSubtargetInfo *STI, uint64_t SectionAddr, - uint64_t Start, uint64_t End, - std::unordered_map &Labels) { - // So far only supports PowerPC and X86. - if (!STI->getTargetTriple().isPPC() && !STI->getTargetTriple().isX86()) - return; - +static void collectLocalBranchTargets( + ArrayRef Bytes, MCInstrAnalysis *MIA, MCDisassembler *DisAsm, + MCInstPrinter *IP, const MCSubtargetInfo *STI, uint64_t SectionAddr, + uint64_t Start, uint64_t End, + std::unordered_map &Labels, ControlFlowPrinter &CFP, + std::vector &Relocs) { if (MIA) MIA->resetState(); @@ -1300,6 +1372,10 @@ collectLocalBranchTargets(ArrayRef Bytes, MCInstrAnalysis *MIA, Start += SectionAddr; End += SectionAddr; uint64_t Index = Start; + + auto RelCur = Relocs.begin(); + auto RelEnd = Relocs.end(); + while (Index < End) { // Disassemble a real instruction and record function-local branch labels. MCInst Inst; @@ -1311,15 +1387,37 @@ collectLocalBranchTargets(ArrayRef Bytes, MCInstrAnalysis *MIA, Size = std::min(ThisBytes.size(), DisAsm->suggestBytesToSkip(ThisBytes, Index)); + // Check for relocations which apply to this instruction. + bool Relocated = false; + while (RelCur != RelEnd) { + // FIXME RelAdjustment for executables & shared objects + uint64_t Offset = RelCur->getOffset(); // - RelAdjustment; + + if (Offset >= Index + Size) + break; + + Relocated = true; + ++RelCur; + } + if (Disassembled && MIA) { uint64_t Target; bool TargetKnown = MIA->evaluateBranch(Inst, Index, Size, Target); - // On PowerPC, if the address of a branch is the same as the target, it - // means that it's a function call. Do not mark the label for this case. - if (TargetKnown && (Target >= Start && Target < End) && - !Labels.count(Target) && - !(STI->getTargetTriple().isPPC() && Target == Index)) - Labels[Target] = ("L" + Twine(LabelCount++)).str(); + if (TargetKnown && (Target >= Start && Target < End)) { + // On PowerPC and ARM, if the address of a branch is the same as the + // target, it means that it's a function call. Do not mark the label for + // this case. + if (!Labels.count(Target) && + !((STI->getTargetTriple().isPPC() || + STI->getTargetTriple().isARM()) && + Target == Index) && + SymbolizeOperands) { + Labels[Target] = ("L" + Twine(LabelCount++)).str(); + } + + if (Target != Index && !Relocated) + CFP.addEdge(Index, Target); + } MIA->updateState(Inst, Index); } else if (!Disassembled && MIA) { MIA->resetState(); @@ -1408,9 +1506,10 @@ static void emitPostInstructionInfo(formatted_raw_ostream &FOS, StringRef Comment; std::tie(Comment, Comments) = Comments.split('\n'); // MAI.getCommentColumn() assumes that instructions are printed at the - // position of 8, while getInstStartColumn() returns the actual position. + // position of 8, while GetColumnIndent() returns the actual position. unsigned CommentColumn = - MAI.getCommentColumn() - 8 + getInstStartColumn(STI); + MAI.getCommentColumn() - 8 + + GetColumnIndent(STI, DisassemblyColumn::Assembly); FOS.PadToColumn(CommentColumn); FOS << MAI.getCommentString() << ' ' << Comment; } @@ -1486,7 +1585,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, } std::map> RelocMap; - if (InlineRelocs) + if (InlineRelocs || VisualizeJumps.enabled()) RelocMap = getRelocsMap(Obj); bool Is64Bits = Obj.getBytesInAddress() > 4; @@ -1850,12 +1949,14 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, Start -= SectionAddr; End -= SectionAddr; + formatted_raw_ostream FOS(outs()); + if (!PrintedSection) { PrintedSection = true; - outs() << "\nDisassembly of section "; + FOS << "\nDisassembly of section "; if (!SegmentName.empty()) - outs() << SegmentName << ","; - outs() << SectionName << ":\n"; + FOS << SegmentName << ","; + FOS << SectionName << ":\n"; } bool PrintedLabel = false; @@ -1867,22 +1968,23 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, const StringRef SymbolName = SymNamesHere[i]; if (!PrintedLabel) { - outs() << '\n'; + FOS << '\n'; PrintedLabel = true; } if (LeadingAddr) - outs() << format(Is64Bits ? "%016" PRIx64 " " : "%08" PRIx64 " ", - SectionAddr + Start + VMAAdjustment); + FOS << format(Is64Bits ? "%016" PRIx64 " " : "%08" PRIx64 " ", + SectionAddr + Start + VMAAdjustment); if (Obj.isXCOFF() && SymbolDescription) { - outs() << getXCOFFSymbolDescription(Symbol, SymbolName) << ":\n"; + FOS << getXCOFFSymbolDescription(Symbol, SymbolName) << ":"; } else - outs() << '<' << SymbolName << ">:\n"; + FOS << '<' << SymbolName << ">:"; + LVP.printAfterOtherLine(FOS, false); } // Don't print raw contents of a virtual section. A virtual section // doesn't have any contents in the file. if (Section.isVirtual()) { - outs() << "...\n"; + FOS << "...\n"; continue; } @@ -1926,12 +2028,12 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, // distance to the next symbol, and sometimes it will be just a // prologue and we should start disassembling instructions from where // it left off. - outs() << DT->Context->getAsmInfo()->getCommentString() - << " error in decoding " << SymNamesHere[SHI] - << " : decoding failed region as bytes.\n"; + FOS << DT->Context->getAsmInfo()->getCommentString() + << " error in decoding " << SymNamesHere[SHI] + << " : decoding failed region as bytes.\n"; for (uint64_t I = 0; I < Size; ++I) { - outs() << "\t.byte\t " << format_hex(Bytes[I], 1, /*Upper=*/true) - << "\n"; + FOS << "\t.byte\t " << format_hex(Bytes[I], 1, /*Upper=*/true) + << "\n"; } } Start += Size; @@ -1943,7 +2045,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, Index = std::max(Index, StartAddress - SectionAddr); if (DisassembleAsELFData) { - dumpELFData(SectionAddr, Index, End, Bytes); + dumpELFData(SectionAddr, Index, End, Bytes, FOS); Index = End; continue; } @@ -1954,8 +2056,6 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, Symbols[SI - 1].XCOFFSymInfo.StorageMappingClass && (*Symbols[SI - 1].XCOFFSymInfo.StorageMappingClass == XCOFF::XMC_PR); - formatted_raw_ostream FOS(outs()); - // FIXME: Workaround for bug in formatted_raw_ostream. Color escape codes // are (incorrectly) written directly to the unbuffered raw_ostream // wrapped by the formatted_raw_ostream. @@ -1965,15 +2065,20 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, std::unordered_map AllLabels; std::unordered_map> BBAddrMapLabels; + ControlFlowPrinter CFP(VisualizeJumps, *DT->SubtargetInfo); + if (SymbolizeOperands || VisualizeJumps.enabled()) { + collectLocalBranchTargets( + Bytes, DT->InstrAnalysis.get(), DT->DisAsm.get(), + DT->InstPrinter.get(), PrimaryTarget.SubtargetInfo.get(), + SectionAddr, Index, End, AllLabels, CFP, Rels); + } if (SymbolizeOperands) { - collectLocalBranchTargets(Bytes, DT->InstrAnalysis.get(), - DT->DisAsm.get(), DT->InstPrinter.get(), - PrimaryTarget.SubtargetInfo.get(), - SectionAddr, Index, End, AllLabels); collectBBAddrMapLabels(AddrToBBAddrMap, SectionAddr, Index, End, BBAddrMapLabels); } + CFP.finalise(); + if (DT->InstrAnalysis) DT->InstrAnalysis->resetState(); @@ -2022,7 +2127,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, uint64_t MaxOffset = End - Index; // For --reloc: print zero blocks patched by relocations, so that // relocations can be shown in the dump. - if (RelCur != RelEnd) + if (InlineRelocs && RelCur != RelEnd) MaxOffset = std::min(RelCur->getOffset() - RelAdjustment - Index, MaxOffset); @@ -2047,12 +2152,18 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, // Print local label if there's any. auto Iter1 = BBAddrMapLabels.find(SectionAddr + Index); if (Iter1 != BBAddrMapLabels.end()) { - for (StringRef Label : Iter1->second) - FOS << "<" << Label << ">:\n"; + for (StringRef Label : Iter1->second) { + FOS << "<" << Label << ">:"; + CFP.printOther(FOS, Index, true); + LVP.printAfterOtherLine(FOS, true); + } } else { auto Iter2 = AllLabels.find(SectionAddr + Index); - if (Iter2 != AllLabels.end()) - FOS << "<" << Iter2->second << ">:\n"; + if (Iter2 != AllLabels.end()) { + FOS << "<" << Iter2->second << ">:"; + CFP.printOther(FOS, Index, true); + LVP.printAfterOtherLine(FOS, true); + } } // Disassemble a real instruction or a data when disassemble all is @@ -2076,7 +2187,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, *DT->InstPrinter, Disassembled ? &Inst : nullptr, Bytes.slice(Index, Size), {SectionAddr + Index + VMAAdjustment, Section.getIndex()}, FOS, - "", *DT->SubtargetInfo, &SP, Obj.getFileName(), &Rels, LVP); + "", *DT->SubtargetInfo, &SP, Obj.getFileName(), &Rels, LVP, CFP); DT->InstPrinter->setCommentStream(llvm::nulls()); @@ -2209,7 +2320,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, printBTFRelocation(FOS, *BTF, {Index, Section.getIndex()}, LVP); // Hexagon does this in pretty printer - if (Obj.getArch() != Triple::hexagon) { + if (Obj.getArch() != Triple::hexagon && InlineRelocs) { // Print relocation for instruction and data. while (RelCur != RelEnd) { uint64_t Offset = RelCur->getOffset() - RelAdjustment; @@ -2236,6 +2347,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj, Offset += AdjustVMA; } + CFP.printOther(FOS, Index, false, true); printRelocation(FOS, Obj.getFileName(), *RelCur, SectionAddr + Offset, Is64Bits); LVP.printAfterOtherLine(FOS, true); @@ -3278,6 +3390,41 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) { if (DisassemblyColor == ColorOutput::Invalid) invalidArgValue(A); } + if (const opt::Arg *A = InputArgs.getLastArg(OBJDUMP_visualize_jumps, + OBJDUMP_visualize_jumps_EQ)) { + if (A->getOption().matches(OBJDUMP_visualize_jumps)) { + // --visualize-jumps without an argument default to unicode, auto-color. + VisualizeJumps = VisualizeJumpsMode::GetDefault(); + } else { + SmallVector Parts; + StringRef(A->getValue()).split(Parts, ","); + VisualizeJumpsMode::Chars_t Chars = VisualizeJumpsMode::Unicode; + VisualizeJumpsMode::Colors_t Color = VisualizeJumpsMode::Auto; + + for (StringRef Part : Parts) { + if (Part == "off") { + Chars = VisualizeJumpsMode::Off; + } else if (Part == "nocolor") { + Color = VisualizeJumpsMode::BlackAndWhite; + } else if (Part == "auto") { + Color = VisualizeJumpsMode::Auto; + } else if (Part == "color") { + Color = VisualizeJumpsMode::ThreeBit; + } else if (Part == "ascii") { + Chars = VisualizeJumpsMode::ASCII; + } else if (Part == "unicode") { + Chars = VisualizeJumpsMode::Unicode; + } else { + reportCmdLineError("'" + Part + "' is not a valid value for '" + + A->getSpelling() + "'"); + } + } + + VisualizeJumps = VisualizeJumpsMode(Chars, Color); + } + + VisualizeJumps.ResolveAutoColor(outs()); + } parseIntArg(InputArgs, OBJDUMP_debug_vars_indent_EQ, DbgIndent); diff --git a/llvm/tools/llvm-objdump/llvm-objdump.h b/llvm/tools/llvm-objdump/llvm-objdump.h index 7778cf6c2784eb..74c142aa80ed92 100644 --- a/llvm/tools/llvm-objdump/llvm-objdump.h +++ b/llvm/tools/llvm-objdump/llvm-objdump.h @@ -144,6 +144,19 @@ void printRawData(llvm::ArrayRef Bytes, uint64_t Address, llvm::formatted_raw_ostream &OS, llvm::MCSubtargetInfo const &STI); +enum class DisassemblyColumn { + Address, + ControlFlow, + Encoding, + Assembly, + Variables, +}; + +void setControlFlowColumnWidth(int Width); +unsigned GetColumnIndent(MCSubtargetInfo const &STI, DisassemblyColumn Col); +void IndentToColumn(MCSubtargetInfo const &STI, formatted_raw_ostream &OS, + DisassemblyColumn Col); + } // namespace objdump } // end namespace llvm diff --git a/llvm/unittests/Support/formatted_raw_ostream_test.cpp b/llvm/unittests/Support/formatted_raw_ostream_test.cpp index 750dc518fee388..5ae5608d0bdb07 100644 --- a/llvm/unittests/Support/formatted_raw_ostream_test.cpp +++ b/llvm/unittests/Support/formatted_raw_ostream_test.cpp @@ -56,15 +56,14 @@ TEST(formatted_raw_ostreamTest, Test_LineColumn) { EXPECT_EQ(1U, C.getLine()); EXPECT_EQ(0U, C.getColumn()); - // '\t' advances column to the next multiple of 8. - // FIXME: If the column number is already a multiple of 8 this will do - // nothing, is this behaviour correct? + // '\t' advances column to the next multiple of 8, and always by at least 1 + // column. C << "1\t"; EXPECT_EQ(8U, C.getColumn()); C << "\t"; - EXPECT_EQ(8U, C.getColumn()); - C << "1234567\t"; EXPECT_EQ(16U, C.getColumn()); + C << "1234567\t"; + EXPECT_EQ(24U, C.getColumn()); EXPECT_EQ(1U, C.getLine()); }