Skip to content

Commit 7404685

Browse files
authored
[lld-macho] Fix compatibility between --icf=safe_thunks and --keep-icf-stabs (#116687)
Currently when `--icf=safe_thunks` is used, `STABS` entries cannot be generated for ICF'ed functions. This is because if ICF converts a full function into a thunk and then we generate a `STABS` entry for the thunk, `dsymutil` will expect to find the entire function body at the location of the thunk. Because just a thunk will be present at the location of the `STABS` entry - dsymutil will generate invalid debug info for such scenarios. With this change, if `--icf=safe_thunks` is used and `--keep-icf-stabs` is also specified, STABS entries will be created for all functions, even merged ones. However, the STABS entries will point at the actual (full) function body while having the name of the thunk. This way we still get program correctness as well as correct DWARF data. When doing this, the debug data will be identical to the scenario where we're using `--icf=all` and `--keep-icf-stabs`, but the actual program will also contain thunks, which won't show up in the DWARF data.
1 parent 81055ff commit 7404685

File tree

7 files changed

+125
-17
lines changed

7 files changed

+125
-17
lines changed

lld/MachO/Arch/ARM64.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct ARM64 : ARM64Common {
4444

4545
void initICFSafeThunkBody(InputSection *thunk,
4646
InputSection *branchTarget) const override;
47+
InputSection *getThunkBranchTarget(InputSection *thunk) const override;
4748
uint32_t getICFSafeThunkSize() const override;
4849
};
4950

@@ -197,6 +198,16 @@ void ARM64::initICFSafeThunkBody(InputSection *thunk,
197198
/*referent=*/branchTarget);
198199
}
199200

201+
InputSection *ARM64::getThunkBranchTarget(InputSection *thunk) const {
202+
assert(thunk->relocs.size() == 1 &&
203+
"expected a single reloc on ARM64 ICF thunk");
204+
auto &reloc = thunk->relocs[0];
205+
assert(reloc.referent.is<InputSection *>() &&
206+
"ARM64 thunk reloc is expected to point to an InputSection");
207+
208+
return reloc.referent.dyn_cast<InputSection *>();
209+
}
210+
200211
uint32_t ARM64::getICFSafeThunkSize() const { return sizeof(icfSafeThunkCode); }
201212

202213
ARM64::ARM64() : ARM64Common(LP64()) {

lld/MachO/ICF.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,33 @@ void macho::markAddrSigSymbols() {
481481
}
482482
}
483483

484+
// Given a symbol that was folded into a thunk, return the symbol pointing to
485+
// the actual body of the function. We use this approach rather than storing the
486+
// needed info in the Defined itself in order to minimize memory usage.
487+
Defined *macho::getBodyForThunkFoldedSym(Defined *foldedSym) {
488+
assert(isa<ConcatInputSection>(foldedSym->originalIsec) &&
489+
"thunk-folded ICF symbol expected to be on a ConcatInputSection");
490+
// foldedSec is the InputSection that was marked as deleted upon fold
491+
ConcatInputSection *foldedSec =
492+
cast<ConcatInputSection>(foldedSym->originalIsec);
493+
494+
// thunkBody is the actual live thunk, containing the code that branches to
495+
// the actual body of the function.
496+
InputSection *thunkBody = foldedSec->replacement;
497+
498+
// The actual (merged) body of the function that the thunk jumps to. This will
499+
// end up in the final binary.
500+
InputSection *functionBody = target->getThunkBranchTarget(thunkBody);
501+
502+
for (Symbol *sym : functionBody->symbols) {
503+
Defined *d = dyn_cast<Defined>(sym);
504+
// The symbol needs to be at the start of the InputSection
505+
if (d && d->value == 0)
506+
return d;
507+
}
508+
509+
llvm_unreachable("could not find body symbol for ICF-generated thunk");
510+
}
484511
void macho::foldIdenticalSections(bool onlyCfStrings) {
485512
TimeTraceScope timeScope("Fold Identical Code Sections");
486513
// The ICF equivalence-class segregation algorithm relies on pre-computed

lld/MachO/ICF.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515

1616
namespace lld::macho {
1717
class Symbol;
18+
class Defined;
1819

1920
void markAddrSigSymbols();
2021
void markSymAsAddrSig(Symbol *s);
2122
void foldIdenticalSections(bool onlyCfStrings);
2223

24+
// Given a symbol that was folded into a thunk, return the symbol pointing to
25+
// the actual body of the function. We expose this function to allow getting the
26+
// main function body for a symbol that was folded via a thunk.
27+
Defined *getBodyForThunkFoldedSym(Defined *foldedSym);
28+
2329
} // namespace lld::macho
2430

2531
#endif

lld/MachO/SyntheticSections.cpp

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "ConcatOutputSection.h"
1111
#include "Config.h"
1212
#include "ExportTrie.h"
13+
#include "ICF.h"
1314
#include "InputFiles.h"
1415
#include "MachOStructs.h"
1516
#include "ObjC.h"
@@ -1204,6 +1205,18 @@ void SymtabSection::emitEndFunStab(Defined *defined) {
12041205
stabs.emplace_back(std::move(stab));
12051206
}
12061207

1208+
// Given a pointer to a function symbol, return the symbol that points to the
1209+
// actual function body that will go in the final binary. Generally this is the
1210+
// symbol itself, but if the symbol was folded using a thunk, we retrieve the
1211+
// target function body from the thunk.
1212+
Defined *SymtabSection::getFuncBodySym(Defined *originalSym) {
1213+
if (originalSym->identicalCodeFoldingKind == Symbol::ICFFoldKind::None ||
1214+
originalSym->identicalCodeFoldingKind == Symbol::ICFFoldKind::Body)
1215+
return originalSym;
1216+
1217+
return macho::getBodyForThunkFoldedSym(originalSym);
1218+
}
1219+
12071220
void SymtabSection::emitStabs() {
12081221
if (config->omitDebugInfo)
12091222
return;
@@ -1229,20 +1242,10 @@ void SymtabSection::emitStabs() {
12291242
if (defined->isAbsolute())
12301243
continue;
12311244

1232-
// Never generate a STABS entry for a symbol that has been ICF'ed using a
1233-
// thunk, just as we do for fully ICF'ed functions. Otherwise, we end up
1234-
// generating invalid DWARF as dsymutil will assume the entire function
1235-
// body is at that location, when, in reality, only the thunk is
1236-
// present. This will end up causing overlapping DWARF entries.
1237-
// TODO: Find an implementation that works in combination with
1238-
// `--keep-icf-stabs`.
1239-
if (defined->identicalCodeFoldingKind == Symbol::ICFFoldKind::Thunk)
1240-
continue;
1241-
12421245
// Constant-folded symbols go in the executable's symbol table, but don't
1243-
// get a stabs entry unless --keep-icf-stabs flag is specified
1246+
// get a stabs entry unless --keep-icf-stabs flag is specified.
12441247
if (!config->keepICFStabs &&
1245-
defined->identicalCodeFoldingKind == Symbol::ICFFoldKind::Body)
1248+
defined->identicalCodeFoldingKind != Symbol::ICFFoldKind::None)
12461249
continue;
12471250

12481251
ObjFile *file = defined->getObjectFile();
@@ -1251,8 +1254,8 @@ void SymtabSection::emitStabs() {
12511254

12521255
// We use 'originalIsec' to get the file id of the symbol since 'isec()'
12531256
// might point to the merged ICF symbol's file
1254-
symbolsNeedingStabs.emplace_back(defined,
1255-
defined->originalIsec->getFile()->id);
1257+
symbolsNeedingStabs.emplace_back(
1258+
defined, getFuncBodySym(defined)->originalIsec->getFile()->id);
12561259
}
12571260
}
12581261

@@ -1269,7 +1272,8 @@ void SymtabSection::emitStabs() {
12691272
Defined *defined = pair.first;
12701273
// We use 'originalIsec' of the symbol since we care about the actual origin
12711274
// of the symbol, not the canonical location returned by `isec()`.
1272-
InputSection *isec = defined->originalIsec;
1275+
Defined *funcBodySym = getFuncBodySym(defined);
1276+
InputSection *isec = funcBodySym->originalIsec;
12731277
ObjFile *file = cast<ObjFile>(isec->getFile());
12741278

12751279
if (lastFile == nullptr || lastFile != file) {
@@ -1284,12 +1288,12 @@ void SymtabSection::emitStabs() {
12841288
StabsEntry symStab;
12851289
symStab.sect = isec->parent->index;
12861290
symStab.strx = stringTableSection.addString(defined->getName());
1287-
symStab.value = defined->getVA();
1291+
symStab.value = funcBodySym->getVA();
12881292

12891293
if (isCodeSection(isec)) {
12901294
symStab.type = N_FUN;
12911295
stabs.emplace_back(std::move(symStab));
1292-
emitEndFunStab(defined);
1296+
emitEndFunStab(funcBodySym);
12931297
} else {
12941298
symStab.type = defined->isExternal() ? N_GSYM : N_STSYM;
12951299
stabs.emplace_back(std::move(symStab));

lld/MachO/SyntheticSections.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ class SymtabSection : public LinkEditSection {
485485
void emitEndSourceStab();
486486
void emitObjectFileStab(ObjFile *);
487487
void emitEndFunStab(Defined *);
488+
Defined *getFuncBodySym(Defined *);
488489
void emitStabs();
489490

490491
protected:

lld/MachO/Target.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ class TargetInfo {
8080
llvm_unreachable("target does not support ICF safe thunks");
8181
}
8282

83+
// Given a thunk for which `initICFSafeThunkBody` was called, return the
84+
// branchTarget it was initialized with.
85+
virtual InputSection *getThunkBranchTarget(InputSection *thunk) const {
86+
llvm_unreachable("target does not support ICF safe thunks");
87+
}
88+
8389
virtual uint32_t getICFSafeThunkSize() const {
8490
llvm_unreachable("target does not support ICF safe thunks");
8591
}

lld/test/MachO/icf-safe-thunks-dwarf.ll

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,59 @@
2020
; VERIFY-STABS: N_FUN{{.*}}_func_A
2121
; VERIFY-STABS: N_FUN{{.*}}_take_func_addr
2222

23+
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Check safe_thunks ICF + keeping STABS entries ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24+
25+
;;; Check scenario with where we do safe_thunks ICF and also generate STABS entries
26+
; RUN: %lld -arch arm64 -lSystem --icf=safe_thunks --keep-icf-stabs -dylib -o %t/a_thunks.dylib %t/a.o
27+
; RUN: dsymutil -s %t/a_thunks.dylib > %t/a_thunks.txt
28+
29+
30+
; RUN: dsymutil --flat --verify-dwarf=none %t/a_thunks.dylib -o %t/a_thunks.dSYM
31+
; RUN: dsymutil -s %t/a_thunks.dSYM >> %t/a_thunks.txt
32+
; RUN: llvm-dwarfdump -a %t/a_thunks.dSYM >> %t/a_thunks.txt
33+
34+
; RUN: cat %t/a_thunks.txt | FileCheck %s --check-prefix=VERIFY-THUNK
35+
36+
# VERIFY-THUNK-LABEL: Symbol table for: '{{.*}}/a_thunks.dylib'
37+
# Capture the 'n_value's for N_FUN entries of _func_A, _func_B, and _func_C
38+
# VERIFY-THUNK: [[MERGED_FUN_ADDR:[0-9a-f]+]] '_func_A'
39+
# VERIFY-THUNK: [[MERGED_FUN_ADDR]] '_func_B'
40+
# VERIFY-THUNK: [[MERGED_FUN_ADDR]] '_func_C'
41+
42+
# Capture the 'n_value's for SECT EXT entries in the first part
43+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_A_NVALUE:[0-9a-f]+]] '_func_A'
44+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_B_NVALUE:[0-9a-f]+]] '_func_B'
45+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_C_NVALUE:[0-9a-f]+]] '_func_C'
46+
47+
# VERIFY-THUNK: ----------------------------------------------------------------------
48+
# VERIFY-THUNK-LABEL: Symbol table for: '{{.*}}/a_thunks.dSYM'
49+
50+
# Verify that the SECT EXT 'n_value's in the second part match the first part
51+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_A_NVALUE]] '_func_A'
52+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_B_NVALUE]] '_func_B'
53+
# VERIFY-THUNK: SECT EXT{{.*}} [[SECT_EXT_C_NVALUE]] '_func_C'
54+
55+
# Ensure that N_FUN 'n_value's match the DW_TAG_subprogram's DW_AT_low_pc
56+
# and that the DW_AT_name is at a specific relative position
57+
58+
# VERIFY-THUNK-LABEL: .debug_info contents:
59+
# VERIFY-THUNK: Compile Unit: length = {{.*}}
60+
61+
# Match the subprogram for func_A
62+
# VERIFY-THUNK: : DW_TAG_subprogram
63+
# VERIFY-THUNK-NEXT: {{ +}}DW_AT_low_pc (0x[[MERGED_FUN_ADDR]])
64+
# VERIFY-THUNK-NEXT-NEXT-NEXT-NEXT-NEXT: {{ +}}DW_AT_name ("func_A")
65+
66+
# Match the subprogram for func_B
67+
# VERIFY-THUNK: : DW_TAG_subprogram
68+
# VERIFY-THUNK-NEXT: {{ +}}DW_AT_low_pc (0x[[MERGED_FUN_ADDR]])
69+
# VERIFY-THUNK-NEXT-NEXT-NEXT-NEXT-NEXT: {{ +}}DW_AT_name ("func_B")
70+
71+
# Match the subprogram for func_C
72+
# VERIFY-THUNK: : DW_TAG_subprogram
73+
# VERIFY-THUNK-NEXT: {{ +}}DW_AT_low_pc (0x[[MERGED_FUN_ADDR]])
74+
# VERIFY-THUNK-NEXT-NEXT-NEXT-NEXT-NEXT: {{ +}}DW_AT_name ("func_C")
75+
2376
;--- a.cpp
2477
#define ATTR __attribute__((noinline)) extern "C"
2578
typedef unsigned long long ULL;

0 commit comments

Comments
 (0)