Skip to content

Commit 12264b2

Browse files
committed
cmd/link: allow deriving GNU build ID from action 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. Introduce a new argument "actionid" that can be passed to `-B` that causes the linker to automatically derive the build ID from the Go build ID's action ID, which is derived from all build inputs. This should thus both be deterministic while the value also changes whenever the build inputs change, which is the desired behaviour for the GNU build ID. Furthermore, given that the `-B` flag currently requires a "0x" prefix for all values passed to it, using "actionid" as value is a backwards compatible change. Fixes #41004
1 parent 5fe3f0a commit 12264b2

File tree

4 files changed

+68
-1
lines changed

4 files changed

+68
-1
lines changed

src/cmd/link/doc.go

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ 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+
Alternatively, you can pass "actionid" in order to derive the GNU build ID
22+
from the Go build ID's action ID.
2123
-E entry
2224
Set entry symbol name.
2325
-H type

src/cmd/link/elf_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
package main
88

99
import (
10+
"bytes"
11+
"cmd/internal/buildid"
12+
"cmd/internal/notsha256"
13+
"cmd/link/internal/ld"
1014
"debug/elf"
1115
"fmt"
1216
"internal/platform"
@@ -199,6 +203,50 @@ func TestMinusRSymsWithSameName(t *testing.T) {
199203
}
200204
}
201205

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

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

+17
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,23 @@ func elfwritefreebsdsig(out *OutBuf) int {
806806
}
807807

808808
func addbuildinfo(val string) {
809+
if val == "actionid" {
810+
buildID := *flagBuildid
811+
if buildID == "" {
812+
Exitf("-B actionid requires a Go build ID supplied via -buildid")
813+
}
814+
815+
if !strings.Contains(buildID, "/") {
816+
Exitf("-B actionid does not contain content ID")
817+
}
818+
819+
actionID := buildID[:strings.Index(buildID, "/")]
820+
hashedID := notsha256.Sum256([]byte(actionID))
821+
buildinfo = hashedID[:20]
822+
823+
return
824+
}
825+
809826
if !strings.HasPrefix(val, "0x") {
810827
Exitf("-B argument must start with 0x: %s", val)
811828
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func Main(arch *sys.Arch, theArch Arch) {
149149
flag.Var(&ctxt.LinkMode, "linkmode", "set link `mode`")
150150
flag.Var(&ctxt.BuildMode, "buildmode", "set build `mode`")
151151
flag.BoolVar(&ctxt.compressDWARF, "compressdwarf", true, "compress DWARF if possible")
152-
objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF", addbuildinfo)
152+
objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF; use \"actionid\" to generate it from the Go build ID", addbuildinfo)
153153
objabi.Flagfn1("L", "add specified `directory` to library path", func(a string) { Lflag(ctxt, a) })
154154
objabi.AddVersionFlag() // -V
155155
objabi.Flagfn1("X", "add string value `definition` of the form importpath.name=value", func(s string) { addstrdata1(ctxt, s) })

0 commit comments

Comments
 (0)