Repository: cilium/ebpf
Pull request #1689 (2025-02-17 17:33:53)

all: encode platform in type constants
Linux and Windows share the concept of program types, map types and so on. For example, XDP is supported on both platforms. Unfortunately the constant values used by both platforms are different. On Linux, XDP has value 0x6 while on Windows it is 0x1.

This problem extends to a CollectionSpec parsed from an ELF. Because of the different values a Windows PerCPUHash aliases with a Linux ProgramArray. This causes all sorts of mayhem that is hard to avoid without making too many changes to the code base.

Introduce the notion of a supported platform. External users only see this as a string, like "linux". Internally each platform has a corresponding tag which is used to encode the platform into a constant. This means that there are distinct constants for each platform. Instead of overloading the XDP constant we will introduce WindowsXDP. This will allow the ELF reader to remain platform independent and makes generating stringers a lot easier.

The value of the platform tag is chosen so that on Linux the constant values do not change and are identical with the value in upstream headers. On Windows the constants have a fixed (but implementation defined) offset.

The downside of this approach is that Windows constants will all require a prefix of some sort to distinguish them from Linux ones. This is probably most onerous for map types, because those tend to be created manually more frequently than programs, for example. We can add some behind the scenes translation of Linux map types to Windows ones if need be.

Another downside is that constant values are now limited to 28 bits since we steal the top 4 bits to store the platform. The constants we're applying this to are all sequentially numbered from 0, so this is hopefully enough.

lmb: ```
github.com/cilium/ebpf/asm
  Incompatible changes:
  - (*Instruction).Unmarshal: changed from func(io.Reader, encoding/binary.ByteOrder) (uint64, error) to func(io.Reader, encoding/binary.ByteOrder, string) error
  - (*Instructions).Unmarshal: removed
  - BuiltinFunc: changed from int32 to uint32
```

Patch:
diff --git a/asm/func.go b/asm/func.go
index 7a59acf..5ee4e95 100644
--- a/asm/func.go
+++ b/asm/func.go
@@ -1,239 +1,18 @@
 package asm
 
+import "github.com/cilium/ebpf/internal/platform"
+
 //go:generate go run golang.org/x/tools/cmd/stringer@latest -output func_string.go -type=BuiltinFunc
 
 // BuiltinFunc is a built-in eBPF function.
-type BuiltinFunc int32
+type BuiltinFunc uint32
 
-// eBPF built-in functions
-//
-// You can regenerate this list using the following gawk script:
+// BuiltinFuncForPlatform returns a platform specific function constant.
 //
-//	/FN\(.+\),/ {
-//	  match($1, /\(([a-z_0-9]+),/, r)
-//	  split(r[1], p, "_")
-//	  printf "Fn"
-//	  for (i in p) {
-//	    printf "%s%s", toupper(substr(p[i], 1, 1)), substr(p[i], 2)
-//	  }
-//	  print ""
-//	}
-//
-// The script expects include/uapi/linux/bpf.h as it's input.
-const (
-	FnUnspec BuiltinFunc = iota
-	FnMapLookupElem
-	FnMapUpdateElem
-	FnMapDeleteElem
-	FnProbeRead
-	FnKtimeGetNs
-	FnTracePrintk
-	FnGetPrandomU32
-	FnGetSmpProcessorId
-	FnSkbStoreBytes
-	FnL3CsumReplace
-	FnL4CsumReplace
-	FnTailCall
-	FnCloneRedirect
-	FnGetCurrentPidTgid
-	FnGetCurrentUidGid
-	FnGetCurrentComm
-	FnGetCgroupClassid
-	FnSkbVlanPush
-	FnSkbVlanPop
-	FnSkbGetTunnelKey
-	FnSkbSetTunnelKey
-	FnPerfEventRead
-	FnRedirect
-	FnGetRouteRealm
-	FnPerfEventOutput
-	FnSkbLoadBytes
-	FnGetStackid
-	FnCsumDiff
-	FnSkbGetTunnelOpt
-	FnSkbSetTunnelOpt
-	FnSkbChangeProto
-	FnSkbChangeType
-	FnSkbUnderCgroup
-	FnGetHashRecalc
-	FnGetCurrentTask
-	FnProbeWriteUser
-	FnCurrentTaskUnderCgroup
-	FnSkbChangeTail
-	FnSkbPullData
-	FnCsumUpdate
-	FnSetHashInvalid
-	FnGetNumaNodeId
-	FnSkbChangeHead
-	FnXdpAdjustHead
-	FnProbeReadStr
-	FnGetSocketCookie
-	FnGetSocketUid
-	FnSetHash
-	FnSetsockopt
-	FnSkbAdjustRoom
-	FnRedirectMap
-	FnSkRedirectMap
-	FnSockMapUpdate
-	FnXdpAdjustMeta
-	FnPerfEventReadValue
-	FnPerfProgReadValue
-	FnGetsockopt
-	FnOverrideReturn
-	FnSockOpsCbFlagsSet
-	FnMsgRedirectMap
-	FnMsgApplyBytes
-	FnMsgCorkBytes
-	FnMsgPullData
-	FnBind
-	FnXdpAdjustTail
-	FnSkbGetXfrmState
-	FnGetStack
-	FnSkbLoadBytesRelative
-	FnFibLookup
-	FnSockHashUpdate
-	FnMsgRedirectHash
-	FnSkRedirectHash
-	FnLwtPushEncap
-	FnLwtSeg6StoreBytes
-	FnLwtSeg6AdjustSrh
-	FnLwtSeg6Action
-	FnRcRepeat
-	FnRcKeydown
-	FnSkbCgroupId
-	FnGetCurrentCgroupId
-	FnGetLocalStorage
-	FnSkSelectReuseport
-	FnSkbAncestorCgroupId
-	FnSkLookupTcp
-	FnSkLookupUdp
-	FnSkRelease
-	FnMapPushElem
-	FnMapPopElem
-	FnMapPeekElem
-	FnMsgPushData
-	FnMsgPopData
-	FnRcPointerRel
-	FnSpinLock
-	FnSpinUnlock
-	FnSkFullsock
-	FnTcpSock
-	FnSkbEcnSetCe
-	FnGetListenerSock
-	FnSkcLookupTcp
-	FnTcpCheckSyncookie
-	FnSysctlGetName
-	FnSysctlGetCurrentValue
-	FnSysctlGetNewValue
-	FnSysctlSetNewValue
-	FnStrtol
-	FnStrtoul
-	FnSkStorageGet
-	FnSkStorageDelete
-	FnSendSignal
-	FnTcpGenSyncookie
-	FnSkbOutput
-	FnProbeReadUser
-	FnProbeReadKernel
-	FnProbeReadUserStr
-	FnProbeReadKernelStr
-	FnTcpSendAck
-	FnSendSignalThread
-	FnJiffies64
-	FnReadBranchRecords
-	FnGetNsCurrentPidTgid
-	FnXdpOutput
-	FnGetNetnsCookie
-	FnGetCurrentAncestorCgroupId
-	FnSkAssign
-	FnKtimeGetBootNs
-	FnSeqPrintf
-	FnSeqWrite
-	FnSkCgroupId
-	FnSkAncestorCgroupId
-	FnRingbufOutput
-	FnRingbufReserve
-	FnRingbufSubmit
-	FnRingbufDiscard
-	FnRingbufQuery
-	FnCsumLevel
-	FnSkcToTcp6Sock
-	FnSkcToTcpSock
-	FnSkcToTcpTimewaitSock
-	FnSkcToTcpRequestSock
-	FnSkcToUdp6Sock
-	FnGetTaskStack
-	FnLoadHdrOpt
-	FnStoreHdrOpt
-	FnReserveHdrOpt
-	FnInodeStorageGet
-	FnInodeStorageDelete
-	FnDPath
-	FnCopyFromUser
-	FnSnprintfBtf
-	FnSeqPrintfBtf
-	FnSkbCgroupClassid
-	FnRedirectNeigh
-	FnPerCpuPtr
-	FnThisCpuPtr
-	FnRedirectPeer
-	FnTaskStorageGet
-	FnTaskStorageDelete
-	FnGetCurrentTaskBtf
-	FnBprmOptsSet
-	FnKtimeGetCoarseNs
-	FnImaInodeHash
-	FnSockFromFile
-	FnCheckMtu
-	FnForEachMapElem
-	FnSnprintf
-	FnSysBpf
-	FnBtfFindByNameKind
-	FnSysClose
-	FnTimerInit
-	FnTimerSetCallback
-	FnTimerStart
-	FnTimerCancel
-	FnGetFuncIp
-	FnGetAttachCookie
-	FnTaskPtRegs
-	FnGetBranchSnapshot
-	FnTraceVprintk
-	FnSkcToUnixSock
-	FnKallsymsLookupName
-	FnFindVma
-	FnLoop
-	FnStrncmp
-	FnGetFuncArg
-	FnGetFuncRet
-	FnGetFuncArgCnt
-	FnGetRetval
-	FnSetRetval
-	FnXdpGetBuffLen
-	FnXdpLoadBytes
-	FnXdpStoreBytes
-	FnCopyFromUserTask
-	FnSkbSetTstamp
-	FnImaFileHash
-	FnKptrXchg
-	FnMapLookupPercpuElem
-	FnSkcToMptcpSock
-	FnDynptrFromMem
-	FnRingbufReserveDynptr
-	FnRingbufSubmitDynptr
-	FnRingbufDiscardDynptr
-	FnDynptrRead
-	FnDynptrWrite
-	FnDynptrData
-	FnTcpRawGenSyncookieIpv4
-	FnTcpRawGenSyncookieIpv6
-	FnTcpRawCheckSyncookieIpv4
-	FnTcpRawCheckSyncookieIpv6
-	FnKtimeGetTaiNs
-	FnUserRingbufDrain
-	FnCgrpStorageGet
-	FnCgrpStorageDelete
-)
+// Use this if the library doesn't provide a constant yet.
+func BuiltinFuncForPlatform(plat string, value uint32) (BuiltinFunc, error) {
+	return platform.EncodeConstant[BuiltinFunc](plat, value)
+}
 
 // Call emits a function call.
 func (fn BuiltinFunc) Call() Instruction {
diff --git a/asm/func_lin.go b/asm/func_lin.go
new file mode 100644
index 0000000..55c5e76
--- /dev/null
+++ b/asm/func_lin.go
@@ -0,0 +1,223 @@
+﻿// Code generated by internal/cmd/genfunctions.awk; DO NOT EDIT.
+
+package asm
+
+// Code in this file is derived from Linux, available under the GPL-2.0 WITH Linux-syscall-note.
+
+import "github.com/cilium/ebpf/internal/platform"
+
+// Built-in functions (Linux).
+const (
+	FnUnspec                     = BuiltinFunc(platform.LinuxTag | 0)
+	FnMapLookupElem              = BuiltinFunc(platform.LinuxTag | 1)
+	FnMapUpdateElem              = BuiltinFunc(platform.LinuxTag | 2)
+	FnMapDeleteElem              = BuiltinFunc(platform.LinuxTag | 3)
+	FnProbeRead                  = BuiltinFunc(platform.LinuxTag | 4)
+	FnKtimeGetNs                 = BuiltinFunc(platform.LinuxTag | 5)
+	FnTracePrintk                = BuiltinFunc(platform.LinuxTag | 6)
+	FnGetPrandomU32              = BuiltinFunc(platform.LinuxTag | 7)
+	FnGetSmpProcessorId          = BuiltinFunc(platform.LinuxTag | 8)
+	FnSkbStoreBytes              = BuiltinFunc(platform.LinuxTag | 9)
+	FnL3CsumReplace              = BuiltinFunc(platform.LinuxTag | 10)
+	FnL4CsumReplace              = BuiltinFunc(platform.LinuxTag | 11)
+	FnTailCall                   = BuiltinFunc(platform.LinuxTag | 12)
+	FnCloneRedirect              = BuiltinFunc(platform.LinuxTag | 13)
+	FnGetCurrentPidTgid          = BuiltinFunc(platform.LinuxTag | 14)
+	FnGetCurrentUidGid           = BuiltinFunc(platform.LinuxTag | 15)
+	FnGetCurrentComm             = BuiltinFunc(platform.LinuxTag | 16)
+	FnGetCgroupClassid           = BuiltinFunc(platform.LinuxTag | 17)
+	FnSkbVlanPush                = BuiltinFunc(platform.LinuxTag | 18)
+	FnSkbVlanPop                 = BuiltinFunc(platform.LinuxTag | 19)
+	FnSkbGetTunnelKey            = BuiltinFunc(platform.LinuxTag | 20)
+	FnSkbSetTunnelKey            = BuiltinFunc(platform.LinuxTag | 21)
+	FnPerfEventRead              = BuiltinFunc(platform.LinuxTag | 22)
+	FnRedirect                   = BuiltinFunc(platform.LinuxTag | 23)
+	FnGetRouteRealm              = BuiltinFunc(platform.LinuxTag | 24)
+	FnPerfEventOutput            = BuiltinFunc(platform.LinuxTag | 25)
+	FnSkbLoadBytes               = BuiltinFunc(platform.LinuxTag | 26)
+	FnGetStackid                 = BuiltinFunc(platform.LinuxTag | 27)
+	FnCsumDiff                   = BuiltinFunc(platform.LinuxTag | 28)
+	FnSkbGetTunnelOpt            = BuiltinFunc(platform.LinuxTag | 29)
+	FnSkbSetTunnelOpt            = BuiltinFunc(platform.LinuxTag | 30)
+	FnSkbChangeProto             = BuiltinFunc(platform.LinuxTag | 31)
+	FnSkbChangeType              = BuiltinFunc(platform.LinuxTag | 32)
+	FnSkbUnderCgroup             = BuiltinFunc(platform.LinuxTag | 33)
+	FnGetHashRecalc              = BuiltinFunc(platform.LinuxTag | 34)
+	FnGetCurrentTask             = BuiltinFunc(platform.LinuxTag | 35)
+	FnProbeWriteUser             = BuiltinFunc(platform.LinuxTag | 36)
+	FnCurrentTaskUnderCgroup     = BuiltinFunc(platform.LinuxTag | 37)
+	FnSkbChangeTail              = BuiltinFunc(platform.LinuxTag | 38)
+	FnSkbPullData                = BuiltinFunc(platform.LinuxTag | 39)
+	FnCsumUpdate                 = BuiltinFunc(platform.LinuxTag | 40)
+	FnSetHashInvalid             = BuiltinFunc(platform.LinuxTag | 41)
+	FnGetNumaNodeId              = BuiltinFunc(platform.LinuxTag | 42)
+	FnSkbChangeHead              = BuiltinFunc(platform.LinuxTag | 43)
+	FnXdpAdjustHead              = BuiltinFunc(platform.LinuxTag | 44)
+	FnProbeReadStr               = BuiltinFunc(platform.LinuxTag | 45)
+	FnGetSocketCookie            = BuiltinFunc(platform.LinuxTag | 46)
+	FnGetSocketUid               = BuiltinFunc(platform.LinuxTag | 47)
+	FnSetHash                    = BuiltinFunc(platform.LinuxTag | 48)
+	FnSetsockopt                 = BuiltinFunc(platform.LinuxTag | 49)
+	FnSkbAdjustRoom              = BuiltinFunc(platform.LinuxTag | 50)
+	FnRedirectMap                = BuiltinFunc(platform.LinuxTag | 51)
+	FnSkRedirectMap              = BuiltinFunc(platform.LinuxTag | 52)
+	FnSockMapUpdate              = BuiltinFunc(platform.LinuxTag | 53)
+	FnXdpAdjustMeta              = BuiltinFunc(platform.LinuxTag | 54)
+	FnPerfEventReadValue         = BuiltinFunc(platform.LinuxTag | 55)
+	FnPerfProgReadValue          = BuiltinFunc(platform.LinuxTag | 56)
+	FnGetsockopt                 = BuiltinFunc(platform.LinuxTag | 57)
+	FnOverrideReturn             = BuiltinFunc(platform.LinuxTag | 58)
+	FnSockOpsCbFlagsSet          = BuiltinFunc(platform.LinuxTag | 59)
+	FnMsgRedirectMap             = BuiltinFunc(platform.LinuxTag | 60)
+	FnMsgApplyBytes              = BuiltinFunc(platform.LinuxTag | 61)
+	FnMsgCorkBytes               = BuiltinFunc(platform.LinuxTag | 62)
+	FnMsgPullData                = BuiltinFunc(platform.LinuxTag | 63)
+	FnBind                       = BuiltinFunc(platform.LinuxTag | 64)
+	FnXdpAdjustTail              = BuiltinFunc(platform.LinuxTag | 65)
+	FnSkbGetXfrmState            = BuiltinFunc(platform.LinuxTag | 66)
+	FnGetStack                   = BuiltinFunc(platform.LinuxTag | 67)
+	FnSkbLoadBytesRelative       = BuiltinFunc(platform.LinuxTag | 68)
+	FnFibLookup                  = BuiltinFunc(platform.LinuxTag | 69)
+	FnSockHashUpdate             = BuiltinFunc(platform.LinuxTag | 70)
+	FnMsgRedirectHash            = BuiltinFunc(platform.LinuxTag | 71)
+	FnSkRedirectHash             = BuiltinFunc(platform.LinuxTag | 72)
+	FnLwtPushEncap               = BuiltinFunc(platform.LinuxTag | 73)
+	FnLwtSeg6StoreBytes          = BuiltinFunc(platform.LinuxTag | 74)
+	FnLwtSeg6AdjustSrh           = BuiltinFunc(platform.LinuxTag | 75)
+	FnLwtSeg6Action              = BuiltinFunc(platform.LinuxTag | 76)
+	FnRcRepeat                   = BuiltinFunc(platform.LinuxTag | 77)
+	FnRcKeydown                  = BuiltinFunc(platform.LinuxTag | 78)
+	FnSkbCgroupId                = BuiltinFunc(platform.LinuxTag | 79)
+	FnGetCurrentCgroupId         = BuiltinFunc(platform.LinuxTag | 80)
+	FnGetLocalStorage            = BuiltinFunc(platform.LinuxTag | 81)
+	FnSkSelectReuseport          = BuiltinFunc(platform.LinuxTag | 82)
+	FnSkbAncestorCgroupId        = BuiltinFunc(platform.LinuxTag | 83)
+	FnSkLookupTcp                = BuiltinFunc(platform.LinuxTag | 84)
+	FnSkLookupUdp                = BuiltinFunc(platform.LinuxTag | 85)
+	FnSkRelease                  = BuiltinFunc(platform.LinuxTag | 86)
+	FnMapPushElem                = BuiltinFunc(platform.LinuxTag | 87)
+	FnMapPopElem                 = BuiltinFunc(platform.LinuxTag | 88)
+	FnMapPeekElem                = BuiltinFunc(platform.LinuxTag | 89)
+	FnMsgPushData                = BuiltinFunc(platform.LinuxTag | 90)
+	FnMsgPopData                 = BuiltinFunc(platform.LinuxTag | 91)
+	FnRcPointerRel               = BuiltinFunc(platform.LinuxTag | 92)
+	FnSpinLock                   = BuiltinFunc(platform.LinuxTag | 93)
+	FnSpinUnlock                 = BuiltinFunc(platform.LinuxTag | 94)
+	FnSkFullsock                 = BuiltinFunc(platform.LinuxTag | 95)
+	FnTcpSock                    = BuiltinFunc(platform.LinuxTag | 96)
+	FnSkbEcnSetCe                = BuiltinFunc(platform.LinuxTag | 97)
+	FnGetListenerSock            = BuiltinFunc(platform.LinuxTag | 98)
+	FnSkcLookupTcp               = BuiltinFunc(platform.LinuxTag | 99)
+	FnTcpCheckSyncookie          = BuiltinFunc(platform.LinuxTag | 100)
+	FnSysctlGetName              = BuiltinFunc(platform.LinuxTag | 101)
+	FnSysctlGetCurrentValue      = BuiltinFunc(platform.LinuxTag | 102)
+	FnSysctlGetNewValue          = BuiltinFunc(platform.LinuxTag | 103)
+	FnSysctlSetNewValue          = BuiltinFunc(platform.LinuxTag | 104)
+	FnStrtol                     = BuiltinFunc(platform.LinuxTag | 105)
+	FnStrtoul                    = BuiltinFunc(platform.LinuxTag | 106)
+	FnSkStorageGet               = BuiltinFunc(platform.LinuxTag | 107)
+	FnSkStorageDelete            = BuiltinFunc(platform.LinuxTag | 108)
+	FnSendSignal                 = BuiltinFunc(platform.LinuxTag | 109)
+	FnTcpGenSyncookie            = BuiltinFunc(platform.LinuxTag | 110)
+	FnSkbOutput                  = BuiltinFunc(platform.LinuxTag | 111)
+	FnProbeReadUser              = BuiltinFunc(platform.LinuxTag | 112)
+	FnProbeReadKernel            = BuiltinFunc(platform.LinuxTag | 113)
+	FnProbeReadUserStr           = BuiltinFunc(platform.LinuxTag | 114)
+	FnProbeReadKernelStr         = BuiltinFunc(platform.LinuxTag | 115)
+	FnTcpSendAck                 = BuiltinFunc(platform.LinuxTag | 116)
+	FnSendSignalThread           = BuiltinFunc(platform.LinuxTag | 117)
+	FnJiffies64                  = BuiltinFunc(platform.LinuxTag | 118)
+	FnReadBranchRecords          = BuiltinFunc(platform.LinuxTag | 119)
+	FnGetNsCurrentPidTgid        = BuiltinFunc(platform.LinuxTag | 120)
+	FnXdpOutput                  = BuiltinFunc(platform.LinuxTag | 121)
+	FnGetNetnsCookie             = BuiltinFunc(platform.LinuxTag | 122)
+	FnGetCurrentAncestorCgroupId = BuiltinFunc(platform.LinuxTag | 123)
+	FnSkAssign                   = BuiltinFunc(platform.LinuxTag | 124)
+	FnKtimeGetBootNs             = BuiltinFunc(platform.LinuxTag | 125)
+	FnSeqPrintf                  = BuiltinFunc(platform.LinuxTag | 126)
+	FnSeqWrite                   = BuiltinFunc(platform.LinuxTag | 127)
+	FnSkCgroupId                 = BuiltinFunc(platform.LinuxTag | 128)
+	FnSkAncestorCgroupId         = BuiltinFunc(platform.LinuxTag | 129)
+	FnRingbufOutput              = BuiltinFunc(platform.LinuxTag | 130)
+	FnRingbufReserve             = BuiltinFunc(platform.LinuxTag | 131)
+	FnRingbufSubmit              = BuiltinFunc(platform.LinuxTag | 132)
+	FnRingbufDiscard             = BuiltinFunc(platform.LinuxTag | 133)
+	FnRingbufQuery               = BuiltinFunc(platform.LinuxTag | 134)
+	FnCsumLevel                  = BuiltinFunc(platform.LinuxTag | 135)
+	FnSkcToTcp6Sock              = BuiltinFunc(platform.LinuxTag | 136)
+	FnSkcToTcpSock               = BuiltinFunc(platform.LinuxTag | 137)
+	FnSkcToTcpTimewaitSock       = BuiltinFunc(platform.LinuxTag | 138)
+	FnSkcToTcpRequestSock        = BuiltinFunc(platform.LinuxTag | 139)
+	FnSkcToUdp6Sock              = BuiltinFunc(platform.LinuxTag | 140)
+	FnGetTaskStack               = BuiltinFunc(platform.LinuxTag | 141)
+	FnLoadHdrOpt                 = BuiltinFunc(platform.LinuxTag | 142)
+	FnStoreHdrOpt                = BuiltinFunc(platform.LinuxTag | 143)
+	FnReserveHdrOpt              = BuiltinFunc(platform.LinuxTag | 144)
+	FnInodeStorageGet            = BuiltinFunc(platform.LinuxTag | 145)
+	FnInodeStorageDelete         = BuiltinFunc(platform.LinuxTag | 146)
+	FnDPath                      = BuiltinFunc(platform.LinuxTag | 147)
+	FnCopyFromUser               = BuiltinFunc(platform.LinuxTag | 148)
+	FnSnprintfBtf                = BuiltinFunc(platform.LinuxTag | 149)
+	FnSeqPrintfBtf               = BuiltinFunc(platform.LinuxTag | 150)
+	FnSkbCgroupClassid           = BuiltinFunc(platform.LinuxTag | 151)
+	FnRedirectNeigh              = BuiltinFunc(platform.LinuxTag | 152)
+	FnPerCpuPtr                  = BuiltinFunc(platform.LinuxTag | 153)
+	FnThisCpuPtr                 = BuiltinFunc(platform.LinuxTag | 154)
+	FnRedirectPeer               = BuiltinFunc(platform.LinuxTag | 155)
+	FnTaskStorageGet             = BuiltinFunc(platform.LinuxTag | 156)
+	FnTaskStorageDelete          = BuiltinFunc(platform.LinuxTag | 157)
+	FnGetCurrentTaskBtf          = BuiltinFunc(platform.LinuxTag | 158)
+	FnBprmOptsSet                = BuiltinFunc(platform.LinuxTag | 159)
+	FnKtimeGetCoarseNs           = BuiltinFunc(platform.LinuxTag | 160)
+	FnImaInodeHash               = BuiltinFunc(platform.LinuxTag | 161)
+	FnSockFromFile               = BuiltinFunc(platform.LinuxTag | 162)
+	FnCheckMtu                   = BuiltinFunc(platform.LinuxTag | 163)
+	FnForEachMapElem             = BuiltinFunc(platform.LinuxTag | 164)
+	FnSnprintf                   = BuiltinFunc(platform.LinuxTag | 165)
+	FnSysBpf                     = BuiltinFunc(platform.LinuxTag | 166)
+	FnBtfFindByNameKind          = BuiltinFunc(platform.LinuxTag | 167)
+	FnSysClose                   = BuiltinFunc(platform.LinuxTag | 168)
+	FnTimerInit                  = BuiltinFunc(platform.LinuxTag | 169)
+	FnTimerSetCallback           = BuiltinFunc(platform.LinuxTag | 170)
+	FnTimerStart                 = BuiltinFunc(platform.LinuxTag | 171)
+	FnTimerCancel                = BuiltinFunc(platform.LinuxTag | 172)
+	FnGetFuncIp                  = BuiltinFunc(platform.LinuxTag | 173)
+	FnGetAttachCookie            = BuiltinFunc(platform.LinuxTag | 174)
+	FnTaskPtRegs                 = BuiltinFunc(platform.LinuxTag | 175)
+	FnGetBranchSnapshot          = BuiltinFunc(platform.LinuxTag | 176)
+	FnTraceVprintk               = BuiltinFunc(platform.LinuxTag | 177)
+	FnSkcToUnixSock              = BuiltinFunc(platform.LinuxTag | 178)
+	FnKallsymsLookupName         = BuiltinFunc(platform.LinuxTag | 179)
+	FnFindVma                    = BuiltinFunc(platform.LinuxTag | 180)
+	FnLoop                       = BuiltinFunc(platform.LinuxTag | 181)
+	FnStrncmp                    = BuiltinFunc(platform.LinuxTag | 182)
+	FnGetFuncArg                 = BuiltinFunc(platform.LinuxTag | 183)
+	FnGetFuncRet                 = BuiltinFunc(platform.LinuxTag | 184)
+	FnGetFuncArgCnt              = BuiltinFunc(platform.LinuxTag | 185)
+	FnGetRetval                  = BuiltinFunc(platform.LinuxTag | 186)
+	FnSetRetval                  = BuiltinFunc(platform.LinuxTag | 187)
+	FnXdpGetBuffLen              = BuiltinFunc(platform.LinuxTag | 188)
+	FnXdpLoadBytes               = BuiltinFunc(platform.LinuxTag | 189)
+	FnXdpStoreBytes              = BuiltinFunc(platform.LinuxTag | 190)
+	FnCopyFromUserTask           = BuiltinFunc(platform.LinuxTag | 191)
+	FnSkbSetTstamp               = BuiltinFunc(platform.LinuxTag | 192)
+	FnImaFileHash                = BuiltinFunc(platform.LinuxTag | 193)
+	FnKptrXchg                   = BuiltinFunc(platform.LinuxTag | 194)
+	FnMapLookupPercpuElem        = BuiltinFunc(platform.LinuxTag | 195)
+	FnSkcToMptcpSock             = BuiltinFunc(platform.LinuxTag | 196)
+	FnDynptrFromMem              = BuiltinFunc(platform.LinuxTag | 197)
+	FnRingbufReserveDynptr       = BuiltinFunc(platform.LinuxTag | 198)
+	FnRingbufSubmitDynptr        = BuiltinFunc(platform.LinuxTag | 199)
+	FnRingbufDiscardDynptr       = BuiltinFunc(platform.LinuxTag | 200)
+	FnDynptrRead                 = BuiltinFunc(platform.LinuxTag | 201)
+	FnDynptrWrite                = BuiltinFunc(platform.LinuxTag | 202)
+	FnDynptrData                 = BuiltinFunc(platform.LinuxTag | 203)
+	FnTcpRawGenSyncookieIpv4     = BuiltinFunc(platform.LinuxTag | 204)
+	FnTcpRawGenSyncookieIpv6     = BuiltinFunc(platform.LinuxTag | 205)
+	FnTcpRawCheckSyncookieIpv4   = BuiltinFunc(platform.LinuxTag | 206)
+	FnTcpRawCheckSyncookieIpv6   = BuiltinFunc(platform.LinuxTag | 207)
+	FnKtimeGetTaiNs              = BuiltinFunc(platform.LinuxTag | 208)
+	FnUserRingbufDrain           = BuiltinFunc(platform.LinuxTag | 209)
+	FnCgrpStorageGet             = BuiltinFunc(platform.LinuxTag | 210)
+	FnCgrpStorageDelete          = BuiltinFunc(platform.LinuxTag | 211)
+)
diff --git a/asm/func_string.go b/asm/func_string.go
index 6c4ba37..4fbef79 100644
--- a/asm/func_string.go
+++ b/asm/func_string.go
@@ -227,7 +227,7 @@ const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElem
 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}
 
 func (i BuiltinFunc) String() string {
-	if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
+	if i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
 		return "BuiltinFunc(" + strconv.FormatInt(int64(i), 10) + ")"
 	}
 	return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]]
diff --git a/asm/instruction.go b/asm/instruction.go
index 86b384c..e71056e 100644
--- a/asm/instruction.go
+++ b/asm/instruction.go
@@ -11,6 +11,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 )
 
@@ -42,10 +43,10 @@ type Instruction struct {
 }
 
 // Unmarshal decodes a BPF instruction.
-func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) {
+func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder, platform string) error {
 	data := make([]byte, InstructionSize)
 	if _, err := io.ReadFull(r, data); err != nil {
-		return 0, err
+		return err
 	}
 
 	ins.OpCode = OpCode(data[0])
@@ -60,7 +61,13 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
 
 	ins.Offset = int16(bo.Uint16(data[2:4]))
 
-	if ins.OpCode.Class().IsALU() {
+	if ins.IsBuiltinCall() {
+		fn, err := BuiltinFuncForPlatform(platform, uint32(ins.Constant))
+		if err != nil {
+			return err
+		}
+		ins.Constant = int64(fn)
+	} else if ins.OpCode.Class().IsALU() {
 		switch ins.OpCode.ALUOp() {
 		case Div:
 			if ins.Offset == 1 {
@@ -92,26 +99,26 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
 	ins.Constant = int64(int32(bo.Uint32(data[4:8])))
 
 	if !ins.OpCode.IsDWordLoad() {
-		return InstructionSize, nil
+		return nil
 	}
 
 	// Pull another instruction from the stream to retrieve the second
 	// half of the 64-bit immediate value.
 	if _, err := io.ReadFull(r, data); err != nil {
 		// No Wrap, to avoid io.EOF clash
-		return 0, errors.New("64bit immediate is missing second half")
+		return errors.New("64bit immediate is missing second half")
 	}
 
 	// Require that all fields other than the value are zero.
 	if bo.Uint32(data[0:4]) != 0 {
-		return 0, errors.New("64bit immediate has non-zero fields")
+		return errors.New("64bit immediate has non-zero fields")
 	}
 
 	cons1 := uint32(ins.Constant)
 	cons2 := int32(bo.Uint32(data[4:8]))
 	ins.Constant = int64(cons2)<<32 | int64(cons1)
 
-	return 2 * InstructionSize, nil
+	return nil
 }
 
 // Marshal encodes a BPF instruction.
@@ -133,7 +140,14 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
 		return 0, fmt.Errorf("can't marshal registers: %s", err)
 	}
 
-	if ins.OpCode.Class().IsALU() {
+	if ins.IsBuiltinCall() {
+		fn := BuiltinFunc(ins.Constant)
+		plat, value := platform.DecodeConstant(fn)
+		if plat != platform.Native {
+			return 0, fmt.Errorf("function %s is not supported on this platform", fn)
+		}
+		cons = int32(value)
+	} else if ins.OpCode.Class().IsALU() {
 		newOffset := int16(0)
 		switch ins.OpCode.ALUOp() {
 		case SDiv:
@@ -529,29 +543,24 @@ type FDer interface {
 // Instructions is an eBPF program.
 type Instructions []Instruction
 
-// Unmarshal unmarshals an Instructions from a binary instruction stream.
-// All instructions in insns are replaced by instructions decoded from r.
-func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) error {
-	if len(*insns) > 0 {
-		*insns = nil
-	}
-
+// AppendInstructions decodes [Instruction] from r and appends them to insns.
+func AppendInstructions(insns Instructions, r io.Reader, bo binary.ByteOrder, platform string) (Instructions, error) {
 	var offset uint64
 	for {
 		var ins Instruction
-		n, err := ins.Unmarshal(r, bo)
+		err := ins.Unmarshal(r, bo, platform)
 		if errors.Is(err, io.EOF) {
 			break
 		}
 		if err != nil {
-			return fmt.Errorf("offset %d: %w", offset, err)
+			return nil, fmt.Errorf("offset %d: %w", offset, err)
 		}
 
-		*insns = append(*insns, ins)
-		offset += n
+		insns = append(insns, ins)
+		offset += ins.Size()
 	}
 
-	return nil
+	return insns, nil
 }
 
 // Name returns the name of the function insns belongs to, if any.
diff --git a/elf_reader.go b/elf_reader.go
index 9e8dbc7..92076aa 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -15,6 +15,7 @@ import (
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 )
 
@@ -184,7 +185,13 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
 		return nil, fmt.Errorf("load programs: %w", err)
 	}
 
-	return &CollectionSpec{ec.maps, progs, ec.vars, btfSpec, ec.ByteOrder}, nil
+	return &CollectionSpec{
+		ec.maps,
+		progs,
+		ec.vars,
+		btfSpec,
+		ec.ByteOrder,
+	}, nil
 }
 
 func loadLicense(sec *elf.Section) (string, error) {
@@ -402,7 +409,8 @@ func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructio
 
 	// Decode the section's instruction stream.
 	insns := make(asm.Instructions, 0, section.Size/asm.InstructionSize)
-	if err := insns.Unmarshal(r, ec.ByteOrder); err != nil {
+	insns, err := asm.AppendInstructions(insns, r, ec.ByteOrder, platform.Linux)
+	if err != nil {
 		return nil, fmt.Errorf("decoding instructions for section %s: %w", section.Name, err)
 	}
 	if len(insns) == 0 {
diff --git a/info.go b/info.go
index c5f47e1..a6edbc3 100644
--- a/info.go
+++ b/info.go
@@ -16,6 +16,7 @@ import (
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 	"github.com/cilium/ebpf/internal/unix"
 )
@@ -74,8 +75,13 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
 		return nil, fmt.Errorf("getting object info: %w", err1)
 	}
 
+	typ, err := MapTypeForPlatform(platform.Native, info.Type)
+	if err != nil {
+		return nil, fmt.Errorf("map type: %w", err)
+	}
+
 	mi := &MapInfo{
-		MapType(info.Type),
+		typ,
 		info.KeySize,
 		info.ValueSize,
 		info.MaxEntries,
@@ -105,8 +111,9 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
 // readMapInfoFromProc queries map information about the given fd from
 // /proc/self/fdinfo. It only writes data into fields that have a zero value.
 func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error {
-	return scanFdInfo(fd, map[string]interface{}{
-		"map_type":    &mi.Type,
+	var mapType uint32
+	err := scanFdInfo(fd, map[string]interface{}{
+		"map_type":    &mapType,
 		"map_id":      &mi.id,
 		"key_size":    &mi.KeySize,
 		"value_size":  &mi.ValueSize,
@@ -116,6 +123,18 @@ func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error {
 		"memlock":     &mi.memlock,
 		"frozen":      &mi.frozen,
 	})
+	if err != nil {
+		return err
+	}
+
+	if mi.Type == 0 {
+		mi.Type, err = MapTypeForPlatform(platform.Linux, mapType)
+		if err != nil {
+			return fmt.Errorf("map type: %w", err)
+		}
+	}
+
+	return nil
 }
 
 // ID returns the map ID.
@@ -251,8 +270,13 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		return nil, err
 	}
 
+	typ, err := ProgramTypeForPlatform(platform.Native, info.Type)
+	if err != nil {
+		return nil, fmt.Errorf("program type: %w", err)
+	}
+
 	pi := ProgramInfo{
-		Type: ProgramType(info.Type),
+		Type: typ,
 		id:   ProgramID(info.Id),
 		Tag:  hex.EncodeToString(info.Tag[:]),
 		Name: unix.ByteSliceToString(info.Name[:]),
@@ -361,8 +385,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 
 func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
 	var info ProgramInfo
+	var progType uint32
 	err := scanFdInfo(fd, map[string]interface{}{
-		"prog_type": &info.Type,
+		"prog_type": &progType,
 		"prog_tag":  &info.Tag,
 	})
 	if errors.Is(err, ErrNotSupported) {
@@ -375,6 +400,11 @@ func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
 		return nil, err
 	}
 
+	info.Type, err = ProgramTypeForPlatform(platform.Linux, progType)
+	if err != nil {
+		return nil, fmt.Errorf("program type: %w", err)
+	}
+
 	return &info, nil
 }
 
@@ -514,8 +544,8 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
 	}
 
 	r := bytes.NewReader(pi.insns)
-	var insns asm.Instructions
-	if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
+	insns, err := asm.AppendInstructions(nil, r, internal.NativeEndian, platform.Native)
+	if err != nil {
 		return nil, fmt.Errorf("unmarshaling instructions: %w", err)
 	}
 
diff --git a/internal/cmd/genfunctions.awk b/internal/cmd/genfunctions.awk
new file mode 100644
index 0000000..6c8d639
--- /dev/null
+++ b/internal/cmd/genfunctions.awk
@@ -0,0 +1,36 @@
+#!/usr/bin/gawk -f
+# Generate constants from Linux headers.
+#
+# This script expects include/uapi/bpf.h as input.
+
+BEGIN {
+	print "// Code generated by internal/cmd/genfunctions.awk; DO NOT EDIT."
+	print ""
+	print "package asm"
+	print ""
+	print "// Code in this file is derived from Linux, available under the GPL-2.0 WITH Linux-syscall-note."
+	print ""
+	print "import \"github.com/cilium/ebpf/internal/platform\""
+	print ""
+	print "// Built-in functions (Linux)."
+	print "const ("
+}
+
+/FN\([[:alnum:]_]+, [[:digit:]]+,.*\)/ {
+	name = gensub(/.*FN\(([[:alnum:]_]+), [[:digit:]]+,.*\).*/, "\\1", 1)
+	id = gensub(/.*FN\([[:alnum:]_]+, ([[:digit:]]+),.*\).*/, "\\1", 1)
+
+	split(tolower(name), parts, "_")
+	result = "Fn"
+	for (i in parts) {
+		part = parts[i]
+		result = result substr(toupper(substr(part,1,1)), 1, 1) substr(part, 2)
+	}
+
+	print "\t" result " = BuiltinFunc(platform.LinuxTag | " id ")"
+}
+
+END {
+	print ")"
+	print ""
+}
diff --git a/internal/platform/constants.go b/internal/platform/constants.go
new file mode 100644
index 0000000..3607414
--- /dev/null
+++ b/internal/platform/constants.go
@@ -0,0 +1,57 @@
+package platform
+
+import "fmt"
+
+// Values used to tag platform specific constants.
+//
+// The value for Linux is zero so that existing constants do not change.
+const (
+	LinuxTag = uint32(iota) << platformShift
+)
+
+const (
+	platformMax   = 0xf
+	platformShift = 28
+	platformMask  = platformMax << platformShift
+)
+
+func tagForPlatform(platform string) (uint32, error) {
+	switch platform {
+	case Linux:
+		return LinuxTag, nil
+	default:
+		return 0, fmt.Errorf("unrecognized platform: %s", platform)
+	}
+}
+
+func platformForConstant(c uint32) string {
+	tag := uint32(c & platformMask)
+	switch tag {
+	case LinuxTag:
+		return Linux
+	default:
+		return ""
+	}
+}
+
+// Encode a platform and a value into a tagged constant.
+//
+// Returns an error if platform is unknown or c is out of bounds.
+func EncodeConstant[T ~uint32](platform string, c uint32) (T, error) {
+	if c>>platformShift > 0 {
+		return 0, fmt.Errorf("invalid constant 0x%x", c)
+	}
+
+	tag, err := tagForPlatform(platform)
+	if err != nil {
+		return 0, err
+	}
+
+	return T(tag | c), nil
+}
+
+// Decode a platform and a value from a tagged constant.
+func DecodeConstant[T ~uint32](c T) (string, uint32) {
+	v := uint32(c) & ^uint32(platformMask)
+	return platformForConstant(uint32(c)), v
+}
diff --git a/internal/platform/goos.go b/internal/platform/platform.go
similarity index 81%
rename from internal/platform/goos.go
rename to internal/platform/platform.go
index 273fec6..065c85d 100644
--- a/internal/platform/goos.go
+++ b/internal/platform/platform.go
@@ -2,6 +2,10 @@ package platform
 
 import "runtime"
 
+const (
+	Linux = "linux"
+)
+
 const (
 	IsLinux   = runtime.GOOS == "linux"
 	IsWindows = runtime.GOOS == "windows"
diff --git a/internal/platform/platform_linux.go b/internal/platform/platform_linux.go
new file mode 100644
index 0000000..f0aa240
--- /dev/null
+++ b/internal/platform/platform_linux.go
@@ -0,0 +1,3 @@
+package platform
+
+const Native = Linux
diff --git a/internal/platform/platform_other.go b/internal/platform/platform_other.go
new file mode 100644
index 0000000..39bd247
--- /dev/null
+++ b/internal/platform/platform_other.go
@@ -0,0 +1,5 @@
+//go:build !linux
+
+package platform
+
+const Native = ""
diff --git a/link/link.go b/link/link.go
index 796769f..4ec6eab 100644
--- a/link/link.go
+++ b/link/link.go
@@ -8,6 +8,7 @@ import (
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 )
 
@@ -336,10 +337,15 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
 		return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd)
 	}
 
+	p, attachType := platform.DecodeConstant(opts.Attach)
+	if p != "linux" {
+		return nil, fmt.Errorf("attach type %s: %w", opts.Attach, internal.ErrNotSupportedOnOS)
+	}
+
 	attr := sys.LinkCreateAttr{
 		TargetFd:    uint32(opts.Target),
 		ProgFd:      uint32(progFd),
-		AttachType:  sys.AttachType(opts.Attach),
+		AttachType:  sys.AttachType(attachType),
 		TargetBtfId: opts.BTF,
 		Flags:       opts.Flags,
 	}
diff --git a/linker.go b/linker.go
index 024b72b..e98ad77 100644
--- a/linker.go
+++ b/linker.go
@@ -15,6 +15,7 @@ import (
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal"
 	"github.com/cilium/ebpf/internal/kallsyms"
+	"github.com/cilium/ebpf/internal/platform"
 )
 
 // handles stores handle objects to avoid gc cleanup
@@ -272,9 +273,11 @@ func fixupAndValidate(insns asm.Instructions) error {
 	return nil
 }
 
-// POISON_CALL_KFUNC_BASE in libbpf.
-// https://github.com/libbpf/libbpf/blob/2778cbce609aa1e2747a69349f7f46a2f94f0522/src/libbpf.c#L5767
-const kfuncCallPoisonBase = 2002000000
+// A constant used to poison calls to non-existent kfuncs.
+//
+// Similar POISON_CALL_KFUNC_BASE in libbpf, except that we use a value lower
+// than 2^28 to fit into a tagged constant.
+const kfuncCallPoisonBase = 0xdedc0de
 
 // fixupKfuncs loops over all instructions in search for kfunc calls.
 // If at least one is found, the current kernels BTF and module BTFis are searched to set Instruction.Constant
@@ -329,10 +332,12 @@ fixups:
 		if kfm.Binding == elf.STB_WEAK && errors.Is(err, btf.ErrNotFound) {
 			if ins.IsKfuncCall() {
 				// If the kfunc call is weak and not found, poison the call. Use a recognizable constant
-				// to make it easier to debug. And set src to zero so the verifier doesn't complain
-				// about the invalid imm/offset values before dead-code elimination.
-				ins.Constant = kfuncCallPoisonBase
-				ins.Src = 0
+				// to make it easier to debug.
+				fn, err := asm.BuiltinFuncForPlatform(platform.Native, kfuncCallPoisonBase)
+				if err != nil {
+					return nil, err
+				}
+				*ins = fn.Call()
 			} else if ins.OpCode.IsDWordLoad() {
 				// If the kfunc DWordLoad is weak and not found, set its address to 0.
 				ins.Constant = 0
diff --git a/map.go b/map.go
index 318481c..77c472e 100644
--- a/map.go
+++ b/map.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 	"github.com/cilium/ebpf/internal/sysenc"
 	"github.com/cilium/ebpf/internal/unix"
@@ -368,7 +369,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
 	}
 
 	var innerFd *sys.FD
-	if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps {
+	if spec.Type.canStoreMap() {
 		if spec.InnerMap == nil {
 			return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
 		}
@@ -474,8 +475,13 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
 		return nil, err
 	}
 
+	p, sysMapType := platform.DecodeConstant(spec.Type)
+	if p != platform.Native {
+		return nil, fmt.Errorf("map type %s: %w", spec.Type, internal.ErrNotSupportedOnOS)
+	}
+
 	attr := sys.MapCreateAttr{
-		MapType:    sys.MapType(spec.Type),
+		MapType:    sys.MapType(sysMapType),
 		KeySize:    spec.KeySize,
 		ValueSize:  spec.ValueSize,
 		MaxEntries: spec.MaxEntries,
diff --git a/prog.go b/prog.go
index 9bcf1c2..6ef28e6 100644
--- a/prog.go
+++ b/prog.go
@@ -16,6 +16,7 @@ import (
 	"github.com/cilium/ebpf/internal"
 	"github.com/cilium/ebpf/internal/kallsyms"
 	"github.com/cilium/ebpf/internal/linux"
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 	"github.com/cilium/ebpf/internal/sysenc"
 	"github.com/cilium/ebpf/internal/unix"
@@ -279,8 +280,13 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 		kv = v.Kernel()
 	}
 
+	p, progType := platform.DecodeConstant(spec.Type)
+	if p != platform.Native {
+		return nil, fmt.Errorf("program type %s: %w", spec.Type, internal.ErrNotSupportedOnOS)
+	}
+
 	attr := &sys.ProgLoadAttr{
-		ProgType:           sys.ProgType(spec.Type),
+		ProgType:           sys.ProgType(progType),
 		ProgFlags:          spec.Flags,
 		ExpectedAttachType: sys.AttachType(spec.AttachType),
 		License:            sys.NewStringPointer(spec.License),
diff --git a/types.go b/types.go
index d7fb005..fb09b33 100644
--- a/types.go
+++ b/types.go
@@ -1,6 +1,7 @@
 package ebpf
 
 import (
+	"github.com/cilium/ebpf/internal/platform"
 	"github.com/cilium/ebpf/internal/sys"
 )
 
@@ -12,7 +13,7 @@ type MapType uint32
 
 // All the various map types that can be created
 const (
-	UnspecifiedMap MapType = iota
+	UnspecifiedMap MapType = MapType(platform.LinuxTag | iota)
 	// Hash is a hash map
 	Hash
 	// Array is an array map
@@ -104,6 +105,13 @@ const (
 	Arena
 )
 
+// MapTypeForPlatform returns a platform specific map type.
+//
+// Use this if the library doesn't provide a constant yet.
+func MapTypeForPlatform(plat string, typ uint32) (MapType, error) {
+	return platform.EncodeConstant[MapType](plat, typ)
+}
+
 // hasPerCPUValue returns true if the Map stores a value per CPU.
 func (mt MapType) hasPerCPUValue() bool {
 	return mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash || mt == PerCPUCGroupStorage
@@ -145,7 +153,7 @@ func (mt MapType) canHaveValueSize() bool {
 // ProgramType of the eBPF program
 type ProgramType uint32
 
-// eBPF program types
+// eBPF program types (Linux).
 const (
 	UnspecifiedProgram    = ProgramType(sys.BPF_PROG_TYPE_UNSPEC)
 	SocketFilter          = ProgramType(sys.BPF_PROG_TYPE_SOCKET_FILTER)
@@ -182,6 +190,13 @@ const (
 	Netfilter             = ProgramType(sys.BPF_PROG_TYPE_NETFILTER)
 )
 
+// ProgramTypeForPlatform returns a platform specific program type.
+//
+// Use this if the library doesn't provide a constant yet.
+func ProgramTypeForPlatform(plat string, value uint32) (ProgramType, error) {
+	return platform.EncodeConstant[ProgramType](plat, value)
+}
+
 // AttachType of the eBPF program, needed to differentiate allowed context accesses in
 // some newer program types like CGroupSockAddr. Should be set to AttachNone if not required.
 // Will cause invalid argument (EINVAL) at program load time if set incorrectly.
@@ -252,6 +267,13 @@ const (
 	AttachNetkitPeer                 = AttachType(sys.BPF_NETKIT_PEER)
 )
 
+// AttachTypeForPlatform returns a platform specific attach type.
+//
+// Use this if the library doesn't provide a constant yet.
+func AttachTypeForPlatform(plat string, value uint32) (AttachType, error) {
+	return platform.EncodeConstant[AttachType](plat, value)
+}
+
 // AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
 type AttachFlags uint32
 


Test patch:
diff --git a/asm/instruction_test.go b/asm/instruction_test.go
index e8d3495..04f160e 100644
--- a/asm/instruction_test.go
+++ b/asm/instruction_test.go
@@ -11,6 +11,8 @@ import (
 	"testing"
 
 	"github.com/go-quicktest/qt"
+
+	"github.com/cilium/ebpf/internal/platform"
 )
 
 var test64bitImmProg = []byte{
@@ -21,13 +23,10 @@ var test64bitImmProg = []byte{
 
 func TestRead64bitImmediate(t *testing.T) {
 	var ins Instruction
-	n, err := ins.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian)
+	err := ins.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian, platform.Linux)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if want := uint64(InstructionSize * 2); n != want {
-		t.Errorf("Expected %d bytes to be read, got %d", want, n)
-	}
 
 	if c := ins.Constant; c != math.MinInt32-1 {
 		t.Errorf("Expected immediate to be %v, got %v", int64(math.MinInt32)-1, c)
@@ -40,7 +39,7 @@ func BenchmarkRead64bitImmediate(b *testing.B) {
 		r.Reset(test64bitImmProg)
 
 		var ins Instruction
-		if _, err := ins.Unmarshal(r, binary.LittleEndian); err != nil {
+		if err := ins.Unmarshal(r, binary.LittleEndian, platform.Linux); err != nil {
 			b.Fatal(err)
 		}
 	}
@@ -74,20 +73,11 @@ func BenchmarkWrite64BitImmediate(b *testing.B) {
 	}
 }
 
-func TestUnmarshalInstructions(t *testing.T) {
+func TestAppendInstructions(t *testing.T) {
 	r := bytes.NewReader(test64bitImmProg)
 
-	var insns Instructions
-	if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
-		t.Fatal(err)
-	}
-
-	// Unmarshaling into the same Instructions multiple times replaces
-	// the instruction stream.
-	r.Reset(test64bitImmProg)
-	if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
-		t.Fatal(err)
-	}
+	insns, err := AppendInstructions(nil, r, binary.LittleEndian, platform.Linux)
+	qt.Assert(t, qt.IsNil(err))
 
 	if len(insns) != 1 {
 		t.Fatalf("Expected one instruction, got %d", len(insns))
@@ -263,7 +253,7 @@ func TestReadSrcDst(t *testing.T) {
 	for _, tc := range testcases {
 		t.Run(tc.bo.String(), func(t *testing.T) {
 			var ins Instruction
-			_, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo)
+			err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo, platform.Linux)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -367,8 +357,7 @@ func TestISAv4(t *testing.T) {
 		0x9c, 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // w2 s%= w4
 	}
 
-	var insns Instructions
-	err := insns.Unmarshal(bytes.NewReader(rawInsns), binary.LittleEndian)
+	insns, err := AppendInstructions(nil, bytes.NewReader(rawInsns), binary.LittleEndian, platform.Linux)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/platform/constants_test.go b/internal/platform/constants_test.go
new file mode 100644
index 0000000..5782469
--- /dev/null
+++ b/internal/platform/constants_test.go
@@ -0,0 +1,25 @@
+package platform
+
+import (
+	"testing"
+
+	"github.com/go-quicktest/qt"
+)
+
+func TestConstant(t *testing.T) {
+	const maxConstant = ^uint32(platformMask)
+	for _, plat := range []string{
+		Linux,
+	} {
+		t.Run(plat, func(t *testing.T) {
+			c, err := EncodeConstant[uint32](plat, maxConstant)
+			qt.Assert(t, qt.IsNil(err))
+			gotPlat, gotValue := DecodeConstant(c)
+			qt.Assert(t, qt.Equals(gotPlat, plat))
+			qt.Assert(t, qt.Equals(gotValue, maxConstant))
+
+			_, err = EncodeConstant[uint32](plat, maxConstant+1)
+			qt.Assert(t, qt.IsNotNil(err))
+		})
+	}
+}