Skip to content

Commit 0360328

Browse files
committed
cmd/link: derive GNU build ID from Go build ID
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 biuld 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. Derive the GNU build ID automatically from the Go build ID whenever the latter has been provided. Given that the Go build ID is intended to be deterministic, the resulting GNU build ID should be deterministic as well, which is the desired behaviour. Users can continue to override the derived GNU build ID by passing a custom value via the `-B` switch. Fixes #41004
1 parent 5fe3f0a commit 0360328

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

src/cmd/link/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Flags:
1818
-B note
1919
Add an ELF_NT_GNU_BUILD_ID note when using ELF.
2020
The value should start with 0x and be an even number of hex digits.
21+
Overrides the default GNU build ID derived from the Go build ID.
2122
-E entry
2223
Set entry symbol name.
2324
-H type

src/cmd/link/elf_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
package main
88

99
import (
10+
"cmd/internal/buildid"
11+
"cmd/link/internal/ld"
1012
"debug/elf"
1113
"fmt"
1214
"internal/platform"
@@ -199,6 +201,47 @@ func TestMinusRSymsWithSameName(t *testing.T) {
199201
}
200202
}
201203

204+
func TestGNUBuildIDDerivedFromGoBuildID(t *testing.T) {
205+
testenv.MustHaveGoBuild(t)
206+
207+
switch runtime.GOOS {
208+
case "linux", "dragonfly", "openbsd", "netbsd", "freebsd":
209+
default:
210+
t.Skip("We should only test on elf output.")
211+
}
212+
t.Parallel()
213+
214+
goFile := filepath.Join(t.TempDir(), "notes.go")
215+
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
216+
t.Fatal(err)
217+
}
218+
outFile := filepath.Join(t.TempDir(), "notes.exe")
219+
goTool := testenv.GoToolPath(t)
220+
221+
cmd := testenv.Command(t, goTool, "build", "-o", outFile, goFile)
222+
cmd.Dir = t.TempDir()
223+
224+
out, err := cmd.CombinedOutput()
225+
if err != nil {
226+
t.Logf("%s", out)
227+
t.Fatal(err)
228+
}
229+
230+
goBuildID, err := buildid.ReadFile(outFile)
231+
if err != nil {
232+
t.Fatalf("can't read Go build ID")
233+
}
234+
235+
gnuBuildID, err := buildid.ReadELFNote(outFile, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG)
236+
if err != nil || gnuBuildID == nil {
237+
t.Fatalf("can't read GNU build ID")
238+
}
239+
240+
if goBuildID != string(gnuBuildID) {
241+
t.Fatalf("GNU build ID %s does not match Go build ID %s", string(gnuBuildID), goBuildID)
242+
}
243+
}
244+
202245
func TestMergeNoteSections(t *testing.T) {
203246
testenv.MustHaveGoBuild(t)
204247
expected := 1

src/cmd/link/internal/ld/main.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,6 @@ func Main(arch *sys.Arch, theArch Arch) {
219219

220220
interpreter = *flagInterpreter
221221

222-
if *flagBuildid == "" && ctxt.Target.IsOpenbsd() {
223-
// TODO(jsing): Remove once direct syscalls are no longer in use.
224-
// OpenBSD 6.7 onwards will not permit direct syscalls from a
225-
// dynamically linked binary unless it identifies the binary
226-
// contains a .note.go.buildid ELF note. See issue #36435.
227-
*flagBuildid = "go-openbsd"
228-
}
229-
230222
// enable benchmarking
231223
var bench *benchmark.Metrics
232224
if len(*benchmarkFlag) != 0 {
@@ -247,6 +239,20 @@ func Main(arch *sys.Arch, theArch Arch) {
247239
bench.Start("Archinit")
248240
thearch.Archinit(ctxt)
249241

242+
// When no GNU build ID was requested explicitly we derive its value
243+
// automatically from the Go build ID, if set.
244+
if len(buildinfo) == 0 && *flagBuildid != "" && ctxt.IsELF {
245+
buildinfo = []byte(*flagBuildid)
246+
}
247+
248+
if *flagBuildid == "" && ctxt.Target.IsOpenbsd() {
249+
// TODO(jsing): Remove once direct syscalls are no longer in use.
250+
// OpenBSD 6.7 onwards will not permit direct syscalls from a
251+
// dynamically linked binary unless it identifies the binary
252+
// contains a .note.go.buildid ELF note. See issue #36435.
253+
*flagBuildid = "go-openbsd"
254+
}
255+
250256
if ctxt.linkShared && !ctxt.IsELF {
251257
Exitf("-linkshared can only be used on elf systems")
252258
}

0 commit comments

Comments
 (0)