cmd/ld: emit TLS relocations during external linking

This CL was written by rsc.  I just tweaked 8l.

This CL adds TLS relocation to the ELF .o file we write during external linking,
so that the host linker (gcc) can decide the final location of m and g.

Similar relocations are not necessary on OS X because we use an alternate
program start-time mechanism to acquire thread-local storage.

Similar relocations are not necessary on ARM or Plan 9 or Windows
because external linking mode is not yet supported on those systems.

On almost all ELF systems, the references we use are like %fs:-0x4 or %gs:-0x4,
which we write in 6a/8a as -0x4(FS) or -0x4(GS). On Linux/ELF, however,
Xen's lack of support for this mode forced us long ago to use a two-instruction
sequence: first we load %gs:0x0 into a register r, and then we use -0x4(r).
(The ELF program loader arranges that %gs:0x0 contains a regular pointer to
that same memory location.) In order to relocate those -0x4(r) references,
the linker must know where they are. This CL adds the equivalent notation
-0x4(r)(GS*1) for this purpose: it assembles to the same encoding as -0x4(r)
but the (GS*1) indicates to the linker that this is one of those thread-local
references that needs relocation.

Thanks to Elias Naur for reminding me about this missing piece and
also for writing the test.

R=r
CC=golang-dev
https://golang.org/cl/7891047
This commit is contained in:
Ian Lance Taylor 2013-03-27 13:27:35 -07:00
parent 23482db9f1
commit 30e29ee9b6
22 changed files with 799 additions and 851 deletions

28
misc/cgo/testtls/tls.go Normal file
View file

@ -0,0 +1,28 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgotlstest
// #include <pthread.h>
// extern void setTLS(int);
// extern int getTLS();
import "C"
import (
"runtime"
"testing"
)
func testTLS(t *testing.T) {
var keyVal C.int = 1234
runtime.LockOSThread()
defer runtime.UnlockOSThread()
C.setTLS(C.int(keyVal))
storedVal := C.getTLS()
if storedVal != keyVal {
t.Fatalf("stored %d want %d", storedVal, keyVal)
}
}

View file

@ -0,0 +1,13 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package cgotlstest
import "testing"
func TestTLS(t *testing.T) {
testTLS(t)
}

View file

@ -0,0 +1,19 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <pthread.h>
static __thread int tls;
void
setTLS(int v)
{
tls = v;
}
int
getTLS()
{
return tls;
}

View file

@ -275,6 +275,7 @@ enum as
#define D_PLT1 (D_NONE+44) // R_ARM_PLT32, 2nd inst: add ip, ip, #0xNN000
#define D_PLT2 (D_NONE+45) // R_ARM_PLT32, 3rd inst: ldr pc, [ip, #0xNNN]!
#define D_CALL (D_NONE+46) // R_ARM_PLT32/R_ARM_CALL/R_ARM_JUMP24, bl xxxxx or b yyyyy
#define D_TLS (D_NONE+47)
/*
* this is the ranlib header

View file

@ -866,6 +866,7 @@ enum
D_SIZE = D_INDIR + D_INDIR, /* 6l internal */
D_PCREL,
D_TLS,
T_TYPE = 1<<0,
T_INDEX = 1<<1,

View file

@ -310,6 +310,13 @@ elfreloc1(Reloc *r, vlong sectoff)
else
return -1;
break;
case D_TLS:
if(r->siz == 4)
VPUT(R_X86_64_TPOFF32 | (uint64)elfsym<<32);
else
return -1;
break;
}
VPUT(r->xadd);

View file

@ -83,7 +83,7 @@ main(int argc, char *argv[])
INITRND = -1;
INITENTRY = 0;
LIBINITENTRY = 0;
linkmode = LinkInternal; // TODO: LinkAuto once everything works.
linkmode = LinkAuto;
nuxiinit();
flagcount("1", "use alternate profiling code", &debug['1']);

View file

@ -32,6 +32,7 @@
#include "l.h"
#include "../ld/lib.h"
#include "../ld/elf.h"
static int rexflag;
static int asmode;
@ -880,7 +881,29 @@ putrelv:
r = addrel(cursym);
*r = rel;
r->off = curp->pc + andptr - and;
} else if(iself && linkmode == LinkExternal && a->type == D_INDIR+D_FS) {
Reloc *r;
Sym *s;
r = addrel(cursym);
r->off = curp->pc + andptr - and;
r->add = 0;
r->xadd = 0;
r->siz = 4;
r->type = D_TLS;
if(a->offset == tlsoffset+0)
s = lookup("runtime.g", 0);
else
s = lookup("runtime.m", 0);
s->type = STLSBSS;
s->reachable = 1;
s->size = PtrSize;
s->hide = 1;
r->sym = s;
r->xsym = s;
v = 0;
}
put4(v);
return;

View file

@ -507,6 +507,15 @@ omem:
$$.scale = $8;
checkscale($$.scale);
}
| con '(' LLREG ')' '(' LSREG '*' con ')'
{
$$ = nullgen;
$$.type = D_INDIR+$3;
$$.offset = $1;
$$.index = $6;
$$.scale = $8;
checkscale($$.scale);
}
| '(' LLREG ')'
{
$$ = nullgen;

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,24 @@
/* A Bison parser, made by GNU Bison 2.5. */
/* A Bison parser, made by GNU Bison 2.3. */
/* Bison interface for Yacc-like parsers in C
Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
/* Skeleton interface for Bison's Yacc-like parsers in C
Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. */
/* As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
@ -26,11 +29,10 @@
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison. */
/* Tokens. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
@ -108,11 +110,8 @@
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
typedef union YYSTYPE
{
/* Line 2068 of yacc.c */
#line 37 "a.y"
{
Sym *sym;
int32 lval;
struct {
@ -123,17 +122,14 @@ typedef union YYSTYPE
char sval[8];
Gen gen;
Gen2 gen2;
/* Line 2068 of yacc.c */
#line 131 "y.tab.h"
} YYSTYPE;
# define YYSTYPE_IS_TRIVIAL 1
}
/* Line 1529 of yacc.c. */
#line 128 "y.tab.h"
YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
# define YYSTYPE_IS_TRIVIAL 1
#endif
extern YYSTYPE yylval;

View file

@ -654,6 +654,7 @@ enum
D_PCREL,
D_GOTOFF,
D_GOTREL,
D_TLS,
T_TYPE = 1<<0,
T_INDEX = 1<<1,

View file

@ -294,6 +294,12 @@ elfreloc1(Reloc *r, vlong sectoff)
else
return -1;
break;
case D_TLS:
if(r->siz == 4)
LPUT(R_386_TLS_LE | elfsym<<8);
else
return -1;
}
return 0;

View file

@ -90,7 +90,7 @@ main(int argc, char *argv[])
INITRND = -1;
INITENTRY = 0;
LIBINITENTRY = 0;
linkmode = LinkInternal; // TODO: LinkAuto once everything works.
linkmode = LinkAuto;
nuxiinit();
flagcount("1", "use alternate profiling code", &debug['1']);

View file

@ -296,16 +296,23 @@ patch(void)
// MOVL 0(GS), reg
// and then off(reg) instead of saying off(GS) directly
// when the offset is negative.
// In external mode we just produce a reloc.
if(p->from.type == D_INDIR+D_GS && p->from.offset < 0
&& p->to.type >= D_AX && p->to.type <= D_DI) {
q = appendp(p);
q->from = p->from;
q->from.type = D_INDIR + p->to.type;
q->to = p->to;
q->as = p->as;
p->as = AMOVL;
p->from.type = D_INDIR+D_GS;
p->from.offset = 0;
if(linkmode != LinkExternal) {
q = appendp(p);
q->from = p->from;
q->from.type = D_INDIR + p->to.type;
q->to = p->to;
q->as = p->as;
p->as = AMOVL;
p->from.type = D_INDIR+D_GS;
p->from.offset = 0;
} else {
// Add signals to relocate.
p->from.index = D_GS;
p->from.scale = 1;
}
}
}
if(HEADTYPE == Hplan9x32) {
@ -450,16 +457,25 @@ dostkoff(void)
break;
case Hlinux:
p->as = AMOVL;
p->from.type = D_INDIR+D_GS;
p->from.offset = 0;
p->to.type = D_CX;
if(linkmode != LinkExternal) {
p->as = AMOVL;
p->from.type = D_INDIR+D_GS;
p->from.offset = 0;
p->to.type = D_CX;
p = appendp(p);
p->as = AMOVL;
p->from.type = D_INDIR+D_CX;
p->from.offset = tlsoffset + 0;
p->to.type = D_CX;
p = appendp(p);
p->as = AMOVL;
p->from.type = D_INDIR+D_CX;
p->from.offset = tlsoffset + 0;
p->to.type = D_CX;
} else {
p->as = AMOVL;
p->from.type = D_INDIR+D_GS;
p->from.offset = tlsoffset + 0;
p->to.type = D_CX;
p->from.index = D_GS;
p->from.scale = 1;
}
break;
case Hplan9x32:

View file

@ -32,6 +32,7 @@
#include "l.h"
#include "../ld/lib.h"
#include "../ld/elf.h"
static int32 vaddr(Adr*, Reloc*);
@ -559,6 +560,14 @@ vaddr(Adr *a, Reloc *r)
return v;
}
static int
istls(Adr *a)
{
if(HEADTYPE == Hlinux)
return a->index == D_GS;
return a->type == D_INDIR+D_GS;
}
void
asmand(Adr *a, int r)
{
@ -569,7 +578,7 @@ asmand(Adr *a, int r)
v = a->offset;
t = a->type;
rel.siz = 0;
if(a->index != D_NONE) {
if(a->index != D_NONE && a->index != D_FS && a->index != D_GS) {
if(t < D_INDIR || t >= 2*D_INDIR) {
switch(t) {
default:
@ -658,7 +667,7 @@ asmand(Adr *a, int r)
*andptr++ = (0 << 6) | (reg[t] << 0) | (r << 3);
return;
}
if(v >= -128 && v < 128 && rel.siz == 0) {
if(v >= -128 && v < 128 && rel.siz == 0 && a->index != D_FS && a->index != D_GS) {
andptr[0] = (1 << 6) | (reg[t] << 0) | (r << 3);
andptr[1] = v;
andptr += 2;
@ -680,7 +689,29 @@ putrelv:
r = addrel(cursym);
*r = rel;
r->off = curp->pc + andptr - and;
} else if(iself && linkmode == LinkExternal && istls(a)) {
Reloc *r;
Sym *s;
r = addrel(cursym);
r->off = curp->pc + andptr - and;
r->add = 0;
r->xadd = 0;
r->siz = 4;
r->type = D_TLS;
if(a->offset == tlsoffset+0)
s = lookup("runtime.g", 0);
else
s = lookup("runtime.m", 0);
s->type = STLSBSS;
s->reachable = 1;
s->hide = 1;
s->size = PtrSize;
r->sym = s;
r->xsym = s;
v = 0;
}
put4(v);
return;

View file

@ -133,12 +133,17 @@ static struct {
"// which is where these macros come into play.\n"
"// get_tls sets up the temporary and then g and r use it.\n"
"//\n"
"// The final wrinkle is that get_tls needs to read from %gs:0,\n"
"// Another wrinkle is that get_tls needs to read from %gs:0,\n"
"// but in 8l input it's called 8(GS), because 8l is going to\n"
"// subtract 8 from all the offsets, as described above.\n"
"//\n"
"// The final wrinkle is that when generating an ELF .o file for\n"
"// external linking mode, we need to be able to relocate the\n"
"// -8(r) and -4(r) instructions. Tag them with an extra (GS*1)\n"
"// that is ignored by the linker except for that identification.\n"
"#define get_tls(r) MOVL 8(GS), r\n"
"#define g(r) -8(r)\n"
"#define m(r) -4(r)\n"
"#define g(r) -8(r)(GS*1)\n"
"#define m(r) -4(r)(GS*1)\n"
},
{"386", "",
"#define get_tls(r)\n"

View file

@ -172,7 +172,7 @@ relocsym(Sym *s)
if(r->sym != S && r->sym->type == SDYNIMPORT)
diag("unhandled relocation for %s (type %d rtype %d)", r->sym->name, r->sym->type, r->type);
if(r->sym != S && !r->sym->reachable)
if(r->sym != S && r->sym->type != STLSBSS && !r->sym->reachable)
diag("unreachable sym in relocation: %s %s", s->name, r->sym->name);
switch(r->type) {
@ -181,6 +181,10 @@ relocsym(Sym *s)
if(linkmode == LinkExternal || archreloc(r, s, &o) < 0)
diag("unknown reloc %d", r->type);
break;
case D_TLS:
r->done = 0;
o = 0;
break;
case D_ADDR:
if(linkmode == LinkExternal && r->sym->type != SCONST) {
r->done = 0;
@ -1193,11 +1197,7 @@ dodata(void)
sect->vaddr = datsize;
lookup("noptrbss", 0)->sect = sect;
lookup("enoptrbss", 0)->sect = sect;
for(; s != nil; s = s->next) {
if(s->type > SNOPTRBSS) {
cursym = s;
diag("unexpected symbol type %d", s->type);
}
for(; s != nil && s->type == SNOPTRBSS; s = s->next) {
datsize = aligndatsize(datsize, s);
s->sect = sect;
s->value = datsize;
@ -1205,6 +1205,25 @@ dodata(void)
}
sect->len = datsize - sect->vaddr;
lookup("end", 0)->sect = sect;
if(iself && linkmode == LinkExternal && s != nil && s->type == STLSBSS) {
sect = addsection(&segdata, ".tbss", 06);
sect->align = PtrSize;
sect->vaddr = 0;
datsize = 0;
for(; s != nil && s->type == STLSBSS; s = s->next) {
datsize = aligndatsize(datsize, s);
s->sect = sect;
s->value = datsize;
datsize += s->size;
}
sect->len = datsize;
}
if(s != nil) {
cursym = nil;
diag("unexpected symbol type %d for %s", s->type, s->name);
}
/* we finished segdata, begin segtext */
s = datap;

View file

@ -758,6 +758,10 @@ elfshbits(Section *sect)
sh->flags |= SHF_EXECINSTR;
if(sect->rwx & 2)
sh->flags |= SHF_WRITE;
if(strcmp(sect->name, ".tbss") == 0) {
sh->flags |= SHF_TLS;
sh->type = SHT_NOBITS;
}
if(linkmode != LinkExternal)
sh->addr = sect->vaddr;
sh->addralign = sect->align;
@ -779,7 +783,7 @@ elfshreloc(Section *sect)
// Also nothing to relocate in .shstrtab.
if(sect->vaddr >= sect->seg->vaddr + sect->seg->filelen)
return nil;
if(strcmp(sect->name, ".shstrtab") == 0)
if(strcmp(sect->name, ".shstrtab") == 0 || strcmp(sect->name, ".tbss") == 0)
return nil;
if(thechar == '6') {
@ -883,6 +887,8 @@ doelf(void)
addstring(shstrtab, ".data");
addstring(shstrtab, ".bss");
addstring(shstrtab, ".noptrbss");
if(linkmode == LinkExternal)
addstring(shstrtab, ".tbss");
if(HEADTYPE == Hnetbsd)
addstring(shstrtab, ".note.netbsd.ident");
if(HEADTYPE == Hopenbsd)

View file

@ -52,6 +52,7 @@ enum
SWINDOWS,
SBSS,
SNOPTRBSS,
STLSBSS,
SXREF,
SMACHOSYMSTR,

View file

@ -152,6 +152,26 @@ asmelfsym(void)
elfbind = STB_LOCAL;
genasmsym(putelfsym);
if(linkmode == LinkExternal) {
s = lookup("runtime.m", 0);
if(s->sect == nil) {
cursym = nil;
diag("missing section for %s", s->name);
errorexit();
}
putelfsyment(putelfstr(s->name), 0, PtrSize, (STB_LOCAL<<4)|STT_TLS, s->sect->elfsect->shnum, 0);
s->elfsym = numelfsym++;
s = lookup("runtime.g", 0);
if(s->sect == nil) {
cursym = nil;
diag("missing section for %s", s->name);
errorexit();
}
putelfsyment(putelfstr(s->name), PtrSize, PtrSize, (STB_LOCAL<<4)|STT_TLS, s->sect->elfsect->shnum, 0);
s->elfsym = numelfsym++;
}
elfbind = STB_GLOBAL;
elfglobalsymndx = numelfsym;

View file

@ -83,8 +83,14 @@ set -e
go test -ldflags '-linkmode=auto'
go test -ldflags '-linkmode=internal'
case "$GOHOSTOS-$GOARCH" in
darwin-386 | darwin-amd64 | freebsd-386 | freebsd-amd64 | linux-386 | linux-amd64 | netbsd-386 | netbsd-amd64 | openbsd-386 | openbsd-amd64)
darwin-386 | darwin-amd64 | openbsd-386 | openbsd-amd64)
# test linkmode=external, but __thread not supported, so skip testtls.
go test -ldflags '-linkmode=external'
;;
freebsd-386 | freebsd-amd64 | linux-386 | linux-amd64 | netbsd-386 | netbsd-amd64)
go test -ldflags '-linkmode=external'
go test -ldflags '-linkmode=auto' ../testtls
go test -ldflags '-linkmode=external' ../testtls
esac
) || exit $?