Commit 30e29ee9 authored by Ian Lance Taylor's avatar Ian Lance Taylor

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
parent 23482db9
// 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)
}
}
// 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)
}
// 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;
}
......@@ -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
......
......@@ -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,
......
......@@ -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);
......
......@@ -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']);
......
......@@ -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;
......
......@@ -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;
......
This diff is collapsed.
/* 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;
......@@ -654,6 +654,7 @@ enum
D_PCREL,
D_GOTOFF,
D_GOTREL,
D_TLS,
T_TYPE = 1<<0,
T_INDEX = 1<<1,
......
......@@ -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;
......
......@@ -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']);
......
......@@ -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:
......
......@@ -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;
......
......@@ -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"
......
......@@ -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;
......
......@@ -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)
......
......@@ -52,6 +52,7 @@ enum
SWINDOWS,
SBSS,
SNOPTRBSS,
STLSBSS,
SXREF,
SMACHOSYMSTR,
......
......@@ -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;
......
......@@ -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 $?
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment