Skip to content

Commit

Permalink
[ELF] Fix TLSGD/TLSLD relaxation
Browse files Browse the repository at this point in the history
When we relax R_X86_64_TLSGD or R_X86_64_TLSGD, we rewrite two
instructions (the one referred by TLSGD/TLSLD and the following one
referred by the following relocation). The second instruction is
usually 5 bytes, but if it can be longer than that, and if that's the
case, we need to emit a nop to fill the gap at the end of the longer
instruction.

We didn't do that. As a result, the remaining garbage of the second
instruction is executed and caused an unpredictable result (illegal
instruction or segv).

This patch fixes the issue.

Fixes #360
  • Loading branch information
rui314 committed Feb 23, 2022
1 parent fb6967c commit 4aa4bfa
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 25 deletions.
95 changes: 70 additions & 25 deletions elf/arch-x86-64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -366,15 +366,36 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_X86_64_TLSGD:
if (sym.get_tlsgd_idx(ctx) == -1) {
// Relax GD to LE
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
};
memcpy(loc - 4, insn, sizeof(insn));
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
};
memcpy(loc - 4, insn, sizeof(insn));

i64 val = S - ctx.tls_end + A + 4;
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));

i64 val = S - ctx.tls_end + A + 4;
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)(loc + 9) = val;
break;
}
default:
unreachable();
}

i64 val = S - ctx.tls_end + A + 4;
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)(loc + 8) = val;
i++;
} else {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
Expand All @@ -383,11 +404,28 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_X86_64_TLSLD:
if (ctx.got->tlsld_idx == -1) {
// Relax LD to LE
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32: {
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_PLTOFF64: {
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
default:
unreachable();
}

i++;
} else {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
Expand Down Expand Up @@ -681,26 +719,33 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
dispatch(ctx, table, i, rel, sym);
break;
}
case R_X86_64_TLSGD:
if (i + 1 == rels.size())
Fatal(ctx) << *this
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";

if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
case R_X86_64_TLSGD: {
bool can_relax = false;
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported &&
i + 1 < rels.size())
if (u32 ty = rels[i + 1].r_type;
ty == R_X86_64_PLT32 || ty == R_X86_64_PLTOFF64)
can_relax = true;

if (can_relax)
i++;
else
sym.flags |= NEEDS_TLSGD;
break;
case R_X86_64_TLSLD:
if (i + 1 == rels.size())
Fatal(ctx) << *this
<< ": TLSLD reloc must be followed by PLT32 or GOTPCREL";

if (ctx.arg.relax && !ctx.arg.shared)
}
case R_X86_64_TLSLD: {
bool can_relax = false;
if (ctx.arg.relax && !ctx.arg.shared && i + 1 < rels.size())
if (u32 ty = rels[i + 1].r_type;
ty == R_X86_64_PLT32 || ty == R_X86_64_PLTOFF64)
can_relax = true;

if (can_relax)
i++;
else
ctx.needs_tlsld = true;
break;
}
case R_X86_64_GOTTPOFF: {
ctx.has_gottp_rel = true;

Expand Down
62 changes: 62 additions & 0 deletions test/elf/tls-gd-mcmodel-large.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash
export LANG=
set -e
CC="${CC:-cc}"
CXX="${CXX:-c++}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
cd "$(dirname "$0")"/../..
mold="$(pwd)/mold"
t=out/test/elf/$testname
mkdir -p $t

if [ "$(uname -m)" = x86_64 ]; then
dialect=gnu
elif [ "$(uname -m)" = aarch64 ]; then
dialect=trad
else
echo skipped
exit 0
fi

cat <<EOF | gcc -mtls-dialect=$dialect -fPIC -c -o $t/a.o -xc - -mcmodel=large
#include <stdio.h>
static _Thread_local int x1 = 1;
static _Thread_local int x2;
extern _Thread_local int x3;
extern _Thread_local int x4;
int get_x5();
int get_x6();
int main() {
x2 = 2;
printf("%d %d %d %d %d %d\n", x1, x2, x3, x4, get_x5(), get_x6());
return 0;
}
EOF

cat <<EOF | gcc -mtls-dialect=$dialect -fPIC -c -o $t/b.o -xc - -mcmodel=large
_Thread_local int x3 = 3;
static _Thread_local int x5 = 5;
int get_x5() { return x5; }
EOF


cat <<EOF | gcc -mtls-dialect=$dialect -fPIC -c -o $t/c.o -xc - -mcmodel=large
_Thread_local int x4 = 4;
static _Thread_local int x6 = 6;
int get_x6() { return x6; }
EOF

$CC -B. -shared -o $t/d.so $t/b.o -mcmodel=large
$CC -B. -shared -o $t/e.so $t/c.o -Wl,--no-relax -mcmodel=large

$CC -B. -o $t/exe $t/a.o $t/d.so $t/e.so -mcmodel=large
$t/exe | grep -q '1 2 3 4 5 6'

$CC -B. -o $t/exe $t/a.o $t/d.so $t/e.so -Wl,-no-relax -mcmodel=large
$t/exe | grep -q '1 2 3 4 5 6'

echo OK
49 changes: 49 additions & 0 deletions test/elf/tls-ld-mcmodel-large.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
export LANG=
set -e
CC="${CC:-cc}"
CXX="${CXX:-c++}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
cd "$(dirname "$0")"/../..
mold="$(pwd)/mold"
t=out/test/elf/$testname
mkdir -p $t

if [ "$(uname -m)" = x86_64 ]; then
dialect=gnu
elif [ "$(uname -m)" = aarch64 ]; then
dialect=trad
else
echo skipped
exit 0
fi

cat <<EOF | gcc -ftls-model=local-dynamic -mtls-dialect=$dialect -fPIC -c -o $t/a.o -xc - -mcmodel=large
#include <stdio.h>
extern _Thread_local int foo;
static _Thread_local int bar;
int *get_foo_addr() { return &foo; }
int *get_bar_addr() { return &bar; }
int main() {
bar = 5;
printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
return 0;
}
EOF

cat <<EOF | gcc -ftls-model=local-dynamic -mtls-dialect=$dialect -fPIC -c -o $t/b.o -xc - -mcmodel=large
_Thread_local int foo = 3;
EOF

$CC -B. -o $t/exe $t/a.o $t/b.o -mcmodel=large
$t/exe | grep -q '3 5 3 5'

$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-no-relax -mcmodel=large
$t/exe | grep -q '3 5 3 5'

echo OK

0 comments on commit 4aa4bfa

Please sign in to comment.