Commit 84207a25 authored by Michael Hudson-Doyle's avatar Michael Hudson-Doyle Committed by Ian Lance Taylor

cmd/internal/obj/x86, cmd/internal/ld, cmd/6l: 6g/asm -dynlink accesses global data via a GOT

Change-Id: I49862e177045369d6c94d6a58afbdace4f13cc96
Reviewed-on: https://go-review.googlesource.com/8237Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 969f1014
......@@ -61,6 +61,9 @@ func betypeinit() {
typedefs[2].Sameas = gc.TUINT32
}
if gc.Ctxt.Flag_dynlink {
gc.Thearch.ReservedRegs = append(gc.Thearch.ReservedRegs, x86.REG_R15)
}
}
func main() {
......
......@@ -123,7 +123,9 @@ func BtoR(b uint64) int {
// BP is part of the calling convention if framepointer_enabled.
b &^= (1 << (x86.REG_BP - x86.REG_AX))
}
if gc.Ctxt.Flag_dynlink {
b &^= (1 << (x86.REG_R15 - x86.REG_AX))
}
if b == 0 {
return 0
}
......
......@@ -332,6 +332,13 @@ func elfreloc1(r *ld.Reloc, sectoff int64) int {
return -1
}
case ld.R_GOTPCREL:
if r.Siz == 4 {
ld.Thearch.Vput(ld.R_X86_64_GOTPCREL | uint64(elfsym)<<32)
} else {
return -1
}
case ld.R_TLS:
if r.Siz == 4 {
if ld.Buildmode == ld.BuildmodeCShared {
......
......@@ -19,6 +19,7 @@ var (
PrintOut = flag.Bool("S", false, "print assembly and machine code")
TrimPath = flag.String("trimpath", "", "remove prefix from recorded source file paths")
Shared = flag.Bool("shared", false, "generate code that can be linked into a shared library")
Dynlink = flag.Bool("dynlink", false, "support references to Go symbols defined in other shared libraries")
)
var (
......
......@@ -41,7 +41,8 @@ func main() {
ctxt.Debugasm = 1
}
ctxt.Trimpath = *flags.TrimPath
if *flags.Shared {
ctxt.Flag_dynlink = *flags.Dynlink
if *flags.Shared || *flags.Dynlink {
ctxt.Flag_shared = 1
}
ctxt.Bso = obj.Binitw(os.Stdout)
......
......@@ -222,15 +222,22 @@ func Main() {
obj.Flagcount("x", "debug lexer", &Debug['x'])
obj.Flagcount("y", "debug declarations in canned imports (with -d)", &Debug['y'])
var flag_shared int
var flag_dynlink bool
if Thearch.Thechar == '6' {
obj.Flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel)
obj.Flagcount("shared", "generate code that can be linked into a shared library", &flag_shared)
flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries")
}
obj.Flagstr("cpuprofile", "file: write cpu profile to file", &cpuprofile)
obj.Flagstr("memprofile", "file: write memory profile to file", &memprofile)
obj.Flagparse(usage)
if flag_dynlink {
flag_shared = 1
}
Ctxt.Flag_shared = int32(flag_shared)
Ctxt.Flag_dynlink = flag_dynlink
Ctxt.Debugasm = int32(Debug['S'])
Ctxt.Debugvlog = int32(Debug['v'])
......
......@@ -476,8 +476,8 @@ func relocsym(s *LSym) {
}
// r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call.
case R_CALL, R_PCREL:
if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != SCONST && r.Sym.Sect != Ctxt.Cursym.Sect {
case R_CALL, R_GOTPCREL, R_PCREL:
if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != SCONST && (r.Sym.Sect != Ctxt.Cursym.Sect || r.Type == R_GOTPCREL) {
r.Done = 0
// set up addend for eventual relocation via outer symbol.
......
......@@ -235,6 +235,7 @@ const (
R_PLT2
R_USEFIELD
R_POWER_TOC
R_GOTPCREL
)
// Reloc.variant
......
......@@ -173,6 +173,9 @@ const (
NAME_STATIC
NAME_AUTO
NAME_PARAM
// A reference to name@GOT(SB) is a reference to the entry in the global offset
// table for 'name'.
NAME_GOTREF
)
const (
......@@ -380,6 +383,7 @@ const (
R_PLT2
R_USEFIELD
R_POWER_TOC
R_GOTPCREL
)
type Auto struct {
......@@ -431,6 +435,7 @@ type Link struct {
Debugdivmod int32
Debugpcln int32
Flag_shared int32
Flag_dynlink bool
Bso *Biobuf
Pathname string
Windows int32
......
......@@ -477,6 +477,9 @@ func Mconv(a *Addr) string {
case NAME_EXTERN:
str = fmt.Sprintf("%s%s(SB)", a.Sym.Name, offConv(a.Offset))
case NAME_GOTREF:
str = fmt.Sprintf("%s%s@GOT(SB)", a.Sym.Name, offConv(a.Offset))
case NAME_STATIC:
str = fmt.Sprintf("%s<>%s(SB)", a.Sym.Name, offConv(a.Offset))
......
......@@ -2028,6 +2028,7 @@ func oclass(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
case obj.TYPE_ADDR:
switch a.Name {
case obj.NAME_EXTERN,
obj.NAME_GOTREF,
obj.NAME_STATIC:
if a.Sym != nil && isextern(a.Sym) || p.Mode == 32 {
return Yi32
......@@ -2437,6 +2438,7 @@ func vaddr(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r *obj.Reloc) int64 {
switch a.Name {
case obj.NAME_STATIC,
obj.NAME_GOTREF,
obj.NAME_EXTERN:
s := a.Sym
if r == nil {
......@@ -2444,7 +2446,10 @@ func vaddr(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r *obj.Reloc) int64 {
log.Fatalf("reloc")
}
if isextern(s) || p.Mode != 64 {
if a.Name == obj.NAME_GOTREF {
r.Siz = 4
r.Type = obj.R_GOTPCREL
} else if isextern(s) || p.Mode != 64 {
r.Siz = 4
r.Type = obj.R_ADDR
} else {
......@@ -2519,6 +2524,7 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
base := int(a.Reg)
switch a.Name {
case obj.NAME_EXTERN,
obj.NAME_GOTREF,
obj.NAME_STATIC:
if !isextern(a.Sym) && p.Mode == 64 {
goto bad
......@@ -2564,6 +2570,7 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
base = int(a.Reg)
switch a.Name {
case obj.NAME_STATIC,
obj.NAME_GOTREF,
obj.NAME_EXTERN:
if a.Sym == nil {
ctxt.Diag("bad addr: %v", p)
......@@ -2582,7 +2589,10 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
ctxt.Rexflag |= regrex[base]&Rxb | rex
if base == REG_NONE || (REG_CS <= base && base <= REG_GS) || base == REG_TLS {
if (a.Sym == nil || !isextern(a.Sym)) && base == REG_NONE && (a.Name == obj.NAME_STATIC || a.Name == obj.NAME_EXTERN) || p.Mode != 64 {
if (a.Sym == nil || !isextern(a.Sym)) && base == REG_NONE && (a.Name == obj.NAME_STATIC || a.Name == obj.NAME_EXTERN || a.Name == obj.NAME_GOTREF) || p.Mode != 64 {
if a.Name == obj.NAME_GOTREF && (a.Offset != 0 || a.Index != 0 || a.Scale != 0) {
ctxt.Diag("%v has offset against gotref", p)
}
ctxt.Andptr[0] = byte(0<<6 | 5<<0 | r<<3)
ctxt.Andptr = ctxt.Andptr[1:]
goto putrelv
......
......@@ -297,6 +297,84 @@ func progedit(ctxt *obj.Link, p *obj.Prog) {
p.From.Offset = 0
}
}
if ctxt.Flag_dynlink {
if p.As == ALEAQ && p.From.Type == obj.TYPE_MEM && p.From.Name == obj.NAME_EXTERN {
p.As = AMOVQ
p.From.Type = obj.TYPE_ADDR
}
if p.From.Type == obj.TYPE_ADDR && p.From.Name == obj.NAME_EXTERN {
if p.As != AMOVQ {
ctxt.Diag("do not know how to handle TYPE_ADDR in %v with -dynlink", p)
}
if p.To.Type != obj.TYPE_REG {
ctxt.Diag("do not know how to handle LEAQ-type insn to non-register in %v with -dynlink", p)
}
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_GOTREF
if p.From.Offset != 0 {
q := obj.Appendp(ctxt, p)
q.As = AADDQ
q.From.Type = obj.TYPE_CONST
q.From.Offset = p.From.Offset
q.To = p.To
p.From.Offset = 0
}
}
if p.From3.Name == obj.NAME_EXTERN {
ctxt.Diag("don't know how to handle %v with -dynlink", p)
}
if p.To2.Name == obj.NAME_EXTERN {
ctxt.Diag("don't know how to handle %v with -dynlink", p)
}
var source *obj.Addr
if p.From.Name == obj.NAME_EXTERN {
if p.To.Name == obj.NAME_EXTERN {
ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p)
}
source = &p.From
} else if p.To.Name == obj.NAME_EXTERN {
source = &p.To
} else {
return
}
if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ACALL || p.As == obj.ARET || p.As == obj.AJMP {
return
}
if source.Type != obj.TYPE_MEM {
ctxt.Diag("don't know how to handle %v with -dynlink", p)
}
p1 := obj.Appendp(ctxt, p)
p2 := obj.Appendp(ctxt, p1)
p1.As = AMOVQ
p1.From.Type = obj.TYPE_MEM
p1.From.Sym = source.Sym
p1.From.Name = obj.NAME_GOTREF
p1.To.Type = obj.TYPE_REG
p1.To.Reg = REG_R15
p2.As = p.As
p2.From = p.From
p2.To = p.To
if p.From.Name == obj.NAME_EXTERN {
p2.From.Reg = REG_R15
p2.From.Name = obj.NAME_NONE
p2.From.Sym = nil
} else if p.To.Name == obj.NAME_EXTERN {
p2.To.Reg = REG_R15
p2.To.Name = obj.NAME_NONE
p2.To.Sym = nil
} else {
return
}
l := p.Link
l2 := p2.Link
*p = *p1
*p1 = *p2
p.Link = l
p1.Link = l2
}
}
func nacladdr(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) {
......
package x86_test
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"testing"
)
const testdata = `
MOVQ AX, AX -> MOVQ AX, AX
LEAQ name(SB), AX -> MOVQ name@GOT(SB), AX
LEAQ name+10(SB), AX -> MOVQ name@GOT(SB), AX; ADDQ $10, AX
MOVQ $name(SB), AX -> MOVQ name@GOT(SB), AX
MOVQ $name+10(SB), AX -> MOVQ name@GOT(SB), AX; ADDQ $10, AX
MOVQ name(SB), AX -> MOVQ name@GOT(SB), R15; MOVQ (R15), AX
MOVQ name+10(SB), AX -> MOVQ name@GOT(SB), R15; MOVQ 10(R15), AX
CMPQ name(SB), $0 -> MOVQ name@GOT(SB), R15; CMPQ (R15), $0
MOVQ $1, name(SB) -> MOVQ name@GOT(SB), R15; MOVQ $1, (R15)
MOVQ $1, name+10(SB) -> MOVQ name@GOT(SB), R15; MOVQ $1, 10(R15)
`
type ParsedTestData struct {
input string
marks []int
marker_to_input map[int][]string
marker_to_expected map[int][]string
marker_to_output map[int][]string
}
const marker_start = 1234
func parseTestData(t *testing.T) *ParsedTestData {
r := &ParsedTestData{}
scanner := bufio.NewScanner(strings.NewReader(testdata))
r.marker_to_input = make(map[int][]string)
r.marker_to_expected = make(map[int][]string)
marker := marker_start
input_insns := []string{}
for scanner.Scan() {
line := scanner.Text()
if len(strings.TrimSpace(line)) == 0 {
continue
}
parts := strings.Split(line, "->")
if len(parts) != 2 {
t.Fatalf("malformed line %v", line)
}
r.marks = append(r.marks, marker)
marker_insn := fmt.Sprintf("MOVQ $%d, AX", marker)
input_insns = append(input_insns, marker_insn)
for _, input_insn := range strings.Split(parts[0], ";") {
input_insns = append(input_insns, input_insn)
r.marker_to_input[marker] = append(r.marker_to_input[marker], normalize(input_insn))
}
for _, expected_insn := range strings.Split(parts[1], ";") {
r.marker_to_expected[marker] = append(r.marker_to_expected[marker], normalize(expected_insn))
}
marker++
}
r.input = "TEXT ·foo(SB),$0\n" + strings.Join(input_insns, "\n") + "\n"
return r
}
var spaces_re *regexp.Regexp = regexp.MustCompile("\\s+")
var marker_re *regexp.Regexp = regexp.MustCompile("MOVQ \\$([0-9]+), AX")
func normalize(s string) string {
return spaces_re.ReplaceAllLiteralString(strings.TrimSpace(s), " ")
}
func asmOutput(t *testing.T, s string) []byte {
tmpdir, err := ioutil.TempDir("", "progedittest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
tmpfile, err := os.Create(filepath.Join(tmpdir, "input.s"))
if err != nil {
t.Fatal(err)
}
defer tmpfile.Close()
_, err = tmpfile.WriteString(s)
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(
build.Default.GOROOT+"/bin/go", "tool", "asm", "-S", "-dynlink",
"-o", filepath.Join(tmpdir, "output.6"), tmpfile.Name())
var env []string
for _, v := range os.Environ() {
if !strings.HasPrefix(v, "GOARCH=") {
env = append(env, v)
}
}
cmd.Env = append(env, "GOARCH=amd64")
asmout, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("error %s output %s", err, asmout)
}
return asmout
}
func parseOutput(t *testing.T, td *ParsedTestData, asmout []byte) {
scanner := bufio.NewScanner(bytes.NewReader(asmout))
marker := regexp.MustCompile("MOVQ \\$([0-9]+), AX")
mark := -1
td.marker_to_output = make(map[int][]string)
for scanner.Scan() {
line := scanner.Text()
if line[0] != '\t' {
continue
}
parts := strings.SplitN(line, "\t", 3)
if len(parts) != 3 {
continue
}
n := normalize(parts[2])
mark_matches := marker.FindStringSubmatch(n)
if mark_matches != nil {
mark, _ = strconv.Atoi(mark_matches[1])
if _, ok := td.marker_to_input[mark]; !ok {
t.Fatalf("unexpected marker %d", mark)
}
} else if mark != -1 {
td.marker_to_output[mark] = append(td.marker_to_output[mark], n)
}
}
}
func TestDynlink(t *testing.T) {
if runtime.GOOS == "nacl" || runtime.GOOS == "android" || (runtime.GOOS == "darwin" && runtime.GOARCH == "arm") {
// iOS and nacl cannot fork
t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
}
testdata := parseTestData(t)
asmout := asmOutput(t, testdata.input)
parseOutput(t, testdata, asmout)
for _, m := range testdata.marks {
i := strings.Join(testdata.marker_to_input[m], "; ")
o := strings.Join(testdata.marker_to_output[m], "; ")
e := strings.Join(testdata.marker_to_expected[m], "; ")
if o != e {
if o == i {
t.Errorf("%s was unchanged; should have become %s", i, e)
} else {
t.Errorf("%s became %s; should have become %s", i, o, e)
}
} else if i != e {
t.Logf("%s correctly became %s", i, o)
}
}
}
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