[llvm] [llvm-objdump] Handle .callgraph section (PR #151009)
Prabhu Rajasekaran via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 28 19:05:00 PDT 2025
https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/151009
>From dc2d0ac21a3dc68584e1b35303d1229888f16989 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 18 Apr 2025 23:04:18 +0000
Subject: [PATCH 1/3] All objdump in one patch
---
llvm/docs/CommandGuide/llvm-objdump.rst | 5 +
...graph-info-callgraph-section-warnings.test | 35 +
.../call-graph-info-callgraph-section.test | 73 ++
.../ELF/call-graph-info-callsites.test | 40 ++
...graph-info-err-invalid-format-version.test | 18 +
...all-graph-info-err-invalid-func-entry.test | 19 +
...call-graph-info-err-invalid-func-kind.test | 20 +
...-info-err-malformed-callgraph-section.test | 22 +
...info-err-malformed-callgraph-section2.test | 22 +
.../ELF/call-graph-info-functions.test | 28 +
...-graph-info-warn-no-callgraph-section.test | 15 +
.../llvm-objdump/ELF/call-graph-info.test | 647 ++++++++++++++++++
llvm/tools/llvm-objdump/ObjdumpOpts.td | 4 +
llvm/tools/llvm-objdump/llvm-objdump.cpp | 301 +++++++-
14 files changed, 1248 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section-warnings.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-callsites.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-format-version.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-entry.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-kind.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section2.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-functions.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info-warn-no-callgraph-section.test
create mode 100644 llvm/test/tools/llvm-objdump/ELF/call-graph-info.test
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index aaf38f84b92e5..09ff59f9e489f 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -25,6 +25,11 @@ combined with other commands:
Display the information contained within an archive's headers.
+.. option:: --call-graph-info
+
+ Dump call graph information including indirect call and target IDs from call
+ graph section, if available.
+
.. option:: -d, --disassemble
Disassemble all executable sections found in the input files. On some
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section-warnings.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section-warnings.test
new file mode 100644
index 0000000000000..c4831c8b889f6
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section-warnings.test
@@ -0,0 +1,35 @@
+## Tests --call-graph-info warnings for missing/invalid .callgraph section
+## contents.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section has type ids for 1 instructions which are not indirect calls
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section does not have type ids for 1 indirect calls
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section does not have information for 1 functions
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets
+
+.text
+.globl foo
+.type foo, at function
+foo:
+.Lfoo_begin:
+.Lnot_indirect_call:
+ retq
+
+.globl bar
+.type bar, at function
+bar:
+ callq *%rcx
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad .Lfoo_begin #< foo()'s entry address.
+.quad 1 #< A warning: function kind is 1: the type id for indirect target foo is unknown.
+.quad 1 #< Count of indirect call sites that follow: 1.
+.quad 0 #< Indirect call type id.
+.quad .Lnot_indirect_call #< A warning: type id for non-indirect call instruction.
+# A warning: .callgraph section does not have information for 1 function (bar).
+# A warning: .callgraph section does not have type id for 1 indirect call (one in bar).
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section.test
new file mode 100644
index 0000000000000..beefc452797f7
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callgraph-section.test
@@ -0,0 +1,73 @@
+## Tests --call-graph-info prints information from call graph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+
+# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: UNKNOWN 6 b
+# CHECK-NEXT: 20 a
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: 10 9
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 6 9
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 0 5 5
+# CHECK-EMPTY:
+# CHECK-NEXT: FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# CHECK-NEXT: 0 foo
+# CHECK-NEXT: 6 bar
+# CHECK-NEXT: a baz
+# CHECK-NEXT: b qux
+
+.text
+
+.globl foo
+.type foo, at function
+foo: #< foo is at 0.
+.Lfoo_begin:
+ callq foo #< direct call is at 5. target is foo (5).
+ retq
+
+.globl bar
+.type bar, at function
+bar: #< bar is at 6.
+ callq *-40(%rbp) #< indirect call is at 9.
+ retq
+
+.globl baz
+.type baz, at function
+baz: #< baz is at 10 (a).
+ retq
+
+.globl qux
+.type qux, at function
+qux: #< qux is at 11 (b).
+ retq
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< foo()'s entry address.
+.quad 0 #< Function kind: not an indirect target.
+.quad 0 #< Count of indirect call sites that follow: 0.
+
+.quad 0 #< Format version number.
+.quad 6 #< bar()'s entry address.
+.quad 1 #< Function kind: indirect target with unknown type id.
+.quad 1 #< Count of indirect call sites that follow: 1.
+.quad 16 #< Indirect call type id.
+.quad 9 #< Indirect call site.
+
+.quad 0 #< Format version number.
+.quad 10 #< baz()'s entry address.
+.quad 2 #< Function kind: indirect target with known type id.
+.quad 32 #< Indirect target type id.
+.quad 0 #< Count of indirect call sites that follow: 1.
+
+# No call graph section entry for qux: will be printed as unknown target.
+
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callsites.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callsites.test
new file mode 100644
index 0000000000000..0a12505aaae8d
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-callsites.test
@@ -0,0 +1,40 @@
+## Tests --call-graph-info prints call sites.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+
+# CHECK: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 1 8
+# CHECK-NEXT: 12 14
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 1 6 6 d d 12 12
+# CHECK-NEXT: 12 19 19
+# CHECK-EMPTY:
+# CHECK-NEXT: FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# CHECK-NEXT: 0 foo
+# CHECK-NEXT: 1 bar
+# CHECK-NEXT: 12 baz
+
+.text
+
+.globl foo
+.type foo, at function
+foo:
+ retq
+
+.globl bar
+.type bar, at function
+bar:
+ callq foo
+ callq *%rcx
+ callq bar
+ callq foo
+
+.globl baz
+.type baz, at function
+baz:
+ callq *%rcx
+ callq foo
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-format-version.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-format-version.test
new file mode 100644
index 0000000000000..bbab998681be6
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-format-version.test
@@ -0,0 +1,18 @@
+## Tests that --call-graph-info fails if .callgraph section has unknown format
+## version number.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# ERR: llvm-objdump: error: '[[FILE]]': Unknown format version in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 1 #< Invalid format version number: the only supported version is 0.
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-entry.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-entry.test
new file mode 100644
index 0000000000000..43f88d5a2b5ff
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-entry.test
@@ -0,0 +1,19 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid
+## function entry address.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# ERR: llvm-objdump: error: '[[FILE]]': Invalid function entry pc in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 12345 #< Invalid function entry address.
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-kind.test
new file mode 100644
index 0000000000000..67d3ebc34e7c7
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-invalid-func-kind.test
@@ -0,0 +1,20 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid
+## function kind value.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# ERR: llvm-objdump: error: '[[FILE]]': Unknown function kind in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< Function entry address.
+.quad 3 #< Invalid function kind: must be one of 0, 1, 2.
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section.test
new file mode 100644
index 0000000000000..c539a34874d44
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -0,0 +1,22 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid size.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# ERR: llvm-objdump: error: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+# Each unit in .callgraph section must have 64bit size. Therefore, byte size
+# must be divisible by 64.
+.quad 0
+.quad 0
+.quad 0
+.byte 0
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section2.test
new file mode 100644
index 0000000000000..959d54799081d
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -0,0 +1,22 @@
+## Tests that --call-graph-info fails if .callgraph section does not have
+## an expected value, e.g., not as much call sites as the given count.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# ERR: llvm-objdump: error: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< Function entry address.
+.quad 0 #< Function kind.
+.quad 2 #< Indirect call site count that follows.
+ #< Missing indirect calls?
+.text
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-functions.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-functions.test
new file mode 100644
index 0000000000000..fb1f2d4d78b23
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-functions.test
@@ -0,0 +1,28 @@
+## Tests --call-graph-info prints functions.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+
+# CHECK: FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# CHECK-NEXT: 0 foo
+# CHECK-NEXT: 1 bar
+# CHECK-NEXT: 2 baz
+
+.text
+
+.globl foo
+.type foo, at function
+foo:
+ retq
+
+.globl bar
+.type bar, at function
+bar:
+ retq
+
+.globl baz
+.type baz, at function
+baz:
+ retq
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-warn-no-callgraph-section.test
new file mode 100644
index 0000000000000..fcee2da238d6c
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info-warn-no-callgraph-section.test
@@ -0,0 +1,15 @@
+## Tests that --call-graph-info warns if there is no .callgraph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': there is no .callgraph section
+# CHECK-NOT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NOT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
diff --git a/llvm/test/tools/llvm-objdump/ELF/call-graph-info.test b/llvm/test/tools/llvm-objdump/ELF/call-graph-info.test
new file mode 100644
index 0000000000000..d928c2272f2fd
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/call-graph-info.test
@@ -0,0 +1,647 @@
+## Tests how --call-graph-info prints the call graph information.
+# RUN: yaml2obj --docnum=1 %s -o %t
+# RUN: llvm-objdump --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# Yaml input is obtained by compiling the below source to object with:
+# clang -fcall-graph-section test.c -o test.o
+# then to yaml with:
+# obj2yaml test.o > test.yaml
+
+# The content of the .callgraph section is fixed with this yaml in raw format.
+
+# Source:
+# void foo() {}
+#
+# void bar() {}
+#
+# int baz(char a) {
+# return 0;
+# }
+#
+# int main() {
+# // Indirect calls.
+# void (*fp_foo)() = foo;
+# fp_foo();
+#
+# void (*fp_bar)() = bar;
+# fp_bar();
+#
+# char a;
+# int (*fp_baz)(char) = baz;
+# fp_baz(a);
+#
+# // Direct calls.
+# foo();
+# bar();
+# baz(a);
+#
+# return 0;
+# }
+
+# CHECK: [[FILE]]: file format elf64-x86-64
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section does not have type ids for 3 indirect calls
+# CHECK-NEXT: llvm-objdump: warning: '[[FILE]]': callgraph section does not have information for 10 functions
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: UNKNOWN 401000 401020 401050 401060 401090 4010d0 401100 4011c0 401220 401224
+# CHECK-NEXT: 3ecbeef531f74424 401110 401120
+# CHECK-NEXT: 308e4b8159bc8654 401130
+# CHECK-NEXT: fa6809609a76afca 401140
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: 3ecbeef531f74424 401165 40117b
+# CHECK-NEXT: 308e4b8159bc8654 401195
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 401000 401012
+# CHECK-NEXT: 401020 40104a
+# CHECK-NEXT: 401140 401165 40117b 401195
+# CHECK-NEXT: 4011c0 401205
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 4010d0 4010e2 401060
+# CHECK-NEXT: 401140 40119a 401110 40119f 401120 4011aa 401130
+# CHECK-NEXT: 4011c0 4011ed 401000
+# CHECK-EMPTY:
+# CHECK-NEXT: FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# CHECK-NEXT: 401000 _init
+# CHECK-NEXT: 401020 _start
+# CHECK-NEXT: 401050 _dl_relocate_static_pie
+# CHECK-NEXT: 401060 deregister_tm_clones
+# CHECK-NEXT: 401090 register_tm_clones
+# CHECK-NEXT: 4010d0 __do_global_dtors_aux
+# CHECK-NEXT: 401100 frame_dummy
+# CHECK-NEXT: 401110 foo
+# CHECK-NEXT: 401120 bar
+# CHECK-NEXT: 401130 baz
+# CHECK-NEXT: 401140 main
+# CHECK-NEXT: 4011c0 __libc_csu_init
+# CHECK-NEXT: 401220 __libc_csu_fini
+# CHECK-NEXT: 401224 _fini
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_X86_64
+ Entry: 0x401020
+ProgramHeaders:
+ - Type: PT_PHDR
+ Flags: [ PF_R ]
+ VAddr: 0x400040
+ Align: 0x8
+ - Type: PT_INTERP
+ Flags: [ PF_R ]
+ FirstSec: .interp
+ LastSec: .interp
+ VAddr: 0x4002A8
+ - Type: PT_LOAD
+ Flags: [ PF_R ]
+ FirstSec: .interp
+ LastSec: .rela.dyn
+ VAddr: 0x400000
+ Align: 0x1000
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ FirstSec: .init
+ LastSec: .fini
+ VAddr: 0x401000
+ Align: 0x1000
+ - Type: PT_LOAD
+ Flags: [ PF_R ]
+ FirstSec: .rodata
+ LastSec: .eh_frame
+ VAddr: 0x402000
+ Align: 0x1000
+ - Type: PT_LOAD
+ Flags: [ PF_W, PF_R ]
+ FirstSec: .init_array
+ LastSec: .bss
+ VAddr: 0x403E40
+ Align: 0x1000
+ - Type: PT_DYNAMIC
+ Flags: [ PF_W, PF_R ]
+ FirstSec: .dynamic
+ LastSec: .dynamic
+ VAddr: 0x403E50
+ Align: 0x8
+ - Type: PT_NOTE
+ Flags: [ PF_R ]
+ FirstSec: .note.ABI-tag
+ LastSec: .note.ABI-tag
+ VAddr: 0x4002C4
+ Align: 0x4
+ - Type: PT_GNU_EH_FRAME
+ Flags: [ PF_R ]
+ FirstSec: .eh_frame_hdr
+ LastSec: .eh_frame_hdr
+ VAddr: 0x402004
+ Align: 0x4
+ - Type: PT_GNU_STACK
+ Flags: [ PF_W, PF_R ]
+ Align: 0x10
+ - Type: PT_GNU_RELRO
+ Flags: [ PF_R ]
+ FirstSec: .init_array
+ LastSec: .got
+ VAddr: 0x403E40
+Sections:
+ - Name: .interp
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4002A8
+ AddressAlign: 0x1
+ Content: 2F6C696236342F6C642D6C696E75782D7838362D36342E736F2E3200
+ - Name: .note.ABI-tag
+ Type: SHT_NOTE
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4002C4
+ AddressAlign: 0x4
+ Notes:
+ - Name: GNU
+ Desc: '00000000030000000200000000000000'
+ Type: NT_VERSION
+ - Name: .hash
+ Type: SHT_HASH
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4002E8
+ Link: .dynsym
+ AddressAlign: 0x8
+ Bucket: [ 2 ]
+ Chain: [ 0, 0, 1 ]
+ - Name: .gnu.hash
+ Type: SHT_GNU_HASH
+ Flags: [ SHF_ALLOC ]
+ Address: 0x400300
+ Link: .dynsym
+ AddressAlign: 0x8
+ Header:
+ SymNdx: 0x1
+ Shift2: 0x0
+ BloomFilter: [ 0x0 ]
+ HashBuckets: [ 0x0 ]
+ HashValues: [ ]
+ - Name: .dynsym
+ Type: SHT_DYNSYM
+ Flags: [ SHF_ALLOC ]
+ Address: 0x400320
+ Link: .dynstr
+ AddressAlign: 0x8
+ - Name: .dynstr
+ Type: SHT_STRTAB
+ Flags: [ SHF_ALLOC ]
+ Address: 0x400368
+ AddressAlign: 0x1
+ - Name: .gnu.version
+ Type: SHT_GNU_versym
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4003A0
+ Link: .dynsym
+ AddressAlign: 0x2
+ Entries: [ 0, 2, 0 ]
+ - Name: .gnu.version_r
+ Type: SHT_GNU_verneed
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4003A8
+ Link: .dynstr
+ AddressAlign: 0x8
+ Dependencies:
+ - Version: 1
+ File: libc.so.6
+ Entries:
+ - Name: GLIBC_2.2.5
+ Hash: 157882997
+ Flags: 0
+ Other: 2
+ - Name: .rela.dyn
+ Type: SHT_RELA
+ Flags: [ SHF_ALLOC ]
+ Address: 0x4003C8
+ Link: .dynsym
+ AddressAlign: 0x8
+ Relocations:
+ - Offset: 0x403FF0
+ Symbol: __libc_start_main
+ Type: R_X86_64_GLOB_DAT
+ - Offset: 0x403FF8
+ Symbol: __gmon_start__
+ Type: R_X86_64_GLOB_DAT
+ - Name: .init
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x401000
+ AddressAlign: 0x4
+ Offset: 0x1000
+ Content: 4883EC08488B05ED2F00004885C07402FFD04883C408C3
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x401020
+ AddressAlign: 0x10
+ Content: 31ED4989D15E4889E24883E4F0505449C7C02012400048C7C1C011400048C7C740114000FF15A62F0000F40F1F440000C3662E0F1F8400000000000F1F440000B828404000483D284040007413B8000000004885C07409BF28404000FFE06690C366662E0F1F8400000000000F1F4000BE284040004881EE284040004889F048C1EE3F48C1F8034801C648D1FE7411B8000000004885C07407BF28404000FFE0C366662E0F1F8400000000000F1F4000803D512F0000007517554889E5E87EFFFFFFC6053F2F0000015DC30F1F440000C366662E0F1F8400000000000F1F4000EB8E662E0F1F8400000000000F1F4000554889E55DC3662E0F1F840000000000554889E55DC3662E0F1F840000000000554889E54088F88845FF31C05DC36690554889E54883EC30C745FC0000000048B81011400000000000488945F0488B4DF031C0FFD148B82011400000000000488945E8488B4DE831C0FFD148B83011400000000000488945D8488B45D88A4DE70FBEF9FFD0E876FFFFFFE881FFFFFF8A45E70FBEF8E886FFFFFF31C04883C4305DC3662E0F1F8400000000000F1F400041574C8D3D772C000041564989D641554989F541544189FC55488D2D682C0000534C29FD4883EC08E813FEFFFF48C1FD03741B31DB0F1F004C89F24C89EE4489E741FF14DF4883C3014839DD75EA4883C4085B5D415C415D415E415FC30F1F00C3
+ - Name: .fini
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x401224
+ AddressAlign: 0x4
+ Content: 4883EC084883C408C3
+ - Name: .rodata
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_MERGE ]
+ Address: 0x402000
+ AddressAlign: 0x4
+ EntSize: 0x4
+ Offset: 0x2000
+ Content: '01000200'
+ - Name: .eh_frame_hdr
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x402004
+ AddressAlign: 0x4
+ Content: 011B033B48000000080000001CF0FFFF640000004CF0FFFF900000000CF1FFFFA40000001CF1FFFFC40000002CF1FFFFE40000003CF1FFFF04010000BCF1FFFF240100001CF2FFFF6C010000
+ - Name: .eh_frame
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x402050
+ AddressAlign: 0x8
+ Content: 1400000000000000017A5200017810011B0C070890010710100000001C000000B0EFFFFF2B000000000000001400000000000000017A5200017810011B0C070890010000100000001C000000B4EFFFFF01000000000000001C0000003000000060F0FFFF0600000000410E108602430D06410C07080000001C0000005000000050F0FFFF0600000000410E108602430D06410C07080000001C0000007000000040F0FFFF0E00000000410E108602430D06490C07080000001C0000009000000030F0FFFF7200000000410E108602430D06026D0C0708000044000000B000000090F0FFFF5D00000000420E108F02490E188E03450E208D04450E288C05440E308606480E388307470E406A0E38410E30410E28420E20420E18420E10420E080010000000F8000000A8F0FFFF010000000000000000000000
+ - Name: .init_array
+ Type: SHT_INIT_ARRAY
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x403E40
+ AddressAlign: 0x8
+ EntSize: 0x8
+ Offset: 0x2E40
+ Content: '0011400000000000'
+ - Name: .fini_array
+ Type: SHT_FINI_ARRAY
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x403E48
+ AddressAlign: 0x8
+ EntSize: 0x8
+ Content: D010400000000000
+ - Name: .dynamic
+ Type: SHT_DYNAMIC
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x403E50
+ Link: .dynstr
+ AddressAlign: 0x8
+ Entries:
+ - Tag: DT_NEEDED
+ Value: 0x13
+ - Tag: DT_INIT
+ Value: 0x401000
+ - Tag: DT_FINI
+ Value: 0x401224
+ - Tag: DT_INIT_ARRAY
+ Value: 0x403E40
+ - Tag: DT_INIT_ARRAYSZ
+ Value: 0x8
+ - Tag: DT_FINI_ARRAY
+ Value: 0x403E48
+ - Tag: DT_FINI_ARRAYSZ
+ Value: 0x8
+ - Tag: DT_HASH
+ Value: 0x4002E8
+ - Tag: DT_GNU_HASH
+ Value: 0x400300
+ - Tag: DT_STRTAB
+ Value: 0x400368
+ - Tag: DT_SYMTAB
+ Value: 0x400320
+ - Tag: DT_STRSZ
+ Value: 0x38
+ - Tag: DT_SYMENT
+ Value: 0x18
+ - Tag: DT_DEBUG
+ Value: 0x0
+ - Tag: DT_RELA
+ Value: 0x4003C8
+ - Tag: DT_RELASZ
+ Value: 0x30
+ - Tag: DT_RELAENT
+ Value: 0x18
+ - Tag: DT_VERNEED
+ Value: 0x4003A8
+ - Tag: DT_VERNEEDNUM
+ Value: 0x1
+ - Tag: DT_VERSYM
+ Value: 0x4003A0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Tag: DT_NULL
+ Value: 0x0
+ - Name: .got
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x403FF0
+ AddressAlign: 0x8
+ EntSize: 0x8
+ Content: '00000000000000000000000000000000'
+ - Name: .got.plt
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x404000
+ AddressAlign: 0x8
+ EntSize: 0x8
+ Content: '503E40000000000000000000000000000000000000000000'
+ - Name: .data
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x404018
+ AddressAlign: 0x8
+ Content: '00000000000000000000000000000000'
+ - Name: .bss
+ Type: SHT_NOBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x404028
+ AddressAlign: 0x1
+ Size: 0x8
+ - Name: .comment
+ Type: SHT_PROGBITS
+ Flags: [ SHF_MERGE, SHF_STRINGS ]
+ AddressAlign: 0x1
+ EntSize: 0x1
+ Content: 4743433A202844656269616E2031302E322E312D362B6275696C6432292031302E322E3120323032313031313000636C616E672076657273696F6E2031332E302E302028676974406769746875622E636F6D3A6C6C766D2F6C6C766D2D70726F6A6563742E67697420663862303066393466366230393538373233363337363565353838313133356532356462646465392900
+ - Name: .callgraph
+ Type: SHT_PROGBITS
+ Flags: [ SHF_LINK_ORDER ]
+ Link: .text
+ AddressAlign: 0x1
+ Content: 0000000000000000101140000000000002000000000000002444F731F5EECB3E00000000000000000000000000000000201140000000000002000000000000002444F731F5EECB3E00000000000000000000000000000000301140000000000002000000000000005486BC59814B8E300000000000000000000000000000000040114000000000000200000000000000CAAF769A600968FA03000000000000002444F731F5EECB3E65114000000000002444F731F5EECB3E7B114000000000005486BC59814B8E309511400000000000
+Symbols:
+ - Name: .interp
+ Type: STT_SECTION
+ Section: .interp
+ Value: 0x4002A8
+ - Name: .note.ABI-tag
+ Type: STT_SECTION
+ Section: .note.ABI-tag
+ Value: 0x4002C4
+ - Name: .hash
+ Type: STT_SECTION
+ Section: .hash
+ Value: 0x4002E8
+ - Name: .gnu.hash
+ Type: STT_SECTION
+ Section: .gnu.hash
+ Value: 0x400300
+ - Name: .dynsym
+ Type: STT_SECTION
+ Section: .dynsym
+ Value: 0x400320
+ - Name: .dynstr
+ Type: STT_SECTION
+ Section: .dynstr
+ Value: 0x400368
+ - Name: .gnu.version
+ Type: STT_SECTION
+ Section: .gnu.version
+ Value: 0x4003A0
+ - Name: .gnu.version_r
+ Type: STT_SECTION
+ Section: .gnu.version_r
+ Value: 0x4003A8
+ - Name: .rela.dyn
+ Type: STT_SECTION
+ Section: .rela.dyn
+ Value: 0x4003C8
+ - Name: .init
+ Type: STT_SECTION
+ Section: .init
+ Value: 0x401000
+ - Name: .text
+ Type: STT_SECTION
+ Section: .text
+ Value: 0x401020
+ - Name: .fini
+ Type: STT_SECTION
+ Section: .fini
+ Value: 0x401224
+ - Name: .rodata
+ Type: STT_SECTION
+ Section: .rodata
+ Value: 0x402000
+ - Name: .eh_frame_hdr
+ Type: STT_SECTION
+ Section: .eh_frame_hdr
+ Value: 0x402004
+ - Name: .eh_frame
+ Type: STT_SECTION
+ Section: .eh_frame
+ Value: 0x402050
+ - Name: .init_array
+ Type: STT_SECTION
+ Section: .init_array
+ Value: 0x403E40
+ - Name: .fini_array
+ Type: STT_SECTION
+ Section: .fini_array
+ Value: 0x403E48
+ - Name: .dynamic
+ Type: STT_SECTION
+ Section: .dynamic
+ Value: 0x403E50
+ - Name: .got
+ Type: STT_SECTION
+ Section: .got
+ Value: 0x403FF0
+ - Name: .got.plt
+ Type: STT_SECTION
+ Section: .got.plt
+ Value: 0x404000
+ - Name: .data
+ Type: STT_SECTION
+ Section: .data
+ Value: 0x404018
+ - Name: .bss
+ Type: STT_SECTION
+ Section: .bss
+ Value: 0x404028
+ - Name: .comment
+ Type: STT_SECTION
+ Section: .comment
+ - Name: .callgraph
+ Type: STT_SECTION
+ Section: .callgraph
+ - Name: crtstuff.c
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: deregister_tm_clones
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x401060
+ - Name: register_tm_clones
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x401090
+ - Name: __do_global_dtors_aux
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x4010D0
+ - Name: completed.0
+ Type: STT_OBJECT
+ Section: .bss
+ Value: 0x404028
+ Size: 0x1
+ - Name: __do_global_dtors_aux_fini_array_entry
+ Type: STT_OBJECT
+ Section: .fini_array
+ Value: 0x403E48
+ - Name: frame_dummy
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x401100
+ - Name: __frame_dummy_init_array_entry
+ Type: STT_OBJECT
+ Section: .init_array
+ Value: 0x403E40
+ - Name: all.c
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: 'crtstuff.c (1)'
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: __FRAME_END__
+ Type: STT_OBJECT
+ Section: .eh_frame
+ Value: 0x402184
+ - Type: STT_FILE
+ Index: SHN_ABS
+ - Name: __init_array_end
+ Section: .init_array
+ Value: 0x403E48
+ - Name: _DYNAMIC
+ Type: STT_OBJECT
+ Section: .dynamic
+ Value: 0x403E50
+ - Name: __init_array_start
+ Section: .init_array
+ Value: 0x403E40
+ - Name: __GNU_EH_FRAME_HDR
+ Section: .eh_frame_hdr
+ Value: 0x402004
+ - Name: _GLOBAL_OFFSET_TABLE_
+ Type: STT_OBJECT
+ Section: .got.plt
+ Value: 0x404000
+ - Name: __libc_csu_fini
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401220
+ Size: 0x1
+ - Name: data_start
+ Section: .data
+ Binding: STB_WEAK
+ Value: 0x404018
+ - Name: baz
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401130
+ Size: 0xE
+ - Name: _edata
+ Section: .data
+ Binding: STB_GLOBAL
+ Value: 0x404028
+ - Name: bar
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401120
+ Size: 0x6
+ - Name: _fini
+ Type: STT_FUNC
+ Section: .fini
+ Binding: STB_GLOBAL
+ Value: 0x401224
+ Other: [ STV_HIDDEN ]
+ - Name: '__libc_start_main at GLIBC_2.2.5'
+ Type: STT_FUNC
+ Binding: STB_GLOBAL
+ - Name: __data_start
+ Section: .data
+ Binding: STB_GLOBAL
+ Value: 0x404018
+ - Name: __gmon_start__
+ Binding: STB_WEAK
+ - Name: __dso_handle
+ Type: STT_OBJECT
+ Section: .data
+ Binding: STB_GLOBAL
+ Value: 0x404020
+ Other: [ STV_HIDDEN ]
+ - Name: _IO_stdin_used
+ Type: STT_OBJECT
+ Section: .rodata
+ Binding: STB_GLOBAL
+ Value: 0x402000
+ Size: 0x4
+ - Name: __libc_csu_init
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x4011C0
+ Size: 0x5D
+ - Name: foo
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401110
+ Size: 0x6
+ - Name: _end
+ Section: .bss
+ Binding: STB_GLOBAL
+ Value: 0x404030
+ - Name: _dl_relocate_static_pie
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401050
+ Size: 0x1
+ Other: [ STV_HIDDEN ]
+ - Name: _start
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401020
+ Size: 0x2B
+ - Name: __bss_start
+ Section: .bss
+ Binding: STB_GLOBAL
+ Value: 0x404028
+ - Name: main
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x401140
+ Size: 0x72
+ - Name: __TMC_END__
+ Type: STT_OBJECT
+ Section: .data
+ Binding: STB_GLOBAL
+ Value: 0x404028
+ Other: [ STV_HIDDEN ]
+ - Name: _init
+ Type: STT_FUNC
+ Section: .init
+ Binding: STB_GLOBAL
+ Value: 0x401000
+ Other: [ STV_HIDDEN ]
+DynamicSymbols:
+ - Name: __libc_start_main
+ Type: STT_FUNC
+ Binding: STB_GLOBAL
+ - Name: __gmon_start__
+ Binding: STB_WEAK
+...
diff --git a/llvm/tools/llvm-objdump/ObjdumpOpts.td b/llvm/tools/llvm-objdump/ObjdumpOpts.td
index c97e06f3ed173..231205c48b47d 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -45,6 +45,10 @@ defm build_id :
def : Flag<["-"], "a">, Alias<archive_headers>,
HelpText<"Alias for --archive-headers">;
+def call_graph_info : Flag<["--"], "call-graph-info">,
+ HelpText<"Dump call graph information including indirect call and target IDs "
+ "from call graph section, if available.">;
+
def demangle : Flag<["--"], "demangle">, HelpText<"Demangle symbol names">;
def : Flag<["-"], "C">, Alias<demangle>, HelpText<"Alias for --demangle">;
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 0316c4ba51da6..c42d71030273f 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -301,6 +301,7 @@ static uint64_t AdjustVMA;
static bool AllHeaders;
static std::string ArchName;
bool objdump::ArchiveHeaders;
+static bool CallGraphInfo;
bool objdump::Demangle;
bool objdump::Disassemble;
bool objdump::DisassembleAll;
@@ -348,6 +349,38 @@ static bool Wide;
std::string objdump::Prefix;
uint32_t objdump::PrefixStrip;
+// Enumeration of function kinds, and their mapping to function kind values
+// from call graph section (.callgraph).
+// Must stay in sync with enum from llvm/include/llvm/CodeGen/AsmPrinter.h.
+enum FunctionKind {
+ // Function cannot be target to indirect calls.
+ NOT_INDIRECT_TARGET = 0,
+ // Function may be target to indirect calls but its type id is unknown.
+ INDIRECT_TARGET_UNKNOWN_TID = 1,
+ // Function may be target to indirect calls and its type id is known.
+ INDIRECT_TARGET_KNOWN_TID = 2,
+
+ // Available in the binary but not listed in the call graph section.
+ NOT_LISTED = -1,
+};
+
+struct FunctionInfo {
+ std::string Name;
+ FunctionKind Kind;
+ struct DirectCallSite {
+ uint64_t CallSite;
+ uint64_t Callee;
+ DirectCallSite(uint64_t CallSite, uint64_t Callee)
+ : CallSite(CallSite), Callee(Callee) {}
+ };
+ SmallVector<DirectCallSite> DirectCallSites;
+ SmallVector<uint64_t> IndirectCallSites;
+};
+// Map function entry pc to function info.
+MapVector<uint64_t, FunctionInfo> FuncInfo;
+// Set of all indirect call sites.
+DenseSet<uint64_t> IndirectCallSites;
+
DebugFormat objdump::DbgVariables = DFDisabled;
DebugFormat objdump::DbgInlinedFunctions = DFDisabled;
@@ -1939,7 +1972,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
}
for (const SectionRef &Section : ToolSectionFilter(Obj)) {
- if (FilterSections.empty() && !DisassembleAll &&
+ if (((FilterSections.empty() && !DisassembleAll) || CallGraphInfo) &&
(!Section.isText() || Section.isVirtual()))
continue;
@@ -2253,6 +2286,14 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
BBAddrMapLabels);
}
+ if (CallGraphInfo && Symbols[SI].Type == ELF::STT_FUNC) {
+ auto FuncPc = Symbols[SI].Addr;
+ auto FuncName = Symbols[SI].Name.str();
+ FuncInfo[FuncPc].Name = FuncName;
+ // Initalize to be later updated while parsing the call graph section.
+ FuncInfo[FuncPc].Kind = NOT_LISTED;
+ }
+
if (DT->InstrAnalysis)
DT->InstrAnalysis->resetState();
@@ -2374,6 +2415,59 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
{ThisAddr + Size, Section.getIndex()},
Index + Size != End);
+ if (CallGraphInfo) {
+ auto MIA = DT->InstrAnalysis.get();
+ if (Disassembled && MIA->isCall(Inst)) {
+ // Call site address is the address of the instruction just
+ // next to the call instruction. This is the return address
+ // as appears on the stack trace.
+ uint64_t CallSitePc = SectionAddr + Index + Size;
+ uint64_t CallerPc = SectionAddr + Start + VMAAdjustment;
+ // Check the operands to decide whether this is an direct or
+ // indirect call.
+ // Assumption: a call instruction with at least one register
+ // operand is an indirect call. Otherwise, it is a direct call
+ // with exactly one immediate operand.
+ bool HasRegOperand = false;
+ unsigned int ImmOperandCount = 0;
+ for (unsigned int I = 0; I < Inst.getNumOperands(); I++) {
+ const auto &Operand = Inst.getOperand(I);
+ if (Operand.isReg()) {
+ HasRegOperand = true;
+ } else if (Operand.isImm()) {
+ ImmOperandCount++;
+ }
+ }
+ // Check if the assumption holds true.
+ assert(HasRegOperand ||
+ (!HasRegOperand && ImmOperandCount == 1) &&
+ "Call instruction is expected to have at least one "
+ "register operand (i.e., indirect call) or exactly "
+ "one immediate operand (i.e., direct call).");
+ if (HasRegOperand) {
+ // Indirect call.
+ IndirectCallSites.insert(CallSitePc);
+ FuncInfo[CallerPc].IndirectCallSites.push_back(CallSitePc);
+ } else {
+ // Direct call.
+ uint64_t CalleePc;
+ bool Res = MIA->evaluateBranch(Inst, SectionAddr + Index, Size,
+ CalleePc);
+ assert(Res && "Failed to evaluate direct call target address.");
+ FuncInfo[CallerPc].DirectCallSites.emplace_back(CallSitePc,
+ CalleePc);
+ }
+
+ if(FuncInfo[CallerPc].Name.empty()) {
+ for (size_t Index = 0; Index < SymbolsHere.size(); ++Index) {
+ if (SymbolsHere[Index].Addr == CallerPc) {
+ FuncInfo[CallerPc].Name = SymNamesHere[Index];
+ }
+ }
+ }
+ }
+ }
+
DT->InstPrinter->setCommentStream(CommentStream);
DT->Printer->printInst(
@@ -3131,6 +3225,207 @@ void Dumper::printSymbol(const SymbolRef &Symbol,
outs() << ' ' << SymName << '\n';
}
+static void printCallGraphInfo(ObjectFile *Obj) {
+ // Get function info through disassembly.
+ disassembleObject(Obj, /*InlineRelocs=*/false, outs());
+
+ // Get the .callgraph section.
+ StringRef CallGraphSectionName(".callgraph");
+ std::optional<object::SectionRef> CallGraphSection;
+ for (auto Sec : ToolSectionFilter(*Obj)) {
+ StringRef Name;
+ if (Expected<StringRef> NameOrErr = Sec.getName())
+ Name = *NameOrErr;
+ else
+ consumeError(NameOrErr.takeError());
+
+ if (Name == CallGraphSectionName) {
+ CallGraphSection = Sec;
+ break;
+ }
+ }
+ if (!CallGraphSection)
+ reportWarning("there is no .callgraph section", Obj->getFileName());
+
+ // Map type id to indirect call sites.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+ // Map type id to indirect targets.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+ // Instructions that are not indirect calls but have a type id are ignored.
+ uint64_t IgnoredICallIdCount = 0;
+ // Number of valid indirect calls with type ids.
+ uint64_t ICallWithTypeIdCount = 0;
+ if (CallGraphSection) {
+ StringRef CGSecContents = unwrapOrError(
+ CallGraphSection.value().getContents(), Obj->getFileName());
+ // TODO: some entries are written in pointer size. are they always 64-bit?
+ if (CGSecContents.size() % sizeof(uint64_t))
+ reportError(Obj->getFileName(), "Malformed .callgraph section.");
+
+ size_t Size = CGSecContents.size() / sizeof(uint64_t);
+ auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+ const auto *const End = It + Size;
+
+ auto CGHasNext = [&]() { return It < End; };
+ auto CGNext = [&]() -> uint64_t {
+ if (!CGHasNext())
+ reportError(Obj->getFileName(), "Malformed .callgraph section.");
+ return *It++;
+ };
+
+ // Parse the content
+ while (CGHasNext()) {
+ // Format version number.
+ uint64_t FormatVersionNumber = CGNext();
+ if (FormatVersionNumber != 0)
+ reportError(Obj->getFileName(),
+ "Unknown format version in .callgraph section.");
+
+ // Function entry pc.
+ uint64_t FuncEntryPc = CGNext();
+ if (!FuncInfo.count(FuncEntryPc))
+ reportError(Obj->getFileName(),
+ "Invalid function entry pc in .callgraph section.");
+
+ // Function kind.
+ uint64_t Kind = CGNext();
+ switch (Kind) {
+ case 0: // not an indirect target
+ FuncInfo[FuncEntryPc].Kind = NOT_INDIRECT_TARGET;
+ break;
+ case 1: // indirect target with unknown type id
+ FuncInfo[FuncEntryPc].Kind = INDIRECT_TARGET_UNKNOWN_TID;
+ break;
+ case 2: // indirect target with known type id
+ FuncInfo[FuncEntryPc].Kind = INDIRECT_TARGET_KNOWN_TID;
+ TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+ break;
+ default:
+ reportError(Obj->getFileName(),
+ "Unknown function kind in .callgraph section.");
+ }
+
+ // Read call sites.
+ uint64_t CallSiteCount = CGNext();
+ for (unsigned long I = 0; I < CallSiteCount; I++) {
+ uint64_t TypeId = CGNext();
+ uint64_t CallSitePc = CGNext();
+ if (IndirectCallSites.count(CallSitePc)) {
+ TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+ ICallWithTypeIdCount++;
+ } else {
+ // FIXME: .callgraph may have type ids for calls that are lowered to
+ // jump, which is why ignoring indirect call type ids are expected.
+ // However, actual indirect call site references stored as relocation
+ // entries here may also be discarded.
+ IgnoredICallIdCount++;
+ }
+ }
+ }
+
+ // Print any required warnings regarding the callgraph section.
+ if (IgnoredICallIdCount)
+ reportWarning("callgraph section has type ids for " +
+ std::to_string(IgnoredICallIdCount) + " instructions " +
+ "which are not indirect calls",
+ Obj->getFileName());
+
+ if (auto ICallWithoutTypeIdCount =
+ IndirectCallSites.size() - ICallWithTypeIdCount)
+ reportWarning("callgraph section does not have type ids for " +
+ std::to_string(ICallWithoutTypeIdCount) +
+ " indirect calls",
+ Obj->getFileName());
+
+ uint64_t NotListedCount = 0;
+ uint64_t UnknownCount = 0;
+ for (const auto &El : FuncInfo) {
+ NotListedCount += El.second.Kind == NOT_LISTED;
+ UnknownCount += El.second.Kind == INDIRECT_TARGET_UNKNOWN_TID;
+ }
+ if (NotListedCount)
+ reportWarning("callgraph section does not have information for " +
+ std::to_string(NotListedCount) + " functions",
+ Obj->getFileName());
+ if (UnknownCount)
+ reportWarning("callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) + " indirect targets",
+ Obj->getFileName());
+
+ // Print indirect targets
+ outs() << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
+
+ // Print indirect targets with unknown type.
+ // For completeness, functions for which the call graph section does not
+ // provide information are included.
+ if (NotListedCount || UnknownCount) {
+ outs() << "\nUNKNOWN";
+ for (const auto &El : FuncInfo) {
+ uint64_t FuncEntryPc = El.first;
+ FunctionKind FuncKind = El.second.Kind;
+ if (FuncKind == NOT_LISTED || FuncKind == INDIRECT_TARGET_UNKNOWN_TID)
+ outs() << " " << format("%lx", FuncEntryPc);
+ }
+ }
+
+ // Print indirect targets to type id mapping.
+ for (const auto &El : TypeIdToIndirTargets) {
+ uint64_t TypeId = El.first;
+ outs() << "\n" << format("%lx", TypeId);
+ for (uint64_t IndirTargetPc : El.second)
+ outs() << " " << format("%lx", IndirTargetPc);
+ }
+
+ // Print indirect calls to type id mapping. Any indirect call without a
+ // type id can be deduced by comparing this list to indirect call sites
+ // list.
+ outs() << "\n\nINDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])";
+ for (const auto &El : TypeIdToIndirCallSites) {
+ uint64_t TypeId = El.first;
+ outs() << "\n" << format("%lx", TypeId);
+ for (uint64_t IndirCallSitePc : El.second)
+ outs() << " " << format("%lx", IndirCallSitePc);
+ }
+ }
+
+ // Print function entry to indirect call site addresses mapping from disasm.
+ outs() << "\n\nINDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])";
+ for (const auto &El : FuncInfo) {
+ auto CallerPc = El.first;
+ auto FuncIndirCallSites = El.second.IndirectCallSites;
+ if (!FuncIndirCallSites.empty()) {
+ outs() << "\n" << format("%lx", CallerPc);
+ for (auto IndirCallSitePc : FuncIndirCallSites)
+ outs() << " " << format("%lx", IndirCallSitePc);
+ }
+ }
+
+ // Print function entry to direct call site and target function entry
+ // addresses mapping from disasm.
+ outs()
+ << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
+ for (const auto &El : FuncInfo) {
+ auto CallerPc = El.first;
+ auto FuncDirCallSites = El.second.DirectCallSites;
+ if (!FuncDirCallSites.empty()) {
+ outs() << "\n" << format("%lx", CallerPc);
+ for (const FunctionInfo::DirectCallSite &DCS : FuncDirCallSites) {
+ outs() << " " << format("%lx", DCS.CallSite) << " "
+ << format("%lx", DCS.Callee);
+ }
+ }
+ }
+
+ // Print function entry pc to function name mapping.
+ outs() << "\n\nFUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)";
+ for (const auto &El : FuncInfo) {
+ uint64_t EntryPc = El.first;
+ const auto &Name = El.second.Name;
+ outs() << "\n" << format("%lx", EntryPc) << " " << Name;
+ }
+ outs() << "\n";
+}
+
static void printUnwindInfo(const ObjectFile *O) {
outs() << "Unwind info:\n\n";
@@ -3405,6 +3700,8 @@ static void dumpObject(ObjectFile *O, const Archive *A = nullptr,
printRawClangAST(O);
if (FaultMapSection)
printFaultMaps(O);
+ if (CallGraphInfo)
+ printCallGraphInfo(O);
if (Offloading)
dumpOffloadBinary(*O, StringRef(ArchName));
}
@@ -3571,6 +3868,7 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
AllHeaders = InputArgs.hasArg(OBJDUMP_all_headers);
ArchName = InputArgs.getLastArgValue(OBJDUMP_arch_name_EQ).str();
ArchiveHeaders = InputArgs.hasArg(OBJDUMP_archive_headers);
+ CallGraphInfo = InputArgs.hasArg(OBJDUMP_call_graph_info);
Demangle = InputArgs.hasArg(OBJDUMP_demangle);
Disassemble = InputArgs.hasArg(OBJDUMP_disassemble);
DisassembleAll = InputArgs.hasArg(OBJDUMP_disassemble_all);
@@ -3812,6 +4110,7 @@ int llvm_objdump_main(int argc, char **argv, const llvm::ToolContext &) {
!DynamicRelocations && !FileHeaders && !PrivateHeaders && !RawClangAST &&
!Relocations && !SectionHeaders && !SectionContents && !SymbolTable &&
!DynamicSymbolTable && !UnwindInfo && !FaultMapSection && !Offloading &&
+ !CallGraphInfo &&
!(MachOOpt &&
(Bind || DataInCode || ChainedFixups || DyldInfo || DylibId ||
DylibsUsed || ExportsTrie || FirstPrivateHeader ||
>From f5613af85f9d4ad3da4b46b8683cbe88276ab158 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 29 Jul 2025 02:04:16 +0000
Subject: [PATCH 2/3] Objdump callgraph section disassembly bug fixes and
formatting.
---
llvm/tools/llvm-objdump/llvm-objdump.cpp | 65 ++++++++++++++----------
1 file changed, 37 insertions(+), 28 deletions(-)
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index c42d71030273f..7a67815a7d3b8 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -352,7 +352,7 @@ uint32_t objdump::PrefixStrip;
// Enumeration of function kinds, and their mapping to function kind values
// from call graph section (.callgraph).
// Must stay in sync with enum from llvm/include/llvm/CodeGen/AsmPrinter.h.
-enum FunctionKind {
+enum class FunctionKind : uint64_t {
// Function cannot be target to indirect calls.
NOT_INDIRECT_TARGET = 0,
// Function may be target to indirect calls but its type id is unknown.
@@ -361,7 +361,7 @@ enum FunctionKind {
INDIRECT_TARGET_KNOWN_TID = 2,
// Available in the binary but not listed in the call graph section.
- NOT_LISTED = -1,
+ NOT_LISTED = 3,
};
struct FunctionInfo {
@@ -2162,6 +2162,8 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
if (!PrintedSection) {
PrintedSection = true;
OS << "\nDisassembly of section ";
+ // llvm::report_fatal_error("NO PRINTING BUSINESS FOR
+ // MEeeeeeeeeeeeeeeeeee");
if (!SegmentName.empty())
OS << SegmentName << ",";
OS << SectionName << ":\n";
@@ -2208,6 +2210,14 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
for (size_t SHI = 0; SHI < SymbolsHere.size(); ++SHI) {
SymbolInfoTy Symbol = SymbolsHere[SHI];
+ if (CallGraphInfo && Symbol.Type == ELF::STT_FUNC) {
+ auto FuncPc = Symbol.Addr;
+ auto FuncName = Symbol.Name.str();
+ FuncInfo[FuncPc].Name = FuncName;
+ // Initalize to be later updated while parsing the call graph section.
+ FuncInfo[FuncPc].Kind = FunctionKind::NOT_LISTED;
+ }
+
Expected<bool> RespondedOrErr = DT->DisAsm->onSymbolStart(
Symbol, Size, Bytes.slice(Start, End - Start), SectionAddr + Start);
@@ -2286,14 +2296,6 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
BBAddrMapLabels);
}
- if (CallGraphInfo && Symbols[SI].Type == ELF::STT_FUNC) {
- auto FuncPc = Symbols[SI].Addr;
- auto FuncName = Symbols[SI].Name.str();
- FuncInfo[FuncPc].Name = FuncName;
- // Initalize to be later updated while parsing the call graph section.
- FuncInfo[FuncPc].Kind = NOT_LISTED;
- }
-
if (DT->InstrAnalysis)
DT->InstrAnalysis->resetState();
@@ -2420,7 +2422,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
if (Disassembled && MIA->isCall(Inst)) {
// Call site address is the address of the instruction just
// next to the call instruction. This is the return address
- // as appears on the stack trace.
+ // as it appears on the stack trace.
uint64_t CallSitePc = SectionAddr + Index + Size;
uint64_t CallerPc = SectionAddr + Start + VMAAdjustment;
// Check the operands to decide whether this is an direct or
@@ -2440,10 +2442,10 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
}
// Check if the assumption holds true.
assert(HasRegOperand ||
- (!HasRegOperand && ImmOperandCount == 1) &&
- "Call instruction is expected to have at least one "
- "register operand (i.e., indirect call) or exactly "
- "one immediate operand (i.e., direct call).");
+ (!HasRegOperand && ImmOperandCount == 1) &&
+ "Call instruction is expected to have at least one "
+ "register operand (i.e., indirect call) or exactly "
+ "one immediate operand (i.e., direct call).");
if (HasRegOperand) {
// Indirect call.
IndirectCallSites.insert(CallSitePc);
@@ -2452,21 +2454,21 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
// Direct call.
uint64_t CalleePc;
bool Res = MIA->evaluateBranch(Inst, SectionAddr + Index, Size,
- CalleePc);
+ CalleePc);
assert(Res && "Failed to evaluate direct call target address.");
FuncInfo[CallerPc].DirectCallSites.emplace_back(CallSitePc,
CalleePc);
}
- if(FuncInfo[CallerPc].Name.empty()) {
+ if (FuncInfo[CallerPc].Name.empty()) {
for (size_t Index = 0; Index < SymbolsHere.size(); ++Index) {
if (SymbolsHere[Index].Addr == CallerPc) {
FuncInfo[CallerPc].Name = SymNamesHere[Index];
}
- }
- }
+ }
+ }
}
- }
+ }
DT->InstPrinter->setCommentStream(CommentStream);
@@ -3226,8 +3228,9 @@ void Dumper::printSymbol(const SymbolRef &Symbol,
}
static void printCallGraphInfo(ObjectFile *Obj) {
- // Get function info through disassembly.
- disassembleObject(Obj, /*InlineRelocs=*/false, outs());
+ // Get function info through disassembly. Suppress disassembler outputs to
+ // console.
+ disassembleObject(Obj, /*InlineRelocs=*/false, nulls());
// Get the .callgraph section.
StringRef CallGraphSectionName(".callgraph");
@@ -3291,13 +3294,13 @@ static void printCallGraphInfo(ObjectFile *Obj) {
uint64_t Kind = CGNext();
switch (Kind) {
case 0: // not an indirect target
- FuncInfo[FuncEntryPc].Kind = NOT_INDIRECT_TARGET;
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
break;
case 1: // indirect target with unknown type id
- FuncInfo[FuncEntryPc].Kind = INDIRECT_TARGET_UNKNOWN_TID;
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
break;
case 2: // indirect target with known type id
- FuncInfo[FuncEntryPc].Kind = INDIRECT_TARGET_KNOWN_TID;
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
break;
default:
@@ -3339,9 +3342,14 @@ static void printCallGraphInfo(ObjectFile *Obj) {
uint64_t NotListedCount = 0;
uint64_t UnknownCount = 0;
+
+ llvm::sort(FuncInfo,
+ [](const auto &A, const auto &B) { return A.first < B.first; });
+
for (const auto &El : FuncInfo) {
- NotListedCount += El.second.Kind == NOT_LISTED;
- UnknownCount += El.second.Kind == INDIRECT_TARGET_UNKNOWN_TID;
+ NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+ UnknownCount +=
+ El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
}
if (NotListedCount)
reportWarning("callgraph section does not have information for " +
@@ -3363,7 +3371,8 @@ static void printCallGraphInfo(ObjectFile *Obj) {
for (const auto &El : FuncInfo) {
uint64_t FuncEntryPc = El.first;
FunctionKind FuncKind = El.second.Kind;
- if (FuncKind == NOT_LISTED || FuncKind == INDIRECT_TARGET_UNKNOWN_TID)
+ if (FuncKind == FunctionKind::NOT_LISTED ||
+ FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
outs() << " " << format("%lx", FuncEntryPc);
}
}
>From 8a60a270dc8ef934caf139a16c24f15aa62d7cc7 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 29 Jul 2025 02:04:36 +0000
Subject: [PATCH 3/3] Formatting fixes in objdumpopts file.
---
llvm/tools/llvm-objdump/ObjdumpOpts.td | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/llvm/tools/llvm-objdump/ObjdumpOpts.td b/llvm/tools/llvm-objdump/ObjdumpOpts.td
index 231205c48b47d..cb9e0d15fca65 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -45,9 +45,11 @@ defm build_id :
def : Flag<["-"], "a">, Alias<archive_headers>,
HelpText<"Alias for --archive-headers">;
-def call_graph_info : Flag<["--"], "call-graph-info">,
- HelpText<"Dump call graph information including indirect call and target IDs "
- "from call graph section, if available.">;
+def call_graph_info
+ : Flag<["--"], "call-graph-info">,
+ HelpText<
+ "Dump call graph information including indirect call and target IDs "
+ "from call graph section, if available.">;
def demangle : Flag<["--"], "demangle">, HelpText<"Demangle symbol names">;
def : Flag<["-"], "C">, Alias<demangle>, HelpText<"Alias for --demangle">;
More information about the llvm-commits
mailing list