]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: allow deriving GNU build ID from Go build ID ID
authorPatrick Steinhardt <ps@pks.im>
Sat, 16 Sep 2023 11:12:12 +0000 (11:12 +0000)
committerCherry Mui <cherryyz@google.com>
Mon, 18 Sep 2023 16:34:01 +0000 (16:34 +0000)
While it is possible to embed a GNU build ID into the linked
executable by passing `-B 0xBUILDID` to the linker, the build ID will
need to be precomputed by the build system somehow. This makes it
unnecessarily complex to generate a deterministic build ID as it
either requires the build system to hash all inputs manually or to
build the binary twice, once to compute its hash and once with the GNU
build ID derived from that hash. Despite being complex, it is also
inefficient as it requires the build system to duplicate some of the
work that the Go linker already performs anyway.

Introduce a new argument "gobuildid" that can be passed to `-B` that
causes the linker to automatically derive the GNU build ID from the Go
build ID. Given that the Go build ID is deterministically computed
from all of its inputs, the resulting GNU build ID should be
deterministic in the same way, which is the desired behaviour.

Furthermore, given that the `-B` flag currently requires a "0x" prefix
for all values passed to it, using "gobuildid" as value is a backwards
compatible change.

An alternative would be to unconditionally calculate the GNU build ID
unless otherwise specified. This would require some larger rework
though because building the Go toolchain would not converge anymore
due the GNU build ID changing on every stage, which in turn would
cause the Go build ID to change as well.

Fixes #41004

Change-Id: I707c5fc321749c00761643d6cc79d44bf2cd744d
GitHub-Last-Rev: 5483305a8566937836e5f39149a3df805d94580b
GitHub-Pull-Request: golang/go#61469
Reviewed-on: https://go-review.googlesource.com/c/go/+/511475
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/link/doc.go
src/cmd/link/elf_test.go
src/cmd/link/internal/ld/elf.go
src/cmd/link/internal/ld/main.go

index ce0166faa5cac61310427711ad2b10ec0236e7d4..c5f43a295443d40166e0dbb29fff6522cdf0e0fa 100644 (file)
@@ -18,6 +18,8 @@ Flags:
        -B note
                Add an ELF_NT_GNU_BUILD_ID note when using ELF.
                The value should start with 0x and be an even number of hex digits.
+               Alternatively, you can pass "gobuildid" in order to derive the
+               GNU build ID from the Go build ID.
        -E entry
                Set entry symbol name.
        -H type
index 902ce28b104e4b85d224665f614ae5e394b8a59e..5dcef1cc22b20cd3c2354674e177affaef84aa22 100644 (file)
@@ -7,6 +7,10 @@
 package main
 
 import (
+       "bytes"
+       "cmd/internal/buildid"
+       "cmd/internal/notsha256"
+       "cmd/link/internal/ld"
        "debug/elf"
        "fmt"
        "internal/platform"
@@ -199,6 +203,39 @@ func TestMinusRSymsWithSameName(t *testing.T) {
        }
 }
 
+func TestGNUBuildIDDerivedFromGoBuildID(t *testing.T) {
+       testenv.MustHaveGoBuild(t)
+
+       t.Parallel()
+
+       goFile := filepath.Join(t.TempDir(), "notes.go")
+       if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
+               t.Fatal(err)
+       }
+       outFile := filepath.Join(t.TempDir(), "notes.exe")
+       goTool := testenv.GoToolPath(t)
+
+       cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", "-buildid 0x1234 -B gobuildid", goFile)
+       cmd.Dir = t.TempDir()
+
+       out, err := cmd.CombinedOutput()
+       if err != nil {
+               t.Logf("%s", out)
+               t.Fatal(err)
+       }
+
+       expectedGoBuildID := notsha256.Sum256([]byte("0x1234"))
+
+       gnuBuildID, err := buildid.ReadELFNote(outFile, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG)
+       if err != nil || gnuBuildID == nil {
+               t.Fatalf("can't read GNU build ID")
+       }
+
+       if !bytes.Equal(gnuBuildID, expectedGoBuildID[:20]) {
+               t.Fatalf("build id not matching")
+       }
+}
+
 func TestMergeNoteSections(t *testing.T) {
        testenv.MustHaveGoBuild(t)
        expected := 1
index a3f99b19608a1118f3ecfe585bcfc9af8f11bc20..be9e22946a38684287b7e839e59c53b391dc1735 100644 (file)
@@ -806,6 +806,18 @@ func elfwritefreebsdsig(out *OutBuf) int {
 }
 
 func addbuildinfo(val string) {
+       if val == "gobuildid" {
+               buildID := *flagBuildid
+               if buildID == "" {
+                       Exitf("-B gobuildid requires a Go build ID supplied via -buildid")
+               }
+
+               hashedBuildID := notsha256.Sum256([]byte(buildID))
+               buildinfo = hashedBuildID[:20]
+
+               return
+       }
+
        if !strings.HasPrefix(val, "0x") {
                Exitf("-B argument must start with 0x: %s", val)
        }
index 589b5065fd794443d230fe611d0f1dc7486f7f29..e120f90a227fffb13ed41ea1f0cf091a093d648b 100644 (file)
@@ -190,7 +190,7 @@ func Main(arch *sys.Arch, theArch Arch) {
        flag.Var(&ctxt.LinkMode, "linkmode", "set link `mode`")
        flag.Var(&ctxt.BuildMode, "buildmode", "set build `mode`")
        flag.BoolVar(&ctxt.compressDWARF, "compressdwarf", true, "compress DWARF if possible")
-       objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF", addbuildinfo)
+       objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF; use \"gobuildid\" to generate it from the Go build ID", addbuildinfo)
        objabi.Flagfn1("L", "add specified `directory` to library path", func(a string) { Lflag(ctxt, a) })
        objabi.AddVersionFlag() // -V
        objabi.Flagfn1("X", "add string value `definition` of the form importpath.name=value", func(s string) { addstrdata1(ctxt, s) })