Repository: cilium/ebpf
Pull request #1775 (2025-05-06 11:03:20)

btf: fix Spec.Copy() for split BTF
Make decoder.Copy do a deep copy of the base, so that types which are inflated in the future also point at the correct copy.


Patch:
diff --git a/btf/btf.go b/btf/btf.go
index 8112b6c..d26931a 100644
--- a/btf/btf.go
+++ b/btf/btf.go
@@ -346,7 +346,10 @@ func fixupDatasecLayout(ds *Datasec) error {
 	return nil
 }
 
-// Copy creates a copy of Spec.
+// Copy a Spec.
+//
+// All contained types are duplicated while preserving any modifications made
+// to them.
 func (s *Spec) Copy() *Spec {
 	if s == nil {
 		return nil
diff --git a/btf/format.go b/btf/format.go
index 3e0deda..7deca33 100644
--- a/btf/format.go
+++ b/btf/format.go
@@ -56,7 +56,7 @@ func (gf *GoFormatter) enumIdentifier(name, element string) string {
 //
 // It encodes https://golang.org/ref/spec#Type_declarations:
 //
-//	type foo struct { bar uint32; }
+//	type foo struct { _ structs.HostLayout; bar uint32; }
 //	type bar int32
 func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
 	if name == "" {
@@ -114,7 +114,7 @@ func (gf *GoFormatter) writeType(typ Type, depth int) error {
 //
 // It encodes https://golang.org/ref/spec#TypeLit.
 //
-//	struct { bar uint32; }
+//	struct { _ structs.HostLayout; bar uint32; }
 //	uint32
 func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
 	depth++
@@ -208,7 +208,7 @@ func (gf *GoFormatter) writeIntLit(i *Int) error {
 }
 
 func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
-	gf.w.WriteString("struct { ")
+	gf.w.WriteString("struct { _ structs.HostLayout; ")
 
 	prevOffset := uint32(0)
 	skippedBitfield := false
@@ -298,7 +298,7 @@ func (gf *GoFormatter) writeStructField(m Member, depth int) error {
 }
 
 func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
-	gf.w.WriteString("struct { ")
+	gf.w.WriteString("struct { _ structs.HostLayout; ")
 
 	prevOffset := uint32(0)
 	for i, vsi := range ds.Vars {
diff --git a/btf/unmarshal.go b/btf/unmarshal.go
index a55d79d..8704140 100644
--- a/btf/unmarshal.go
+++ b/btf/unmarshal.go
@@ -47,7 +47,6 @@ func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *dec
 			return nil, fmt.Errorf("can't use split BTF as base")
 		}
 
-		base = base.Copy()
 		firstTypeID = TypeID(len(base.offsets))
 	}
 
@@ -166,23 +165,35 @@ func allBtfTypeOffsets(buf []byte, bo binary.ByteOrder, header *btfType) iter.Se
 	}
 }
 
+// Copy performs a deep copy of a decoder and its base.
 func (d *decoder) Copy() *decoder {
 	if d == nil {
 		return nil
 	}
 
+	return d.copy(nil)
+}
+
+func (d *decoder) copy(copiedTypes map[Type]Type) *decoder {
+	if d == nil {
+		return nil
+	}
+
 	d.mu.Lock()
 	defer d.mu.Unlock()
 
+	if copiedTypes == nil {
+		copiedTypes = make(map[Type]Type, len(d.types))
+	}
+
 	types := make(map[TypeID]Type, len(d.types))
-	copiedTypes := make(map[Type]Type, len(d.types))
 	typeIDs := make(map[Type]TypeID, len(d.typeIDs))
 	for id, typ := range d.types {
 		types[id] = copyType(typ, d.typeIDs, copiedTypes, typeIDs)
 	}
 
 	return &decoder{
-		d.base,
+		d.base.copy(copiedTypes),
 		d.byteOrder,
 		d.raw,
 		d.strings,
diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go
index 9f0fc06..fbda8df 100644
--- a/cmd/bpf2go/gen/output.go
+++ b/cmd/bpf2go/gen/output.go
@@ -161,20 +161,31 @@ func Generate(args GenerateArgs) error {
 		Identifier: args.Identifier,
 	}
 
+	var typeDecls []string
+	needsStructsPkg := false
+	for _, typ := range types {
+		name := typeNames[typ]
+		decl, err := gf.TypeDeclaration(name, typ)
+		if err != nil {
+			return fmt.Errorf("generating %s: %w", name, err)
+		}
+		_, ok := typ.(*btf.Struct)
+		needsStructsPkg = needsStructsPkg || ok
+		typeDecls = append(typeDecls, decl)
+	}
+
 	ctx := struct {
-		*btf.GoFormatter
-		Module      string
-		Package     string
-		Constraints constraint.Expr
-		Name        templateName
-		Maps        map[string]string
-		Variables   map[string]string
-		Programs    map[string]string
-		Types       []btf.Type
-		TypeNames   map[btf.Type]string
-		File        string
+		Module           string
+		Package          string
+		Constraints      constraint.Expr
+		Name             templateName
+		Maps             map[string]string
+		Variables        map[string]string
+		Programs         map[string]string
+		TypeDeclarations []string
+		File             string
+		NeedsStructsPkg  bool
 	}{
-		gf,
 		b2gInt.CurrentModule,
 		args.Package,
 		args.Constraints,
@@ -182,9 +193,9 @@ func Generate(args GenerateArgs) error {
 		maps,
 		variables,
 		programs,
-		types,
-		typeNames,
+		typeDecls,
 		args.ObjectFile,
+		needsStructsPkg,
 	}
 
 	var buf bytes.Buffer
diff --git a/cmd/bpf2go/gen/output.tpl b/cmd/bpf2go/gen/output.tpl
index 474c9c5..350b0ca 100644
--- a/cmd/bpf2go/gen/output.tpl
+++ b/cmd/bpf2go/gen/output.tpl
@@ -8,13 +8,16 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+{{- if .NeedsStructsPkg }}
+	"structs"
+{{- end }}
 
 	"{{ .Module }}"
 )
 
-{{- if .Types }}
-{{- range $type := .Types }}
-{{ $.TypeDeclaration (index $.TypeNames $type) $type }}
+{{- if .TypeDeclarations }}
+{{- range $type := .TypeDeclarations }}
+{{ $type }}
 
 {{ end }}
 {{- end }}
diff --git a/examples/fentry/bpf_bpfeb.go b/examples/fentry/bpf_bpfeb.go
index 7fab2f4..db8b37b 100644
--- a/examples/fentry/bpf_bpfeb.go
+++ b/examples/fentry/bpf_bpfeb.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_     structs.HostLayout
 	Comm  [16]uint8
 	Sport uint16
 	Dport uint16
diff --git a/examples/fentry/bpf_bpfel.go b/examples/fentry/bpf_bpfel.go
index 96515ee..69a89fa 100644
--- a/examples/fentry/bpf_bpfel.go
+++ b/examples/fentry/bpf_bpfel.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_     structs.HostLayout
 	Comm  [16]uint8
 	Sport uint16
 	Dport uint16
diff --git a/examples/ringbuffer/bpf_bpfeb.go b/examples/ringbuffer/bpf_bpfeb.go
index 8e716a8..8ef3284 100644
--- a/examples/ringbuffer/bpf_bpfeb.go
+++ b/examples/ringbuffer/bpf_bpfeb.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_    structs.HostLayout
 	Pid  uint32
 	Comm [16]uint8
 }
diff --git a/examples/ringbuffer/bpf_bpfel.go b/examples/ringbuffer/bpf_bpfel.go
index 897a938..fbb1c4a 100644
--- a/examples/ringbuffer/bpf_bpfel.go
+++ b/examples/ringbuffer/bpf_bpfel.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_    structs.HostLayout
 	Pid  uint32
 	Comm [16]uint8
 }
diff --git a/examples/tcprtt/bpf_bpfeb.go b/examples/tcprtt/bpf_bpfeb.go
index 8d320fd..95f71bd 100644
--- a/examples/tcprtt/bpf_bpfeb.go
+++ b/examples/tcprtt/bpf_bpfeb.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_     structs.HostLayout
 	Sport uint16
 	Dport uint16
 	Saddr uint32
diff --git a/examples/tcprtt/bpf_bpfel.go b/examples/tcprtt/bpf_bpfel.go
index 2efbd26..3112677 100644
--- a/examples/tcprtt/bpf_bpfel.go
+++ b/examples/tcprtt/bpf_bpfel.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_     structs.HostLayout
 	Sport uint16
 	Dport uint16
 	Saddr uint32
diff --git a/examples/tcprtt_sockops/bpf_bpfeb.go b/examples/tcprtt_sockops/bpf_bpfeb.go
index 870c24c..361a967 100644
--- a/examples/tcprtt_sockops/bpf_bpfeb.go
+++ b/examples/tcprtt_sockops/bpf_bpfeb.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfRttEvent struct {
+	_     structs.HostLayout
 	Sport uint16
 	Dport uint16
 	Saddr uint32
@@ -21,12 +23,14 @@ type bpfRttEvent struct {
 }
 
 type bpfSkInfo struct {
+	_      structs.HostLayout
 	SkKey  bpfSkKey
 	SkType uint8
 	_      [3]byte
 }
 
 type bpfSkKey struct {
+	_          structs.HostLayout
 	LocalIp4   uint32
 	RemoteIp4  uint32
 	LocalPort  uint32
diff --git a/examples/tcprtt_sockops/bpf_bpfel.go b/examples/tcprtt_sockops/bpf_bpfel.go
index dd04d6f..d5e9d86 100644
--- a/examples/tcprtt_sockops/bpf_bpfel.go
+++ b/examples/tcprtt_sockops/bpf_bpfel.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfRttEvent struct {
+	_     structs.HostLayout
 	Sport uint16
 	Dport uint16
 	Saddr uint32
@@ -21,12 +23,14 @@ type bpfRttEvent struct {
 }
 
 type bpfSkInfo struct {
+	_      structs.HostLayout
 	SkKey  bpfSkKey
 	SkType uint8
 	_      [3]byte
 }
 
 type bpfSkKey struct {
+	_          structs.HostLayout
 	LocalIp4   uint32
 	RemoteIp4  uint32
 	LocalPort  uint32
diff --git a/examples/uretprobe/bpf_x86_bpfel.go b/examples/uretprobe/bpf_x86_bpfel.go
index fd8616a..cbdd704 100644
--- a/examples/uretprobe/bpf_x86_bpfel.go
+++ b/examples/uretprobe/bpf_x86_bpfel.go
@@ -8,11 +8,13 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type bpfEvent struct {
+	_    structs.HostLayout
 	Pid  uint32
 	Line [80]uint8
 }
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 66c1175..7171e6a 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -837,8 +837,6 @@ func outputPatchedStruct(gf *btf.GoFormatter, w *bytes.Buffer, id string, s *btf
 		return err
 	}
 
-	decl = strings.Replace(decl, "struct {", "struct { structs.HostLayout;", 1)
-
 	w.WriteString(decl)
 	w.WriteString("\n\n")
 	return nil
diff --git a/internal/sys/types.go b/internal/sys/types.go
index a76d4a5..2847f17 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -690,7 +690,7 @@ const (
 )
 
 type BtfInfo struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	Btf       TypedPointer[uint8]
 	BtfSize   uint32
 	Id        BTFID
@@ -700,13 +700,13 @@ type BtfInfo struct {
 }
 
 type FuncInfo struct {
-	structs.HostLayout
+	_       structs.HostLayout
 	InsnOff uint32
 	TypeId  uint32
 }
 
 type LineInfo struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	InsnOff     uint32
 	FileNameOff uint32
 	LineOff     uint32
@@ -714,7 +714,7 @@ type LineInfo struct {
 }
 
 type LinkInfo struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Type   LinkType
 	Id     LinkID
 	ProgId uint32
@@ -723,7 +723,7 @@ type LinkInfo struct {
 }
 
 type MapInfo struct {
-	structs.HostLayout
+	_                     structs.HostLayout
 	Type                  uint32
 	Id                    MapID
 	KeySize               uint32
@@ -743,7 +743,7 @@ type MapInfo struct {
 }
 
 type ProgInfo struct {
-	structs.HostLayout
+	_                    structs.HostLayout
 	Type                 uint32
 	Id                   uint32
 	Tag                  [8]uint8
@@ -786,7 +786,7 @@ type ProgInfo struct {
 }
 
 type SkLookup struct {
-	structs.HostLayout
+	_              structs.HostLayout
 	Cookie         uint64
 	Family         uint32
 	Protocol       uint32
@@ -802,7 +802,7 @@ type SkLookup struct {
 }
 
 type XdpMd struct {
-	structs.HostLayout
+	_              structs.HostLayout
 	Data           uint32
 	DataEnd        uint32
 	DataMeta       uint32
@@ -812,7 +812,7 @@ type XdpMd struct {
 }
 
 type BtfGetFdByIdAttr struct {
-	structs.HostLayout
+	_  structs.HostLayout
 	Id uint32
 }
 
@@ -825,7 +825,7 @@ func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
 }
 
 type BtfGetNextIdAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Id     BTFID
 	NextId BTFID
 }
@@ -836,7 +836,7 @@ func BtfGetNextId(attr *BtfGetNextIdAttr) error {
 }
 
 type BtfLoadAttr struct {
-	structs.HostLayout
+	_              structs.HostLayout
 	Btf            TypedPointer[uint8]
 	BtfLogBuf      TypedPointer[uint8]
 	BtfSize        uint32
@@ -856,7 +856,7 @@ func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
 }
 
 type EnableStatsAttr struct {
-	structs.HostLayout
+	_    structs.HostLayout
 	Type uint32
 }
 
@@ -869,7 +869,7 @@ func EnableStats(attr *EnableStatsAttr) (*FD, error) {
 }
 
 type IterCreateAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	LinkFd uint32
 	Flags  uint32
 }
@@ -883,7 +883,7 @@ func IterCreate(attr *IterCreateAttr) (*FD, error) {
 }
 
 type LinkCreateAttr struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -901,7 +901,7 @@ func LinkCreate(attr *LinkCreateAttr) (*FD, error) {
 }
 
 type LinkCreateIterAttr struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -920,7 +920,7 @@ func LinkCreateIter(attr *LinkCreateIterAttr) (*FD, error) {
 }
 
 type LinkCreateKprobeMultiAttr struct {
-	structs.HostLayout
+	_                structs.HostLayout
 	ProgFd           uint32
 	TargetFd         uint32
 	AttachType       AttachType
@@ -942,7 +942,7 @@ func LinkCreateKprobeMulti(attr *LinkCreateKprobeMultiAttr) (*FD, error) {
 }
 
 type LinkCreateNetfilterAttr struct {
-	structs.HostLayout
+	_              structs.HostLayout
 	ProgFd         uint32
 	TargetFd       uint32
 	AttachType     AttachType
@@ -963,7 +963,7 @@ func LinkCreateNetfilter(attr *LinkCreateNetfilterAttr) (*FD, error) {
 }
 
 type LinkCreateNetkitAttr struct {
-	structs.HostLayout
+	_                structs.HostLayout
 	ProgFd           uint32
 	TargetIfindex    uint32
 	AttachType       AttachType
@@ -983,7 +983,7 @@ func LinkCreateNetkit(attr *LinkCreateNetkitAttr) (*FD, error) {
 }
 
 type LinkCreatePerfEventAttr struct {
-	structs.HostLayout
+	_          structs.HostLayout
 	ProgFd     uint32
 	TargetFd   uint32
 	AttachType AttachType
@@ -1001,7 +1001,7 @@ func LinkCreatePerfEvent(attr *LinkCreatePerfEventAttr) (*FD, error) {
 }
 
 type LinkCreateTcxAttr struct {
-	structs.HostLayout
+	_                structs.HostLayout
 	ProgFd           uint32
 	TargetIfindex    uint32
 	AttachType       AttachType
@@ -1021,7 +1021,7 @@ func LinkCreateTcx(attr *LinkCreateTcxAttr) (*FD, error) {
 }
 
 type LinkCreateTracingAttr struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -1041,7 +1041,7 @@ func LinkCreateTracing(attr *LinkCreateTracingAttr) (*FD, error) {
 }
 
 type LinkCreateUprobeMultiAttr struct {
-	structs.HostLayout
+	_                structs.HostLayout
 	ProgFd           uint32
 	TargetFd         uint32
 	AttachType       AttachType
@@ -1065,7 +1065,7 @@ func LinkCreateUprobeMulti(attr *LinkCreateUprobeMultiAttr) (*FD, error) {
 }
 
 type LinkGetFdByIdAttr struct {
-	structs.HostLayout
+	_  structs.HostLayout
 	Id LinkID
 }
 
@@ -1078,7 +1078,7 @@ func LinkGetFdById(attr *LinkGetFdByIdAttr) (*FD, error) {
 }
 
 type LinkGetNextIdAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Id     LinkID
 	NextId LinkID
 }
@@ -1089,7 +1089,7 @@ func LinkGetNextId(attr *LinkGetNextIdAttr) error {
 }
 
 type LinkUpdateAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	LinkFd    uint32
 	NewProgFd uint32
 	Flags     uint32
@@ -1102,7 +1102,7 @@ func LinkUpdate(attr *LinkUpdateAttr) error {
 }
 
 type MapCreateAttr struct {
-	structs.HostLayout
+	_                     structs.HostLayout
 	MapType               MapType
 	KeySize               uint32
 	ValueSize             uint32
@@ -1130,7 +1130,7 @@ func MapCreate(attr *MapCreateAttr) (*FD, error) {
 }
 
 type MapDeleteBatchAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1147,7 +1147,7 @@ func MapDeleteBatch(attr *MapDeleteBatchAttr) error {
 }
 
 type MapDeleteElemAttr struct {
-	structs.HostLayout
+	_     structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1161,7 +1161,7 @@ func MapDeleteElem(attr *MapDeleteElemAttr) error {
 }
 
 type MapFreezeAttr struct {
-	structs.HostLayout
+	_     structs.HostLayout
 	MapFd uint32
 }
 
@@ -1171,7 +1171,7 @@ func MapFreeze(attr *MapFreezeAttr) error {
 }
 
 type MapGetFdByIdAttr struct {
-	structs.HostLayout
+	_  structs.HostLayout
 	Id uint32
 }
 
@@ -1184,7 +1184,7 @@ func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
 }
 
 type MapGetNextIdAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Id     uint32
 	NextId uint32
 }
@@ -1195,7 +1195,7 @@ func MapGetNextId(attr *MapGetNextIdAttr) error {
 }
 
 type MapGetNextKeyAttr struct {
-	structs.HostLayout
+	_       structs.HostLayout
 	MapFd   uint32
 	_       [4]byte
 	Key     Pointer
@@ -1208,7 +1208,7 @@ func MapGetNextKey(attr *MapGetNextKeyAttr) error {
 }
 
 type MapLookupAndDeleteBatchAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1225,7 +1225,7 @@ func MapLookupAndDeleteBatch(attr *MapLookupAndDeleteBatchAttr) error {
 }
 
 type MapLookupAndDeleteElemAttr struct {
-	structs.HostLayout
+	_     structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1239,7 +1239,7 @@ func MapLookupAndDeleteElem(attr *MapLookupAndDeleteElemAttr) error {
 }
 
 type MapLookupBatchAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1256,7 +1256,7 @@ func MapLookupBatch(attr *MapLookupBatchAttr) error {
 }
 
 type MapLookupElemAttr struct {
-	structs.HostLayout
+	_     structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1270,7 +1270,7 @@ func MapLookupElem(attr *MapLookupElemAttr) error {
 }
 
 type MapUpdateBatchAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1287,7 +1287,7 @@ func MapUpdateBatch(attr *MapUpdateBatchAttr) error {
 }
 
 type MapUpdateElemAttr struct {
-	structs.HostLayout
+	_     structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1301,7 +1301,7 @@ func MapUpdateElem(attr *MapUpdateElemAttr) error {
 }
 
 type ObjGetAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	Pathname  StringPointer
 	BpfFd     uint32
 	FileFlags uint32
@@ -1318,7 +1318,7 @@ func ObjGet(attr *ObjGetAttr) (*FD, error) {
 }
 
 type ObjGetInfoByFdAttr struct {
-	structs.HostLayout
+	_       structs.HostLayout
 	BpfFd   uint32
 	InfoLen uint32
 	Info    Pointer
@@ -1330,7 +1330,7 @@ func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
 }
 
 type ObjPinAttr struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	Pathname  StringPointer
 	BpfFd     uint32
 	FileFlags uint32
@@ -1344,7 +1344,7 @@ func ObjPin(attr *ObjPinAttr) error {
 }
 
 type ProgAttachAttr struct {
-	structs.HostLayout
+	_                 structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachBpfFd       uint32
 	AttachType        uint32
@@ -1360,7 +1360,7 @@ func ProgAttach(attr *ProgAttachAttr) error {
 }
 
 type ProgBindMapAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	ProgFd uint32
 	MapFd  uint32
 	Flags  uint32
@@ -1372,7 +1372,7 @@ func ProgBindMap(attr *ProgBindMapAttr) error {
 }
 
 type ProgDetachAttr struct {
-	structs.HostLayout
+	_                 structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachBpfFd       uint32
 	AttachType        uint32
@@ -1388,7 +1388,7 @@ func ProgDetach(attr *ProgDetachAttr) error {
 }
 
 type ProgGetFdByIdAttr struct {
-	structs.HostLayout
+	_  structs.HostLayout
 	Id uint32
 }
 
@@ -1401,7 +1401,7 @@ func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
 }
 
 type ProgGetNextIdAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Id     uint32
 	NextId uint32
 }
@@ -1412,7 +1412,7 @@ func ProgGetNextId(attr *ProgGetNextIdAttr) error {
 }
 
 type ProgLoadAttr struct {
-	structs.HostLayout
+	_                  structs.HostLayout
 	ProgType           ProgType
 	InsnCnt            uint32
 	Insns              TypedPointer[uint8]
@@ -1452,7 +1452,7 @@ func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
 }
 
 type ProgQueryAttr struct {
-	structs.HostLayout
+	_                 structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachType        AttachType
 	QueryFlags        uint32
@@ -1472,7 +1472,7 @@ func ProgQuery(attr *ProgQueryAttr) error {
 }
 
 type ProgRunAttr struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	ProgFd      uint32
 	Retval      uint32
 	DataSizeIn  uint32
@@ -1497,7 +1497,7 @@ func ProgRun(attr *ProgRunAttr) error {
 }
 
 type RawTracepointOpenAttr struct {
-	structs.HostLayout
+	_      structs.HostLayout
 	Name   StringPointer
 	ProgFd uint32
 	_      [4]byte
@@ -1513,7 +1513,7 @@ func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
 }
 
 type CgroupLinkInfo struct {
-	structs.HostLayout
+	_          structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1524,7 +1524,7 @@ type CgroupLinkInfo struct {
 }
 
 type IterLinkInfo struct {
-	structs.HostLayout
+	_             structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1534,7 +1534,7 @@ type IterLinkInfo struct {
 }
 
 type KprobeLinkInfo struct {
-	structs.HostLayout
+	_             structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1550,7 +1550,7 @@ type KprobeLinkInfo struct {
 }
 
 type KprobeMultiLinkInfo struct {
-	structs.HostLayout
+	_       structs.HostLayout
 	Type    LinkType
 	Id      LinkID
 	ProgId  uint32
@@ -1564,7 +1564,7 @@ type KprobeMultiLinkInfo struct {
 }
 
 type NetNsLinkInfo struct {
-	structs.HostLayout
+	_          structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1575,7 +1575,7 @@ type NetNsLinkInfo struct {
 }
 
 type NetfilterLinkInfo struct {
-	structs.HostLayout
+	_        structs.HostLayout
 	Type     LinkType
 	Id       LinkID
 	ProgId   uint32
@@ -1588,7 +1588,7 @@ type NetfilterLinkInfo struct {
 }
 
 type NetkitLinkInfo struct {
-	structs.HostLayout
+	_          structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1599,7 +1599,7 @@ type NetkitLinkInfo struct {
 }
 
 type PerfEventLinkInfo struct {
-	structs.HostLayout
+	_             structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1608,7 +1608,7 @@ type PerfEventLinkInfo struct {
 }
 
 type RawTracepointLinkInfo struct {
-	structs.HostLayout
+	_         structs.HostLayout
 	Type      LinkType
 	Id        LinkID
 	ProgId    uint32
@@ -1619,7 +1619,7 @@ type RawTracepointLinkInfo struct {
 }
 
 type TcxLinkInfo struct {
-	structs.HostLayout
+	_          structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1630,7 +1630,7 @@ type TcxLinkInfo struct {
 }
 
 type TracingLinkInfo struct {
-	structs.HostLayout
+	_           structs.HostLayout
 	Type        LinkType
 	Id          LinkID
 	ProgId      uint32
@@ -1642,7 +1642,7 @@ type TracingLinkInfo struct {
 }
 
 type XDPLinkInfo struct {
-	structs.HostLayout
+	_       structs.HostLayout
 	Type    LinkType
 	Id      LinkID
 	ProgId  uint32


Test patch:
diff --git a/btf/btf_test.go b/btf/btf_test.go
index 68f57f3..594533e 100644
--- a/btf/btf_test.go
+++ b/btf/btf_test.go
@@ -447,50 +447,59 @@ func TestLoadSplitSpecFromReader(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	typ, err := splitSpec.AnyTypeByName("bpf_testmod_init")
-	if err != nil {
-		t.Fatal(err)
-	}
-	typeID, err := splitSpec.TypeID(typ)
-	if err != nil {
-		t.Fatal(err)
-	}
+	var fnType *Func
+	qt.Assert(t, qt.IsNil(splitSpec.TypeByName("bpf_testmod_init", &fnType)))
+	typeID, err := splitSpec.TypeID(fnType)
+	qt.Assert(t, qt.IsNil(err))
 
 	typeByID, err := splitSpec.TypeByID(typeID)
 	qt.Assert(t, qt.IsNil(err))
-	qt.Assert(t, qt.Equals(typeByID, typ))
+	qt.Assert(t, qt.Equals(typeByID, Type(fnType)))
 
-	fnType := typ.(*Func)
 	fnProto := fnType.Type.(*FuncProto)
+	_, err = spec.TypeID(fnProto)
+	qt.Assert(t, qt.IsNil(err), qt.Commentf("FuncProto should be in base"))
 
 	// 'int' is defined in the base BTF...
 	intType, err := spec.AnyTypeByName("int")
-	if err != nil {
-		t.Fatal(err)
-	}
+	qt.Assert(t, qt.IsNil(err))
+
 	// ... but not in the split BTF
 	_, err = splitSpec.AnyTypeByName("int")
-	if err == nil {
-		t.Fatal("'int' is not supposed to be found in the split BTF")
-	}
+	qt.Assert(t, qt.ErrorIs(err, ErrNotFound))
+
+	qt.Assert(t, qt.Equals(fnProto.Return, intType),
+		qt.Commentf("types found in base of split spec should be reused"))
 
-	qt.Assert(t, qt.Not(qt.Equals(fnProto.Return, intType)),
-		qt.Commentf("types found in base of split spec should be copies"))
+	fnProto.Params = []FuncParam{{"a", &Pointer{(*Void)(nil)}}}
 
-	// Check that copied split-BTF's spec has correct type indexing
+	// The behaviour of copying a split spec is quite subtle. When initially
+	// creating a split spec, types in the split base are shared. This allows
+	// amortising the cost of decoding vmlinux.
+	//
+	// However, we currently define copying a spec to be like forking a process:
+	// in-memory changes to types are preserved. After the copy finished we have
+	// two fully independent states.
+	//
+	// For split BTF this means that we also need to copy the base and ensure
+	// that future references to a modified type work correctly.
 	splitSpecCopy := splitSpec.Copy()
-	copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init")
-	if err != nil {
-		t.Fatal(err)
-	}
-	copyTypeID, err := splitSpecCopy.TypeID(copyType)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if copyTypeID != typeID {
-		t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)",
-			typeID, copyTypeID)
-	}
+
+	var fnCopyType *Func
+	qt.Assert(t, qt.IsNil(splitSpecCopy.TypeByName("bpf_testmod_init", &fnCopyType)))
+	qt.Assert(t, testutils.IsDeepCopy(fnCopyType, fnType))
+
+	// Pull out a second type which refers to "int" in the base, but which hasn't
+	// been inflated yet. This forces inflating int from the base.
+	var str *Struct
+	qt.Assert(t, qt.IsNil(splitSpecCopy.TypeByName("bpf_testmod_struct_arg_1", &str)))
+
+	// Ensure that the int types are indeed the same.
+	qt.Assert(t, qt.Equals(str.Members[0].Type, fnCopyType.Type.(*FuncProto).Return))
+
+	copyTypeID, err := splitSpecCopy.TypeID(fnCopyType)
+	qt.Assert(t, qt.IsNil(err))
+	qt.Assert(t, qt.Equals(copyTypeID, typeID), qt.Commentf("ID of copied type must match"))
 }
 
 func TestFixupDatasecLayout(t *testing.T) {
diff --git a/btf/format_test.go b/btf/format_test.go
index 4bf21c0..78c1a21 100644
--- a/btf/format_test.go
+++ b/btf/format_test.go
@@ -42,7 +42,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "enum", Type: &Enum{Values: []EnumValue{{"BAR", 1}}, Size: 2}, Offset: 0},
 				},
 			},
-			"type t struct { enum uint16; }",
+			"type t struct { _ structs.HostLayout; enum uint16; }",
 		},
 		{&Array{Nelems: 2, Type: &Int{Size: 1}}, "type t [2]uint8"},
 		{
@@ -53,7 +53,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "b", Type: &Int{Size: 8}},
 				},
 			},
-			"type t struct { a uint32; _ [4]byte; }",
+			"type t struct { _ structs.HostLayout; a uint32; _ [4]byte; }",
 		},
 		{
 			&Struct{
@@ -64,7 +64,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "foo", Type: &Int{Size: 8}, Offset: 8 * 8},
 				},
 			},
-			"type t struct { frob uint32; _ [4]byte; foo uint64; }",
+			"type t struct { _ structs.HostLayout; frob uint32; _ [4]byte; foo uint64; }",
 		},
 		{
 			&Struct{
@@ -75,7 +75,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "frob", Type: &Int{Size: 4}, Offset: 8 * 8},
 				},
 			},
-			"type t struct { foo uint64; frob uint32; _ [4]byte; }",
+			"type t struct { _ structs.HostLayout; foo uint64; frob uint32; _ [4]byte; }",
 		},
 		{
 			&Struct{
@@ -86,7 +86,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
 				},
 			},
-			"type t struct { _ [4]byte /* unsupported bitfield */; frob uint32; }",
+			"type t struct { _ structs.HostLayout; _ [4]byte /* unsupported bitfield */; frob uint32; }",
 		},
 		{
 			&Struct{
@@ -105,7 +105,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
 				},
 			},
-			"type t struct { foo struct { bar uint32; }; frob uint32; }",
+			"type t struct { _ structs.HostLayout; foo struct { _ structs.HostLayout; bar uint32; }; frob uint32; }",
 		},
 		{
 			&Struct{
@@ -124,7 +124,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					},
 				},
 			},
-			"type t struct { foo uint32; _ [4]byte; }",
+			"type t struct { _ structs.HostLayout; foo uint32; _ [4]byte; }",
 		},
 		{
 			&Datasec{
@@ -135,7 +135,7 @@ func TestGoTypeDeclaration(t *testing.T) {
 					{&Var{Name: "e", Type: &Int{Size: 8}, Linkage: ExternVar}, 8, 8},
 				},
 			},
-			"type t struct { _ [4]byte; g uint32; _ [8]byte; }",
+			"type t struct { _ structs.HostLayout; _ [4]byte; g uint32; _ [8]byte; }",
 		},
 		{&Var{Type: &Int{Size: 4}}, "type t uint32"},
 	}
@@ -175,9 +175,9 @@ func TestGoTypeDeclarationNamed(t *testing.T) {
 		output string
 	}{
 		{e1, []Type{e1}, "type t uint32"},
-		{s1, []Type{e1, s1}, "type t struct { frob E1; }"},
-		{s2, []Type{e1}, "type t struct { frood struct { frob E1; }; }"},
-		{s2, []Type{e1, s1}, "type t struct { frood S1; }"},
+		{s1, []Type{e1, s1}, "type t struct { _ structs.HostLayout; frob E1; }"},
+		{s2, []Type{e1}, "type t struct { _ structs.HostLayout; frood struct { _ structs.HostLayout; frob E1; }; }"},
+		{s2, []Type{e1, s1}, "type t struct { _ structs.HostLayout; frood S1; }"},
 		{td, nil, "type t uint32"},
 		{td, []Type{td}, "type t uint32"},
 		{arr, []Type{td}, "type t [1]TD"},
diff --git a/cmd/bpf2go/test/test_bpfeb.go b/cmd/bpf2go/test/test_bpfeb.go
index a1d0e54..85885e8 100644
--- a/cmd/bpf2go/test/test_bpfeb.go
+++ b/cmd/bpf2go/test/test_bpfeb.go
@@ -8,24 +8,30 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type testBar struct {
+	_ structs.HostLayout
 	A uint64
 	B uint32
 	_ [4]byte
 }
 
 type testBarfoo struct {
+	_   structs.HostLayout
 	Bar int64
 	Baz bool
 	_   [3]byte
 	Boo testE
 }
 
-type testBaz struct{ A uint64 }
+type testBaz struct {
+	_ structs.HostLayout
+	A uint64
+}
 
 type testE uint32
 
@@ -35,6 +41,7 @@ const (
 )
 
 type testUbar struct {
+	_ structs.HostLayout
 	A uint32
 	_ [4]byte
 }
diff --git a/cmd/bpf2go/test/test_bpfel.go b/cmd/bpf2go/test/test_bpfel.go
index 169b88d..5034af1 100644
--- a/cmd/bpf2go/test/test_bpfel.go
+++ b/cmd/bpf2go/test/test_bpfel.go
@@ -8,24 +8,30 @@ import (
 	_ "embed"
 	"fmt"
 	"io"
+	"structs"
 
 	"github.com/cilium/ebpf"
 )
 
 type testBar struct {
+	_ structs.HostLayout
 	A uint64
 	B uint32
 	_ [4]byte
 }
 
 type testBarfoo struct {
+	_   structs.HostLayout
 	Bar int64
 	Baz bool
 	_   [3]byte
 	Boo testE
 }
 
-type testBaz struct{ A uint64 }
+type testBaz struct {
+	_ structs.HostLayout
+	A uint64
+}
 
 type testE uint32
 
@@ -35,6 +41,7 @@ const (
 )
 
 type testUbar struct {
+	_ structs.HostLayout
 	A uint32
 	_ [4]byte
 }