From 43530e6b25ce542420967efa9d1016fad0336a4f Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Wed, 24 Oct 2018 09:25:05 -0400 Subject: [PATCH] cmd/cgo: handle new-style gccgo packagepath mangling With https://golang.org/cl/135455, gccgo now uses a different mangling scheme for package paths; add code to use this new scheme for function and variable symbols. Since users sometimes use older versions of gccgo with newer versions of go, perform a test at runtime to see which mangling scheme is in effect for the version of 'gccgo' in the path. Updates #27534. Change-Id: If7ecab06a72e1361129fe40ca6582070a3e8e737 Reviewed-on: https://go-review.googlesource.com/c/144418 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot --- src/cmd/cgo/main.go | 2 + src/cmd/cgo/out.go | 111 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index b6f059001f..5bcb9754d7 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -211,6 +211,8 @@ var exportHeader = flag.String("exportheader", "", "where to write export header var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo") var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo") var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo") +var gccgoMangleCheckDone bool +var gccgoNewmanglingInEffect bool var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code") var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code") var goarch, goos string diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 8a26d5c063..a93ff365b0 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -15,7 +15,9 @@ import ( "go/printer" "go/token" "io" + "io/ioutil" "os" + "os/exec" "path/filepath" "regexp" "sort" @@ -1186,12 +1188,91 @@ func (p *Package) writeExportHeader(fgcch io.Writer) { fmt.Fprintf(fgcch, "%s\n", p.gccExportHeaderProlog()) } -// Return the package prefix when using gccgo. -func (p *Package) gccgoSymbolPrefix() string { - if !*gccgo { - return "" +// gccgoUsesNewMangling returns whether gccgo uses the new collision-free +// packagepath mangling scheme (see determineGccgoManglingScheme for more +// info). +func gccgoUsesNewMangling() bool { + if !gccgoMangleCheckDone { + gccgoNewmanglingInEffect = determineGccgoManglingScheme() + gccgoMangleCheckDone = true + } + return gccgoNewmanglingInEffect +} + +const mangleCheckCode = ` +package läufer +func Run(x int) int { + return 1 +} +` + +// determineGccgoManglingScheme performs a runtime test to see which +// flavor of packagepath mangling gccgo is using. Older versions of +// gccgo use a simple mangling scheme where there can be collisions +// between packages whose paths are different but mangle to the same +// string. More recent versions of gccgo use a new mangler that avoids +// these collisions. Return value is whether gccgo uses the new mangling. +func determineGccgoManglingScheme() bool { + + // Emit a small Go file for gccgo to compile. + filepat := "*_gccgo_manglecheck.go" + var f *os.File + var err error + if f, err = ioutil.TempFile(*objDir, filepat); err != nil { + fatalf("%v", err) + } + gofilename := f.Name() + defer os.Remove(gofilename) + + if err = ioutil.WriteFile(gofilename, []byte(mangleCheckCode), 0666); err != nil { + fatalf("%v", err) + } + + // Compile with gccgo, capturing generated assembly. + gccgocmd := os.Getenv("GCCGO") + if gccgocmd == "" { + gpath, gerr := exec.LookPath("gccgo") + if gerr != nil { + fatalf("unable to locate gccgo: %v", gerr) + } + gccgocmd = gpath + } + cmd := exec.Command(gccgocmd, "-S", "-o", "-", gofilename) + buf, cerr := cmd.CombinedOutput() + if cerr != nil { + fatalf("%s", err) + } + + // New mangling: expect go.l..u00e4ufer.Run + // Old mangling: expect go.l__ufer.Run + return regexp.MustCompile(`go\.l\.\.u00e4ufer\.Run`).Match(buf) +} + +// gccgoPkgpathToSymbolNew converts a package path to a gccgo-style +// package symbol. +func gccgoPkgpathToSymbolNew(ppath string) string { + bsl := []byte{} + changed := false + for _, c := range []byte(ppath) { + switch { + case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z', + '0' <= c && c <= '9', '_' == c: + bsl = append(bsl, c) + default: + changed = true + encbytes := []byte(fmt.Sprintf("..z%02x", c)) + bsl = append(bsl, encbytes...) + } + } + if !changed { + return ppath } + return string(bsl) +} +// gccgoPkgpathToSymbolOld converts a package path to a gccgo-style +// package symbol using the older mangling scheme. +func gccgoPkgpathToSymbolOld(ppath string) string { clean := func(r rune) rune { switch { case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z', @@ -1200,14 +1281,32 @@ func (p *Package) gccgoSymbolPrefix() string { } return '_' } + return strings.Map(clean, ppath) +} + +// gccgoPkgpathToSymbol converts a package path to a mangled packagepath +// symbol. +func gccgoPkgpathToSymbol(ppath string) string { + if gccgoUsesNewMangling() { + return gccgoPkgpathToSymbolNew(ppath) + } else { + return gccgoPkgpathToSymbolOld(ppath) + } +} + +// Return the package prefix when using gccgo. +func (p *Package) gccgoSymbolPrefix() string { + if !*gccgo { + return "" + } if *gccgopkgpath != "" { - return strings.Map(clean, *gccgopkgpath) + return gccgoPkgpathToSymbol(*gccgopkgpath) } if *gccgoprefix == "" && p.PackageName == "main" { return "main" } - prefix := strings.Map(clean, *gccgoprefix) + prefix := gccgoPkgpathToSymbol(*gccgoprefix) if prefix == "" { prefix = "go" } -- 2.50.0