Commit d6b06f02 authored by Russ Cox's avatar Russ Cox

cmd/link: make it easy to find binary versions

It is useful to be able to dig the Go version out of a binary,
even a stripped binary. rsc.io/goversion does this for x86
binaries by disassembling the binary to find runtime/proc.go's
startup-time reference to runtime.buildVersion's address.
That approach is quite fragile: the implementation doesn't
work for non-x86 and must be updated as the generated
code changes.

rsc.io/goversion finds the module version string by looking
for random 16-byte framing around the actual string.
This is less fragile but fairly kludgy and requires scanning
the entire data segment.

cmd/buildid finds the build ID by looking for an ELF note
or else falling back to scanning the beginning of the text
segment for a magic string. This has proved quite reliable
and doesn't require scanning much of the binary.

This CL makes it possible to find the Go and module versions
using a scan more like the build ID scan: a symbol early in
the writable data segment starts with a magic number and
then has pointers to the two string variables.

Setting up for "go version <binary>".

For #31624.

Change-Id: I78ea8c52fe1686b5cc5a829ca5f198104d10ebf0
Reviewed-on: https://go-review.googlesource.com/c/go/+/173342
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarJay Conrod <jayconrod@google.com>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent a62887aa
...@@ -1302,6 +1302,7 @@ func (ctxt *Link) dodata() { ...@@ -1302,6 +1302,7 @@ func (ctxt *Link) dodata() {
// Writable data sections that do not need any specialized handling. // Writable data sections that do not need any specialized handling.
writable := []sym.SymKind{ writable := []sym.SymKind{
sym.SBUILDINFO,
sym.SELFSECT, sym.SELFSECT,
sym.SMACHO, sym.SMACHO,
sym.SMACHOGOT, sym.SMACHOGOT,
...@@ -1963,6 +1964,39 @@ func (ctxt *Link) textbuildid() { ...@@ -1963,6 +1964,39 @@ func (ctxt *Link) textbuildid() {
ctxt.Textp[0] = s ctxt.Textp[0] = s
} }
func (ctxt *Link) buildinfo() {
if ctxt.linkShared || ctxt.BuildMode == BuildModePlugin {
// -linkshared and -buildmode=plugin get confused
// about the relocations in go.buildinfo
// pointing at the other data sections.
// The version information is only available in executables.
return
}
s := ctxt.Syms.Lookup(".go.buildinfo", 0)
s.Attr |= sym.AttrReachable
s.Type = sym.SBUILDINFO
s.Align = 16
// The \xff is invalid UTF-8, meant to make it less likely
// to find one of these accidentally.
const prefix = "\xff Go buildinf:" // 14 bytes, plus 2 data bytes filled in below
data := make([]byte, 32)
copy(data, prefix)
data[len(prefix)] = byte(ctxt.Arch.PtrSize)
data[len(prefix)+1] = 0
if ctxt.Arch.ByteOrder == binary.BigEndian {
data[len(prefix)+1] = 1
}
s.P = data
s.Size = int64(len(s.P))
s1 := ctxt.Syms.Lookup("runtime.buildVersion", 0)
s2 := ctxt.Syms.Lookup("runtime.modinfo", 0)
s.R = []sym.Reloc{
{Off: 16, Siz: uint8(ctxt.Arch.PtrSize), Type: objabi.R_ADDR, Sym: s1},
{Off: 16 + int32(ctxt.Arch.PtrSize), Siz: uint8(ctxt.Arch.PtrSize), Type: objabi.R_ADDR, Sym: s2},
}
}
// assign addresses to text // assign addresses to text
func (ctxt *Link) textaddress() { func (ctxt *Link) textaddress() {
addsection(ctxt.Arch, &Segtext, ".text", 05) addsection(ctxt.Arch, &Segtext, ".text", 05)
......
...@@ -1439,6 +1439,7 @@ func (ctxt *Link) doelf() { ...@@ -1439,6 +1439,7 @@ func (ctxt *Link) doelf() {
Addstring(shstrtab, ".data") Addstring(shstrtab, ".data")
Addstring(shstrtab, ".bss") Addstring(shstrtab, ".bss")
Addstring(shstrtab, ".noptrbss") Addstring(shstrtab, ".noptrbss")
Addstring(shstrtab, ".go.buildinfo")
// generate .tbss section for dynamic internal linker or external // generate .tbss section for dynamic internal linker or external
// linking, so that various binutils could correctly calculate // linking, so that various binutils could correctly calculate
...@@ -1485,6 +1486,7 @@ func (ctxt *Link) doelf() { ...@@ -1485,6 +1486,7 @@ func (ctxt *Link) doelf() {
if ctxt.UseRelro() { if ctxt.UseRelro() {
Addstring(shstrtab, elfRelType+".data.rel.ro") Addstring(shstrtab, elfRelType+".data.rel.ro")
} }
Addstring(shstrtab, elfRelType+".go.buildinfo")
// add a .note.GNU-stack section to mark the stack as non-executable // add a .note.GNU-stack section to mark the stack as non-executable
Addstring(shstrtab, ".note.GNU-stack") Addstring(shstrtab, ".note.GNU-stack")
......
...@@ -828,7 +828,7 @@ func machoShouldExport(ctxt *Link, s *sym.Symbol) bool { ...@@ -828,7 +828,7 @@ func machoShouldExport(ctxt *Link, s *sym.Symbol) bool {
if strings.HasPrefix(s.Name, "go.link.pkghash") { if strings.HasPrefix(s.Name, "go.link.pkghash") {
return true return true
} }
return s.Type >= sym.SELFSECT // only writable sections return s.Type >= sym.SFirstWritable // only writable sections
} }
func machosymtab(ctxt *Link) { func machosymtab(ctxt *Link) {
......
...@@ -237,6 +237,7 @@ func Main(arch *sys.Arch, theArch Arch) { ...@@ -237,6 +237,7 @@ func Main(arch *sys.Arch, theArch Arch) {
ctxt.findfunctab() ctxt.findfunctab()
ctxt.typelink() ctxt.typelink()
ctxt.symtab() ctxt.symtab()
ctxt.buildinfo()
ctxt.dodata() ctxt.dodata()
order := ctxt.address() order := ctxt.address()
dwarfcompress(ctxt) dwarfcompress(ctxt)
......
...@@ -81,6 +81,8 @@ const ( ...@@ -81,6 +81,8 @@ const (
SPCLNTAB SPCLNTAB
// Writable sections. // Writable sections.
SFirstWritable
SBUILDINFO
SELFSECT SELFSECT
SMACHO SMACHO
SMACHOGOT SMACHOGOT
......
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