[llvm-branch-commits] [llvm] [llvm-readobj][ELF] Fall back to Address when no single relocation applies (PR #230653)

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


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

>From fbced9e59e9c1d1116168e3f470bd84b748083a0 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 9 Oct 2026 11:46:27 -0700
Subject: [PATCH] [llvm-readobj][ELF] Fall back to Address when no single
 relocation applies

In a relocatable object file, --call-graph-info identifies a function by
the relocation that applies to its address field. If a field had no
relocation, nothing in the output identified the function. If the
relocation section had an invalid sh_link, the dump stopped. If a field
had more than one relocation, the first one was used silently.

In all of these cases, print the value stored in the field as Address,
as for linked files. Warn once if a call graph section has no relocation
section, and warn about each field with no relocation or more than one.
If the relocations can't be read, only that is reported.

Assisted-by: Gemini
---
 .../ELF/call-graph-info-warn-malformed.test   | 157 ++++++++++++++++--
 llvm/tools/llvm-readobj/ELFDumper.cpp         | 118 +++++++------
 2 files changed, 216 insertions(+), 59 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 491c946290f557..c6d607fe1fa732 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
@@ -200,19 +200,50 @@ Sections:
     ]
 ...
 
-## Check missing relocation information.
+## Check that a single warning is reported when a SHT_LLVM_CALL_GRAPH section in
+## a relocatable object file has no relocation section, and that each function
+## is then identified by the value stored in its address field.
 # RUN: yaml2obj --docnum=6 %s -o %t11
 # RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t11 2>&1 | count 0
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t11 2>&1 | \
-# RUN:   FileCheck %s -DFILE=%t11 --check-prefixes=WARN-MISSING-INFO
+# RUN:   FileCheck %s -DFILE=%t11 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=WARN-MISSING-INFO,LLVM-MISSING-INFO
 # RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t11 2>&1 | \
-# RUN:   FileCheck %s -DFILE=%t11 --check-prefixes=WARN-MISSING-INFO
+# RUN:   FileCheck %s -DFILE=%t11 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=WARN-MISSING-INFO,JSON-MISSING-INFO
 
 # WARN-MISSING-INFO: warning: '[[FILE]]': SHT_LLVM_CALL_GRAPH type section has unknown type ID for 2 indirect targets
-# WARN-MISSING-INFO: warning: '[[FILE]]': missing relocation for symbol at offset 2
-# WARN-MISSING-INFO: warning: '[[FILE]]': missing relocation for symbol at offset 19
-# WARN-MISSING-INFO: warning: '[[FILE]]': missing relocation for symbol at offset 29
-# WARN-MISSING-INFO: warning: '[[FILE]]': missing relocation for symbol at offset 56
+# WARN-MISSING-INFO: warning: '[[FILE]]': unable to get relocation section for SHT_LLVM_CALL_GRAPH section with index 2
+
+# LLVM-MISSING-INFO:      CallGraph [
+# LLVM-MISSING-INFO-NEXT:   Function {
+# LLVM-MISSING-INFO-NEXT:     Address: 0x1{{$}}
+# LLVM-MISSING-INFO-NEXT:     Version: 0
+# LLVM-MISSING-INFO-NEXT:     IsIndirectTarget: Yes
+# LLVM-MISSING-INFO-NEXT:     TypeID: 0x0
+# LLVM-MISSING-INFO-NEXT:     DirectCallees [
+# LLVM-MISSING-INFO-NEXT:       {
+# LLVM-MISSING-INFO-NEXT:         Address: 0x1{{$}}
+# LLVM-MISSING-INFO-NEXT:       }
+# LLVM-MISSING-INFO-NEXT:     ]
+# LLVM-MISSING-INFO-NEXT:     IndirectTypeIDs: []
+# LLVM-MISSING-INFO-NEXT:   }
+# LLVM-MISSING-INFO-NEXT:   Function {
+# LLVM-MISSING-INFO-NEXT:     Address: 0x6{{$}}
+# LLVM-MISSING-INFO:        Function {
+# LLVM-MISSING-INFO-NEXT:     Address: 0xA{{$}}
+
+# JSON-MISSING-INFO:      "Function": {
+# JSON-MISSING-INFO-NEXT:   "Address": 1,
+# JSON-MISSING-INFO:        "DirectCallees": [
+# JSON-MISSING-INFO-NEXT:     {
+# JSON-MISSING-INFO-NEXT:       "Address": 1{{$}}
+# JSON-MISSING-INFO-NEXT:     }
+# JSON-MISSING-INFO-NEXT:   ],
+# JSON-MISSING-INFO:        "Function": {
+# JSON-MISSING-INFO-NEXT:   "Address": 6,
+# JSON-MISSING-INFO:        "Function": {
+# JSON-MISSING-INFO-NEXT:   "Address": 10,
 
 --- !ELF
 FileHeader:
@@ -227,10 +258,10 @@ Sections:
     ContentArray: [
         '0', ## Format version number
         '3', ## Flag IsIndirectTarget true, HasDirectCallees true
-        '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
+        '1', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
         '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s TypeID Unknown
         '1', ## NumDirectCallees
-        '0', '0', '0', '0', '0', '0', '0', '0', ## Direct callee foo()'s address
+        '1', '0', '0', '0', '0', '0', '0', '0', ## Direct callee foo()'s address
         '0', ## Format version number
         '5', ## Flag IsIndirectTarget true, HasIndirectTargetTypeIDs true
         '6', '0', '0', '0', '0', '0', '0', '0', ## bar()'s address
@@ -285,13 +316,16 @@ Symbols:
 # BAD-SYMBOL-NEXT:   }
 
 ## Check warning when the relocation section linked to SHT_LLVM_CALL_GRAPH has an invalid sh_link.
+## The function is still dumped, identified by the value stored in its address field.
 # RUN: yaml2obj --docnum=7 -DLINK=255 -DINFO=.llvm_call_graph %s -o %t13
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t13 2>&1 | \
-# RUN:   FileCheck %s -DFILE=%t13 --check-prefix=INVALID-RELA-LINK
+# RUN:   FileCheck %s -DFILE=%t13 --check-prefixes=INVALID-RELA-LINK,INVALID-RELA-LINK-LLVM
 # RUN: llvm-readobj --elf-output-style=JSON --call-graph-info %t13 2>&1 | \
 # RUN:   FileCheck %s -DFILE=%t13 --check-prefix=INVALID-RELA-LINK
 
 # INVALID-RELA-LINK: warning: '[[FILE]]': invalid section linked to SHT_RELA section with index 2: invalid section index: 255
+# INVALID-RELA-LINK-LLVM:      Function {
+# INVALID-RELA-LINK-LLVM-NEXT:   Address: 0x1{{$}}
 
 ## Check warning when getSectionAndRelocations fails to read sections.
 # RUN: yaml2obj --docnum=7 -DLINK=0 -DINFO=255 %s -o %t14
@@ -302,6 +336,17 @@ Symbols:
 
 # ERR-READ-SECTION: warning: '[[FILE]]': unable to read SHT_LLVM_CALL_GRAPH section: SHT_RELA section with index 2: failed to get a relocated section: invalid section index: 255
 
+## Check that when the relocations can't be read, no warning is reported for each
+## address field, and the function is identified by the value stored in it.
+# RUN: yaml2obj --docnum=7 -DLINK=.symtab -DINFO=.llvm_call_graph -DENTSIZE=1 %s -o %t15
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t15 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t15 --implicit-check-not=warning: --check-prefix=BAD-RELA
+
+# BAD-RELA:      warning: '[[FILE]]': SHT_LLVM_CALL_GRAPH type section has unknown type ID for 1 indirect targets
+# BAD-RELA-NEXT: warning: '[[FILE]]': unable to read relocations from SHT_RELA section with index 2: section [index 2] has invalid sh_entsize: expected 24, but got 1
+# BAD-RELA:      Function {
+# BAD-RELA-NEXT:   Address: 0x1{{$}}
+
 --- !ELF
 FileHeader:
   Class:    ELFCLASS64
@@ -314,16 +359,106 @@ Sections:
     ContentArray: [
         '0', ## Format version number
         '1', ## Flag IsIndirectTarget true
-        '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
+        '1', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
         '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s TypeID Unknown
       ]
   - Name: .rela.llvm_call_graph
     Type: SHT_RELA
     Link: [[LINK]]
     Info: [[INFO]]
+    EntSize: [[ENTSIZE=<none>]]
     Relocations:
       - Offset: 0x2
         Symbol: [[SYMBOL=foo]]
         Type:   R_X86_64_64
 Symbols:
   - Name: foo
+...
+
+## Check that a warning is reported for each address field that more than one
+## relocation, or no relocation, applies to, and that the function is then
+## identified by the value stored in the field. A field with exactly one
+## relocation is identified by it.
+# RUN: yaml2obj --docnum=8 %s -o %t16
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t16 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t16 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=RELOC-COUNT-WARN,RELOC-COUNT-LLVM
+# RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t16 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t16 --implicit-check-not=warning: \
+# RUN:     --check-prefixes=RELOC-COUNT-WARN,RELOC-COUNT-JSON
+
+# RELOC-COUNT-WARN: warning: '[[FILE]]': more than one relocation at offset 0x2 in SHT_LLVM_CALL_GRAPH section with index 1
+# RELOC-COUNT-WARN: warning: '[[FILE]]': no relocation at offset 0x13 in SHT_LLVM_CALL_GRAPH section with index 1
+
+# RELOC-COUNT-LLVM:      CallGraph [
+# RELOC-COUNT-LLVM-NEXT:   Function {
+# RELOC-COUNT-LLVM-NEXT:     Address: 0x1{{$}}
+# RELOC-COUNT-LLVM-NEXT:     Version: 0
+# RELOC-COUNT-LLVM-NEXT:     IsIndirectTarget: No
+# RELOC-COUNT-LLVM-NEXT:     TypeID: 0x0
+# RELOC-COUNT-LLVM-NEXT:     DirectCallees [
+# RELOC-COUNT-LLVM-NEXT:       {
+# RELOC-COUNT-LLVM-NEXT:         Address: 0x2{{$}}
+# RELOC-COUNT-LLVM-NEXT:       }
+# RELOC-COUNT-LLVM-NEXT:       {
+# RELOC-COUNT-LLVM-NEXT:         Relocation {
+# RELOC-COUNT-LLVM-NEXT:           SymbolIndex: 2
+# RELOC-COUNT-LLVM-NEXT:           SymbolName: bar
+# RELOC-COUNT-LLVM-NEXT:         }
+# RELOC-COUNT-LLVM-NEXT:       }
+# RELOC-COUNT-LLVM-NEXT:     ]
+# RELOC-COUNT-LLVM-NEXT:     IndirectTypeIDs: []
+# RELOC-COUNT-LLVM-NEXT:   }
+# RELOC-COUNT-LLVM-NEXT: ]
+
+# RELOC-COUNT-JSON:      "Function": {
+# RELOC-COUNT-JSON-NEXT:   "Address": 1,
+# RELOC-COUNT-JSON:        "DirectCallees": [
+# RELOC-COUNT-JSON-NEXT:     {
+# RELOC-COUNT-JSON-NEXT:       "Address": 2{{$}}
+# RELOC-COUNT-JSON-NEXT:     },
+# RELOC-COUNT-JSON-NEXT:     {
+# RELOC-COUNT-JSON-NEXT:       "Relocation": {
+# RELOC-COUNT-JSON-NEXT:         "SymbolIndex": 2,
+# RELOC-COUNT-JSON-NEXT:         "SymbolName": "bar"
+# RELOC-COUNT-JSON-NEXT:       }
+# RELOC-COUNT-JSON-NEXT:     }
+# RELOC-COUNT-JSON-NEXT:   ],
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name: .llvm_call_graph
+    Type: SHT_LLVM_CALL_GRAPH
+    ContentArray: [
+        '0', ## Format version number
+        '2', ## Flag HasDirectCallees true
+        '1', '0', '0', '0', '0', '0', '0', '0', ## foo()'s address
+        '0', '0', '0', '0', '0', '0', '0', '0', ## foo()'s TypeID
+        '2', ## NumDirectCallees
+        '2', '0', '0', '0', '0', '0', '0', '0', ## First direct callee's address
+        '3', '0', '0', '0', '0', '0', '0', '0', ## Second direct callee's address
+      ]
+  - Name: .rela.llvm_call_graph
+    Type: SHT_RELA
+    Info: .llvm_call_graph
+    ## The relocations are not sorted by offset. Two relocations apply to foo()'s
+    ## address at 0x2, none to the first callee's address at 0x13, and one to the
+    ## second callee's address at 0x1B.
+    Relocations:
+      - Offset: 0x1B
+        Symbol: bar
+        Type:   R_X86_64_64
+      - Offset: 0x2
+        Symbol: foo
+        Type:   R_X86_64_64
+      - Offset: 0x2
+        Symbol: bar
+        Type:   R_X86_64_64
+Symbols:
+  - Name: foo
+  - Name: bar
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 82619477b09650..dbd5f47b55db5c 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -215,7 +215,7 @@ template <class ELFT> struct CallGraphFunc {
   uint64_t FieldOffset = 0;
   // The value stored in the address field.
   uint64_t FieldValue = 0;
-  // The relocation that applies to the address field, if any.
+  // The relocation that applies to the address field, if there is exactly one.
   std::optional<Relocation<ELFT>> Reloc = std::nullopt;
 };
 
@@ -468,10 +468,18 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
   SmallVector<FunctionCallGraphInfo<ELFT>, 16>
   processCallGraphSection(const Elf_Shdr *CGSection);
   // Attach to each address field in FuncCGInfos the relocation from
-  // CGRelSection that applies to it, if any.
+  // CGRelSection that applies to it. A field that doesn't have exactly one
+  // relocation gets a warning instead, and falls back to the Address stored
+  // in it.
   void attachCallGraphRelocations(
-      const Elf_Shdr &CGRelSection,
+      const Elf_Shdr &CGSection, const Elf_Shdr &CGRelSection,
       MutableArrayRef<FunctionCallGraphInfo<ELFT>> FuncCGInfos);
+  // Attach to Func the relocation in Relocs that applies to its address field,
+  // as above. Relocs must be sorted by offset. The relocations up to and
+  // including those for the field are dropped from it.
+  void attachCallGraphRelocation(const Elf_Shdr &CGSection,
+                                 CallGraphFunc<ELFT> &Func,
+                                 ArrayRef<Relocation<ELFT>> &Relocs);
 
   std::string getProgramHeadersNumString();
 
@@ -5483,35 +5491,54 @@ ELFDumper<ELFT>::processCallGraphSection(const Elf_Shdr *CGSection) {
 
 template <class ELFT>
 void ELFDumper<ELFT>::attachCallGraphRelocations(
-    const Elf_Shdr &CGRelSection,
+    const Elf_Shdr &CGSection, const Elf_Shdr &CGRelSection,
     MutableArrayRef<FunctionCallGraphInfo<ELFT>> FuncCGInfos) {
   std::vector<Relocation<ELFT>> Relocations;
   forEachRelocationDo(CGRelSection,
                       [&](const Relocation<ELFT> &R, unsigned, const Elf_Shdr &,
                           const Elf_Shdr *) { Relocations.push_back(R); });
+  // Don't warn about each address field if there are no relocations. If they
+  // could not be read, forEachRelocationDo has already reported a warning.
+  if (Relocations.empty())
+    return;
   llvm::stable_sort(Relocations, [](const Relocation<ELFT> &LHS,
                                     const Relocation<ELFT> &RHS) {
     return LHS.Offset < RHS.Offset;
   });
 
   // Address fields are parsed in increasing offset order, so one pass over the
-  // sorted relocations finds the relocation for each field. If more than one
-  // relocation applies to a field, the first one is used.
+  // sorted relocations finds the relocations for each field.
   ArrayRef<Relocation<ELFT>> Remaining = Relocations;
-  auto Attach = [&](CallGraphFunc<ELFT> &Func) {
-    Remaining = Remaining.drop_while(
-        [&](const Relocation<ELFT> &R) { return R.Offset < Func.FieldOffset; });
-    if (!Remaining.empty() && Remaining.front().Offset == Func.FieldOffset)
-      Func.Reloc = Remaining.front();
-  };
-
   for (FunctionCallGraphInfo<ELFT> &CGInfo : FuncCGInfos) {
-    Attach(CGInfo.Entry);
+    attachCallGraphRelocation(CGSection, CGInfo.Entry, Remaining);
     for (CallGraphFunc<ELFT> &Callee : CGInfo.DirectCallees)
-      Attach(Callee);
+      attachCallGraphRelocation(CGSection, Callee, Remaining);
   }
 }
 
+template <class ELFT>
+void ELFDumper<ELFT>::attachCallGraphRelocation(
+    const Elf_Shdr &CGSection, CallGraphFunc<ELFT> &Func,
+    ArrayRef<Relocation<ELFT>> &Relocs) {
+  Relocs = Relocs.drop_while(
+      [&](const Relocation<ELFT> &R) { return R.Offset < Func.FieldOffset; });
+  ArrayRef<Relocation<ELFT>> Matches = Relocs.take_while(
+      [&](const Relocation<ELFT> &R) { return R.Offset == Func.FieldOffset; });
+  Relocs = Relocs.drop_front(Matches.size());
+
+  // A field without exactly one relocation is identified by the value stored
+  // in it instead.
+  if (Matches.size() == 1)
+    Func.Reloc = Matches.front();
+  else if (Matches.empty())
+    reportUniqueWarning(formatv("no relocation at offset {0:x+} in {1}",
+                                Func.FieldOffset, describe(CGSection)));
+  else
+    reportUniqueWarning(
+        formatv("more than one relocation at offset {0:x+} in {1}",
+                Func.FieldOffset, describe(CGSection)));
+}
+
 template <class ELFT>
 void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) {
   OS << "GNU output style is not supported for --bb-addr-map\n";
@@ -8437,21 +8464,25 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     if (FuncCGInfos.empty())
       continue;
 
+    // In a relocatable object file, an address field holds a placeholder, and
+    // the relocation that applies to it identifies the function.
     const Elf_Shdr *RelocSymTab = nullptr;
-    if (IsRelocatable) {
-      if (CGRelSection) {
-        Expected<const typename ELFT::Shdr *> SymtabOrErr =
-            this->Obj.getSection(CGRelSection->sh_link);
-        if (!SymtabOrErr) {
-          reportWarning(createError("invalid section linked to " +
-                                    this->describe(*CGRelSection) + ": " +
-                                    toString(SymtabOrErr.takeError())),
-                        this->FileName);
-          return;
-        }
+    if (IsRelocatable && CGRelSection) {
+      Expected<const typename ELFT::Shdr *> SymtabOrErr =
+          this->Obj.getSection(CGRelSection->sh_link);
+      if (SymtabOrErr) {
         RelocSymTab = *SymtabOrErr;
-        this->attachCallGraphRelocations(*CGRelSection, FuncCGInfos);
+        this->attachCallGraphRelocations(*CGSection, *CGRelSection,
+                                         FuncCGInfos);
+      } else {
+        reportWarning(createError("invalid section linked to " +
+                                  this->describe(*CGRelSection) + ": " +
+                                  toString(SymtabOrErr.takeError())),
+                      this->FileName);
       }
+    } else if (IsRelocatable) {
+      this->reportUniqueWarning("unable to get relocation section for " +
+                                this->describe(*CGSection));
     }
 
     auto GetFunctionNames = [&](uint64_t FuncAddr) {
@@ -8464,36 +8495,27 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
       return FuncSymNames;
     };
 
-    auto PrintNonRelocatableFuncSymbol = [&](uint64_t FuncEntryPC) {
-      SmallVector<std::string> FuncSymNames = GetFunctionNames(FuncEntryPC);
-      if (!FuncSymNames.empty())
-        W.printList("Names", FuncSymNames);
-      W.printHex("Address", FuncEntryPC);
-    };
-
-    auto PrintRelocatableFuncSymbol = [&](const CallGraphFunc<ELFT> &Func) {
-      if (!Func.Reloc) {
-        this->reportUniqueWarning("missing relocation for symbol at offset " +
-                                  Twine(Func.FieldOffset));
-        return;
-      }
-      printCallGraphRelocation(Func, *RelocSymTab);
-    };
-
     auto PrintFunc = [&](const CallGraphFunc<ELFT> &Func) {
-      // In a relocatable object file, the address field holds a placeholder,
-      // and the relocation that applies to it identifies the function.
-      if (IsRelocatable) {
-        PrintRelocatableFuncSymbol(Func);
+      if (Func.Reloc) {
+        printCallGraphRelocation(Func, *RelocSymTab);
         return;
       }
+      // Without a relocation, the function is identified by the value stored
+      // in the address field.
       uint64_t FuncEntryPC = Func.FieldValue;
       // In ARM thumb mode the LSB of the function pointer is set to 1. Since
       // this detail is unnecessary in call graph reconstruction, we are
       // clearing this bit to facilitate tooling.
       if (this->Obj.getHeader().e_machine == ELF::EM_ARM)
         FuncEntryPC &= ~1;
-      PrintNonRelocatableFuncSymbol(FuncEntryPC);
+      // In a relocatable object file that value is not a final address, so
+      // there are no symbol names to look up for it.
+      if (!IsRelocatable) {
+        SmallVector<std::string> FuncSymNames = GetFunctionNames(FuncEntryPC);
+        if (!FuncSymNames.empty())
+          W.printList("Names", FuncSymNames);
+      }
+      W.printHex("Address", FuncEntryPC);
     };
     if (!CGI)
       CGI = std::make_unique<ListScope>(W, "CallGraph");



More information about the llvm-branch-commits mailing list