Skip to content

Commit 025394f

Browse files
authored
Reapply "[lld] Support thumb PLTs" (#93631) (#93644)
This reverts commit 7832769. This was reverted prior due to a test failure on the windows builder. I think this was because we didn't specify the triple and assumed windows. The other tests use the full triple specifying linux, so we follow suite here. --- We are using PLTs for cortex-m33 which only supports thumb. More specifically, this is for a very restricted use case. There's no MMU so there's no sharing of virtual addresses between two processes, but this is fine. The MCU is used for running [chre nanoapps](https://android.googlesource.com/platform/system/chre/+/HEAD/doc/nanoapp_overview.md) for android. Each nanoapp is a shared library (but effectively acts as an executable containing a test suite) that is loaded and run on the MCU one binary at a time and there's only one process running at a time, so we ensure that the same text segment cannot be shared by two different running executables. GNU LD supports thumb PLTs but we want to migrate to a clang toolchain and use LLD, so thumb PLTs are needed.
1 parent 87e8ce3 commit 025394f

File tree

4 files changed

+262
-53
lines changed

4 files changed

+262
-53
lines changed

lld/ELF/Arch/ARM.cpp

Lines changed: 123 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -231,36 +231,71 @@ static void writePltHeaderLong(uint8_t *buf) {
231231
// The default PLT header requires the .got.plt to be within 128 Mb of the
232232
// .plt in the positive direction.
233233
void ARM::writePltHeader(uint8_t *buf) const {
234-
// Use a similar sequence to that in writePlt(), the difference is the calling
235-
// conventions mean we use lr instead of ip. The PLT entry is responsible for
236-
// saving lr on the stack, the dynamic loader is responsible for reloading
237-
// it.
238-
const uint32_t pltData[] = {
239-
0xe52de004, // L1: str lr, [sp,#-4]!
240-
0xe28fe600, // add lr, pc, #0x0NN00000 &(.got.plt - L1 - 4)
241-
0xe28eea00, // add lr, lr, #0x000NN000 &(.got.plt - L1 - 4)
242-
0xe5bef000, // ldr pc, [lr, #0x00000NNN] &(.got.plt -L1 - 4)
243-
};
244-
245-
uint64_t offset = in.gotPlt->getVA() - in.plt->getVA() - 4;
246-
if (!llvm::isUInt<27>(offset)) {
247-
// We cannot encode the Offset, use the long form.
248-
writePltHeaderLong(buf);
249-
return;
234+
if (config->armThumbPLTs) {
235+
// The instruction sequence for thumb:
236+
//
237+
// 0: b500 push {lr}
238+
// 2: f8df e008 ldr.w lr, [pc, #0x8] @ 0xe <func+0xe>
239+
// 6: 44fe add lr, pc
240+
// 8: f85e ff08 ldr pc, [lr, #8]!
241+
// e: .word .got.plt - .plt - 16
242+
//
243+
// At 0x8, we want to jump to .got.plt, the -16 accounts for 8 bytes from
244+
// `pc` in the add instruction and 8 bytes for the `lr` adjustment.
245+
//
246+
uint64_t offset = in.gotPlt->getVA() - in.plt->getVA() - 16;
247+
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
248+
write16(buf + 0, 0xb500);
249+
// Split into two halves to support endianness correctly.
250+
write16(buf + 2, 0xf8df);
251+
write16(buf + 4, 0xe008);
252+
write16(buf + 6, 0x44fe);
253+
// Split into two halves to support endianness correctly.
254+
write16(buf + 8, 0xf85e);
255+
write16(buf + 10, 0xff08);
256+
write32(buf + 12, offset);
257+
258+
memcpy(buf + 16, trapInstr.data(), 4); // Pad to 32-byte boundary
259+
memcpy(buf + 20, trapInstr.data(), 4);
260+
memcpy(buf + 24, trapInstr.data(), 4);
261+
memcpy(buf + 28, trapInstr.data(), 4);
262+
} else {
263+
// Use a similar sequence to that in writePlt(), the difference is the
264+
// calling conventions mean we use lr instead of ip. The PLT entry is
265+
// responsible for saving lr on the stack, the dynamic loader is responsible
266+
// for reloading it.
267+
const uint32_t pltData[] = {
268+
0xe52de004, // L1: str lr, [sp,#-4]!
269+
0xe28fe600, // add lr, pc, #0x0NN00000 &(.got.plt - L1 - 4)
270+
0xe28eea00, // add lr, lr, #0x000NN000 &(.got.plt - L1 - 4)
271+
0xe5bef000, // ldr pc, [lr, #0x00000NNN] &(.got.plt -L1 - 4)
272+
};
273+
274+
uint64_t offset = in.gotPlt->getVA() - in.plt->getVA() - 4;
275+
if (!llvm::isUInt<27>(offset)) {
276+
// We cannot encode the Offset, use the long form.
277+
writePltHeaderLong(buf);
278+
return;
279+
}
280+
write32(buf + 0, pltData[0]);
281+
write32(buf + 4, pltData[1] | ((offset >> 20) & 0xff));
282+
write32(buf + 8, pltData[2] | ((offset >> 12) & 0xff));
283+
write32(buf + 12, pltData[3] | (offset & 0xfff));
284+
memcpy(buf + 16, trapInstr.data(), 4); // Pad to 32-byte boundary
285+
memcpy(buf + 20, trapInstr.data(), 4);
286+
memcpy(buf + 24, trapInstr.data(), 4);
287+
memcpy(buf + 28, trapInstr.data(), 4);
250288
}
251-
write32(buf + 0, pltData[0]);
252-
write32(buf + 4, pltData[1] | ((offset >> 20) & 0xff));
253-
write32(buf + 8, pltData[2] | ((offset >> 12) & 0xff));
254-
write32(buf + 12, pltData[3] | (offset & 0xfff));
255-
memcpy(buf + 16, trapInstr.data(), 4); // Pad to 32-byte boundary
256-
memcpy(buf + 20, trapInstr.data(), 4);
257-
memcpy(buf + 24, trapInstr.data(), 4);
258-
memcpy(buf + 28, trapInstr.data(), 4);
259289
}
260290

261291
void ARM::addPltHeaderSymbols(InputSection &isec) const {
262-
addSyntheticLocal("$a", STT_NOTYPE, 0, 0, isec);
263-
addSyntheticLocal("$d", STT_NOTYPE, 16, 0, isec);
292+
if (config->armThumbPLTs) {
293+
addSyntheticLocal("$t", STT_NOTYPE, 0, 0, isec);
294+
addSyntheticLocal("$d", STT_NOTYPE, 12, 0, isec);
295+
} else {
296+
addSyntheticLocal("$a", STT_NOTYPE, 0, 0, isec);
297+
addSyntheticLocal("$d", STT_NOTYPE, 16, 0, isec);
298+
}
264299
}
265300

266301
// Long form PLT entries that do not have any restrictions on the displacement
@@ -279,32 +314,65 @@ static void writePltLong(uint8_t *buf, uint64_t gotPltEntryAddr,
279314
// .plt in the positive direction.
280315
void ARM::writePlt(uint8_t *buf, const Symbol &sym,
281316
uint64_t pltEntryAddr) const {
282-
// The PLT entry is similar to the example given in Appendix A of ELF for
283-
// the Arm Architecture. Instead of using the Group Relocations to find the
284-
// optimal rotation for the 8-bit immediate used in the add instructions we
285-
// hard code the most compact rotations for simplicity. This saves a load
286-
// instruction over the long plt sequences.
287-
const uint32_t pltData[] = {
288-
0xe28fc600, // L1: add ip, pc, #0x0NN00000 Offset(&(.got.plt) - L1 - 8
289-
0xe28cca00, // add ip, ip, #0x000NN000 Offset(&(.got.plt) - L1 - 8
290-
0xe5bcf000, // ldr pc, [ip, #0x00000NNN] Offset(&(.got.plt) - L1 - 8
291-
};
292317

293-
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 8;
294-
if (!llvm::isUInt<27>(offset)) {
295-
// We cannot encode the Offset, use the long form.
296-
writePltLong(buf, sym.getGotPltVA(), pltEntryAddr);
297-
return;
318+
if (!config->armThumbPLTs) {
319+
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 8;
320+
321+
// The PLT entry is similar to the example given in Appendix A of ELF for
322+
// the Arm Architecture. Instead of using the Group Relocations to find the
323+
// optimal rotation for the 8-bit immediate used in the add instructions we
324+
// hard code the most compact rotations for simplicity. This saves a load
325+
// instruction over the long plt sequences.
326+
const uint32_t pltData[] = {
327+
0xe28fc600, // L1: add ip, pc, #0x0NN00000 Offset(&(.got.plt) - L1 - 8
328+
0xe28cca00, // add ip, ip, #0x000NN000 Offset(&(.got.plt) - L1 - 8
329+
0xe5bcf000, // ldr pc, [ip, #0x00000NNN] Offset(&(.got.plt) - L1 - 8
330+
};
331+
if (!llvm::isUInt<27>(offset)) {
332+
// We cannot encode the Offset, use the long form.
333+
writePltLong(buf, sym.getGotPltVA(), pltEntryAddr);
334+
return;
335+
}
336+
write32(buf + 0, pltData[0] | ((offset >> 20) & 0xff));
337+
write32(buf + 4, pltData[1] | ((offset >> 12) & 0xff));
338+
write32(buf + 8, pltData[2] | (offset & 0xfff));
339+
memcpy(buf + 12, trapInstr.data(), 4); // Pad to 16-byte boundary
340+
} else {
341+
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 12;
342+
assert(llvm::isUInt<32>(offset) && "This should always fit into a 32-bit offset");
343+
344+
// A PLT entry will be:
345+
//
346+
// movw ip, #<lower 16 bits>
347+
// movt ip, #<upper 16 bits>
348+
// add ip, pc
349+
// L1: ldr.w pc, [ip]
350+
// b L1
351+
//
352+
// where ip = r12 = 0xc
353+
354+
// movw ip, #<lower 16 bits>
355+
write16(buf + 2, 0x0c00); // use `ip`
356+
relocateNoSym(buf, R_ARM_THM_MOVW_ABS_NC, offset);
357+
358+
// movt ip, #<upper 16 bits>
359+
write16(buf + 6, 0x0c00); // use `ip`
360+
relocateNoSym(buf + 4, R_ARM_THM_MOVT_ABS, offset);
361+
362+
write16(buf + 8, 0x44fc); // add ip, pc
363+
write16(buf + 10, 0xf8dc); // ldr.w pc, [ip] (bottom half)
364+
write16(buf + 12, 0xf000); // ldr.w pc, [ip] (upper half)
365+
write16(buf + 14, 0xe7fc); // Branch to previous instruction
298366
}
299-
write32(buf + 0, pltData[0] | ((offset >> 20) & 0xff));
300-
write32(buf + 4, pltData[1] | ((offset >> 12) & 0xff));
301-
write32(buf + 8, pltData[2] | (offset & 0xfff));
302-
memcpy(buf + 12, trapInstr.data(), 4); // Pad to 16-byte boundary
303367
}
304368

305369
void ARM::addPltSymbols(InputSection &isec, uint64_t off) const {
306-
addSyntheticLocal("$a", STT_NOTYPE, off, 0, isec);
307-
addSyntheticLocal("$d", STT_NOTYPE, off + 12, 0, isec);
370+
if (config->armThumbPLTs) {
371+
addSyntheticLocal("$t", STT_NOTYPE, off, 0, isec);
372+
} else {
373+
addSyntheticLocal("$a", STT_NOTYPE, off, 0, isec);
374+
addSyntheticLocal("$d", STT_NOTYPE, off + 12, 0, isec);
375+
}
308376
}
309377

310378
bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
@@ -325,6 +393,8 @@ bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
325393
case R_ARM_JUMP24:
326394
// Source is ARM, all PLT entries are ARM so no interworking required.
327395
// Otherwise we need to interwork if STT_FUNC Symbol has bit 0 set (Thumb).
396+
assert(!config->armThumbPLTs &&
397+
"If the source is ARM, we should not need Thumb PLTs");
328398
if (s.isFunc() && expr == R_PC && (s.getVA() & 1))
329399
return true;
330400
[[fallthrough]];
@@ -335,9 +405,9 @@ bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
335405
}
336406
case R_ARM_THM_JUMP19:
337407
case R_ARM_THM_JUMP24:
338-
// Source is Thumb, all PLT entries are ARM so interworking is required.
408+
// Source is Thumb, when all PLT entries are ARM interworking is required.
339409
// Otherwise we need to interwork if STT_FUNC Symbol has bit 0 clear (ARM).
340-
if (expr == R_PLT_PC || (s.isFunc() && (s.getVA() & 1) == 0))
410+
if ((expr == R_PLT_PC && !config->armThumbPLTs) || (s.isFunc() && (s.getVA() & 1) == 0))
341411
return true;
342412
[[fallthrough]];
343413
case R_ARM_THM_CALL: {
@@ -547,7 +617,6 @@ void ARM::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
547617
// STT_FUNC we choose whether to write a BL or BLX depending on the
548618
// value of bit 0 of Val. With bit 0 == 1 denoting Thumb. If the symbol is
549619
// not of type STT_FUNC then we must preserve the original instruction.
550-
// PLT entries are always ARM state so we know we don't need to interwork.
551620
assert(rel.sym); // R_ARM_CALL is always reached via relocate().
552621
bool bit0Thumb = val & 1;
553622
bool isBlx = (read32(loc) & 0xfe000000) == 0xfa000000;
@@ -606,12 +675,13 @@ void ARM::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
606675
// PLT entries are always ARM state so we know we need to interwork.
607676
assert(rel.sym); // R_ARM_THM_CALL is always reached via relocate().
608677
bool bit0Thumb = val & 1;
678+
bool useThumb = bit0Thumb || config->armThumbPLTs;
609679
bool isBlx = (read16(loc + 2) & 0x1000) == 0;
610680
// lld 10.0 and before always used bit0Thumb when deciding to write a BLX
611-
// even when type not STT_FUNC. PLT entries generated by LLD are always ARM.
612-
if (!rel.sym->isFunc() && !rel.sym->isInPlt() && isBlx == bit0Thumb)
681+
// even when type not STT_FUNC.
682+
if (!rel.sym->isFunc() && !rel.sym->isInPlt() && isBlx == useThumb)
613683
stateChangeWarning(loc, rel.type, *rel.sym);
614-
if (rel.sym->isFunc() || rel.sym->isInPlt() ? !bit0Thumb : isBlx) {
684+
if ((rel.sym->isFunc() || rel.sym->isInPlt()) ? !useThumb : isBlx) {
615685
// We are writing a BLX. Ensure BLX destination is 4-byte aligned. As
616686
// the BLX instruction may only be two byte aligned. This must be done
617687
// before overflow check.

lld/ELF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ struct Config {
217217
bool allowMultipleDefinition;
218218
bool fatLTOObjects;
219219
bool androidPackDynRelocs = false;
220+
bool armThumbPLTs = false;
220221
bool armHasBlx = false;
221222
bool armHasMovtMovw = false;
222223
bool armJ1J2BranchEncoding = false;

lld/ELF/InputFiles.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,18 @@ static void updateSupportedARMFeatures(const ARMAttributeParser &attributes) {
194194
if (arch >= ARMBuildAttrs::CPUArch::v8_M_Base &&
195195
profile == ARMBuildAttrs::MicroControllerProfile)
196196
config->armCMSESupport = true;
197+
198+
// The thumb PLT entries require Thumb2 which can be used on multiple archs.
199+
// For now, let's limit it to ones where ARM isn't available and we know have
200+
// Thumb2.
201+
std::optional<unsigned> armISA =
202+
attributes.getAttributeValue(ARMBuildAttrs::ARM_ISA_use);
203+
std::optional<unsigned> thumb =
204+
attributes.getAttributeValue(ARMBuildAttrs::THUMB_ISA_use);
205+
bool noArmISA = !armISA || *armISA == ARMBuildAttrs::Not_Allowed;
206+
bool hasThumb2 = thumb && *thumb >= ARMBuildAttrs::AllowThumb32;
207+
if (noArmISA && hasThumb2)
208+
config->armThumbPLTs = true;
197209
}
198210

199211
InputFile::InputFile(Kind k, MemoryBufferRef m)

lld/test/ELF/armv8-thumb-plt-reloc.s

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// REQUIRES: arm
2+
// RUN: llvm-mc -filetype=obj -arm-add-build-attributes -triple=thumbv8-none-linux-gnueabi --arch=thumb --mcpu=cortex-m33 %p/Inputs/arm-plt-reloc.s -o %t1.o
3+
// RUN: llvm-mc -filetype=obj -arm-add-build-attributes -triple=thumbv8-none-linux-gnueabi --arch=thumb --mcpu=cortex-m33 %s -o %t2.o
4+
// RUN: ld.lld %t1.o %t2.o -o %t
5+
// RUN: llvm-objdump --no-print-imm-hex -d %t | FileCheck %s
6+
// RUN: ld.lld -shared %t1.o %t2.o -o %t.so
7+
// RUN: llvm-objdump --no-print-imm-hex -d %t.so | FileCheck --check-prefix=DSO %s
8+
// RUN: llvm-readelf -S -r %t.so | FileCheck -check-prefix=DSOREL %s
9+
10+
// RUN: llvm-mc -filetype=obj -arm-add-build-attributes -triple=thumbv8-none-linux-gnueabi --arch=thumbeb --mcpu=cortex-m33 %p/Inputs/arm-plt-reloc.s -o %t1.be.o
11+
// RUN: llvm-mc -filetype=obj -arm-add-build-attributes -triple=thumbv8-none-linux-gnueabi --arch=thumbeb --mcpu=cortex-m33 %s -o %t2.be.o
12+
// RUN: ld.lld %t1.be.o %t2.be.o -o %t.be
13+
// RUN: llvm-objdump --no-print-imm-hex -d %t.be | FileCheck %s
14+
// RUN: ld.lld -shared %t1.be.o %t2.be.o -o %t.so.be
15+
// RUN: llvm-objdump --no-print-imm-hex -d %t.so.be | FileCheck --check-prefix=DSO %s
16+
// RUN: llvm-readelf -S -r %t.so.be | FileCheck -check-prefix=DSOREL %s
17+
18+
// RUN: ld.lld --be8 %t1.be.o %t2.be.o -o %t.be
19+
// RUN: llvm-objdump --no-print-imm-hex -d %t.be | FileCheck %s
20+
// RUN: ld.lld --be8 -shared %t1.be.o %t2.be.o -o %t.so.be
21+
// RUN: llvm-objdump --no-print-imm-hex -d %t.so.be | FileCheck --check-prefix=DSO %s
22+
// RUN: llvm-readelf -S -r %t.so.be | FileCheck -check-prefix=DSOREL %s
23+
24+
/// Test PLT entry generation
25+
.text
26+
.align 2
27+
.globl _start
28+
.type _start,%function
29+
_start:
30+
bl func1
31+
bl func2
32+
bl func3
33+
b.w func1
34+
b.w func2
35+
b.w func3
36+
beq.w func1
37+
beq.w func2
38+
beq.w func3
39+
40+
/// Executable, expect no PLT
41+
// CHECK: Disassembly of section .text:
42+
// CHECK-EMPTY:
43+
// CHECK-NEXT: <func1>:
44+
// CHECK-NEXT: bx lr
45+
// CHECK: <func2>:
46+
// CHECK-NEXT: bx lr
47+
// CHECK: <func3>:
48+
// CHECK-NEXT: bx lr
49+
// CHECK-NEXT: d4d4
50+
// CHECK: <_start>:
51+
// CHECK-NEXT: bl {{.*}} <func1>
52+
// CHECK-NEXT: bl {{.*}} <func2>
53+
// CHECK-NEXT: bl {{.*}} <func3>
54+
// CHECK-NEXT: b.w {{.*}} <func1>
55+
// CHECK-NEXT: b.w {{.*}} <func2>
56+
// CHECK-NEXT: b.w {{.*}} <func3>
57+
// CHECK-NEXT: beq.w {{.*}} <func1>
58+
// CHECK-NEXT: beq.w {{.*}} <func2>
59+
// CHECK-NEXT: beq.w {{.*}} <func3>
60+
61+
// DSO: Disassembly of section .text:
62+
// DSO-EMPTY:
63+
// DSO-NEXT: <func1>:
64+
// DSO-NEXT: bx lr
65+
// DSO: <func2>:
66+
// DSO-NEXT: bx lr
67+
// DSO: <func3>:
68+
// DSO-NEXT: bx lr
69+
// DSO-NEXT: d4d4
70+
// DSO: <_start>:
71+
/// 0x10260 = PLT func1
72+
// DSO-NEXT: bl 0x10260
73+
/// 0x10270 = PLT func2
74+
// DSO-NEXT: bl 0x10270
75+
/// 0x10280 = PLT func3
76+
// DSO-NEXT: bl 0x10280
77+
/// 0x10260 = PLT func1
78+
// DSO-NEXT: b.w 0x10260
79+
/// 0x10270 = PLT func2
80+
// DSO-NEXT: b.w 0x10270
81+
/// 0x10280 = PLT func3
82+
// DSO-NEXT: b.w 0x10280
83+
/// 0x10260 = PLT func1
84+
// DSO-NEXT: beq.w 0x10260
85+
/// 0x10270 = PLT func2
86+
// DSO-NEXT: beq.w 0x10270
87+
/// 0x10280 = PLT func3
88+
// DSO-NEXT: beq.w 0x10280
89+
// DSO: Disassembly of section .plt:
90+
// DSO-EMPTY:
91+
// DSO-NEXT: 10240 <.plt>:
92+
// DSO-NEXT: push {lr}
93+
// DSO-NEXT: ldr.w lr, [pc, #8]
94+
// DSO-NEXT: add lr, pc
95+
// DSO-NEXT: ldr pc, [lr, #8]!
96+
/// 0x20098 = .got.plt (0x302D8) - pc (0x10238 = .plt + 8) - 8
97+
// DSO-NEXT: .word 0x00020098
98+
// DSO-NEXT: .word 0xd4d4d4d4
99+
// DSO-NEXT: .word 0xd4d4d4d4
100+
// DSO-NEXT: .word 0xd4d4d4d4
101+
// DSO-NEXT: .word 0xd4d4d4d4
102+
103+
/// 136 + 2 << 16 + 0x1026c = 0x302f4 = got entry 1
104+
// DSO-NEXT: 10260: f240 0c88 movw r12, #136
105+
// DSO-NEXT: f2c0 0c02 movt r12, #2
106+
// DSO-NEXT: 44fc add r12, pc
107+
// DSO-NEXT: f8dc f000 ldr.w pc, [r12]
108+
// DSO-NEXT: e7fc b 0x1026a
109+
/// 124 + 2 << 16 + 0x1027c = 0x302f8 = got entry 2
110+
// DSO-NEXT: 10270: f240 0c7c movw r12, #124
111+
// DSO-NEXT: f2c0 0c02 movt r12, #2
112+
// DSO-NEXT: 44fc add r12, pc
113+
// DSO-NEXT: f8dc f000 ldr.w pc, [r12]
114+
// DSO-NEXT: e7fc b 0x1027a
115+
/// 112 + 2 << 16 + 0x1028c = 0x302fc = got entry 3
116+
// DSO-NEXT: 10280: f240 0c70 movw r12, #112
117+
// DSO-NEXT: f2c0 0c02 movt r12, #2
118+
// DSO-NEXT: 44fc add r12, pc
119+
// DSO-NEXT: f8dc f000 ldr.w pc, [r12]
120+
// DSO-NEXT: e7fc b 0x1028a
121+
122+
// DSOREL: .got.plt PROGBITS 000302e8 {{.*}} 000018 00 WA 0 0 4
123+
// DSOREL: Relocation section '.rel.plt'
124+
// DSOREL: 000302f4 {{.*}} R_ARM_JUMP_SLOT {{.*}} func1
125+
// DSOREL: 000302f8 {{.*}} R_ARM_JUMP_SLOT {{.*}} func2
126+
// DSOREL: 000302fc {{.*}} R_ARM_JUMP_SLOT {{.*}} func3

0 commit comments

Comments
 (0)