From: Michael Pratt Date: Fri, 26 Jul 2024 14:02:55 +0000 (-0400) Subject: debug/buildinfo: improve format documentation X-Git-Tag: go1.24rc1~1365 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=d531e344aea38f165a9d13d1e4173816fec35050;p=gostls13.git debug/buildinfo: improve format documentation Existing documentation is a bit sparse, and more importantly focuses almost entirely on the old pre-1.18 format, with the new format as an afterthought. Since the new format is the primary format, make it more prominent. Updates #68592. Change-Id: I108ecde1b33650b4812fa5d278b08cb9197f6329 Reviewed-on: https://go-review.googlesource.com/c/go/+/601456 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Michael Pratt --- diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 0f1289cccc..92a8656c35 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2351,9 +2351,13 @@ func (ctxt *Link) buildinfo() { s := ldr.CreateSymForUpdate("go:buildinfo", 0) s.SetType(sym.SBUILDINFO) s.SetAlign(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 + const prefix = "\xff Go buildinf:" // 14 bytes, plus 1 data byte filled in below + + // Header is always 32-bytes, a hold-over from before + // https://go.dev/cl/369977. data := make([]byte, 32) copy(data, prefix) data[len(prefix)] = byte(ctxt.Arch.PtrSize) @@ -2364,7 +2368,7 @@ func (ctxt *Link) buildinfo() { data[len(prefix)+1] |= 2 // signals new pointer-free format data = appendString(data, strdata["runtime.buildVersion"]) data = appendString(data, strdata["runtime.modinfo"]) - // MacOS linker gets very upset if the size os not a multiple of alignment. + // MacOS linker gets very upset if the size is not a multiple of alignment. for len(data)%16 != 0 { data = append(data, 0) } diff --git a/src/debug/buildinfo/buildinfo.go b/src/debug/buildinfo/buildinfo.go index 1dd70a9f33..8338f03fa5 100644 --- a/src/debug/buildinfo/buildinfo.go +++ b/src/debug/buildinfo/buildinfo.go @@ -52,10 +52,9 @@ var errUnrecognizedFormat = errors.New("unrecognized file format") //go:linkname errNotGoExe var errNotGoExe = errors.New("not a Go executable") -// The build info blob left by the linker is identified by -// a 16-byte header, consisting of buildInfoMagic (14 bytes), -// the binary's pointer size (1 byte), -// and whether the binary is big endian (1 byte). +// The build info blob left by the linker is identified by a 32-byte header, +// consisting of buildInfoMagic (14 bytes), followed by version-dependent +// fields. var buildInfoMagic = []byte("\xff Go buildinf:") // ReadFile returns build information embedded in a Go binary @@ -171,37 +170,64 @@ func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) { return "", "", err } const ( - buildInfoAlign = 16 - buildInfoSize = 32 + buildInfoAlign = 16 + buildInfoHeaderSize = 32 + + ptrSizeOffset = 14 + flagsOffset = 15 + versPtrOffset = 16 + + flagsEndianMask = 0x1 + flagsEndianLittle = 0x0 + flagsEndianBig = 0x1 + + flagsVersionMask = 0x2 + flagsVersionPtr = 0x0 + flagsVersionInl = 0x2 ) for { i := bytes.Index(data, buildInfoMagic) - if i < 0 || len(data)-i < buildInfoSize { + if i < 0 || len(data)-i < buildInfoHeaderSize { return "", "", errNotGoExe } - if i%buildInfoAlign == 0 && len(data)-i >= buildInfoSize { + if i%buildInfoAlign == 0 && len(data)-i >= buildInfoHeaderSize { data = data[i:] break } data = data[(i+buildInfoAlign-1)&^(buildInfoAlign-1):] } - // Decode the blob. - // The first 14 bytes are buildInfoMagic. - // The next two bytes indicate pointer size in bytes (4 or 8) and endianness - // (0 for little, 1 for big). - // Two virtual addresses to Go strings follow that: runtime.buildVersion, - // and runtime.modinfo. - // On 32-bit platforms, the last 8 bytes are unused. - // If the endianness has the 2 bit set, then the pointers are zero - // and the 32-byte header is followed by varint-prefixed string data - // for the two string values we care about. - ptrSize := int(data[14]) - if data[15]&2 != 0 { - vers, data = decodeString(data[32:]) + // Decode the blob. The blob is a 32-byte header, optionally followed + // by 2 varint-prefixed string contents. + // + // type buildInfoHeader struct { + // magic [14]byte + // ptrSize uint8 // used if flagsVersionPtr + // flags uint8 + // versPtr targetUintptr // used if flagsVersionPtr + // modPtr targetUintptr // used if flagsVersionPtr + // } + // + // The version bit of the flags field determines the details of the format. + // + // Prior to 1.18, the flags version bit is flagsVersionPtr. In this + // case, the header includes pointers to the version and modinfo Go + // strings in the header. The ptrSize field indicates the size of the + // pointers and the endian bit of the flag indicates the pointer + // endianness. + // + // Since 1.18, the flags version bit is flagsVersionInl. In this case, + // the header is followed by the string contents inline as + // length-prefixed (as varint) string contents. First is the version + // string, followed immediately by the modinfo string. + flags := data[flagsOffset] + if flags&flagsVersionMask == flagsVersionInl { + vers, data = decodeString(data[buildInfoHeaderSize:]) mod, data = decodeString(data) } else { - bigEndian := data[15] != 0 + // flagsVersionPtr (<1.18) + ptrSize := int(data[ptrSizeOffset]) + bigEndian := flags&flagsEndianMask == flagsEndianBig var bo binary.ByteOrder if bigEndian { bo = binary.BigEndian @@ -216,8 +242,8 @@ func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) { } else { return "", "", errNotGoExe } - vers = readString(x, ptrSize, readPtr, readPtr(data[16:])) - mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:])) + vers = readString(x, ptrSize, readPtr, readPtr(data[versPtrOffset:])) + mod = readString(x, ptrSize, readPtr, readPtr(data[versPtrOffset+ptrSize:])) } if vers == "" { return "", "", errNotGoExe