diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index f26fff687..e9e858646 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -91,6 +91,8 @@ jobs: run: make BUILDTAGS="" - name: compile with runc_nocriu build tag run: make RUNC_BUILDTAGS="runc_nocriu" + - name: verify linker dead code elimination is not disabled + run: make verify-dce codespell: runs-on: ubuntu-24.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index db1cd1924..82ce8f6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Switched to opencontainers/cgroups v0.1.0, which no longer uses the high-level cilium/ebpf API to manage cgroup v2 device rules. As a result, the runc binary shrunk by about 1 MiB (7.5%) on amd64. (#5403) +- The runc binary is now built with the `urfave_cli_no_template` build tag, + which, together with removing the reflect-based logging of CRIU options, + re-enables the linker's dead code elimination of exported methods. As a + result, the runc binary shrunk by about 2 MB (15%) on amd64. (#5486, #5506) - The `cpuAffinity` and NUMA `memoryPolicy` settings are no longer limited to 1024 CPUs/nodes, as runc now uses a dynamically-sized CPU mask. (#5343) - A container configuration which asks for a read-only rootfs diff --git a/Makefile b/Makefile index 4b3c47d48..d293fd44e 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ ifneq (,$(filter $(GOARCH),386 amd64 arm arm64 loong64 ppc64le riscv64 s390x)) endif endif GO_BUILD := $(GO) build $(TRIMPATH) $(GO_BUILDMODE) \ - $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \ + $(EXTRA_FLAGS) -tags "$(BUILDTAGS) urfave_cli_no_template" \ -ldflags "$(LDFLAGS_COMMON) $(EXTRA_LDFLAGS)" GO_BUILDMODE_STATIC := @@ -61,7 +61,7 @@ ifneq (,$(filter $(GOARCH),arm64 amd64)) endif # Enable static PIE binaries on supported platforms. GO_BUILD_STATIC := $(GO) build $(TRIMPATH) $(GO_BUILDMODE_STATIC) \ - $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \ + $(EXTRA_FLAGS) -tags "$(BUILDTAGS) urfave_cli_no_template netgo osusergo" \ -ldflags "$(LDFLAGS_COMMON) $(LDFLAGS_STATIC) $(EXTRA_LDFLAGS)" GPG_KEYID ?= cyphar@cyphar.com @@ -249,6 +249,20 @@ verify-dependencies: vendor || (echo -e "git status:\n $$(git status -- go.mod go.sum vendor/)\nerror: vendor/, go.mod and/or go.sum not up to date. Run \"make vendor\" to update"; exit 1) \ && echo "all vendor files are up to date." +# Check that nothing disables the linker's dead code elimination of +# exported methods, which noticeably increases the binary size. This is +# caused by calling reflect.Value.Method or reflect.Value.MethodByName +# with a non-constant argument, including indirectly (e.g. text/template). +# The check itself is verified using tests/dce-canary, which disables DCE. +.PHONY: verify-dce +verify-dce: + $(GO) build -o /dev/null -ldflags=-dumpdep ./tests/dce-canary 2>&1 \ + | grep -q '' \ + || { echo "error: verify-dce self-check failed (tests/dce-canary not detected)"; exit 1; } + set -o pipefail; $(GO_BUILD) -o /dev/null -ldflags=-dumpdep . 2>&1 \ + | { ! grep -q '' \ + || { echo "error: linker dead code elimination is disabled; use github.com/aarzilli/whydeadcode to find out why"; exit 1; }; } + .PHONY: validate-keyring validate-keyring: script/keyring_validate.sh diff --git a/tests/dce-canary/main.go b/tests/dce-canary/main.go new file mode 100644 index 000000000..407d8c052 --- /dev/null +++ b/tests/dce-canary/main.go @@ -0,0 +1,18 @@ +// dce-canary is a positive control for "make verify-dce". It calls +// reflect.Value.MethodByName with a non-constant argument, which makes +// the linker disable dead code elimination of exported methods, so +// "verify-dce" must detect it. If it does not, the check is broken. +package main + +import ( + "os" + "reflect" +) + +type t struct{} + +func (t) Foo() {} + +func main() { + reflect.ValueOf(t{}).MethodByName(os.Args[0]) +}