[llvm-branch-commits] [llvm] [llvm-readobj][ELF] Print Names for call graph relocations (PR #230654)

Prabhu Rajasekaran via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Oct 15 04:28:44 PDT 2026


https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/230654

>From b357264dbbb2cd57bf55d3c1edea7fd65abc745e Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 9 Oct 2026 12:19:20 -0700
Subject: [PATCH] [llvm-readobj][ELF] Print Names for call graph relocations

In a relocatable object file, an entry identified by a relocation had no
Names. Compute the address that the relocation resolves to with
RelocationResolver, look up the functions at that address in the section
of the relocation's symbol, and print them as Names before the
Relocation object.

As with Names for an Address, the names follow --demangle. Nothing is
printed for an undefined symbol, or when no function is at the address.
A relocation type that RelocationResolver doesn't support, or a symbol
whose section can't be found, gets a warning.

Assisted-by: Gemini
---
 .../ELF/call-graph-info-warn-malformed.test   | 52 +++++++++++++++
 .../llvm-readobj/ELF/call-graph-info.test     | 34 +++++++---
 llvm/tools/llvm-readobj/ELFDumper.cpp         | 63 ++++++++++++++++++-
 3 files changed, 138 insertions(+), 11 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
index c6d607fe1fa732..0a623f694d79c1 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
@@ -462,3 +462,55 @@ Sections:
 Symbols:
   - Name: foo
   - Name: bar
+...
+
+## Check that a warning is reported when the address that a relocation resolves
+## to can't be computed, because the relocation has an unsupported type or the
+## section of its symbol can't be found. The Relocation object is still
+## printed, without Names.
+# RUN: yaml2obj --docnum=9 -DTYPE=R_X86_64_PLT32 %s -o %t17
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t17 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t17 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=NO-NAMES,UNSUPPORTED-RELOC
+# RUN: yaml2obj --docnum=9 -DINDEX=255 %s -o %t18
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t18 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t18 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=NO-NAMES,BAD-SYM-SECTION
+
+# NO-NAMES:               Function {
+# UNSUPPORTED-RELOC-NEXT: warning: '[[FILE]]': SHT_RELA section with index 3 contains an unsupported relocation at offset 0x2: R_X86_64_PLT32
+# BAD-SYM-SECTION-NEXT:   warning: '[[FILE]]': unable to get the section of symbol with index 1: invalid section index: 255
+# NO-NAMES-NEXT:            Relocation {
+# NO-NAMES-NEXT:              SymbolIndex: 1
+# NO-NAMES-NEXT:              SymbolName: foo
+# NO-NAMES-NEXT:            }
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+  - Name: .llvm_call_graph
+    Type: SHT_LLVM_CALL_GRAPH
+    ContentArray: [
+        '0', ## Format version number
+        '0', ## Flags
+        '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
+        '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s TypeID
+      ]
+  - Name: .rela.llvm_call_graph
+    Type: SHT_RELA
+    Info: .llvm_call_graph
+    Relocations:
+      - Offset: 0x2
+        Symbol: foo
+        Type:   [[TYPE=R_X86_64_64]]
+Symbols:
+  - Name:  foo
+    Type:  STT_FUNC
+    Index: [[INDEX=1]] ## .text
+...
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
index 1be0c72ff2a59f..c5f76396df489a 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -453,7 +453,8 @@ Symbols:
 ## relocations. An implicit addend is the value stored in the address field,
 ## sign-extended, and the Thumb bit is kept in it. The Thumb bit is not cleared
 ## from the offsets of address fields either, so a field at an odd offset still
-## gets its relocation.
+## gets its relocation. Names are looked up at the address that a relocation
+## resolves to, with the Thumb bit cleared.
 # RUN: yaml2obj --docnum=3 %s -o %t3
 # RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t3 2>&1 | count 0
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t3 2>&1 | \
@@ -465,6 +466,7 @@ Symbols:
 
 # LLVM-ARM-THUMB:      CallGraph [
 # LLVM-ARM-THUMB-NEXT:   Function {
+# LLVM-ARM-THUMB-NEXT:     Names: [foo]
 # LLVM-ARM-THUMB-NEXT:     Relocation {
 # LLVM-ARM-THUMB-NEXT:       SymbolIndex: 1
 # LLVM-ARM-THUMB-NEXT:       SymbolName: foo
@@ -482,6 +484,7 @@ Symbols:
 # LLVM-ARM-THUMB-NEXT:         }
 # LLVM-ARM-THUMB-NEXT:       }
 # LLVM-ARM-THUMB-NEXT:       {
+# LLVM-ARM-THUMB-NEXT:         Names: [baz]
 # LLVM-ARM-THUMB-NEXT:         Relocation {
 # LLVM-ARM-THUMB-NEXT:           SymbolIndex: 3
 # LLVM-ARM-THUMB-NEXT:           Addend: 33
@@ -495,6 +498,9 @@ Symbols:
 # JSON-ARM-THUMB:       "CallGraph": [
 # JSON-ARM-THUMB-NEXT:    {
 # JSON-ARM-THUMB-NEXT:      "Function": {
+# JSON-ARM-THUMB-NEXT:        "Names": [
+# JSON-ARM-THUMB-NEXT:          "foo"
+# JSON-ARM-THUMB-NEXT:        ],
 # JSON-ARM-THUMB-NEXT:        "Relocation": {
 # JSON-ARM-THUMB-NEXT:          "SymbolIndex": 1,
 # JSON-ARM-THUMB-NEXT:          "SymbolName": "foo",
@@ -512,6 +518,9 @@ Symbols:
 # JSON-ARM-THUMB-NEXT:            }
 # JSON-ARM-THUMB-NEXT:          },
 # JSON-ARM-THUMB-NEXT:          {
+# JSON-ARM-THUMB-NEXT:            "Names": [
+# JSON-ARM-THUMB-NEXT:              "baz"
+# JSON-ARM-THUMB-NEXT:            ],
 # JSON-ARM-THUMB-NEXT:            "Relocation": {
 # JSON-ARM-THUMB-NEXT:              "SymbolIndex": 3,
 # JSON-ARM-THUMB-NEXT:              "Addend": 33
@@ -541,7 +550,7 @@ Sections:
       0x01, 0x00, 0x00, 0x00, ## Offset 0x2: FunctionEntryPC, implicit addend 1 (the Thumb bit)
       0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12, ## FunctionTypeID: 0x123456789ABCDEF0
       0x02, ## NumDirectCallees
-      0xFC, 0xFF, 0xFF, 0xFF, ## Offset 0xF: Callee 1, implicit addend -4
+      0xFC, 0xFF, 0xFF, 0xFF, ## Offset 0xF: Callee 1, implicit addend -4 (no function at bar-4)
       0x21, 0x00, 0x00, 0x00  ## Offset 0x13: Callee 2 (baz), implicit addend 0x21
     ]
   - Name:   .rel.llvm.callgraph
@@ -1021,14 +1030,16 @@ Symbols:
     Value:    0x17D0
 ...
 
-## Check the fields of the Relocation object. SymbolName is the name exactly as
-## recorded in st_name: it is not demangled, and it is omitted for an unnamed
-## STT_SECTION symbol and for symbol index 0. With --demangle, the demangled
-## name is printed as DemangledName. Addend is omitted when it is 0.
+## Check the fields of the Relocation object, and the Names printed before it.
+## SymbolName is the name exactly as recorded in st_name: it is not demangled,
+## and it is omitted for an unnamed STT_SECTION symbol and for symbol index 0.
+## With --demangle, the demangled name is printed as DemangledName. Addend is
+## omitted when it is 0. Names lists the functions at the address that the
+## relocation resolves to, and is demangled with --demangle.
 # RUN: yaml2obj --docnum=9 %s -o %t9
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t9 2>&1 | \
 # RUN:   FileCheck %s --match-full-lines --implicit-check-not=warning: \
-# RUN:     --check-prefix=LLVM-FIELDS
+# RUN:     --check-prefixes=LLVM-FIELDS,MANGLED
 # RUN: llvm-readobj --elf-output-style=LLVM --demangle --call-graph-info %t9 2>&1 | \
 # RUN:   FileCheck %s --match-full-lines --implicit-check-not=warning: \
 # RUN:     --check-prefixes=LLVM-FIELDS,DEMANGLE
@@ -1038,6 +1049,8 @@ Symbols:
 
 # LLVM-FIELDS:      CallGraph [
 # LLVM-FIELDS-NEXT:   Function {
+# MANGLED-NEXT:         Names: [_Z3foov]
+# DEMANGLE-NEXT:        Names: [foo()]
 # LLVM-FIELDS-NEXT:     Relocation {
 # LLVM-FIELDS-NEXT:       SymbolIndex: 1
 # LLVM-FIELDS-NEXT:       Addend: 16
@@ -1047,6 +1060,8 @@ Symbols:
 # LLVM-FIELDS-NEXT:     TypeID: 0x0
 # LLVM-FIELDS-NEXT:     DirectCallees [
 # LLVM-FIELDS-NEXT:       {
+# MANGLED-NEXT:             Names: [_Z3foov]
+# DEMANGLE-NEXT:            Names: [foo()]
 # LLVM-FIELDS-NEXT:         Relocation {
 # LLVM-FIELDS-NEXT:           SymbolIndex: 2
 # LLVM-FIELDS-NEXT:           SymbolName: _Z3foov
@@ -1072,7 +1087,10 @@ Symbols:
 # LLVM-FIELDS-NEXT:   }
 # LLVM-FIELDS-NEXT: ]
 
-# JSON-FIELDS:      "Relocation": {
+# JSON-FIELDS:      "Names": [
+# JSON-FIELDS-NEXT:   "_Z3foov"
+# JSON-FIELDS-NEXT: ],
+# JSON-FIELDS-NEXT: "Relocation": {
 # JSON-FIELDS-NEXT:   "SymbolIndex": 1,
 # JSON-FIELDS-NEXT:   "Addend": 16
 # JSON-FIELDS-NEXT: },
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index dbd5f47b55db5c..b85271ea9c9b53 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -809,7 +809,14 @@ template <typename ELFT> class LLVMELFDumper : public ELFDumper<ELFT> {
   void printStackSizeEntry(uint64_t Size,
                            ArrayRef<std::string> FuncNames) override;
   void printCallGraphRelocation(const CallGraphFunc<ELFT> &Func,
-                                const Elf_Shdr &SymTab);
+                                const Elf_Shdr &RelSec, const Elf_Shdr &SymTab);
+  // Returns the names of the functions at the address that the relocation for
+  // Func resolves to. Sym is the symbol of the relocation, and only functions
+  // in its section are looked up.
+  SmallVector<std::string>
+  getCallGraphTargetNames(const CallGraphFunc<ELFT> &Func,
+                          const Elf_Shdr &RelSec, const Elf_Shdr &SymTab,
+                          const Elf_Sym &Sym);
 
   void printMipsGOT(const MipsGOTParser<ELFT> &Parser) override;
   void printMipsPLT(const MipsGOTParser<ELFT> &Parser) override;
@@ -8397,9 +8404,53 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
   }
 }
 
+template <class ELFT>
+SmallVector<std::string> LLVMELFDumper<ELFT>::getCallGraphTargetNames(
+    const CallGraphFunc<ELFT> &Func, const Elf_Shdr &RelSec,
+    const Elf_Shdr &SymTab, const Elf_Sym &Sym) {
+  const Relocation<ELFT> &R = *Func.Reloc;
+  Expected<const Elf_Shdr *> SymSecOrErr =
+      this->Obj.getSection(Sym, &SymTab, this->getShndxTable(&SymTab));
+  if (!SymSecOrErr) {
+    this->reportUniqueWarning(
+        "unable to get the section of symbol with index " + Twine(R.Symbol) +
+        ": " + toString(SymSecOrErr.takeError()));
+    return {};
+  }
+  // Only a symbol defined in a section, e.g. not an undefined one, has an
+  // address to look up.
+  if (!*SymSecOrErr)
+    return {};
+
+  auto [IsSupported, Resolve] = getRelocationResolver(this->ObjF);
+  if (!IsSupported || !IsSupported(R.Type)) {
+    this->reportUniqueWarning(
+        formatv("{0} contains an unsupported relocation at offset {1:x+}: {2}",
+                this->describe(RelSec), R.Offset,
+                this->Obj.getRelocationTypeName(R.Type)));
+    return {};
+  }
+  // Pass the value stored in the field only for a relocation without an
+  // explicit addend. Some resolvers, like ARM's, expect one of them to be 0.
+  uint64_t Addr = Resolve(R.Type, R.Offset, Sym.st_value,
+                          R.Addend ? 0 : Func.FieldValue, R.Addend.value_or(0));
+  // Truncate the result to the width of an address field.
+  Addr = static_cast<typename ELFT::uint>(Addr);
+  // Clear the Thumb bit, as for Address.
+  if (this->Obj.getHeader().e_machine == ELF::EM_ARM)
+    Addr &= ~1;
+
+  SmallVector<std::string> Names;
+  for (uint32_t Index :
+       this->getSymbolIndexesForFunctionAddress(Addr, *SymSecOrErr))
+    Names.push_back(this->getStaticSymbolName(Index));
+  return Names;
+}
+
 template <class ELFT>
 void LLVMELFDumper<ELFT>::printCallGraphRelocation(
-    const CallGraphFunc<ELFT> &Func, const Elf_Shdr &SymTab) {
+    const CallGraphFunc<ELFT> &Func, const Elf_Shdr &RelSec,
+    const Elf_Shdr &SymTab) {
   const Relocation<ELFT> &R = *Func.Reloc;
   // Print the symbol name exactly as recorded in st_name. RelSymbol::Name is
   // not used because it is demangled with --demangle, and is made up from the
@@ -8416,6 +8467,12 @@ void LLVMELFDumper<ELFT>::printCallGraphRelocation(
       SymbolName = *NameOrErr;
     else
       this->reportUniqueWarning(NameOrErr.takeError());
+
+    // As with Address, Names comes before the Relocation object.
+    SmallVector<std::string> Names =
+        getCallGraphTargetNames(Func, RelSec, SymTab, *Sym);
+    if (!Names.empty())
+      W.printList("Names", Names);
   }
 
   DictScope D(W, "Relocation");
@@ -8497,7 +8554,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
 
     auto PrintFunc = [&](const CallGraphFunc<ELFT> &Func) {
       if (Func.Reloc) {
-        printCallGraphRelocation(Func, *RelocSymTab);
+        printCallGraphRelocation(Func, *CGRelSection, *RelocSymTab);
         return;
       }
       // Without a relocation, the function is identified by the value stored



More information about the llvm-branch-commits mailing list