Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2a2dd17

Browse files
authored
[lld-macho] Fix branch relocations with addends to target actual function (#177430)
When a branch relocation has a non-zero addend (e.g., `bl _func+16`), the linker was incorrectly computing `stub_address + addend` instead of `function_address + addend`. This caused the branch to land in the wrong location (past the stub section) rather than at the intended interior point of the function. The fix checks for non-zero addends on branch relocations and uses the actual symbol VA in those cases. This makes sense semantically—branching to an interior offset implies reliance on the original function's layout, which an interposed replacement wouldn't preserve anyway. Added test `arm64-branch-addend-stubs.s` that verifies the correct behavior using `-flat_namespace` (which makes local symbols interposable and thus routed through stubs). [Assisted-by](https://t.ly/Dkjjk): Cursor IDE + claude-opus-4.5-high
1 parent 3007e2f commit 2a2dd17

2 files changed

Lines changed: 99 additions & 9 deletions

File tree

lld/MachO/InputSection.cpp

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,25 @@ uint64_t InputSection::getVA(uint64_t off) const {
8989
return parent->addr + getOffset(off);
9090
}
9191

92-
static uint64_t resolveSymbolVA(const Symbol *sym, uint8_t type) {
92+
static uint64_t resolveSymbolOffsetVA(const Symbol *sym, uint8_t type,
93+
int64_t offset) {
9394
const RelocAttrs &relocAttrs = target->getRelocAttrs(type);
94-
if (relocAttrs.hasAttr(RelocAttrBits::BRANCH))
95-
return sym->resolveBranchVA();
96-
if (relocAttrs.hasAttr(RelocAttrBits::GOT))
97-
return sym->resolveGotVA();
98-
if (relocAttrs.hasAttr(RelocAttrBits::TLV))
99-
return sym->resolveTlvVA();
100-
return sym->getVA();
95+
uint64_t symVA;
96+
if (relocAttrs.hasAttr(RelocAttrBits::BRANCH)) {
97+
// For branch relocations with non-zero offsets, use the actual function
98+
// address rather than the stub address. Branching to an interior point
99+
// of a function (e.g., _func+16) implies reliance on the original
100+
// function's layout, which an interposed replacement wouldn't preserve.
101+
// There's no meaningful way to "interpose" an interior offset.
102+
symVA = (offset != 0) ? sym->getVA() : sym->resolveBranchVA();
103+
} else if (relocAttrs.hasAttr(RelocAttrBits::GOT)) {
104+
symVA = sym->resolveGotVA();
105+
} else if (relocAttrs.hasAttr(RelocAttrBits::TLV)) {
106+
symVA = sym->resolveTlvVA();
107+
} else {
108+
symVA = sym->getVA();
109+
}
110+
return symVA + offset;
101111
}
102112

103113
const Defined *InputSection::getContainingSymbol(uint64_t off) const {
@@ -243,7 +253,7 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
243253
target->handleDtraceReloc(referentSym, r, loc);
244254
continue;
245255
}
246-
referentVA = resolveSymbolVA(referentSym, r.type) + r.addend;
256+
referentVA = resolveSymbolOffsetVA(referentSym, r.type, r.addend);
247257

248258
if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym)) {
249259
// References from thread-local variable sections are treated as offsets
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# REQUIRES: aarch64
2+
3+
## Test that branch relocations with non-zero addends correctly target the
4+
## actual function address, not the stub address. When a symbol is accessed
5+
## via both a regular call (goes through stub) and a branch with addend
6+
## (targeting an interior point), the addend must be applied to the real
7+
## function VA, not the stub VA.
8+
##
9+
## This test uses -flat_namespace on a dylib, which makes locally-defined
10+
## symbols interposable and thus accessible via stubs. This creates the
11+
## scenario where a function is both defined locally AND in stubs.
12+
13+
# RUN: rm -rf %t; mkdir -p %t
14+
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t/test.o
15+
# RUN: %lld -arch arm64 -dylib -lSystem -flat_namespace %t/test.o -o %t/test.dylib
16+
17+
# RUN: llvm-objdump --no-print-imm-hex --macho -d %t/test.dylib | FileCheck %s
18+
19+
## With -flat_namespace, _target_func is interposable so regular calls go
20+
## through stubs. But the branch with addend must go to the actual function
21+
## address + addend, not stub + addend.
22+
##
23+
## Note: This means `bl _target_func` and `bl _target_func+16` could target
24+
## different functions if interposition occurs at runtime. This is intentional:
25+
## branching to an interior point implies reliance on the original function's
26+
## layout, which an interposed replacement wouldn't preserve. There's no
27+
## meaningful way to "interpose" an interior offset, so we target the original.
28+
29+
## _target_func layout:
30+
## offset 0: nop
31+
## offset 4: nop
32+
## offset 8: nop
33+
## offset 12: nop
34+
## offset 16: mov w0, #42 <- this is what _target_func+16 should reach
35+
## offset 20: ret
36+
37+
## Verify _target_func layout and capture the address of the mov instruction
38+
## (which is at _target_func + 16)
39+
# CHECK-LABEL: _target_func:
40+
# CHECK: nop
41+
# CHECK-NEXT: nop
42+
# CHECK-NEXT: nop
43+
# CHECK-NEXT: nop
44+
# CHECK-NEXT: [[#%x,INTERIOR:]]:{{.*}}mov w0, #42
45+
# CHECK-NEXT: ret
46+
47+
## Verify the caller structure:
48+
## - First bl goes to stub (marked with "symbol stub for:")
49+
## - Second bl goes to [[INTERIOR]] (the _target_func+16 address captured above)
50+
##
51+
## The key assertion: the second bl MUST target _target_func+16 (INTERIOR),
52+
## NOT stub+16. If the bug exists, it would target stub+16 which would be
53+
## garbage (pointing past the stub section).
54+
# CHECK-LABEL: _caller:
55+
# CHECK: bl {{.*}} symbol stub for: _target_func
56+
# CHECK-NEXT: bl 0x[[#INTERIOR]]
57+
# CHECK-NEXT: ret
58+
59+
.text
60+
.globl _target_func, _caller
61+
.p2align 2
62+
63+
_target_func:
64+
## 4 nops = 16 bytes to offset 0x10
65+
nop
66+
nop
67+
nop
68+
nop
69+
## This is at _target_func + 16
70+
mov w0, #42
71+
ret
72+
73+
_caller:
74+
## Regular call to _target_func - goes through stub due to -flat_namespace
75+
bl _target_func
76+
## Branch with addend - must go to actual function + 16, not stub + 16
77+
bl _target_func + 16
78+
ret
79+
80+
.subsections_via_symbols

0 commit comments

Comments
 (0)