diff --git a/lld/ELF/Arch/X86.cpp b/lld/ELF/Arch/X86.cpp --- a/lld/ELF/Arch/X86.cpp +++ b/lld/ELF/Arch/X86.cpp @@ -56,6 +56,7 @@ iRelativeRel = R_386_IRELATIVE; relativeRel = R_386_RELATIVE; symbolicRel = R_386_32; + tlsDescRel = R_386_TLS_DESC; tlsGotRel = R_386_TLS_TPOFF; tlsModuleIndexRel = R_386_TLS_DTPMOD32; tlsOffsetRel = R_386_TLS_DTPOFF32; @@ -71,7 +72,8 @@ } int X86::getTlsGdRelaxSkip(RelType type) const { - return 2; + // TLSDESC relocations are processed separately. See relaxTlsGdToLe below. + return type == R_386_TLS_GOTDESC || type == R_386_TLS_DESC_CALL ? 1 : 2; } RelExpr X86::getRelExpr(RelType type, const Symbol &s, @@ -143,6 +145,10 @@ // the byte, we can determine whether the instruction uses the operand as an // absolute address (R_GOT) or a register-relative address (R_GOTPLT). return (loc[-1] & 0xc7) == 0x5 ? R_GOT : R_GOTPLT; + case R_386_TLS_GOTDESC: + return R_TLSDESC_GOTPLT; + case R_386_TLS_DESC_CALL: + return R_TLSDESC_CALL; case R_386_TLS_GOTIE: return R_GOTPLT; case R_386_GOTOFF: @@ -167,7 +173,8 @@ case R_RELAX_TLS_GD_TO_IE: return R_RELAX_TLS_GD_TO_IE_GOTPLT; case R_RELAX_TLS_GD_TO_LE: - return R_RELAX_TLS_GD_TO_LE_NEG; + return type == R_386_TLS_GD ? R_RELAX_TLS_GD_TO_LE_NEG + : R_RELAX_TLS_GD_TO_LE; } } @@ -259,6 +266,8 @@ case R_386_PC32: case R_386_PLT32: case R_386_RELATIVE: + case R_386_TLS_GOTDESC: + case R_386_TLS_DESC_CALL: case R_386_TLS_DTPMOD32: case R_386_TLS_DTPOFF32: case R_386_TLS_LDO_32: @@ -273,6 +282,8 @@ case R_386_TLS_TPOFF: case R_386_TLS_TPOFF32: return SignExtend64<32>(read32le(buf)); + case R_386_TLS_DESC: + return SignExtend64<32>(read32le(buf + 4)); case R_386_NONE: case R_386_JUMP_SLOT: // These relocations are defined as not having an implicit addend. @@ -323,6 +334,8 @@ case R_386_PC32: case R_386_PLT32: case R_386_RELATIVE: + case R_386_TLS_GOTDESC: + case R_386_TLS_DESC_CALL: case R_386_TLS_DTPMOD32: case R_386_TLS_DTPOFF32: case R_386_TLS_GD: @@ -337,39 +350,79 @@ checkInt(loc, val, 32, rel); write32le(loc, val); break; + case R_386_TLS_DESC: + // The addend is stored in the second 32-bit word. + write32le(loc + 4, val); + break; default: llvm_unreachable("unknown relocation"); } } -void X86::relaxTlsGdToLe(uint8_t *loc, const Relocation &, uint64_t val) const { - // Convert - // leal x@tlsgd(, %ebx, 1), - // call __tls_get_addr@plt - // to - // movl %gs:0,%eax - // subl $x@ntpoff,%eax - const uint8_t inst[] = { - 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax - 0x81, 0xe8, 0, 0, 0, 0, // subl Val(%ebx), %eax - }; - memcpy(loc - 3, inst, sizeof(inst)); - write32le(loc + 5, val); +void X86::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, + uint64_t val) const { + if (rel.type == R_386_TLS_GD) { + // Convert + // leal x@tlsgd(, %ebx, 1), %eax + // call __tls_get_addr@plt + // to + // movl %gs:0, %eax + // subl $x@tpoff, %eax + const uint8_t inst[] = { + 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax + 0x81, 0xe8, 0, 0, 0, 0, // subl val(%ebx), %eax + }; + memcpy(loc - 3, inst, sizeof(inst)); + write32le(loc + 5, val); + } else if (rel.type == R_386_TLS_GOTDESC) { + // Convert leal x@tlsdesc(%ebx), %eax to leal x@ntpoff, %eax. + // + // Note: call *x@tlsdesc(%eax) may not immediately follow this instruction. + if (memcmp(loc - 2, "\x8d\x83", 2)) { + error(getErrorLocation(loc - 2) + + "R_386_TLS_GOTDESC must be used in leal x@tlsdesc(%ebx), %eax"); + return; + } + loc[-1] = 0x05; + write32le(loc, val); + } else { + // Convert call *x@tlsdesc(%eax) to xchg ax, ax. + assert(rel.type == R_386_TLS_DESC_CALL); + loc[0] = 0x66; + loc[1] = 0x90; + } } -void X86::relaxTlsGdToIe(uint8_t *loc, const Relocation &, uint64_t val) const { - // Convert - // leal x@tlsgd(, %ebx, 1), - // call __tls_get_addr@plt - // to - // movl %gs:0, %eax - // addl x@gotntpoff(%ebx), %eax - const uint8_t inst[] = { - 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax - 0x03, 0x83, 0, 0, 0, 0, // addl Val(%ebx), %eax - }; - memcpy(loc - 3, inst, sizeof(inst)); - write32le(loc + 5, val); +void X86::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, + uint64_t val) const { + if (rel.type == R_386_TLS_GD) { + // Convert + // leal x@tlsgd(, %ebx, 1), %eax + // call __tls_get_addr@plt + // to + // movl %gs:0, %eax + // addl x@gotntpoff(%ebx), %eax + const uint8_t inst[] = { + 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax + 0x03, 0x83, 0, 0, 0, 0, // addl val(%ebx), %eax + }; + memcpy(loc - 3, inst, sizeof(inst)); + write32le(loc + 5, val); + } else if (rel.type == R_386_TLS_GOTDESC) { + // Convert leal x@tlsdesc(%ebx), %eax to movl x@gotntpoff(%ebx), %eax. + if (memcmp(loc - 2, "\x8d\x83", 2)) { + error(getErrorLocation(loc - 2) + + "R_386_TLS_GOTDESC must be used in leal x@tlsdesc(%ebx), %eax"); + return; + } + loc[-2] = 0x8b; + write32le(loc, val); + } else { + // Convert call *x@tlsdesc(%eax) to xchg ax, ax. + assert(rel.type == R_386_TLS_DESC_CALL); + loc[0] = 0x66; + loc[1] = 0x90; + } } // In some conditions, relocations can be optimized to avoid using GOT. diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -832,6 +832,8 @@ return in.got->getGlobalDynAddr(sym) + a; case R_TLSDESC_PC: return in.got->getGlobalDynAddr(sym) + a - p; + case R_TLSDESC_GOTPLT: + return in.got->getGlobalDynAddr(sym) + a - in.gotPlt->getVA(); case R_AARCH64_TLSDESC_PAGE: return getAArch64Page(in.got->getGlobalDynAddr(sym) + a) - getAArch64Page(p); diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h --- a/lld/ELF/Relocations.h +++ b/lld/ELF/Relocations.h @@ -63,6 +63,7 @@ R_TLSDESC, R_TLSDESC_CALL, R_TLSDESC_PC, + R_TLSDESC_GOTPLT, R_TLSGD_GOT, R_TLSGD_GOTPLT, R_TLSGD_PC, diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -230,8 +230,9 @@ R_AARCH64_GOT_PAGE_PC, R_GOT_PC, R_GOTONLY_PC, R_GOTPLTONLY_PC, R_PLT_PC, R_PLT_GOTPLT, R_TLSGD_GOT, R_TLSGD_GOTPLT, R_TLSGD_PC, R_PPC32_PLTREL, R_PPC64_CALL_PLT, R_PPC64_RELAX_TOC, R_RISCV_ADD, - R_TLSDESC_CALL, R_TLSDESC_PC, R_AARCH64_TLSDESC_PAGE, R_TLSLD_HINT, - R_TLSIE_HINT, R_AARCH64_GOT_PAGE>(e)) + R_TLSDESC_CALL, R_TLSDESC_PC, R_TLSDESC_GOTPLT, + R_AARCH64_TLSDESC_PAGE, R_TLSLD_HINT, R_TLSIE_HINT, + R_AARCH64_GOT_PAGE>(e)) return true; // These never do, except if the entire file is position dependent or if @@ -1157,8 +1158,8 @@ if (config->emachine == EM_MIPS) return handleMipsTlsRelocation(type, sym, c, offset, addend, expr); - if (oneof( - expr) && + if (oneof(expr) && config->shared) { if (in.got->addDynTlsEntry(sym)) { uint64_t off = in.got->getGlobalDynOffset(sym); @@ -1235,7 +1236,7 @@ } if (oneof(expr)) { + R_TLSDESC_GOTPLT, R_TLSGD_GOT, R_TLSGD_GOTPLT, R_TLSGD_PC>(expr)) { if (!toExecRelax) { if (in.got->addDynTlsEntry(sym)) { uint64_t off = in.got->getGlobalDynOffset(sym); @@ -1401,7 +1402,7 @@ // // The 5 types that relative GOTPLT are all x86 and x86-64 specific. if (oneof(expr)) { + R_TLSDESC_GOTPLT, R_TLSGD_GOTPLT>(expr)) { in.gotPlt->hasGotPltOffRel = true; } else if (oneof( expr)) { diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1960,7 +1960,7 @@ 0x800, STV_DEFAULT); } - if (config->emachine == EM_X86_64) { + if (config->emachine == EM_386 || config->emachine == EM_X86_64) { // On targets that support TLSDESC, _TLS_MODULE_BASE_ is defined in such a // way that: // diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -32,6 +32,11 @@ Instead, a value of 0 will be written. (`D110014 `_) +Architecture specific changes: + +* The x86-32 port now supports TLSDESC (``-mtls-dialect=gnu2``). + (`D112582 `_) + Breaking changes ---------------- diff --git a/lld/test/ELF/i386-tlsdesc-gd.s b/lld/test/ELF/i386-tlsdesc-gd.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/i386-tlsdesc-gd.s @@ -0,0 +1,113 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=i386 %s -o %t.o +# RUN: echo '.tbss; .globl c; c: .zero 4' | llvm-mc -filetype=obj -triple=i386 - -o %t1.o +# RUN: ld.lld -shared -soname=t1.so %t1.o -o %t1.so + +# RUN: ld.lld -shared -z now %t.o %t1.o -o %t.so +# RUN: llvm-readobj -r -x .got %t.so | FileCheck --check-prefix=GD-REL %s +# RUN: llvm-objdump -h -d --no-show-raw-insn %t.so | FileCheck --check-prefix=GD %s + +# RUN: ld.lld -shared -z now %t.o %t1.o -o %t-rela.so -z rela +# RUN: llvm-readobj -r -x .got %t-rela.so | FileCheck --check-prefix=GD-RELA %s + +# RUN: ld.lld -z now %t.o %t1.o -o %t +# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s +# RUN: llvm-objdump -h -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s + +# RUN: ld.lld -z now %t.o %t1.so -o %t +# RUN: llvm-readobj -r %t | FileCheck --check-prefix=IE-REL %s +# RUN: llvm-objdump -h -d --no-show-raw-insn %t | FileCheck --check-prefix=IE %s + +# GD-REL: .rel.dyn { +# GD-REL-NEXT: 0x2250 R_386_TLS_DESC - +# GD-REL-NEXT: 0x2248 R_386_TLS_DESC a +# GD-REL-NEXT: 0x2258 R_386_TLS_DESC c +# GD-REL-NEXT: } +# GD-REL: Hex dump of section '.got': +# GD-REL-NEXT: 0x00002248 00000000 00000000 00000000 0b000000 +# GD-REL-NEXT: 0x00002258 00000000 00000000 + +# GD-RELA: .rela.dyn { +# GD-RELA-NEXT: 0x225C R_386_TLS_DESC - 0xB +# GD-RELA-NEXT: 0x2254 R_386_TLS_DESC a 0x0 +# GD-RELA-NEXT: 0x2264 R_386_TLS_DESC c 0x0 +# GD-RELA-NEXT: } +# GD-RELA: Hex dump of section '.got': +# GD-RELA-NEXT: 0x00002254 00000000 00000000 00000000 00000000 +# GD-RELA-NEXT: 0x00002264 00000000 00000000 + +# GD: .got 00000018 00002248 +# GD: .got.plt 0000000c 00002260 + +# &.rel.dyn[a]-.got.plt = 0x2248-0x2260 = -24 +# GD: leal -24(%ebx), %eax +# GD-NEXT: calll *(%eax) +# GD-NEXT: movl %gs:(%eax), %eax + +# &.rel.dyn[b]-.got.plt = 0x2250-0x2260 = -16 +# GD-NEXT: leal -16(%ebx), %eax +# GD-NEXT: movl %edx, %ebx +# GD-NEXT: calll *(%eax) +# GD-NEXT: movl %gs:(%eax), %eax + +# &.rel.dyn[c]-.got.plt = 0x2258-0x2260 = -8 +# GD-NEXT: leal -8(%ebx), %eax +# GD-NEXT: calll *(%eax) +# GD-NEXT: movl %gs:(%eax), %eax + +# NOREL: no relocations + +## st_value(a) - tls_size = -8 +# LE: leal -8, %eax +# LE-NEXT: nop +# LE-NEXT: movl %gs:(%eax), %eax +## st_value(b) - tls_size = -5 +# LE: leal -5, %eax +# LE-NEXT: movl %edx, %ebx +# LE-NEXT: nop +# LE-NEXT: movl %gs:(%eax), %eax +## st_value(c) - tls_size = -4 +# LE: leal -4, %eax +# LE-NEXT: nop +# LE-NEXT: movl %gs:(%eax), %eax + +# IE-REL: .rel.dyn { +# IE-REL-NEXT: 0x40222C R_386_TLS_TPOFF c +# IE-REL-NEXT: } + +# IE: .got 00000004 0040222c +# IE: .got.plt 0000000c 00402230 + +## a and b are relaxed to use LE. +# IE: leal -4, %eax +# IE-NEXT: nop +# IE-NEXT: movl %gs:(%eax), %eax +# IE-NEXT: leal -1, %eax +# IE-NEXT: movl %edx, %ebx +# IE-NEXT: nop +# IE-NEXT: movl %gs:(%eax), %eax +## &.got[a]-.got.plt = 0x2220 - 0x2224 = -4 +# IE-NEXT: movl -4(%ebx), %eax +# IE-NEXT: nop +# IE-NEXT: movl %gs:(%eax), %eax + +leal a@tlsdesc(%ebx), %eax +call *a@tlscall(%eax) +movl %gs:(%eax), %eax + +leal b@tlsdesc(%ebx), %eax +movl %edx, %ebx # GCC -O0 may add an extra insn in between. +call *b@tlscall(%eax) +movl %gs:(%eax), %eax + +leal c@tlsdesc(%ebx), %eax +call *c@tlscall(%eax) +movl %gs:(%eax), %eax + +.section .tbss +.globl a +.zero 8 +a: +.zero 3 +b: +.zero 1 diff --git a/lld/test/ELF/i386-tlsdesc-ld.s b/lld/test/ELF/i386-tlsdesc-ld.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/i386-tlsdesc-ld.s @@ -0,0 +1,49 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=i386 %s -o %t.o + +# RUN: ld.lld -shared -z now %t.o -o %t.so +# RUN: llvm-readobj -r %t.so | FileCheck --check-prefix=LD-REL %s +# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck --check-prefix=LD %s + +# RUN: ld.lld -z now %t.o -o %t +# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s +# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s + +## Check _TLS_MODULE_BASE_ used by LD produces a dynamic relocation with a value of 0. +# LD-REL: .rel.dyn { +# LD-REL-NEXT: R_386_TLS_DESC - +# LD-REL-NEXT: } + +## 0x2318-0x1267 = 4273 +## dtpoff(a) = 8, dtpoff(b) = 12 +# LD: leal -8(%ebx), %eax +# LD-NEXT: calll *(%eax) +# LD-NEXT: leal 8(%eax), %ecx +# LD-NEXT: leal 12(%eax), %edx + +## When producing an executable, the LD code sequence can be relaxed to LE. +## It is the same as GD->LE. +## tpoff(_TLS_MODULE_BASE_) = 0, tpoff(a) = -8, tpoff(b) = -4 + +# NOREL: no relocations + +# LE: leal 0, %eax +# LE-NEXT: nop +# LE-NEXT: leal -8(%eax), %ecx +# LE-NEXT: leal -4(%eax), %edx +# LE-NEXT: addl %gs:0, %ecx +# LE-NEXT: addl %gs:0, %edx + +leal _TLS_MODULE_BASE_@tlsdesc(%ebx), %eax +call *_TLS_MODULE_BASE_@tlscall(%eax) +leal a@dtpoff(%eax), %ecx +leal b@dtpoff(%eax), %edx +addl %gs:0, %ecx +addl %gs:0, %edx + +.section .tbss +.zero 8 +a: +.zero 4 +b: +.zero 4 diff --git a/lld/test/ELF/invalid/i386-tlsdesc-gd.s b/lld/test/ELF/invalid/i386-tlsdesc-gd.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/invalid/i386-tlsdesc-gd.s @@ -0,0 +1,14 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=i386 %s -o %t.o +# RUN: echo '.tbss; .globl a; a:' | llvm-mc -filetype=obj -triple=i386 - -o %t1.o +# RUN: ld.lld -shared %t1.o -o %t1.so + +## GD to LE relaxation. +# RUN: not ld.lld %t.o %t1.o -o /dev/null 2>&1 | FileCheck -DINPUT=%t.o %s +## GD to IE relaxation. +# RUN: not ld.lld %t.o %t1.so -o /dev/null 2>&1 | FileCheck -DINPUT=%t.o %s + +# CHECK: error: [[INPUT]]:(.text+0x0): R_386_TLS_GOTDESC must be used in leal x@tlsdesc(%ebx), %eax + +leal a@tlsdesc(%ebx), %ecx +call *a@tlscall(%eax)