From: Michael Hudson-Doyle Date: Mon, 30 Mar 2015 00:49:25 +0000 (+0000) Subject: cmd/internal/obj/x86, cmd/internal/ld, cmd/6l: 6g/asm -dynlink accesses global data... X-Git-Tag: go1.5beta1~1209 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=84207a2500ea2efea47fdb85b94ab8543225b04c;p=gostls13.git 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/8237 Reviewed-by: Ian Lance Taylor --- diff --git a/src/cmd/6g/galign.go b/src/cmd/6g/galign.go index 74be60e5ee..fb31710a14 100644 --- a/src/cmd/6g/galign.go +++ b/src/cmd/6g/galign.go @@ -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() { diff --git a/src/cmd/6g/reg.go b/src/cmd/6g/reg.go index dd06bc54d5..7ad31f924a 100644 --- a/src/cmd/6g/reg.go +++ b/src/cmd/6g/reg.go @@ -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 } diff --git a/src/cmd/6l/asm.go b/src/cmd/6l/asm.go index 07bb7c418f..329eb657a9 100644 --- a/src/cmd/6l/asm.go +++ b/src/cmd/6l/asm.go @@ -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 { diff --git a/src/cmd/asm/internal/flags/flags.go b/src/cmd/asm/internal/flags/flags.go index 0fa997f06e..c74f26974a 100644 --- a/src/cmd/asm/internal/flags/flags.go +++ b/src/cmd/asm/internal/flags/flags.go @@ -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 ( diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go index 690ec2ef89..9b07dd22e1 100644 --- a/src/cmd/asm/main.go +++ b/src/cmd/asm/main.go @@ -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) diff --git a/src/cmd/internal/gc/lex.go b/src/cmd/internal/gc/lex.go index 774b9a6245..1f0e88375c 100644 --- a/src/cmd/internal/gc/lex.go +++ b/src/cmd/internal/gc/lex.go @@ -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']) diff --git a/src/cmd/internal/ld/data.go b/src/cmd/internal/ld/data.go index 39dfea910f..e9a890d84f 100644 --- a/src/cmd/internal/ld/data.go +++ b/src/cmd/internal/ld/data.go @@ -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. diff --git a/src/cmd/internal/ld/link.go b/src/cmd/internal/ld/link.go index 47af2ae77f..83cfe283f4 100644 --- a/src/cmd/internal/ld/link.go +++ b/src/cmd/internal/ld/link.go @@ -235,6 +235,7 @@ const ( R_PLT2 R_USEFIELD R_POWER_TOC + R_GOTPCREL ) // Reloc.variant diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index a714057028..7d4026c312 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -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 diff --git a/src/cmd/internal/obj/util.go b/src/cmd/internal/obj/util.go index b0d10b5fd5..f76c9362bd 100644 --- a/src/cmd/internal/obj/util.go +++ b/src/cmd/internal/obj/util.go @@ -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)) diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index e0869722c6..dfabfd4124 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -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 diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index 05a966a772..2d30e9ebd4 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -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) { diff --git a/src/cmd/internal/obj/x86/obj6_test.go b/src/cmd/internal/obj/x86/obj6_test.go new file mode 100644 index 0000000000..93f8f3c262 --- /dev/null +++ b/src/cmd/internal/obj/x86/obj6_test.go @@ -0,0 +1,167 @@ +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) + } + } +}