From 24e4a128c9a038a221789fb8ff40530acde31501 Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Sat, 30 Sep 2017 12:36:34 +0000 Subject: [PATCH] cmd/link: type symbol name mangling for plugins Moves type symbol name mangling out of the object reader and into a separate pass. Requires some care, as changing the name of a type may require dealing with duplicate symbols for the first time. Disables DWARF for both plugins and programs that use plugin.Open, because type manging is currently incompatible with the go.info.* symbol generator in cmd/link. (It relies on the symbol names to find type information.) A future fix for this would be moving the go.info.* generation into the compiler, with the logic we use for generating the type.* symbols. Fixes #19529 Change-Id: I75615f8bdda86ff9e767e536d9aa36e15c194098 Reviewed-on: https://go-review.googlesource.com/67312 Run-TryBot: David Crawshaw TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- misc/cgo/testplugin/src/issue19529/plugin.go | 15 +++++ misc/cgo/testplugin/test.bash | 3 + src/cmd/link/internal/ld/lib.go | 62 +++++++++++++++++++- src/cmd/link/internal/ld/objfile.go | 26 -------- src/cmd/link/internal/ld/symbols.go | 22 +++++++ 5 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 misc/cgo/testplugin/src/issue19529/plugin.go diff --git a/misc/cgo/testplugin/src/issue19529/plugin.go b/misc/cgo/testplugin/src/issue19529/plugin.go new file mode 100644 index 0000000000..ad2df6cc7c --- /dev/null +++ b/misc/cgo/testplugin/src/issue19529/plugin.go @@ -0,0 +1,15 @@ +package main + +import ( + "reflect" +) + +type Foo struct { + Bar string `json:"Bar@baz,omitempty"` +} + +func F() { + println(reflect.TypeOf(Foo{}).Field(0).Tag) +} + +func main() {} diff --git a/misc/cgo/testplugin/test.bash b/misc/cgo/testplugin/test.bash index ae3368b45f..6c13aa5fd7 100755 --- a/misc/cgo/testplugin/test.bash +++ b/misc/cgo/testplugin/test.bash @@ -71,3 +71,6 @@ GOPATH=$(pwd) go build -gcflags "$GO_GCFLAGS" -o issue18584 src/issue18584/main. GOPATH=$(pwd) go build -gcflags "$GO_GCFLAGS" -buildmode=plugin "-ldflags=-X main.Val=linkstr" -o plugin.so src/issue19418/plugin.go GOPATH=$(pwd) go build -gcflags "$GO_GCFLAGS" -o issue19418 src/issue19418/main.go ./issue19418 + +# Test for issue 19529 +GOPATH=$(pwd) go build -gcflags "$GO_GCFLAGS" -buildmode=plugin -o plugin.so src/issue19529/plugin.go diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 42498cbc9b..1cedb44a38 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -38,6 +38,7 @@ import ( "cmd/internal/sys" "crypto/sha1" "debug/elf" + "encoding/base64" "encoding/binary" "encoding/hex" "fmt" @@ -583,9 +584,30 @@ func (ctxt *Link) loadlib() { } } + // If type. symbols are visible in the symbol table, rename them + // using a SHA-1 prefix. This reduces binary size (the full + // string of a type symbol can be multiple kilobytes) and removes + // characters that upset external linkers. + // + // Keep the type.. prefix, which parts of the linker (like the + // DWARF generator) know means the symbol is not decodable. + // + // Leave type.runtime. symbols alone, because other parts of + // the linker manipulates them, and also symbols whose names + // would not be shortened by this process. + if typeSymbolMangling(ctxt.Syms) { + *FlagW = true // disable DWARF generation + for _, s := range ctxt.Syms.Allsym { + newName := typeSymbolMangle(ctxt.Syms, s.Name) + if newName != s.Name { + ctxt.Syms.Rename(s.Name, newName, int(s.Version)) + } + } + } + // If package versioning is required, generate a hash of the // the packages used in the link. - if Buildmode == BuildmodeShared || Buildmode == BuildmodePlugin || ctxt.Syms.ROLookup("plugin.Open", 0) != nil { + if Buildmode == BuildmodeShared || Buildmode == BuildmodePlugin || ctxt.CanUsePlugins() { for _, lib := range ctxt.Library { if lib.Shlib == "" { genhash(ctxt, lib) @@ -642,6 +664,44 @@ func (ctxt *Link) loadlib() { } } +// typeSymbolMangling reports whether the linker should shorten the +// names of symbols that represent Go types. +// +// As the names of these symbols are derived from the string of +// the type, they can run to many kilobytes long. So we shorten +// them using a SHA-1 when the name appears in the final binary. +// +// These are the symbols that begin with the prefix 'type.' and +// contain run-time type information used by the runtime and reflect +// packages. All Go binaries contain these symbols, but only only +// those programs loaded dynamically in multiple parts need these +// symbols to have entries in the symbol table. +func typeSymbolMangling(syms *Symbols) bool { + return Buildmode == BuildmodeShared || *FlagLinkshared || Buildmode == BuildmodePlugin || syms.ROLookup("plugin.Open", 0) != nil +} + +// typeSymbolMangle mangles the given symbol name into something shorter. +func typeSymbolMangle(syms *Symbols, name string) string { + if !typeSymbolMangling(syms) { + return name + } + if !strings.HasPrefix(name, "type.") { + return name + } + if strings.HasPrefix(name, "type.runtime.") { + return name + } + if len(name) <= 14 && !strings.Contains(name, "@") { // Issue 19529 + return name + } + hash := sha1.Sum([]byte(name)) + prefix := "type." + if name[5] == '.' { + prefix = "type.." + } + return prefix + base64.StdEncoding.EncodeToString(hash[:6]) +} + /* * look for the next file in an archive. * adapted from libmach. diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go index a7ca2a61ec..c3bf281a6c 100644 --- a/src/cmd/link/internal/ld/objfile.go +++ b/src/cmd/link/internal/ld/objfile.go @@ -13,8 +13,6 @@ import ( "cmd/internal/dwarf" "cmd/internal/objabi" "cmd/internal/sys" - "crypto/sha1" - "encoding/base64" "io" "log" "strconv" @@ -513,30 +511,6 @@ func (r *objReader) readSymName() string { r.readFull(r.rdBuf[:n]) } r.rdBuf = adjName[:0] // in case 2*n wasn't enough - - if Buildmode == BuildmodeShared || *FlagLinkshared { - // These types are included in the symbol - // table when dynamically linking. To keep - // binary size down, we replace the names - // with SHA-1 prefixes. - // - // Keep the type.. prefix, which parts of the - // linker (like the DWARF generator) know means - // the symbol is not decodable. - // - // Leave type.runtime. symbols alone, because - // other parts of the linker manipulates them, - // and also symbols whose names would not be - // shortened by this process. - if len(s) > 14 && strings.HasPrefix(s, "type.") && !strings.HasPrefix(s, "type.runtime.") { - hash := sha1.Sum([]byte(s)) - prefix := "type." - if s[5] == '.' { - prefix = "type.." - } - s = prefix + base64.StdEncoding.EncodeToString(hash[:6]) - } - } return s } adjName = append(adjName, origName[:i]...) diff --git a/src/cmd/link/internal/ld/symbols.go b/src/cmd/link/internal/ld/symbols.go index 154507ddd7..8708fc8aae 100644 --- a/src/cmd/link/internal/ld/symbols.go +++ b/src/cmd/link/internal/ld/symbols.go @@ -82,3 +82,25 @@ func (syms *Symbols) IncVersion() int { syms.hash = append(syms.hash, make(map[string]*Symbol)) return len(syms.hash) - 1 } + +// Rename renames a symbol. +func (syms *Symbols) Rename(old, new string, v int) { + s := syms.hash[v][old] + s.Name = new + if s.Extname == old { + s.Extname = new + } + delete(syms.hash[v], old) + + dup := syms.hash[v][new] + if dup == nil { + syms.hash[v][new] = s + } else { + if s.Type == 0 { + *s = *dup + } else if dup.Type == 0 { + *dup = *s + syms.hash[v][new] = s + } + } +} -- 2.48.1