[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