Repository: cilium/ebpf
Pull request #1002 (2023-04-06 13:03:24)

btf: optimize Spec.Copy
btf: avoid allocating typeDeque in copier.copy
    
        goos: linux
        goarch: amd64
        pkg: github.com/cilium/ebpf/btf
        cpu: 12th Gen Intel(R) Core(TM) i7-1260P
                    │   old.txt   │              new.txt               │
                    │   sec/op    │   sec/op     vs base               │
        SpecCopy-16   37.76m ± 2%   33.27m ± 5%  -11.91% (p=0.002 n=6)
    
                    │   old.txt    │               new.txt               │
                    │     B/op     │     B/op      vs base               │
        SpecCopy-16   38.10Mi ± 0%   32.28Mi ± 0%  -15.27% (p=0.002 n=6)
    
                    │   old.txt   │              new.txt               │
                    │  allocs/op  │  allocs/op   vs base               │
        SpecCopy-16   294.7k ± 0%   203.2k ± 0%  -31.05% (p=0.002 n=6)
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

btf: pre-size copier
    
    Make sure copier has the appropriate size from the start.
    
        goos: linux
        goarch: amd64
        pkg: github.com/cilium/ebpf/btf
        cpu: 12th Gen Intel(R) Core(TM) i7-1260P
                    │   old.txt   │              new.txt               │
                    │   sec/op    │   sec/op     vs base               │
        SpecCopy-16   43.96m ± 8%   37.76m ± 2%  -14.10% (p=0.002 n=6)
    
                    │   old.txt    │               new.txt               │
                    │     B/op     │     B/op      vs base               │
        SpecCopy-16   48.44Mi ± 0%   38.10Mi ± 0%  -21.36% (p=0.002 n=6)
    
                    │   old.txt   │              new.txt              │
                    │  allocs/op  │  allocs/op   vs base              │
        SpecCopy-16   299.3k ± 0%   294.7k ± 0%  -1.56% (p=0.002 n=6)
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

btf: add Spec.Copy benchmark
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>



Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f71b8a1..bf57da9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,6 +12,17 @@ a better understanding for the high-level goals.
 2. (*optional*) Create a draft PR if you want to discuss the implementation or have hit a problem. It's fine if this doesn't compile or contains debug statements.
 3. Create a PR that is ready to merge. This must pass CI and have tests.
 
+### API stability
+
+The library doesn't guarantee the stability of its API at the moment.
+
+1. If possible avoid breakage by introducing new API and deprecating the old one
+   at the same time. If an API was deprecated in v0.x it can be removed in v0.x+1.
+2. Breaking API in a way that causes compilation failures is acceptable but must
+   have good reasons.
+3. Changing the semantics of the API without causing compilation failures is
+   heavily discouraged.
+
 ## Running the tests
 
 Many of the tests require privileges to set resource limits and load eBPF code.
diff --git a/asm/func.go b/asm/func.go
index 4c0ac9a..18f6a75 100644
--- a/asm/func.go
+++ b/asm/func.go
@@ -14,7 +14,7 @@ func (_ BuiltinFunc) Max() BuiltinFunc {
 // You can regenerate this list using the following gawk script:
 //
 //	/FN\(.+\),/ {
-//	  match($1, /\((.+)\)/, r)
+//	  match($1, /\(([a-z_0-9]+),/, r)
 //	  split(r[1], p, "_")
 //	  printf "Fn"
 //	  for (i in p) {
@@ -229,6 +229,14 @@ const (
 	FnDynptrRead
 	FnDynptrWrite
 	FnDynptrData
+	FnTcpRawGenSyncookieIpv4
+	FnTcpRawGenSyncookieIpv6
+	FnTcpRawCheckSyncookieIpv4
+	FnTcpRawCheckSyncookieIpv6
+	FnKtimeGetTaiNs
+	FnUserRingbufDrain
+	FnCgrpStorageGet
+	FnCgrpStorageDelete
 
 	maxBuiltinFunc
 )
diff --git a/asm/func_string.go b/asm/func_string.go
index b7431b7..47150bc 100644
--- a/asm/func_string.go
+++ b/asm/func_string.go
@@ -212,12 +212,20 @@ func _() {
 	_ = x[FnDynptrRead-201]
 	_ = x[FnDynptrWrite-202]
 	_ = x[FnDynptrData-203]
-	_ = x[maxBuiltinFunc-204]
+	_ = x[FnTcpRawGenSyncookieIpv4-204]
+	_ = x[FnTcpRawGenSyncookieIpv6-205]
+	_ = x[FnTcpRawCheckSyncookieIpv4-206]
+	_ = x[FnTcpRawCheckSyncookieIpv6-207]
+	_ = x[FnKtimeGetTaiNs-208]
+	_ = x[FnUserRingbufDrain-209]
+	_ = x[FnCgrpStorageGet-210]
+	_ = x[FnCgrpStorageDelete-211]
+	_ = x[maxBuiltinFunc-212]
 }
 
-const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDatamaxBuiltinFunc"
+const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDeletemaxBuiltinFunc"
 
-var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3011}
+var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165, 3179}
 
 func (i BuiltinFunc) String() string {
 	if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
diff --git a/btf/btf.go b/btf/btf.go
index 8682357..1fa7fcc 100644
--- a/btf/btf.go
+++ b/btf/btf.go
@@ -240,6 +240,10 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error
 			return nil, fmt.Errorf("can't use split BTF as base")
 		}
 
+		if base.strings == nil {
+			return nil, fmt.Errorf("parse split BTF: base must be loaded from an ELF")
+		}
+
 		baseStrings = base.strings
 
 		firstTypeID, err = base.nextTypeID()
@@ -770,10 +774,6 @@ func (s *Spec) TypeByName(name string, typ interface{}) error {
 // Types from base are used to resolve references in the split BTF.
 // The returned Spec only contains types from the split BTF, not from the base.
 func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) {
-	if base.strings == nil {
-		return nil, fmt.Errorf("parse split BTF: base must be loaded from an ELF")
-	}
-
 	return loadRawSpec(r, internal.NativeEndian, base)
 }
 
diff --git a/btf/handle.go b/btf/handle.go
index 87ec2d7..ebcc01c 100644
--- a/btf/handle.go
+++ b/btf/handle.go
@@ -110,7 +110,10 @@ func NewHandleFromID(id ID) (*Handle, error) {
 }
 
 // Spec parses the kernel BTF into Go types.
-func (h *Handle) Spec() (*Spec, error) {
+//
+// base must contain type information for vmlinux if the handle is for
+// a kernel module. It may be nil otherwise.
+func (h *Handle) Spec(base *Spec) (*Spec, error) {
 	var btfInfo sys.BtfInfo
 	btfBuffer := make([]byte, h.size)
 	btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
@@ -119,18 +122,8 @@ func (h *Handle) Spec() (*Spec, error) {
 		return nil, err
 	}
 
-	if !h.needsKernelBase {
-		return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, nil)
-	}
-
-	base, fallback, err := kernelSpec()
-	if err != nil {
-		return nil, fmt.Errorf("load BTF base: %w", err)
-	}
-	base = base.Copy()
-
-	if fallback {
-		return nil, fmt.Errorf("can't load split BTF without access to /sys")
+	if h.needsKernelBase && base == nil {
+		return nil, fmt.Errorf("missing base types")
 	}
 
 	return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base)
diff --git a/btf/types.go b/btf/types.go
index 6d7a110..3fb5499 100644
--- a/btf/types.go
+++ b/btf/types.go
@@ -684,7 +684,7 @@ type Transformer func(Type) Type
 // typ may form a cycle. If transform is not nil, it is called with the
 // to be copied type, and the returned value is copied instead.
 func Copy(typ Type, transform Transformer) Type {
-	copies := make(copier)
+	copies := copier{copies: make(map[Type]Type)}
 	copies.copy(&typ, transform)
 	return typ
 }
@@ -696,7 +696,7 @@ func copyTypes(types []Type, transform Transformer) []Type {
 	result := make([]Type, len(types))
 	copy(result, types)
 
-	copies := make(copier)
+	copies := copier{copies: make(map[Type]Type, len(types))}
 	for i := range result {
 		copies.copy(&result[i], transform)
 	}
@@ -704,13 +704,15 @@ func copyTypes(types []Type, transform Transformer) []Type {
 	return result
 }
 
-type copier map[Type]Type
+type copier struct {
+	copies map[Type]Type
+	work   typeDeque
+}
 
-func (c copier) copy(typ *Type, transform Transformer) {
-	var work typeDeque
-	for t := typ; t != nil; t = work.Pop() {
+func (c *copier) copy(typ *Type, transform Transformer) {
+	for t := typ; t != nil; t = c.work.Pop() {
 		// *t is the identity of the type.
-		if cpy := c[*t]; cpy != nil {
+		if cpy := c.copies[*t]; cpy != nil {
 			*t = cpy
 			continue
 		}
@@ -722,11 +724,11 @@ func (c copier) copy(typ *Type, transform Transformer) {
 			cpy = (*t).copy()
 		}
 
-		c[*t] = cpy
+		c.copies[*t] = cpy
 		*t = cpy
 
 		// Mark any nested types for copying.
-		walkType(cpy, work.Push)
+		walkType(cpy, c.work.Push)
 	}
 }
 
diff --git a/cmd/bpf2go/output.go b/cmd/bpf2go/output.go
index 241fad8..51f1970 100644
--- a/cmd/bpf2go/output.go
+++ b/cmd/bpf2go/output.go
@@ -112,10 +112,7 @@ func output(args outputArgs) error {
 		return err
 	}
 
-	module, err := currentModule()
-	if err != nil {
-		return err
-	}
+	module := currentModule()
 
 	gf := &btf.GoFormatter{
 		Names:      typeNames,
diff --git a/cmd/bpf2go/tools.go b/cmd/bpf2go/tools.go
index 1a3936f..d2e020b 100644
--- a/cmd/bpf2go/tools.go
+++ b/cmd/bpf2go/tools.go
@@ -83,11 +83,12 @@ func toUpperFirst(str string) string {
 	return string(unicode.ToUpper(first)) + str[n:]
 }
 
-func currentModule() (string, error) {
+func currentModule() string {
 	bi, ok := debug.ReadBuildInfo()
 	if !ok {
-		return "", fmt.Errorf("determine current module: missing build info")
+		// Fall back to constant since bazel doesn't support BuildInfo.
+		return "github.com/cilium/ebpf"
 	}
 
-	return bi.Main.Path, nil
+	return bi.Main.Path
 }
diff --git a/go.mod b/go.mod
index a0e9b84..1743d77 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/cilium/ebpf
 go 1.19
 
 require (
-	github.com/frankban/quicktest v1.14.4
+	github.com/frankban/quicktest v1.14.5
 	github.com/google/go-cmp v0.5.9
 	golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
 	golang.org/x/sys v0.6.0
diff --git a/go.sum b/go.sum
index 0cdba88..f514657 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,6 @@
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
-github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
+github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
diff --git a/info.go b/info.go
index e0168cd..a02e8a4 100644
--- a/info.go
+++ b/info.go
@@ -94,8 +94,10 @@ type ProgramInfo struct {
 	// Name as supplied by user space at load time. Available from 4.15.
 	Name string
 
-	btf   btf.ID
-	stats *programStats
+	createdByUID     uint32
+	haveCreatedByUID bool
+	btf              btf.ID
+	stats            *programStats
 
 	maps  []MapID
 	insns []byte
@@ -138,6 +140,12 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		pi.maps = nil
 	}
 
+	// createdByUID and NrMapIds were introduced in the same kernel version.
+	if pi.maps != nil {
+		pi.createdByUID = info.CreatedByUid
+		pi.haveCreatedByUID = true
+	}
+
 	if info.XlatedProgLen > 0 {
 		pi.insns = make([]byte, info.XlatedProgLen)
 		info2.XlatedProgLen = info.XlatedProgLen
@@ -181,6 +189,15 @@ func (pi *ProgramInfo) ID() (ProgramID, bool) {
 	return pi.id, pi.id > 0
 }
 
+// CreatedByUID returns the Uid that created the program.
+//
+// Available from 4.15.
+//
+// The bool return value indicates whether this optional field is available.
+func (pi *ProgramInfo) CreatedByUID() (uint32, bool) {
+	return pi.createdByUID, pi.haveCreatedByUID
+}
+
 // BTFID returns the BTF ID associated with the program.
 //
 // The ID is only valid as long as the associated program is kept alive.
diff --git a/link/tracing.go b/link/tracing.go
index 76340fd..1e1a783 100644
--- a/link/tracing.go
+++ b/link/tracing.go
@@ -49,7 +49,7 @@ func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (
 		}
 		defer btfHandle.Close()
 
-		spec, err := btfHandle.Spec()
+		spec, err := btfHandle.Spec(nil)
 		if err != nil {
 			return nil, err
 		}
diff --git a/prog.go b/prog.go
index cb79522..781fb1c 100644
--- a/prog.go
+++ b/prog.go
@@ -895,7 +895,7 @@ func findTargetInKernel(name string, progType ProgramType, attachType AttachType
 
 	err = spec.TypeByName(typeName, &target)
 	if errors.Is(err, btf.ErrNotFound) {
-		module, id, err := findTargetInModule(typeName, target)
+		module, id, err := findTargetInModule(spec, typeName, target)
 		if errors.Is(err, btf.ErrNotFound) {
 			return nil, 0, &internal.UnsupportedFeatureError{Name: featureName}
 		}
@@ -923,7 +923,7 @@ func findTargetInKernel(name string, progType ProgramType, attachType AttachType
 // vmlinux must contain the kernel's types and is used to parse kmod BTF.
 //
 // Returns btf.ErrNotFound if the target can't be found in any module.
-func findTargetInModule(typeName string, target btf.Type) (*btf.Handle, btf.TypeID, error) {
+func findTargetInModule(base *btf.Spec, typeName string, target btf.Type) (*btf.Handle, btf.TypeID, error) {
 	it := new(btf.HandleIterator)
 	defer it.Handle.Close()
 
@@ -937,7 +937,7 @@ func findTargetInModule(typeName string, target btf.Type) (*btf.Handle, btf.Type
 			continue
 		}
 
-		spec, err := it.Handle.Spec()
+		spec, err := it.Handle.Spec(base)
 		if err != nil {
 			return nil, 0, fmt.Errorf("parse types for module %s: %w", info.Name, err)
 		}
@@ -987,7 +987,7 @@ func findTargetInProgram(prog *Program, name string, progType ProgramType, attac
 	}
 	defer btfHandle.Close()
 
-	spec, err := btfHandle.Spec()
+	spec, err := btfHandle.Spec(nil)
 	if err != nil {
 		return 0, err
 	}


Test patch:
diff --git a/btf/btf_test.go b/btf/btf_test.go
index 76bbe2e..b5de0a6 100644
--- a/btf/btf_test.go
+++ b/btf/btf_test.go
@@ -581,3 +581,12 @@ func TestFixupDatasecLayout(t *testing.T) {
 	qt.Assert(t, ds.Vars[4].Offset, qt.Equals, uint32(16))
 	qt.Assert(t, ds.Vars[5].Offset, qt.Equals, uint32(32))
 }
+
+func BenchmarkSpecCopy(b *testing.B) {
+	spec := vmlinuxTestdataSpec(b)
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		spec.Copy()
+	}
+}
diff --git a/btf/handle_test.go b/btf/handle_test.go
index a6c22e0..4634d5b 100644
--- a/btf/handle_test.go
+++ b/btf/handle_test.go
@@ -70,7 +70,12 @@ func TestParseModuleSplitSpec(t *testing.T) {
 	}
 	defer vmlinux.Close()
 
-	_, err = module.Spec()
+	base, err := vmlinux.Spec(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = module.Spec(base)
 	if err != nil {
 		t.Fatal("Parse module BTF:", err)
 	}
diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go
index a9e2ab6..f8fa637 100644
--- a/cmd/bpf2go/main_test.go
+++ b/cmd/bpf2go/main_test.go
@@ -49,10 +49,7 @@ func TestRun(t *testing.T) {
 		}
 	}
 
-	module, err := currentModule()
-	if err != nil {
-		t.Fatal(err)
-	}
+	module := currentModule()
 
 	execInModule("go", "mod", "init", "bpf2go-test")
 
diff --git a/info_test.go b/info_test.go
index c50e18d..c41ae6c 100644
--- a/info_test.go
+++ b/info_test.go
@@ -3,6 +3,7 @@ package ebpf
 import (
 	"errors"
 	"fmt"
+	"os"
 	"strings"
 	"testing"
 
@@ -117,6 +118,19 @@ func TestProgramInfo(t *testing.T) {
 			} else if name == "proc" && ok {
 				t.Error("Expected ID to not be available")
 			}
+
+			if name == "proc" {
+				_, ok := info.CreatedByUID()
+				qt.Assert(t, ok, qt.IsFalse)
+			} else {
+				uid, ok := info.CreatedByUID()
+				if testutils.IsKernelLessThan(t, "4.15") {
+					qt.Assert(t, ok, qt.IsFalse)
+				} else {
+					qt.Assert(t, ok, qt.IsTrue)
+					qt.Assert(t, uid, qt.Equals, uint32(os.Getuid()))
+				}
+			}
 		})
 	}
 }