Skip to content

cmd/link: support -buildmode=pie with internal linking on freebsd/amd64 #82412

Open

Description

@delphij

Go version

go1.26.9 freebsd/amd64; also reproduced at tip (d8b7594)

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/delphij/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/delphij/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build4002369253=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='freebsd'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/delphij/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='freebsd'
GOPATH='/home/delphij/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go126'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/delphij/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go126/pkg/tool/freebsd_amd64'
GOVCS=''
GOVERSION='go1.26.9'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Build a pure-Go program as a PIE with cgo disabled on FreeBSD 15.1/amd64:

$ cat main.go
package main

import (
	"fmt"
	"net"
	"os"
	"sync"
)

var global int

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func() { defer wg.Done() }()
	}
	wg.Wait()
	_, err := net.LookupHost("localhost")
	fmt.Printf("main=%p global=%p heap=%p lookup_err=%v pid=%d\n", main, &global, new(int), err, os.Getpid())
}
$ CGO_ENABLED=0 go build -buildmode=pie -o hello .

This comes up in practice in the FreeBSD Ports Collection: when a user sets
WITH_PIE=yes, Go ports are built with -buildmode=pie, and ports that set
CGO_ENABLED=0 (matching how their upstreams build release binaries) fail
to build.

What did you see happen?

-buildmode=pie requires external (cgo) linking, but cgo is not enabled

freebsd/amd64 is not listed in internal/platform.InternalLinkPIESupported,
so PIE always requires the external linker, and therefore cgo.

What did you expect to see?

A working PIE built by the internal linker, as on linux/amd64.

Notes

The internal linker already seems to have everything needed. With
freebsd/amd64 added to InternalLinkPIESupported, the build above produces
an ET_DYN executable with PT_INTERP set to /libexec/ld-elf.so.1 and
DF_1_PIE, and only R_X86_64_RELATIVE dynamic relocations. Larger
programs (for example, pomerium, a ~120 MB binary) build and run fine.

There is one FreeBSD-specific difference. The internal linker uses a default
text address of 0x400000 for PIEs. Linux randomizes the load address of an
ET_DYN executable regardless of the address in its program headers, but in
my testing the FreeBSD kernel only picks a load address itself when the
first PT_LOAD has address 0; otherwise it maps the executable at the
requested address. So the resulting PIE is always loaded at 0x400000 and
gets no ASLR (output trimmed to the relevant fields):

$ ./hello; ./hello
main=0x4de2a0 global=0x677798
main=0x4de2a0 global=0x677798

Linking at address 0 fixes this. With ASLR enabled
(kern.elf64.aslr.pie_enable=1, the default), the load address changes on
every run; with ASLR disabled, the kernel uses its usual ET_DYN base:

$ ./hello; ./hello
main=0x2202d2e875a0 global=0x2202d3009820
main=0x2a2a241ca5a0 global=0x2a2a2434c820
$ proccontrol -m aslr -s disable ./hello
main=0x10f85a0 global=0x127a820

I have a CL ready that:

  • adds freebsd/amd64 to InternalLinkPIESupported;
  • adds freebsd-amd64 to the pie_internal tests in cmd/dist;
  • makes the amd64 linker use text address 0 (that is, HEADR) for PIEs on
    FreeBSD, unless -T is given.

With it, run.bash passes on FreeBSD 15.1/amd64 apart from some
net/http and cmd/go tests that fail on my machine for reasons unrelated
to this change (they also fail with an unmodified go1.26.9: the loopback
connections come from a non-loopback address in my environment).

Activity

  1. gopherbot commented on Oct 22, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/852525 mentions this issue: cmd/dist, cmd/link, internal/platform: enable internal linking PIE on…

  2. added a commit that references this issue on Oct 22, 2026
    23075f9
  3. dr2chase commented on Oct 26, 2026

    @dr2chase
    Contributor

    @golang/compiler CL will need reviewers, looks superficially fine.

  4. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions