<html>
<head>
<base href="https://bugs.llvm.org/">
</head>
<body><table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Bug ID</th>
<td><a class="bz_bug_link
bz_status_NEW "
title="NEW - lld ignores addend for weak undefined TLS symbol in static executable"
href="https://bugs.llvm.org/show_bug.cgi?id=40570">40570</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>lld ignores addend for weak undefined TLS symbol in static executable
</td>
</tr>
<tr>
<th>Product</th>
<td>lld
</td>
</tr>
<tr>
<th>Version</th>
<td>unspecified
</td>
</tr>
<tr>
<th>Hardware</th>
<td>PC
</td>
</tr>
<tr>
<th>OS</th>
<td>Linux
</td>
</tr>
<tr>
<th>Status</th>
<td>NEW
</td>
</tr>
<tr>
<th>Severity</th>
<td>enhancement
</td>
</tr>
<tr>
<th>Priority</th>
<td>P
</td>
</tr>
<tr>
<th>Component</th>
<td>ELF
</td>
</tr>
<tr>
<th>Assignee</th>
<td>unassignedbugs@nondot.org
</td>
</tr>
<tr>
<th>Reporter</th>
<td>rprichard@google.com
</td>
</tr>
<tr>
<th>CC</th>
<td>llvm-bugs@lists.llvm.org, peter.smith@linaro.org
</td>
</tr></table>
<p>
<div>
<pre>lld appears to discard an addend when it computes the address of a weak
undefined TLS symbol in a static executable. This shows up in a couple of ways.
(FWIW: This issue didn't come up in practice. A weak-undefined test case I
wrote for Bionic stumbled onto it.)
With an LE-model access:
__attribute__((weak,tls_model("local-exec")))
extern __thread char tls_var[64];
char* get_addr() { return &tls_var[32]; }
// Run: clang -O2 test.c -static -nostdlib -fuse-ld=lld
With lld, the generated instructions use a TPOFF of zero rather than 32. i.e.
&tls_var[0] and &tls_var[32] would evaluate to the same address.
0000000000201000 <get_addr>:
201000: 64 48 8b 04 25 00 00 mov %fs:0x0,%rax
201007: 00 00
201009: 48 8d 80 00 00 00 00 lea 0x0(%rax),%rax
201010: c3 retq
Dropping the addend affects x86-64 in a different way. On that target, a TLS IE
relocation always(?) has a -4 addend, which should apply to the PC-to-GOT
offset but not to the TP-to-symbol offset. When lld relaxes the IE relocation
to LE, it creates a R_RELAX_TLS_IE_TO_LE relocation internally. The -4 addend
is applied for non-weak-undef symbols, then it's canceled back out in
X86_64<ELFT>::relaxTlsIeToLe:
// The original code used a PC relative relocation.
// Need to compensate for the -4 it had in the addend.
write32le(Loc, Val + 4);
For a weak-undef symbol, the -4 addend is ignored here (added in
<a href="https://reviews.llvm.org/D24832">https://reviews.llvm.org/D24832</a>):
case R_RELAX_TLS_GD_TO_LE:
case R_RELAX_TLS_IE_TO_LE:
case R_RELAX_TLS_LD_TO_LE:
case R_TLS:
// A weak undefined TLS symbol resolves to the base of the TLS
// block, i.e. gets a value of zero. If we pass --gc-sections to
// lld and .tbss is not referenced, it gets reclaimed and we don't
// create a TLS program header. Therefore, we resolve this
// statically to zero.
if (Sym.isTls() && Sym.isUndefWeak())
return 0;
return Sym.getVA(A) + getTlsTpOffset();
The result is that lld resolves a weak-undef symbol to 4 on x86-64:
__attribute__((weak,tls_model("initial-exec")))
extern __thread char tls_var;
char* get_addr() { return &tls_var; }
// Run: clang -O2 test.c -static -nostdlib -fuse-ld=lld
lld output:
0000000000201000 <get_addr>:
201000: 64 48 8b 04 25 00 00 mov %fs:0x0,%rax
201007: 00 00
201009: 48 8d 80 04 00 00 00 lea 0x4(%rax),%rax
201010: c3 retq
Including the addend in getRelocTargetVA corrects the two behaviors described
above. If lld's behavior should change, maybe this is the fix:
diff --git a/ELF/InputSection.cpp b/ELF/InputSection.cpp
index 78f4812a5..c986012cb 100644
--- a/ELF/InputSection.cpp
+++ b/ELF/InputSection.cpp
@@ -741,7 +741,7 @@ static uint64_t getRelocTargetVA(const InputFile *File,
RelType Type, int64_t A,
// create a TLS program header. Therefore, we resolve this
// statically to zero.
if (Sym.isTls() && Sym.isUndefWeak())
- return 0;
+ return A;
return Sym.getVA(A) + getTlsTpOffset();
case R_RELAX_TLS_GD_TO_LE_NEG:
case R_NEG_TLS:
I'm not sure what guarantees exist for a weak-undefined TLS symbol. While I
think lld's behavior makes sense, AFAICT it is different from bfd/gold. I think
lld's behavior matches what a dynamic linker will do with a dynamic TPREL
relocation.
I studied bfd and gold output for a while (binutils 2.30), on x86-{32,64} and
arm{32,64}, and I tried to summarize the variety of behavior I saw when linking
a static executable. I'm trying to determine what value gets added to the
thread pointer for a reference to a weak-undefined symbol:
- bfd:
- Generally, bfd uses the something like (-p_vaddr + getTlsTpOffset()).
If we instead had the VA of a defined symbol, the LE calculation would
be (VA - p_vaddr + getTlsTpOffset()), so I think bfd is just treating
the weak-undef VA as 0. p_vaddr is the address of the TLS initialization
image, stored in the PT_TLS segment. The resulting &tls_var is not very
meaningful -- it could point to unmapped memory.
- [arm64] segfaults if the program has no TLS segment
- [otherwise] Uses 0 if the program has no TLS segment (matching lld)
- gold (special case: IE-to-LE on x86-64 only)
- Uses 0 like lld. This special case doesn't apply to arm{32,64} or x86-32.
I'm not sure what's up here -- this special case doesn't apply if I just
use an LE access.
- gold (otherwise):
- internal error if the program has no TLS segment
- typically in relocate_tls
- for arm32 IE, it's in do_write instead
- Otherwise uses getTlsTpOffset(), the start of the executable's TLS
segment.</pre>
</div>
</p>
<hr>
<span>You are receiving this mail because:</span>
<ul>
<li>You are on the CC list for the bug.</li>
</ul>
</body>
</html>