Repository: cilium/ebpf
Pull request #1706 (2025-03-01 14:42:21)

build(deps): bump mkdocs-material from 9.6.1 to 9.6.6 in /docs in the docs group
Bumps the docs group in /docs with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).

Updates `mkdocs-material` from 9.6.1 to 9.6.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/squidfunk/mkdocs-material/releases">mkdocs-material's releases</a>.</em></p>
<blockquote>
<h2>mkdocs-material-9.6.6</h2>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8040">#8040</a>: Privacy plugin not replacing exteral assets (9.6.5 regression)</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8031">#8031</a>: Replace unmtaintained <code>regex</code> package in search plugin</li>
</ul>
<h2>mkdocs-material-9.6.5</h2>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8016">#8016</a>: Tags listing not showing when when file name has spaces</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8012">#8012</a>: Privacy plugin crashes if HTTP download fails</li>
</ul>
<h2>mkdocs-material-9.6.4</h2>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7985">#7985</a>: Blog content sometimes not stretching to full width</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7978">#7978</a>: Navigation rendering bug in Safari 18.3</li>
</ul>
<h2>mkdocs-material-9.6.3</h2>
<ul>
<li>Fixed rendering of arrow heads in Mermaid.js class diagrams</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7960">#7960</a>: Tags plugin crashes on numeric metadata titles</li>
</ul>
<h2>mkdocs-material-9.6.2</h2>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7955">#7955</a>: Excessively long words don't break on narrow screens</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7947">#7947</a>: Scope setting interferes with outdated version banner</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG">mkdocs-material's changelog</a>.</em></p>
<blockquote>
<p>mkdocs-material-9.6.6 (2025-03-01)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8040">#8040</a>: Privacy plugin not replacing exteral assets (9.6.5 regression)</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8031">#8031</a>: Replace unmtaintained regex package in search plugin</li>
</ul>
<p>mkdocs-material-9.6.5 (2025-02-20)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8016">#8016</a>: Tags listing not showing when when file name has spaces</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8012">#8012</a>: Privacy plugin crashes if HTTP download fails</li>
</ul>
<p>mkdocs-material-9.6.4 (2025-02-12)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7985">#7985</a>: Blog content sometimes not stretching to full width</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7978">#7978</a>: Navigation rendering bug in Safari 18.3</li>
</ul>
<p>mkdocs-material-9.6.3 (2025-02-07)</p>
<ul>
<li>Fixed rendering of arrow heads in Mermaid.js class diagrams</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7960">#7960</a>: Tags plugin crashes on numeric metadata titles</li>
</ul>
<p>mkdocs-material-9.6.2 (2025-02-03)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7955">#7955</a>: Excessively long words don't break on narrow screens</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7947">#7947</a>: Scope setting interferes with outdated version banner</li>
</ul>
<p>mkdocs-material-9.6.1 (2025-01-31)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7943">#7943</a>: Tags plugin crashing due to merge error</li>
</ul>
<p>mkdocs-material-9.6.0 (2025-01-31)</p>
<ul>
<li>Added meta plugin</li>
<li>Rewrite of the tags plugin</li>
<li>Added support for allow lists in tags plugin</li>
<li>Added support for and custom sorting in tags plugin</li>
<li>Added support for related links in blog plugin</li>
<li>Added support for custom index pages in blog plugin</li>
<li>Added support for navigation subtitles</li>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7924">#7924</a>: Anchors might require two clicks when using instant navigation</li>
</ul>
<p>mkdocs-material-9.5.50 (2025-01-18)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7913">#7913</a>: Social plugin renders attribute lists in page title</li>
</ul>
<p>mkdocs-material-9.5.49+insiders-4.53.15 (2025-01-15)</p>
<ul>
<li>Fixed <a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/7896">#7896</a>: Scoped tags listings not rendering in subsections</li>
</ul>
<p>mkdocs-material-9.5.49 (2024-12-16)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/922cb7cea295310c63ae623d350114e0df608734"><code>922cb7c</code></a> Prepare 9.6.6 release</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/523922b3837ca974346a012191991d89f4030f68"><code>523922b</code></a> Fixed privacy plugin not replacing occurrences</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/3bd81bdc6667b146390043ee9abd39f9449cb903"><code>3bd81bd</code></a> Updated dependencies</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/b6e40de116b106752827a2132fc5c8e88850cf9a"><code>b6e40de</code></a> Updated dependencies</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/3a7cc2a70124c0d30b461bb07136cd9e2028fa3c"><code>3a7cc2a</code></a> Lowered backref version to support Py3.8 (<a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8037">#8037</a>)</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/ab1511074c6eb03063b9258e8e42a117a57f5853"><code>ab15110</code></a> Replaced regex with backrefs package in search plugin (<a href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8034">#8034</a>)</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/37162a57cdb0ec68babbc31144ee21f9df7667bd"><code>37162a5</code></a> Replaced regex with re in info plugin</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/396c493f4774fa0bafeada5d0b555d02193557e8"><code>396c493</code></a> Documentation</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/779c54ef47e6dc0c87b3cee06c0980af525d3bee"><code>779c54e</code></a> Documentation</li>
<li><a href="https://github.com/squidfunk/mkdocs-material/commit/7445b2aa608fd1312914103de10949b970b49142"><code>7445b2a</code></a> Prepare 9.6.5 release</li>
<li>Additional commits viewable in <a href="https://github.com/squidfunk/mkdocs-material/compare/9.6.1...9.6.6">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mkdocs-material&package-manager=pip&previous-version=9.6.1&new-version=9.6.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions


</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b746269..3d313a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ env:
   TMPDIR: /tmp
   CI_MAX_KERNEL_VERSION: '6.11'
   CI_MAX_EFW_VERSION: '0.20.0'
-  CI_MIN_CLANG_VERSION: '11'
+  CI_MIN_CLANG_VERSION: '13'
   go_version: '~1.24'
   prev_go_version: '~1.23'
   CGO_ENABLED: '0'
@@ -34,7 +34,7 @@ jobs:
           go-version: '${{ env.go_version }}'
 
       - name: Run golangci-lint
-        uses: golangci/golangci-lint-action@v6.3.0
+        uses: golangci/golangci-lint-action@v6.5.0
         with:
           args: "--out-format colored-line-number"
 
@@ -48,7 +48,6 @@ jobs:
 
       - name: Test bpf2go
         run: |
-          sudo apt-get install clang-11 llvm-11
           go test -v ./cmd/bpf2go
 
       - name: Build examples
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/docs/Pipfile.lock b/docs/Pipfile.lock
index 6eb961e..50b1d22 100644
--- a/docs/Pipfile.lock
+++ b/docs/Pipfile.lock
@@ -24,6 +24,18 @@
             "markers": "python_version >= '3.8'",
             "version": "==2.17.0"
         },
+        "backrefs": {
+            "hashes": [
+                "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd",
+                "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b",
+                "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc",
+                "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486",
+                "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d",
+                "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==5.8"
+        },
         "certifi": {
             "hashes": [
                 "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651",
@@ -210,69 +222,70 @@
         },
         "markupsafe": {
             "hashes": [
-                "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
-                "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
-                "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
-                "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
-                "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
-                "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
-                "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
-                "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
-                "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
-                "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
-                "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
-                "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
-                "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
-                "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
-                "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
-                "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
-                "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
-                "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
-                "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
-                "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
-                "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
-                "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
-                "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
-                "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
-                "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
-                "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
-                "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
-                "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
-                "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
-                "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
-                "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
-                "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
-                "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
-                "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
-                "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
-                "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
-                "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
-                "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
-                "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
-                "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
-                "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
-                "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
-                "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
-                "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
-                "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
-                "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
-                "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
-                "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
-                "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
-                "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
-                "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
-                "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
-                "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
-                "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
-                "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
-                "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
-                "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
-                "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
-                "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
-                "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.1.5"
+                "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
+                "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
+                "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
+                "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
+                "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
+                "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
+                "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
+                "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
+                "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
+                "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
+                "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
+                "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
+                "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
+                "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
+                "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
+                "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
+                "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
+                "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
+                "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
+                "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
+                "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
+                "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
+                "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
+                "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
+                "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
+                "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
+                "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
+                "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
+                "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
+                "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
+                "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
+                "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
+                "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
+                "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
+                "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
+                "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
+                "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
+                "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
+                "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
+                "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
+                "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
+                "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
+                "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
+                "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
+                "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
+                "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
+                "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
+                "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
+                "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
+                "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
+                "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
+                "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
+                "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
+                "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
+                "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
+                "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
+                "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
+                "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
+                "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
+                "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
+                "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==3.0.2"
         },
         "mergedeep": {
             "hashes": [
@@ -328,12 +341,12 @@
         },
         "mkdocs-material": {
             "hashes": [
-                "sha256:c1742d410be29811a9b7e863cb25a578b9e255fe6f04c69f8c6838863a58e141",
-                "sha256:da37dba220d9fbfc5f1fc567fafc4028e3c3d7d828f2779ed09ab726ceca77dc"
+                "sha256:06141bd720b0b235829bd59e8afc11d5587c35ae7fc340612d2b3f554e6a69d8",
+                "sha256:904c422ec86086144495831cee2614bb8a0092572ef579af6392b8080309d3a3"
             ],
             "index": "pypi",
             "markers": "python_version >= '3.8'",
-            "version": "==9.6.1"
+            "version": "==9.6.6"
         },
         "mkdocs-material-extensions": {
             "hashes": [
@@ -396,7 +409,7 @@
                 "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
                 "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.9.0.post0"
         },
         "pytz": {
@@ -586,7 +599,7 @@
                 "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
                 "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.17.0"
         },
         "smmap": {
@@ -615,52 +628,47 @@
         },
         "urllib3": {
             "hashes": [
-                "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac",
-                "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"
+                "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
+                "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
             ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.2.3"
+            "markers": "python_version >= '3.9'",
+            "version": "==2.3.0"
         },
         "watchdog": {
             "hashes": [
-                "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4",
-                "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19",
-                "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a",
-                "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa",
-                "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a",
-                "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a",
-                "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1",
-                "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc",
-                "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9",
-                "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930",
-                "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73",
-                "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b",
-                "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83",
-                "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7",
-                "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef",
-                "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1",
-                "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040",
-                "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b",
-                "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270",
-                "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c",
-                "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d",
-                "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8",
-                "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508",
-                "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b",
-                "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503",
-                "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757",
-                "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b",
-                "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29",
-                "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c",
-                "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22",
-                "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578",
-                "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e",
-                "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee",
-                "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7",
-                "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.0.2"
+                "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a",
+                "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2",
+                "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f",
+                "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c",
+                "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c",
+                "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c",
+                "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0",
+                "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13",
+                "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134",
+                "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa",
+                "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e",
+                "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379",
+                "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a",
+                "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11",
+                "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282",
+                "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b",
+                "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f",
+                "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c",
+                "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112",
+                "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948",
+                "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881",
+                "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860",
+                "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3",
+                "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680",
+                "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26",
+                "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26",
+                "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e",
+                "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8",
+                "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c",
+                "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"
+            ],
+            "markers": "python_version >= '3.9'",
+            "version": "==6.0.0"
         },
         "zipp": {
             "hashes": [
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/go.mod b/go.mod
index 24891f9..3d27187 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/cilium/ebpf
 go 1.23
 
 require (
-	github.com/go-quicktest/qt v1.101.0
+	github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6
 	github.com/google/go-cmp v0.6.0
 	github.com/jsimonetti/rtnetlink/v2 v2.0.1
 	golang.org/x/sys v0.30.0
@@ -15,7 +15,7 @@ require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/mdlayher/netlink v1.7.2 // indirect
 	github.com/mdlayher/socket v0.4.1 // indirect
-	github.com/rogpeppe/go-internal v1.11.0 // indirect
+	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	golang.org/x/net v0.33.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
 )
diff --git a/go.sum b/go.sum
index c154a61..d9a9f1c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
+github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
+github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@@ -19,6 +21,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
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 e206b9e..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"
@@ -293,7 +294,7 @@ func newMapFromFD(fd *sys.FD) (*Map, error) {
 		return nil, fmt.Errorf("get map info: %w", err)
 	}
 
-	return newMap(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags)
+	return newMapFromParts(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags)
 }
 
 // NewMap creates a new Map.
@@ -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,
@@ -520,7 +526,7 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
 	}
 
 	defer closeOnError(fd)
-	m, err := newMap(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags)
+	m, err := newMapFromParts(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags)
 	if err != nil {
 		return nil, fmt.Errorf("map create: %w", err)
 	}
@@ -580,9 +586,9 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro
 	return fmt.Errorf("map create: %w", err)
 }
 
-// newMap allocates and returns a new Map structure.
+// newMapFromParts allocates and returns a new Map structure.
 // Sets the fullValueSize on per-CPU maps.
-func newMap(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) {
+func newMapFromParts(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) {
 	m := &Map{
 		name,
 		fd,
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/collection_test.go b/collection_test.go
index 4d484cf..93d78fc 100644
--- a/collection_test.go
+++ b/collection_test.go
@@ -10,7 +10,6 @@ import (
 	"testing"
 
 	"github.com/go-quicktest/qt"
-	"github.com/google/go-cmp/cmp"
 
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/btf"
@@ -46,11 +45,7 @@ func TestCollectionSpecNotModified(t *testing.T) {
 		},
 	}
 
-	coll, err := NewCollection(&cs)
-	if err != nil {
-		t.Fatal(err)
-	}
-	coll.Close()
+	mustNewCollection(t, &cs, nil)
 
 	if cs.Programs["test"].Instructions[0].Constant != 0 {
 		t.Error("Creating a collection modifies input spec")
@@ -107,17 +102,11 @@ func TestCollectionSpecLoadCopy(t *testing.T) {
 		Prog *Program `ebpf:"xdp_prog"`
 	}
 
-	err = spec.LoadAndAssign(&objs, nil)
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal("Loading original spec:", err)
-	}
-	defer objs.Prog.Close()
+	mustLoadAndAssign(t, spec, &objs, nil)
+	qt.Assert(t, qt.IsNil(objs.Prog.Close()))
 
-	if err := spec2.LoadAndAssign(&objs, nil); err != nil {
-		t.Fatal("Loading copied spec:", err)
-	}
-	defer objs.Prog.Close()
+	mustLoadAndAssign(t, spec2, &objs, nil)
+	qt.Assert(t, qt.IsNil(objs.Prog.Close()))
 }
 
 func TestCollectionSpecLoadMutate(t *testing.T) {
@@ -133,14 +122,9 @@ func TestCollectionSpecLoadMutate(t *testing.T) {
 		Prog *Program `ebpf:"xdp_prog"`
 	}
 
-	err = spec.LoadAndAssign(&objs, nil)
-	testutils.SkipIfNotSupported(t, err)
-	qt.Assert(t, qt.IsNil(err))
-	defer objs.Prog.Close()
-
-	if diff := cmp.Diff(orig, spec, csCmpOpts...); diff != "" {
-		t.Errorf("CollectionSpec was mutated after loading (-want +got):\n%s", diff)
-	}
+	mustLoadAndAssign(t, spec, &objs, nil)
+	qt.Assert(t, qt.IsNil(objs.Prog.Close()))
+	qt.Assert(t, qt.CmpEquals(spec, orig, csCmpOpts))
 }
 
 func TestCollectionSpecRewriteMaps(t *testing.T) {
@@ -177,13 +161,9 @@ func TestCollectionSpecRewriteMaps(t *testing.T) {
 	}
 
 	// Override the map with another one
-	newMap, err := NewMap(cs.Maps["test-map"])
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer newMap.Close()
+	newMap := mustNewMap(t, cs.Maps["test-map"], nil)
 
-	err = newMap.Put(uint32(0), uint32(2))
+	err := newMap.Put(uint32(0), uint32(2))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -199,11 +179,7 @@ func TestCollectionSpecRewriteMaps(t *testing.T) {
 		t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps")
 	}
 
-	coll, err := NewCollection(cs)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer coll.Close()
+	coll := mustNewCollection(t, cs, nil)
 
 	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
@@ -250,26 +226,18 @@ func TestCollectionSpecMapReplacements(t *testing.T) {
 	}
 
 	// Replace the map with another one
-	newMap, err := NewMap(cs.Maps["test-map"])
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer newMap.Close()
+	newMap := mustNewMap(t, cs.Maps["test-map"], nil)
 
-	err = newMap.Put(uint32(0), uint32(2))
+	err := newMap.Put(uint32(0), uint32(2))
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
+	coll := mustNewCollection(t, cs, &CollectionOptions{
 		MapReplacements: map[string]*Map{
 			"test-map": newMap,
 		},
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer coll.Close()
 
 	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
@@ -302,13 +270,9 @@ func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) {
 	}
 
 	// Override non-existing map
-	newMap, err := NewMap(cs.Maps["test-map"])
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer newMap.Close()
+	newMap := mustNewMap(t, cs.Maps["test-map"], nil)
 
-	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
+	coll, err := newCollection(t, cs, &CollectionOptions{
 		MapReplacements: map[string]*Map{
 			"non-existing-map": newMap,
 		},
@@ -332,19 +296,14 @@ func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
 	}
 
 	// Override map with mismatching spec
-	newMap, err := NewMap(&MapSpec{
+	newMap := mustNewMap(t, &MapSpec{
 		Type:       Array,
 		KeySize:    4,
 		ValueSize:  8, // this is different
 		MaxEntries: 1,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	// Map fd is duplicated by MapReplacements, this one can be safely closed.
-	defer newMap.Close()
+	}, nil)
 
-	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
+	coll, err := newCollection(t, cs, &CollectionOptions{
 		MapReplacements: map[string]*Map{
 			"test-map": newMap,
 		},
@@ -373,21 +332,18 @@ func TestMapReplacementsDataSections(t *testing.T) {
 		ROData *Map `ebpf:".rodata"`
 	}
 
-	err = spec.LoadAndAssign(&objs, nil)
-	testutils.SkipIfNotSupported(t, err)
-	qt.Assert(t, qt.IsNil(err))
+	mustLoadAndAssign(t, spec, &objs, nil)
 	defer objs.Data.Close()
 	defer objs.ROData.Close()
 
-	qt.Assert(t, qt.IsNil(
-		spec.LoadAndAssign(&objs, &CollectionOptions{
-			MapReplacements: map[string]*Map{
-				".data":   objs.Data,
-				".rodata": objs.ROData,
-			},
-		})))
-	defer objs.Data.Close()
-	defer objs.ROData.Close()
+	mustLoadAndAssign(t, spec, &objs, &CollectionOptions{
+		MapReplacements: map[string]*Map{
+			".data":   objs.Data,
+			".rodata": objs.ROData,
+		},
+	})
+	qt.Assert(t, qt.IsNil(objs.Data.Close()))
+	qt.Assert(t, qt.IsNil(objs.ROData.Close()))
 }
 
 func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
@@ -429,9 +385,7 @@ func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
 		Map  *Map     `ebpf:"valid"`
 	}
 
-	if err := spec.LoadAndAssign(&objs, nil); err != nil {
-		t.Fatal("Assign loads a map or program that isn't requested in the struct:", err)
-	}
+	mustLoadAndAssign(t, spec, &objs, nil)
 	defer objs.Prog.Close()
 	defer objs.Map.Close()
 
@@ -600,9 +554,7 @@ func TestCollectionAssign(t *testing.T) {
 		},
 	}
 
-	coll, err := NewCollection(cs)
-	qt.Assert(t, qt.IsNil(err))
-	defer coll.Close()
+	coll := mustNewCollection(t, cs, nil)
 
 	qt.Assert(t, qt.IsNil(coll.Assign(&objs)))
 	defer objs.Program.Close()
@@ -645,9 +597,7 @@ func TestCollectionAssignFail(t *testing.T) {
 		},
 	}
 
-	coll, err := NewCollection(cs)
-	qt.Assert(t, qt.IsNil(err))
-	defer coll.Close()
+	coll := mustNewCollection(t, cs, nil)
 
 	qt.Assert(t, qt.IsNotNil(coll.Assign(&objs)))
 
@@ -684,7 +634,7 @@ func TestIncompleteLoadAndAssign(t *testing.T) {
 		Invalid *Program `ebpf:"invalid"`
 	}{}
 
-	if err := spec.LoadAndAssign(&s, nil); err == nil {
+	if err := loadAndAssign(t, spec, &s, nil); err == nil {
 		t.Fatal("expected error loading invalid ProgramSpec")
 	}
 
diff --git a/elf_reader_test.go b/elf_reader_test.go
index 07711dd..6dbf51d 100644
--- a/elf_reader_test.go
+++ b/elf_reader_test.go
@@ -250,9 +250,7 @@ func TestLoadCollectionSpec(t *testing.T) {
 		qt.Assert(t, qt.Equals(have.Maps["perf_event_array"].ValueSize, 0))
 		qt.Assert(t, qt.IsNotNil(have.Maps["perf_event_array"].Value))
 
-		if diff := cmp.Diff(coll, have, csCmpOpts); diff != "" {
-			t.Errorf("MapSpec mismatch (-want +got):\n%s", diff)
-		}
+		qt.Assert(t, qt.CmpEquals(have, coll, csCmpOpts))
 
 		if have.ByteOrder != internal.NativeEndian {
 			return
diff --git a/helpers_test.go b/helpers_test.go
index 5768570..d6271b9 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -5,6 +5,9 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/go-quicktest/qt"
+
+	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/btf"
 	"github.com/cilium/ebpf/internal/testutils"
 )
@@ -35,3 +38,144 @@ func requireTestmod(tb testing.TB) {
 		tb.Skip("bpf_testmod not loaded")
 	}
 }
+
+func newMap(tb testing.TB, spec *MapSpec, opts *MapOptions) (*Map, error) {
+	tb.Helper()
+
+	if opts == nil {
+		opts = new(MapOptions)
+	}
+
+	m, err := NewMapWithOptions(spec, *opts)
+	testutils.SkipIfNotSupportedOnOS(tb, err)
+	if err != nil {
+		return nil, err
+	}
+
+	tb.Cleanup(func() { m.Close() })
+	return m, nil
+}
+
+func mustNewMap(tb testing.TB, spec *MapSpec, opts *MapOptions) *Map {
+	tb.Helper()
+
+	m, err := newMap(tb, spec, opts)
+	qt.Assert(tb, qt.IsNil(err))
+
+	return m
+}
+
+func createMap(tb testing.TB, typ MapType, maxEntries uint32) *Map {
+	tb.Helper()
+
+	return mustNewMap(tb, &MapSpec{
+		Name:       "test",
+		Type:       typ,
+		KeySize:    4,
+		ValueSize:  4,
+		MaxEntries: maxEntries,
+	}, nil)
+}
+
+func createMapInMap(tb testing.TB, outer, inner MapType) *Map {
+	tb.Helper()
+
+	return mustNewMap(tb, &MapSpec{
+		Type:       outer,
+		KeySize:    4,
+		MaxEntries: 2,
+		InnerMap: &MapSpec{
+			Type:       inner,
+			KeySize:    4,
+			ValueSize:  4,
+			MaxEntries: 2,
+		},
+	}, nil)
+}
+
+func newProgram(tb testing.TB, spec *ProgramSpec, opts *ProgramOptions) (*Program, error) {
+	tb.Helper()
+
+	if opts == nil {
+		opts = new(ProgramOptions)
+	}
+
+	prog, err := NewProgramWithOptions(spec, *opts)
+	testutils.SkipIfNotSupportedOnOS(tb, err)
+	if err != nil {
+		return nil, err
+	}
+
+	tb.Cleanup(func() { prog.Close() })
+	return prog, nil
+}
+
+func mustNewProgram(tb testing.TB, spec *ProgramSpec, opts *ProgramOptions) *Program {
+	prog, err := newProgram(tb, spec, opts)
+	qt.Assert(tb, qt.IsNil(err))
+	return prog
+}
+
+func createProgram(tb testing.TB, typ ProgramType, retval int64) *Program {
+	tb.Helper()
+
+	return mustNewProgram(tb, &ProgramSpec{
+		Name: "test",
+		Type: typ,
+		Instructions: asm.Instructions{
+			asm.LoadImm(asm.R0, retval, asm.DWord),
+			asm.Return(),
+		},
+		License: "MIT",
+	}, nil)
+}
+
+var basicProgramSpec = &ProgramSpec{
+	Name: "test",
+	Type: SocketFilter,
+	Instructions: asm.Instructions{
+		asm.LoadImm(asm.R0, 2, asm.DWord),
+		asm.Return(),
+	},
+	License: "MIT",
+}
+
+// createBasicProgram returns a program of an unspecified type which returns
+// a non-zero value when executed.
+func createBasicProgram(tb testing.TB) *Program {
+	return mustNewProgram(tb, basicProgramSpec, nil)
+}
+
+func newCollection(tb testing.TB, spec *CollectionSpec, opts *CollectionOptions) (*Collection, error) {
+	tb.Helper()
+
+	if opts == nil {
+		opts = new(CollectionOptions)
+	}
+
+	c, err := NewCollectionWithOptions(spec, *opts)
+	if err != nil {
+		return nil, err
+	}
+
+	tb.Cleanup(func() { c.Close() })
+	return c, nil
+}
+
+func mustNewCollection(tb testing.TB, spec *CollectionSpec, opts *CollectionOptions) *Collection {
+	tb.Helper()
+	c, err := newCollection(tb, spec, opts)
+	qt.Assert(tb, qt.IsNil(err))
+	return c
+}
+
+func loadAndAssign(tb testing.TB, spec *CollectionSpec, to any, opts *CollectionOptions) error {
+	tb.Helper()
+	err := spec.LoadAndAssign(to, opts)
+	testutils.SkipIfNotSupported(tb, err)
+	return err
+}
+
+func mustLoadAndAssign(tb testing.TB, spec *CollectionSpec, to any, opts *CollectionOptions) {
+	qt.Assert(tb, qt.IsNil(loadAndAssign(tb, spec, to, opts)))
+}
diff --git a/info_test.go b/info_test.go
index 7be0ea8..febdf44 100644
--- a/info_test.go
+++ b/info_test.go
@@ -43,21 +43,16 @@ var multiprogSpec = &ProgramSpec{
 }
 
 func TestMapInfoFromProc(t *testing.T) {
-	hash, err := NewMap(&MapSpec{
+	hash := mustNewMap(t, &MapSpec{
 		Type:       Hash,
 		KeySize:    4,
 		ValueSize:  5,
 		MaxEntries: 2,
 		Flags:      sys.BPF_F_NO_PREALLOC,
-	})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer hash.Close()
+	}, nil)
 
 	var info MapInfo
-	err = readMapInfoFromProc(hash.fd, &info)
+	err := readMapInfoFromProc(hash.fd, &info)
 	testutils.SkipIfNotSupported(t, err)
 
 	qt.Assert(t, qt.IsNil(err))
@@ -69,7 +64,7 @@ func TestMapInfoFromProc(t *testing.T) {
 }
 
 func TestMapInfoFromProcOuterMap(t *testing.T) {
-	outer, err := NewMap(&MapSpec{
+	outer := mustNewMap(t, &MapSpec{
 		Type:       ArrayOfMaps,
 		KeySize:    4,
 		MaxEntries: 2,
@@ -79,15 +74,10 @@ func TestMapInfoFromProcOuterMap(t *testing.T) {
 			ValueSize:  4,
 			MaxEntries: 2,
 		},
-	})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer outer.Close()
+	}, nil)
 
 	var info MapInfo
-	err = readMapInfoFromProc(outer.fd, &info)
+	err := readMapInfoFromProc(outer.fd, &info)
 	testutils.SkipIfNotSupported(t, err)
 
 	qt.Assert(t, qt.IsNil(err))
@@ -103,7 +93,7 @@ func validateProgInfo(t *testing.T, info *ProgramInfo) {
 }
 
 func TestProgramInfo(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	info, err := newProgramInfoFromFd(prog.fd)
 	testutils.SkipIfNotSupported(t, err)
@@ -165,7 +155,7 @@ func TestProgramInfo(t *testing.T) {
 }
 
 func TestProgramInfoProc(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	info, err := newProgramInfoFromProc(prog.fd)
 	testutils.SkipIfNotSupported(t, err)
@@ -175,10 +165,9 @@ func TestProgramInfoProc(t *testing.T) {
 }
 
 func TestProgramInfoBTF(t *testing.T) {
-	prog, err := NewProgram(multiprogSpec)
+	prog, err := newProgram(t, multiprogSpec, nil)
 	testutils.SkipIfNotSupported(t, err)
 	qt.Assert(t, qt.IsNil(err))
-	t.Cleanup(func() { prog.Close() })
 
 	info, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -227,16 +216,9 @@ func TestProgramInfoBTF(t *testing.T) {
 }
 
 func TestProgramInfoMapIDs(t *testing.T) {
-	arr, err := NewMap(&MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	qt.Assert(t, qt.IsNil(err))
-	defer arr.Close()
+	arr := createMap(t, Array, 1)
 
-	prog, err := NewProgram(&ProgramSpec{
+	prog := mustNewProgram(t, &ProgramSpec{
 		Type: SocketFilter,
 		Instructions: asm.Instructions{
 			asm.LoadMapPtr(asm.R0, arr.FD()),
@@ -244,9 +226,7 @@ func TestProgramInfoMapIDs(t *testing.T) {
 			asm.Return(),
 		},
 		License: "MIT",
-	})
-	qt.Assert(t, qt.IsNil(err))
-	defer prog.Close()
+	}, nil)
 
 	info, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -271,16 +251,7 @@ func TestProgramInfoMapIDs(t *testing.T) {
 }
 
 func TestProgramInfoMapIDsNoMaps(t *testing.T) {
-	prog, err := NewProgram(&ProgramSpec{
-		Type: SocketFilter,
-		Instructions: asm.Instructions{
-			asm.LoadImm(asm.R0, 0, asm.DWord),
-			asm.Return(),
-		},
-		License: "MIT",
-	})
-	qt.Assert(t, qt.IsNil(err))
-	defer prog.Close()
+	prog := createBasicProgram(t)
 
 	info, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -328,7 +299,7 @@ func TestScanFdInfoReader(t *testing.T) {
 func TestStats(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.8", "BPF_ENABLE_STATS")
 
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	pi, err := prog.Info()
 	if err != nil {
@@ -368,7 +339,7 @@ func TestStats(t *testing.T) {
 func BenchmarkStats(b *testing.B) {
 	testutils.SkipOnOldKernel(b, "5.8", "BPF_ENABLE_STATS")
 
-	prog := mustSocketFilter(b)
+	prog := createBasicProgram(b)
 
 	for n := 0; n < b.N; n++ {
 		if err := testStats(prog); err != nil {
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))
+		})
+	}
+}
diff --git a/linker_test.go b/linker_test.go
index cbe31d6..bd387a7 100644
--- a/linker_test.go
+++ b/linker_test.go
@@ -41,12 +41,9 @@ func TestFindReferences(t *testing.T) {
 
 	flattenPrograms(progs, []string{"entrypoint"})
 
-	prog, err := NewProgram(progs["entrypoint"])
+	prog, err := newProgram(t, progs["entrypoint"], nil)
 	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	qt.Assert(t, qt.IsNil(err))
 
 	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	if err != nil {
@@ -68,7 +65,7 @@ func TestForwardFunctionDeclaration(t *testing.T) {
 	spec := coll.Programs["call_fwd"]
 
 	// This program calls an unimplemented forward function declaration.
-	_, err = NewProgram(spec)
+	_, err = newProgram(t, spec, nil)
 	if !errors.Is(err, asm.ErrUnsatisfiedProgramReference) {
 		t.Fatal("Expected an error wrapping ErrUnsatisfiedProgramReference, got:", err)
 	}
@@ -90,12 +87,9 @@ func TestForwardFunctionDeclaration(t *testing.T) {
 		}
 	}
 
-	prog, err := NewProgram(spec)
+	prog, err := newProgram(t, spec, nil)
 	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatalf("%+v", err)
-	}
-	defer prog.Close()
+	qt.Assert(t, qt.IsNil(err))
 
 	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	if err != nil {
diff --git a/map_test.go b/map_test.go
index 56e65b9..6f4f1e9 100644
--- a/map_test.go
+++ b/map_test.go
@@ -7,6 +7,7 @@ import (
 	"math"
 	"os"
 	"path/filepath"
+	"slices"
 	"sort"
 	"testing"
 	"unsafe"
@@ -32,23 +33,8 @@ var (
 	}
 )
 
-// newHash returns a new Map of type Hash. Cleanup is handled automatically.
-func newHash(t *testing.T) *Map {
-	hash, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    5,
-		ValueSize:  4,
-		MaxEntries: 10,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Cleanup(func() { hash.Close() })
-	return hash
-}
-
 func TestMap(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 
 	t.Log(m)
 
@@ -124,20 +110,6 @@ func TestMapBatch(t *testing.T) {
 		42, 4242, 23, 2323,
 	}
 
-	mustNewMap := func(t *testing.T, mapType MapType, max uint32) *Map {
-		m, err := NewMap(&MapSpec{
-			Type:       mapType,
-			KeySize:    4,
-			ValueSize:  4,
-			MaxEntries: max,
-		})
-		if err != nil {
-			t.Fatal(err)
-		}
-		t.Cleanup(func() { m.Close() })
-		return m
-	}
-
 	keysAndValuesForMap := func(m *Map, contents []uint32) (keys, values []uint32, stride int) {
 		possibleCPU := 1
 		if m.Type().hasPerCPUValue() {
@@ -163,7 +135,7 @@ func TestMapBatch(t *testing.T) {
 				testutils.SkipOnOldKernel(t, "5.13", "batched ops support for percpu array")
 			}
 
-			m := mustNewMap(t, typ, uint32(len(contents)))
+			m := createMap(t, typ, uint32(len(contents)))
 			keys, values, _ := keysAndValuesForMap(m, contents)
 			count, err := m.BatchUpdate(keys, values, nil)
 			qt.Assert(t, qt.IsNil(err))
@@ -187,7 +159,7 @@ func TestMapBatch(t *testing.T) {
 
 	for _, typ := range []MapType{Hash, PerCPUHash} {
 		t.Run(typ.String(), func(t *testing.T) {
-			m := mustNewMap(t, typ, uint32(len(contents)))
+			m := createMap(t, typ, uint32(len(contents)))
 			keys, values, stride := keysAndValuesForMap(m, contents)
 			count, err := m.BatchUpdate(keys, values, nil)
 			qt.Assert(t, qt.IsNil(err))
@@ -229,29 +201,13 @@ func TestMapBatch(t *testing.T) {
 }
 
 func TestMapBatchCursorReuse(t *testing.T) {
-	spec := &MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 4,
-	}
-
-	arr1, err := NewMap(spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer arr1.Close()
-
-	arr2, err := NewMap(spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer arr2.Close()
+	arr1 := createMap(t, Array, 4)
+	arr2 := createMap(t, Array, 4)
 
 	tmp := make([]uint32, 2)
 
 	var cursor MapBatchCursor
-	_, err = arr1.BatchLookup(&cursor, tmp, tmp, nil)
+	_, err := arr1.BatchLookup(&cursor, tmp, tmp, nil)
 	testutils.SkipIfNotSupported(t, err)
 	qt.Assert(t, qt.IsNil(err))
 
@@ -260,7 +216,7 @@ func TestMapBatchCursorReuse(t *testing.T) {
 }
 
 func TestMapLookupKeyTooSmall(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 	defer m.Close()
 
 	var small uint16
@@ -269,7 +225,7 @@ func TestMapLookupKeyTooSmall(t *testing.T) {
 }
 
 func TestMapLookupKeyNotFoundAllocations(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 	defer m.Close()
 	var key, out uint32 = 3, 0
 
@@ -283,16 +239,8 @@ func TestBatchAPIMapDelete(t *testing.T) {
 	if err := haveBatchAPI(); err != nil {
 		t.Skipf("batch api not available: %v", err)
 	}
-	m, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 10,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer m.Close()
+
+	m := createMap(t, Hash, 10)
 
 	var (
 		keys   = []uint32{0, 1}
@@ -329,7 +277,7 @@ func TestBatchAPIMapDelete(t *testing.T) {
 }
 
 func TestMapClose(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 
 	if err := m.Close(); err != nil {
 		t.Fatal("Can't close map:", err)
@@ -472,7 +420,7 @@ func TestMapCloneNil(t *testing.T) {
 }
 
 func TestMapPin(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 
 	if err := m.Put(uint32(0), uint32(42)); err != nil {
 		t.Fatal("Can't put:", err)
@@ -508,23 +456,7 @@ func TestMapPin(t *testing.T) {
 }
 
 func TestNestedMapPin(t *testing.T) {
-	m, err := NewMap(&MapSpec{
-		Type:       ArrayOfMaps,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 2,
-		InnerMap: &MapSpec{
-			Type:       Array,
-			KeySize:    4,
-			ValueSize:  4,
-			MaxEntries: 1,
-		},
-	})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer m.Close()
+	m := createMapInMap(t, ArrayOfMaps, Array)
 
 	tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test")
 	if err != nil {
@@ -547,7 +479,7 @@ func TestNestedMapPin(t *testing.T) {
 }
 
 func TestNestedMapPinNested(t *testing.T) {
-	if _, err := NewMap(&MapSpec{
+	if _, err := newMap(t, &MapSpec{
 		Type:       ArrayOfMaps,
 		KeySize:    4,
 		ValueSize:  4,
@@ -560,7 +492,7 @@ func TestNestedMapPinNested(t *testing.T) {
 			MaxEntries: 1,
 			Pinning:    PinByName,
 		},
-	}); err == nil {
+	}, nil); err == nil {
 		t.Error("Inner maps should not be pinnable")
 	}
 }
@@ -572,16 +504,12 @@ func TestMapPinMultiple(t *testing.T) {
 
 	spec := spec1.Copy()
 
-	m1, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	if err != nil {
-		t.Fatal("Can't create map:", err)
-	}
-	defer m1.Close()
+	m1 := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 	pinned := m1.IsPinned()
 	qt.Assert(t, qt.IsTrue(pinned))
 
 	newPath := filepath.Join(tmp, "bar")
-	err = m1.Pin(newPath)
+	err := m1.Pin(newPath)
 	testutils.SkipIfNotSupported(t, err)
 	qt.Assert(t, qt.IsNil(err))
 	oldPath := filepath.Join(tmp, spec.Name)
@@ -596,7 +524,7 @@ func TestMapPinMultiple(t *testing.T) {
 }
 
 func TestMapPinWithEmptyPath(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 
 	err := m.Pin("")
 
@@ -609,16 +537,9 @@ func TestMapPinFailReplace(t *testing.T) {
 	spec2 := spec1.Copy()
 	spec2.Name = spec1.Name + "bar"
 
-	m, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	if err != nil {
-		t.Fatal("Failed to create map:", err)
-	}
-	defer m.Close()
-	m2, err := NewMapWithOptions(spec2, MapOptions{PinPath: tmp})
-	if err != nil {
-		t.Fatal("Failed to create map2:", err)
-	}
-	defer m2.Close()
+	m := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
+	_ = mustNewMap(t, spec2, &MapOptions{PinPath: tmp})
+
 	qt.Assert(t, qt.IsTrue(m.IsPinned()))
 	newPath := filepath.Join(tmp, spec2.Name)
 
@@ -630,11 +551,7 @@ func TestMapUnpin(t *testing.T) {
 	tmp := testutils.TempBPFFS(t)
 	spec := spec1.Copy()
 
-	m, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	if err != nil {
-		t.Fatal("Failed to create map:", err)
-	}
-	defer m.Close()
+	m := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 
 	pinned := m.IsPinned()
 	qt.Assert(t, qt.IsTrue(pinned))
@@ -657,9 +574,7 @@ func TestMapLoadPinned(t *testing.T) {
 
 	spec := spec1.Copy()
 
-	m1, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	qt.Assert(t, qt.IsNil(err))
-	defer m1.Close()
+	m1 := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 	pinned := m1.IsPinned()
 	qt.Assert(t, qt.IsTrue(pinned))
 
@@ -691,13 +606,8 @@ func TestMapLoadReusePinned(t *testing.T) {
 				Pinning:    PinByName,
 			}
 
-			m1, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-			qt.Assert(t, qt.IsNil(err))
-			defer m1.Close()
-
-			m2, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-			qt.Assert(t, qt.IsNil(err))
-			defer m2.Close()
+			_ = mustNewMap(t, spec, &MapOptions{PinPath: tmp})
+			_ = mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 		})
 	}
 }
@@ -707,9 +617,7 @@ func TestMapLoadPinnedUnpin(t *testing.T) {
 
 	spec := spec1.Copy()
 
-	m1, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	qt.Assert(t, qt.IsNil(err))
-	defer m1.Close()
+	m1 := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 	pinned := m1.IsPinned()
 	qt.Assert(t, qt.IsTrue(pinned))
 
@@ -728,7 +636,7 @@ func TestMapLoadPinnedWithOptions(t *testing.T) {
 	// Introduced in commit 6e71b04a8224.
 	testutils.SkipOnOldKernel(t, "4.15", "file_flags in BPF_OBJ_GET")
 
-	array := createArray(t)
+	array := createMap(t, Array, 2)
 
 	tmp := testutils.TempBPFFS(t)
 
@@ -785,13 +693,9 @@ func TestMapPinFlags(t *testing.T) {
 		Pinning:    PinByName,
 	}
 
-	m, err := NewMapWithOptions(spec, MapOptions{
-		PinPath: tmp,
-	})
-	qt.Assert(t, qt.IsNil(err))
-	m.Close()
+	_ = mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 
-	_, err = NewMapWithOptions(spec, MapOptions{
+	_, err := newMap(t, spec, &MapOptions{
 		PinPath: tmp,
 		LoadPinOptions: LoadPinOptions{
 			Flags: math.MaxUint32,
@@ -802,34 +706,14 @@ func TestMapPinFlags(t *testing.T) {
 	}
 }
 
-func createArray(t *testing.T) *Map {
-	t.Helper()
-
-	m, err := NewMap(&MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 2,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Cleanup(func() { m.Close() })
-	return m
-}
-
 func TestMapQueue(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.20", "map type queue")
 
-	m, err := NewMap(&MapSpec{
+	m := mustNewMap(t, &MapSpec{
 		Type:       Queue,
 		ValueSize:  4,
 		MaxEntries: 2,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer m.Close()
+	}, nil)
 
 	for _, v := range []uint32{42, 4242} {
 		if err := m.Put(nil, v); err != nil {
@@ -873,34 +757,12 @@ func TestMapQueue(t *testing.T) {
 func TestMapInMap(t *testing.T) {
 	for _, typ := range []MapType{ArrayOfMaps, HashOfMaps} {
 		t.Run(typ.String(), func(t *testing.T) {
-			spec := &MapSpec{
-				Type:       typ,
-				KeySize:    4,
-				MaxEntries: 2,
-				InnerMap: &MapSpec{
-					Type:       Array,
-					KeySize:    4,
-					ValueSize:  4,
-					MaxEntries: 2,
-				},
-			}
-
-			inner, err := NewMap(spec.InnerMap)
-			if err != nil {
-				t.Fatal(err)
-			}
+			inner := createMap(t, Array, 2)
 			if err := inner.Put(uint32(1), uint32(4242)); err != nil {
 				t.Fatal(err)
 			}
-			defer inner.Close()
-
-			outer, err := NewMap(spec)
-			testutils.SkipIfNotSupported(t, err)
-			if err != nil {
-				t.Fatal(err)
-			}
-			defer outer.Close()
 
+			outer := createMapInMap(t, typ, Array)
 			if err := outer.Put(uint32(0), inner); err != nil {
 				t.Fatal("Can't put inner map:", err)
 			}
@@ -939,22 +801,7 @@ func TestMapInMap(t *testing.T) {
 }
 
 func TestNewMapInMapFromFD(t *testing.T) {
-	nested, err := NewMap(&MapSpec{
-		Type:       ArrayOfMaps,
-		KeySize:    4,
-		MaxEntries: 2,
-		InnerMap: &MapSpec{
-			Type:       Array,
-			KeySize:    4,
-			ValueSize:  4,
-			MaxEntries: 2,
-		},
-	})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nested.Close()
+	nested := createMapInMap(t, ArrayOfMaps, Array)
 
 	// Do not copy this, use Clone instead.
 	another, err := NewMapFromFD(dupFD(t, nested.FD()))
@@ -972,45 +819,15 @@ func TestPerfEventArray(t *testing.T) {
 	}
 
 	for _, spec := range specs {
-		m, err := NewMap(spec)
-		if err != nil {
-			t.Errorf("Can't create perf event array from %v: %s", spec, err)
-		} else {
-			m.Close()
-		}
+		_ = mustNewMap(t, spec, nil)
 	}
 }
 
 func TestCPUMap(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.15", "cpu map")
 
-	m, err := NewMap(&MapSpec{Type: CPUMap, KeySize: 4, ValueSize: 4})
-	qt.Assert(t, qt.IsNil(err))
+	m := mustNewMap(t, &MapSpec{Type: CPUMap, KeySize: 4, ValueSize: 4}, nil)
 	qt.Assert(t, qt.Equals(m.MaxEntries(), uint32(MustPossibleCPU())))
-	m.Close()
-}
-
-func createMapInMap(t *testing.T, typ MapType) *Map {
-	t.Helper()
-
-	spec := &MapSpec{
-		Type:       typ,
-		KeySize:    4,
-		MaxEntries: 2,
-		InnerMap: &MapSpec{
-			Type:       Array,
-			KeySize:    4,
-			ValueSize:  4,
-			MaxEntries: 2,
-		},
-	}
-
-	m, err := NewMap(spec)
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	return m
 }
 
 func TestMapInMapValueSize(t *testing.T) {
@@ -1027,41 +844,28 @@ func TestMapInMapValueSize(t *testing.T) {
 		},
 	}
 
-	m, err := NewMap(spec)
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	m.Close()
+	_ = mustNewMap(t, spec, nil)
 
 	spec.ValueSize = 4
-	m, err = NewMap(spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	m.Close()
+	_ = mustNewMap(t, spec, nil)
 
 	spec.ValueSize = 1
-	if _, err := NewMap(spec); err == nil {
-		t.Fatal("Expected an error")
-	}
+	_, err := newMap(t, spec, nil)
+	qt.Assert(t, qt.IsNotNil(err))
 }
 
 func TestIterateEmptyMap(t *testing.T) {
 	makeMap := func(t *testing.T, mapType MapType) *Map {
-		m, err := NewMap(&MapSpec{
+		m, err := newMap(t, &MapSpec{
 			Type:       mapType,
 			KeySize:    4,
 			ValueSize:  8,
 			MaxEntries: 2,
-		})
+		}, nil)
 		if errors.Is(err, unix.EINVAL) {
 			t.Skip(mapType, "is not supported")
 		}
-		if err != nil {
-			t.Fatal("Can't create map:", err)
-		}
-		t.Cleanup(func() { m.Close() })
+		qt.Assert(t, qt.IsNil(err))
 		return m
 	}
 
@@ -1104,18 +908,10 @@ func TestIterateEmptyMap(t *testing.T) {
 }
 
 func TestMapIterate(t *testing.T) {
-	hash, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    5,
-		ValueSize:  4,
-		MaxEntries: 2,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer hash.Close()
+	hash := createMap(t, Hash, 2)
 
-	data := []string{"hello", "world"}
+	data := []string{"test", "more"}
+	slices.Sort(data)
 	for i, k := range data {
 		if err := hash.Put(k, uint32(i)); err != nil {
 			t.Fatal(err)
@@ -1139,7 +935,7 @@ func TestMapIterate(t *testing.T) {
 func TestIterateWrongMap(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.20", "map type queue")
 
-	m, err := NewMap(&MapSpec{
+	m := mustNewMap(t, &MapSpec{
 		Type:       Queue,
 		ValueSize:  4,
 		MaxEntries: 2,
@@ -1147,9 +943,7 @@ func TestIterateWrongMap(t *testing.T) {
 			{nil, uint32(0)},
 			{nil, uint32(1)},
 		},
-	})
-	qt.Assert(t, qt.IsNil(err))
-	defer m.Close()
+	}, nil)
 
 	var value uint32
 	entries := m.Iterate()
@@ -1159,16 +953,7 @@ func TestIterateWrongMap(t *testing.T) {
 }
 
 func TestMapIteratorAllocations(t *testing.T) {
-	arr, err := NewMap(&MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 10,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer arr.Close()
+	arr := createMap(t, Array, 10)
 
 	var k, v uint32
 	iter := arr.Iterate()
@@ -1186,16 +971,7 @@ func TestMapIteratorAllocations(t *testing.T) {
 func TestMapBatchLookupAllocations(t *testing.T) {
 	testutils.SkipIfNotSupported(t, haveBatchAPI())
 
-	arr, err := NewMap(&MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 10,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer arr.Close()
+	arr := createMap(t, Array, 10)
 
 	var cursor MapBatchCursor
 	tmp := make([]uint32, 2)
@@ -1213,16 +989,12 @@ func TestMapBatchLookupAllocations(t *testing.T) {
 }
 
 func TestMapIterateHashKeyOneByteFull(t *testing.T) {
-	hash, err := NewMap(&MapSpec{
+	hash := mustNewMap(t, &MapSpec{
 		Type:       Hash,
 		KeySize:    1,
 		ValueSize:  1,
 		MaxEntries: 256,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer hash.Close()
+	}, nil)
 
 	for i := 0; i < int(hash.MaxEntries()); i++ {
 		if err := hash.Put(uint8(i), uint8(i)); err != nil {
@@ -1289,16 +1061,12 @@ func TestMapGuessNonExistentKey(t *testing.T) {
 				maxEntries = 1
 			}
 
-			m, err := NewMap(&MapSpec{
+			m := mustNewMap(t, &MapSpec{
 				Type:       tt.mapType,
 				KeySize:    4,
 				ValueSize:  4,
 				MaxEntries: maxEntries,
-			})
-			if err != nil {
-				t.Fatal(err)
-			}
-			defer m.Close()
+			}, nil)
 
 			for _, key := range tt.keys {
 				if err := m.Put(key, key); err != nil {
@@ -1325,16 +1093,12 @@ func TestMapGuessNonExistentKey(t *testing.T) {
 	t.Run("Hash: full", func(t *testing.T) {
 		const n = math.MaxUint8 + 1
 
-		hash, err := NewMap(&MapSpec{
+		hash := mustNewMap(t, &MapSpec{
 			Type:       Hash,
 			KeySize:    1,
 			ValueSize:  1,
 			MaxEntries: n,
-		})
-		if err != nil {
-			t.Fatal(err)
-		}
-		defer hash.Close()
+		}, nil)
 
 		for i := 0; i < n; i++ {
 			if err := hash.Put(uint8(i), uint8(i)); err != nil {
@@ -1342,7 +1106,7 @@ func TestMapGuessNonExistentKey(t *testing.T) {
 			}
 		}
 
-		_, err = hash.guessNonExistentKey()
+		_, err := hash.guessNonExistentKey()
 		if err == nil {
 			t.Fatal("guessNonExistentKey doesn't return error on full hash table")
 		}
@@ -1350,15 +1114,15 @@ func TestMapGuessNonExistentKey(t *testing.T) {
 }
 
 func TestNotExist(t *testing.T) {
-	hash := newHash(t)
+	hash := createMap(t, Hash, 10)
 
 	var tmp uint32
-	err := hash.Lookup("hello", &tmp)
+	err := hash.Lookup("test", &tmp)
 	if !errors.Is(err, ErrKeyNotExist) {
 		t.Error("Lookup doesn't return ErrKeyNotExist")
 	}
 
-	buf, err := hash.LookupBytes("hello")
+	buf, err := hash.LookupBytes("test")
 	if err != nil {
 		t.Error("Looking up non-existent key return an error:", err)
 	}
@@ -1366,11 +1130,11 @@ func TestNotExist(t *testing.T) {
 		t.Error("LookupBytes returns non-nil buffer for non-existent key")
 	}
 
-	if err := hash.Delete("hello"); !errors.Is(err, ErrKeyNotExist) {
+	if err := hash.Delete("test"); !errors.Is(err, ErrKeyNotExist) {
 		t.Error("Deleting unknown key doesn't return ErrKeyNotExist", err)
 	}
 
-	var k = []byte{1, 2, 3, 4, 5}
+	var k = []byte{1, 2, 3, 4}
 	if err := hash.NextKey(&k, &tmp); !errors.Is(err, ErrKeyNotExist) {
 		t.Error("Looking up next key in empty map doesn't return a non-existing error", err)
 	}
@@ -1381,13 +1145,13 @@ func TestNotExist(t *testing.T) {
 }
 
 func TestExist(t *testing.T) {
-	hash := newHash(t)
+	hash := createMap(t, Hash, 10)
 
-	if err := hash.Put("hello", uint32(21)); err != nil {
+	if err := hash.Put("test", uint32(21)); err != nil {
 		t.Errorf("Failed to put key/value pair into hash: %v", err)
 	}
 
-	if err := hash.Update("hello", uint32(42), UpdateNoExist); !errors.Is(err, ErrKeyExist) {
+	if err := hash.Update("test", uint32(42), UpdateNoExist); !errors.Is(err, ErrKeyExist) {
 		t.Error("Updating existing key doesn't return ErrKeyExist")
 	}
 }
@@ -1395,10 +1159,10 @@ func TestExist(t *testing.T) {
 func TestIterateMapInMap(t *testing.T) {
 	const idx = uint32(1)
 
-	parent := createMapInMap(t, ArrayOfMaps)
+	parent := createMapInMap(t, ArrayOfMaps, Array)
 	defer parent.Close()
 
-	a := createArray(t)
+	a := createMap(t, Array, 2)
 
 	if err := parent.Put(idx, a); err != nil {
 		t.Fatal(err)
@@ -1438,20 +1202,11 @@ func TestPerCPUMarshaling(t *testing.T) {
 				testutils.SkipOnOldKernel(t, "4.10", "LRU per-CPU hash")
 			}
 
-			arr, err := NewMap(&MapSpec{
-				Type:       typ,
-				KeySize:    4,
-				ValueSize:  5,
-				MaxEntries: 1,
-			})
-			if err != nil {
-				t.Fatal(err)
-			}
-			defer arr.Close()
+			arr := createMap(t, typ, 1)
 
 			values := []*customEncoding{
-				{"hello"},
-				{"world"},
+				{"test"},
+				{"more"},
 			}
 			if err := arr.Put(uint32(0), values); err != nil {
 				t.Fatal(err)
@@ -1473,7 +1228,7 @@ func TestPerCPUMarshaling(t *testing.T) {
 				t.Fatal("Can't retrieve key 0:", err)
 			}
 
-			for i, want := range []string{"HELLO", "WORLD"} {
+			for i, want := range []string{"TEST", "MORE"} {
 				if retrieved[i] == nil {
 					t.Error("First item is nil")
 				} else if have := retrieved[i].data; have != want {
@@ -1500,19 +1255,13 @@ func TestCgroupPerCPUStorageMarshaling(t *testing.T) {
 
 	cgroup := testutils.CreateCgroup(t)
 
-	arr, err := NewMap(&MapSpec{
+	arr := mustNewMap(t, &MapSpec{
 		Type:      PerCPUCGroupStorage,
 		KeySize:   uint32(unsafe.Sizeof(bpfCgroupStorageKey{})),
 		ValueSize: uint32(unsafe.Sizeof(uint64(0))),
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Cleanup(func() {
-		arr.Close()
-	})
+	}, nil)
 
-	prog, err := NewProgram(&ProgramSpec{
+	prog := mustNewProgram(t, &ProgramSpec{
 		Type:       CGroupSKB,
 		AttachType: AttachCGroupInetEgress,
 		License:    "MIT",
@@ -1523,11 +1272,7 @@ func TestCgroupPerCPUStorageMarshaling(t *testing.T) {
 			asm.Mov.Imm(asm.R0, 0),
 			asm.Return(),
 		},
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	}, nil)
 
 	progAttachAttrs := sys.ProgAttachAttr{
 		TargetFdOrIfindex: uint32(cgroup.Fd()),
@@ -1536,7 +1281,7 @@ func TestCgroupPerCPUStorageMarshaling(t *testing.T) {
 		AttachFlags:       0,
 		ReplaceBpfFd:      0,
 	}
-	err = sys.ProgAttach(&progAttachAttrs)
+	err := sys.ProgAttach(&progAttachAttrs)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1576,16 +1321,7 @@ func TestCgroupPerCPUStorageMarshaling(t *testing.T) {
 }
 
 func TestMapMarshalUnsafe(t *testing.T) {
-	m, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer m.Close()
+	m := createMap(t, Hash, 1)
 
 	key := uint32(1)
 	value := uint32(42)
@@ -1631,17 +1367,7 @@ func TestMapName(t *testing.T) {
 		t.Skip(err)
 	}
 
-	m, err := NewMap(&MapSpec{
-		Name:       "test",
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer m.Close()
+	m := createMap(t, Array, 1)
 
 	var info sys.MapInfo
 	if err := sys.ObjInfo(m.fd, &info); err != nil {
@@ -1654,7 +1380,7 @@ func TestMapName(t *testing.T) {
 }
 
 func TestMapFromFD(t *testing.T) {
-	m := createArray(t)
+	m := createMap(t, Array, 2)
 
 	if err := m.Put(uint32(0), uint32(123)); err != nil {
 		t.Fatal(err)
@@ -1691,11 +1417,7 @@ func TestMapContents(t *testing.T) {
 		},
 	}
 
-	m, err := NewMap(spec)
-	if err != nil {
-		t.Fatal("Can't create map:", err)
-	}
-	defer m.Close()
+	m := mustNewMap(t, spec, nil)
 
 	var value uint32
 	if err := m.Lookup(uint32(0), &value); err != nil {
@@ -1715,13 +1437,13 @@ func TestMapContents(t *testing.T) {
 		{uint32(14), uint32(0)},
 	}
 
-	if _, err = NewMap(spec); err == nil {
-		t.Error("Invalid contents should be rejected")
-	}
+	// Invalid contents should be rejected
+	_, err := newMap(t, spec, nil)
+	qt.Assert(t, qt.IsNotNil(err))
 }
 
 func TestMapFreeze(t *testing.T) {
-	arr := createArray(t)
+	arr := createMap(t, Array, 2)
 
 	err := arr.Freeze()
 	testutils.SkipIfNotSupported(t, err)
@@ -1741,7 +1463,7 @@ func TestMapGetNextID(t *testing.T) {
 	var err error
 
 	// Ensure there is at least one map on the system.
-	_ = newHash(t)
+	_ = createMap(t, Hash, 10)
 
 	if next, err = MapGetNextID(MapID(0)); err != nil {
 		t.Fatal("Can't get next ID:", err)
@@ -1767,7 +1489,7 @@ func TestMapGetNextID(t *testing.T) {
 }
 
 func TestNewMapFromID(t *testing.T) {
-	hash := newHash(t)
+	hash := createMap(t, Hash, 10)
 
 	info, err := hash.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -1805,11 +1527,7 @@ func TestMapPinning(t *testing.T) {
 		Pinning:    PinByName,
 	}
 
-	m1, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	if err != nil {
-		t.Fatal("Can't create map:", err)
-	}
-	defer m1.Close()
+	m1 := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 	pinned := m1.IsPinned()
 	qt.Assert(t, qt.IsTrue(pinned))
 
@@ -1820,12 +1538,7 @@ func TestMapPinning(t *testing.T) {
 		t.Fatal("Can't write value:", err)
 	}
 
-	m2, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal("Can't create map:", err)
-	}
-	defer m2.Close()
+	m2 := mustNewMap(t, spec, &MapOptions{PinPath: tmp})
 
 	m2Info, err := m2.Info()
 	qt.Assert(t, qt.IsNil(err))
@@ -1846,9 +1559,8 @@ func TestMapPinning(t *testing.T) {
 
 	spec.KeySize = 8
 	spec.ValueSize = 8
-	m3, err := NewMapWithOptions(spec, MapOptions{PinPath: tmp})
+	_, err = newMap(t, spec, &MapOptions{PinPath: tmp})
 	if err == nil {
-		m3.Close()
 		t.Fatalf("Opening a pinned map with a mismatching spec did not fail")
 	}
 	if !errors.Is(err, ErrMapIncompatible) {
@@ -1864,16 +1576,14 @@ func TestMapHandle(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.18", "btf_id in map info")
 
 	kv := &btf.Int{Size: 4}
-	m, err := NewMap(&MapSpec{
+	m := mustNewMap(t, &MapSpec{
 		Type:       Hash,
 		KeySize:    kv.Size,
 		ValueSize:  kv.Size,
 		Key:        kv,
 		Value:      kv,
 		MaxEntries: 1,
-	})
-	qt.Assert(t, qt.IsNil(err))
-	defer m.Close()
+	}, nil)
 
 	h, err := m.Handle()
 	qt.Assert(t, qt.IsNil(err))
@@ -1893,10 +1603,7 @@ func TestPerfEventArrayCompatible(t *testing.T) {
 		Type: PerfEventArray,
 	}
 
-	m, err := NewMap(ms)
-	qt.Assert(t, qt.IsNil(err))
-	defer m.Close()
-
+	m := mustNewMap(t, ms, nil)
 	qt.Assert(t, qt.IsNil(ms.Compatible(m)))
 
 	ms.MaxEntries = m.MaxEntries() - 1
@@ -1904,8 +1611,8 @@ func TestPerfEventArrayCompatible(t *testing.T) {
 }
 
 func TestLoadWrongPin(t *testing.T) {
-	p := mustSocketFilter(t)
-	m := newHash(t)
+	p := createBasicProgram(t)
+	m := createMap(t, Hash, 10)
 	tmp := testutils.TempBPFFS(t)
 
 	ppath := filepath.Join(tmp, "prog")
@@ -1976,16 +1683,12 @@ func (bk *benchKey) MarshalBinary() ([]byte, error) {
 
 func BenchmarkMarshaling(b *testing.B) {
 	newMap := func(valueSize uint32) *Map {
-		m, err := NewMap(&MapSpec{
+		return mustNewMap(b, &MapSpec{
 			Type:       Hash,
 			KeySize:    8,
 			ValueSize:  valueSize,
 			MaxEntries: 1,
-		})
-		if err != nil {
-			b.Fatal(err)
-		}
-		return m
+		}, nil)
 	}
 
 	key := uint64(0)
@@ -2075,17 +1778,13 @@ func BenchmarkPerCPUMarshalling(b *testing.B) {
 		val[i] = uint64(i)
 	}
 
-	m, err := NewMap(&MapSpec{
+	m := mustNewMap(b, &MapSpec{
 		Type:       PerCPUHash,
 		KeySize:    8,
 		ValueSize:  8,
 		MaxEntries: 1,
-	})
-	if err != nil {
-		b.Fatal(err)
-	}
+	}, nil)
 
-	b.Cleanup(func() { m.Close() })
 	if err := m.Put(key, val[0:]); err != nil {
 		b.Fatal(err)
 	}
@@ -2106,16 +1805,7 @@ func BenchmarkPerCPUMarshalling(b *testing.B) {
 }
 
 func BenchmarkMap(b *testing.B) {
-	m, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	if err != nil {
-		b.Fatal(err)
-	}
-	b.Cleanup(func() { m.Close() })
+	m := createMap(b, Hash, 1)
 
 	if err := m.Put(uint32(0), uint32(42)); err != nil {
 		b.Fatal(err)
@@ -2176,18 +1866,13 @@ func BenchmarkMap(b *testing.B) {
 
 func BenchmarkIterate(b *testing.B) {
 	for _, mt := range []MapType{Hash, PerCPUHash} {
-		m, err := NewMap(&MapSpec{
+		m := mustNewMap(b, &MapSpec{
 			Type:       mt,
 			KeySize:    8,
 			ValueSize:  8,
 			MaxEntries: 1000,
-		})
-		if err != nil {
-			b.Fatal(err)
-		}
-		b.Cleanup(func() {
-			m.Close()
-		})
+		}, nil)
+
 		possibleCPU := 1
 		if m.Type().hasPerCPUValue() {
 			possibleCPU = MustPossibleCPU()
@@ -2205,7 +1890,7 @@ func BenchmarkIterate(b *testing.B) {
 			}
 		}
 
-		_, err = m.BatchUpdate(keys, values, nil)
+		_, err := m.BatchUpdate(keys, values, nil)
 		testutils.SkipIfNotSupported(b, err)
 		qt.Assert(b, qt.IsNil(err))
 
diff --git a/marshaler_example_test.go b/marshaler_example_test.go
index 6228ac8..e9d1b78 100644
--- a/marshaler_example_test.go
+++ b/marshaler_example_test.go
@@ -55,6 +55,4 @@ func Example_customMarshaler() {
 	if err := entries.Err(); err != nil {
 		panic(err)
 	}
-
-	// Output: key: HELLO, value: 111
 }
diff --git a/memory_test.go b/memory_test.go
index fdb992a..92f0306 100644
--- a/memory_test.go
+++ b/memory_test.go
@@ -16,19 +16,16 @@ import (
 func mustMmapableArray(tb testing.TB, extraFlags uint32) *Map {
 	tb.Helper()
 
-	m, err := NewMap(&MapSpec{
+	m, err := newMap(tb, &MapSpec{
 		Name:       "ebpf_mmap",
 		Type:       Array,
 		KeySize:    4,
 		ValueSize:  8,
 		MaxEntries: 8,
 		Flags:      sys.BPF_F_MMAPABLE | extraFlags,
-	})
+	}, nil)
 	testutils.SkipIfNotSupported(tb, err)
 	qt.Assert(tb, qt.IsNil(err))
-	tb.Cleanup(func() {
-		m.Close()
-	})
 	return m
 }
 
diff --git a/prog_test.go b/prog_test.go
index 7781a0c..ff2fc07 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -56,16 +56,12 @@ func TestProgramRun(t *testing.T) {
 
 	t.Log(ins)
 
-	prog, err := NewProgram(&ProgramSpec{
+	prog := mustNewProgram(t, &ProgramSpec{
 		Name:         "test",
 		Type:         XDP,
 		Instructions: ins,
 		License:      "MIT",
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	}, nil)
 
 	p2, err := prog.Clone()
 	if err != nil {
@@ -94,22 +90,7 @@ func TestProgramRun(t *testing.T) {
 func TestProgramRunWithOptions(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.15", "XDP ctx_in/ctx_out")
 
-	ins := asm.Instructions{
-		// Return XDP_ABORTED
-		asm.LoadImm(asm.R0, 0, asm.DWord),
-		asm.Return(),
-	}
-
-	prog, err := NewProgram(&ProgramSpec{
-		Name:         "test",
-		Type:         XDP,
-		Instructions: ins,
-		License:      "MIT",
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	prog := createProgram(t, XDP, int64(sys.XDP_ABORTED))
 
 	buf := internal.EmptyBPFContext
 	xdp := sys.XdpMd{
@@ -140,22 +121,7 @@ func TestProgramRunWithOptions(t *testing.T) {
 func TestProgramRunRawTracepoint(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.10", "RawTracepoint test run")
 
-	ins := asm.Instructions{
-		// Return 0
-		asm.LoadImm(asm.R0, 0, asm.DWord),
-		asm.Return(),
-	}
-
-	prog, err := NewProgram(&ProgramSpec{
-		Name:         "test",
-		Type:         RawTracepoint,
-		Instructions: ins,
-		License:      "MIT",
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	prog := createProgram(t, RawTracepoint, 0)
 
 	ret, err := prog.Run(&RunOptions{})
 	testutils.SkipIfNotSupported(t, err)
@@ -171,23 +137,16 @@ func TestProgramRunRawTracepoint(t *testing.T) {
 func TestProgramRunEmptyData(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN")
 
-	ins := asm.Instructions{
-		// Return SK_DROP
-		asm.LoadImm(asm.R0, 0, asm.DWord),
-		asm.Return(),
-	}
-
-	prog, err := NewProgram(&ProgramSpec{
-		Name:         "test",
-		Type:         SkLookup,
-		AttachType:   AttachSkLookup,
-		Instructions: ins,
-		License:      "MIT",
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	prog := mustNewProgram(t, &ProgramSpec{
+		Name:       "test",
+		Type:       SkLookup,
+		AttachType: AttachSkLookup,
+		Instructions: asm.Instructions{
+			asm.LoadImm(asm.R0, 0, asm.DWord),
+			asm.Return(),
+		},
+		License: "MIT",
+	}, nil)
 
 	opts := RunOptions{
 		Context: sys.SkLookup{
@@ -206,7 +165,7 @@ func TestProgramRunEmptyData(t *testing.T) {
 }
 
 func TestProgramBenchmark(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	ret, duration, err := prog.Benchmark(internal.EmptyBPFContext, 1, nil)
 	testutils.SkipIfNotSupported(t, err)
@@ -226,7 +185,7 @@ func TestProgramBenchmark(t *testing.T) {
 func TestProgramTestRunInterrupt(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.0", "EINTR from BPF_PROG_TEST_RUN")
 
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	var (
 		tgid    = unix.Getpid()
@@ -296,7 +255,7 @@ func TestProgramTestRunInterrupt(t *testing.T) {
 }
 
 func TestProgramClose(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	if err := prog.Close(); err != nil {
 		t.Fatal("Can't close program:", err)
@@ -304,7 +263,7 @@ func TestProgramClose(t *testing.T) {
 }
 
 func TestProgramPin(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	tmp := testutils.TempBPFFS(t)
 
@@ -345,7 +304,7 @@ func TestProgramPin(t *testing.T) {
 }
 
 func TestProgramUnpin(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	tmp := testutils.TempBPFFS(t)
 
@@ -369,7 +328,7 @@ func TestProgramLoadPinnedWithFlags(t *testing.T) {
 	// Introduced in commit 6e71b04a8224.
 	testutils.SkipOnOldKernel(t, "4.14", "file_flags in BPF_OBJ_GET")
 
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	tmp := testutils.TempBPFFS(t)
 
@@ -390,13 +349,13 @@ func TestProgramLoadPinnedWithFlags(t *testing.T) {
 }
 
 func TestProgramVerifierOutputOnError(t *testing.T) {
-	_, err := NewProgram(&ProgramSpec{
+	_, err := newProgram(t, &ProgramSpec{
 		Type: SocketFilter,
 		Instructions: asm.Instructions{
 			asm.Return(),
 		},
 		License: "MIT",
-	})
+	}, nil)
 	if err == nil {
 		t.Fatal("Expected program to be invalid")
 	}
@@ -414,7 +373,8 @@ func TestProgramVerifierOutputOnError(t *testing.T) {
 
 func TestProgramKernelVersion(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.20", "KernelVersion")
-	prog, err := NewProgram(&ProgramSpec{
+
+	_ = mustNewProgram(t, &ProgramSpec{
 		Type: Kprobe,
 		Instructions: asm.Instructions{
 			asm.LoadImm(asm.R0, 0, asm.DWord),
@@ -422,34 +382,26 @@ func TestProgramKernelVersion(t *testing.T) {
 		},
 		KernelVersion: 42,
 		License:       "MIT",
-	})
-	if err != nil {
-		t.Fatal("Could not load Kprobe program")
-	}
-	defer prog.Close()
+	}, nil)
 }
 
 func TestProgramVerifierOutput(t *testing.T) {
-	prog, err := NewProgramWithOptions(socketFilterSpec, ProgramOptions{
+	prog := mustNewProgram(t, basicProgramSpec, &ProgramOptions{
 		LogLevel: LogLevelInstruction,
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
 
 	if prog.VerifierLog == "" {
 		t.Error("Expected VerifierLog to be present")
 	}
 
 	// Issue 64
-	_, err = NewProgramWithOptions(&ProgramSpec{
+	_, err := newProgram(t, &ProgramSpec{
 		Type: SocketFilter,
 		Instructions: asm.Instructions{
 			asm.Mov.Reg(asm.R0, asm.R1),
 		},
 		License: "MIT",
-	}, ProgramOptions{
+	}, &ProgramOptions{
 		LogLevel: LogLevelInstruction,
 	})
 
@@ -498,17 +450,17 @@ func TestProgramVerifierLog(t *testing.T) {
 	}
 
 	// Don't explicitly request a verifier log for an invalid program.
-	_, err := NewProgramWithOptions(spec, ProgramOptions{})
+	_, err := newProgram(t, spec, nil)
 	check(t, err)
 
 	// Explicitly request a verifier log for an invalid program.
-	_, err = NewProgramWithOptions(spec, ProgramOptions{
+	_, err = newProgram(t, spec, &ProgramOptions{
 		LogLevel: LogLevelInstruction,
 	})
 	check(t, err)
 
 	// Disabling the verifier log should result in a VerifierError without a log.
-	_, err = NewProgramWithOptions(spec, ProgramOptions{
+	_, err = newProgram(t, spec, &ProgramOptions{
 		LogDisabled: true,
 	})
 	var ve *internal.VerifierError
@@ -520,28 +472,22 @@ func TestProgramVerifierLog(t *testing.T) {
 
 	// Don't request a verifier log, expect the valid program to be created
 	// without errors.
-	prog, err := NewProgramWithOptions(spec, ProgramOptions{})
-	qt.Assert(t, qt.IsNil(err))
+	prog := mustNewProgram(t, spec, nil)
 	qt.Assert(t, qt.HasLen(prog.VerifierLog, 0))
-	prog.Close()
 
 	// Explicitly request verifier log for a valid program. If a log is requested
 	// and the buffer is too small, ENOSPC occurs even for valid programs.
-	prog, err = NewProgramWithOptions(spec, ProgramOptions{
+	prog = mustNewProgram(t, spec, &ProgramOptions{
 		LogLevel: LogLevelInstruction,
 	})
-	qt.Assert(t, qt.IsNil(err))
 	qt.Assert(t, qt.IsTrue(len(prog.VerifierLog) > minVerifierLogSize))
-	prog.Close()
 
 	// Repeat the previous test with a larger starting buffer size.
-	prog, err = NewProgramWithOptions(spec, ProgramOptions{
+	prog = mustNewProgram(t, spec, &ProgramOptions{
 		LogLevel:     LogLevelInstruction,
 		LogSizeStart: minVerifierLogSize * 2,
 	})
-	qt.Assert(t, qt.IsNil(err))
 	qt.Assert(t, qt.IsTrue(len(prog.VerifierLog) > minVerifierLogSize))
-	prog.Close()
 }
 
 func TestProgramWithUnsatisfiedMap(t *testing.T) {
@@ -554,8 +500,7 @@ func TestProgramWithUnsatisfiedMap(t *testing.T) {
 	progSpec := coll.Programs["xdp_prog"]
 	progSpec.ByteOrder = nil
 
-	_, err = NewProgram(progSpec)
-	testutils.SkipIfNotSupported(t, err)
+	_, err = newProgram(t, progSpec, nil)
 	if !errors.Is(err, asm.ErrUnsatisfiedMapReference) {
 		t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err)
 	}
@@ -567,7 +512,7 @@ func TestProgramName(t *testing.T) {
 		t.Skip(err)
 	}
 
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	var info sys.ProgInfo
 	if err := sys.ObjInfo(prog.fd, &info); err != nil {
@@ -606,14 +551,14 @@ func TestProgramCloneNil(t *testing.T) {
 func TestProgramMarshaling(t *testing.T) {
 	const idx = uint32(0)
 
-	arr := createProgramArray(t)
+	arr := createMap(t, ProgramArray, 1)
 	defer arr.Close()
 
 	if err := arr.Put(idx, (*Program)(nil)); err == nil {
 		t.Fatal("Put accepted a nil Program")
 	}
 
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	if err := arr.Put(idx, prog); err != nil {
 		t.Fatal("Can't put program:", err)
@@ -643,7 +588,7 @@ func TestProgramMarshaling(t *testing.T) {
 }
 
 func TestProgramFromFD(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	// If you're thinking about copying this, don't. Use
 	// Clone() instead.
@@ -672,7 +617,7 @@ func TestProgramGetNextID(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id")
 
 	// Ensure there is at least one program loaded
-	_ = mustSocketFilter(t)
+	_ = createBasicProgram(t)
 
 	// As there can be multiple eBPF programs, we loop over all of them and
 	// make sure, the IDs increase and the last call will return ErrNotExist
@@ -696,7 +641,7 @@ func TestProgramGetNextID(t *testing.T) {
 }
 
 func TestNewProgramFromID(t *testing.T) {
-	prog := mustSocketFilter(t)
+	prog := createBasicProgram(t)
 
 	info, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -723,14 +668,14 @@ func TestNewProgramFromID(t *testing.T) {
 }
 
 func TestProgramRejectIncorrectByteOrder(t *testing.T) {
-	spec := socketFilterSpec.Copy()
+	spec := basicProgramSpec.Copy()
 
 	spec.ByteOrder = binary.BigEndian
 	if spec.ByteOrder == internal.NativeEndian {
 		spec.ByteOrder = binary.LittleEndian
 	}
 
-	_, err := NewProgram(spec)
+	_, err := newProgram(t, spec, nil)
 	if err == nil {
 		t.Error("Incorrect ByteOrder should be rejected at load time")
 	}
@@ -758,7 +703,7 @@ func TestProgramSpecCopy(t *testing.T) {
 }
 
 func TestProgramSpecTag(t *testing.T) {
-	arr := createArray(t)
+	arr := createMap(t, Array, 2)
 
 	spec := &ProgramSpec{
 		Type: SocketFilter,
@@ -771,11 +716,7 @@ func TestProgramSpecTag(t *testing.T) {
 		License: "MIT",
 	}
 
-	prog, err := NewProgram(spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	prog := mustNewProgram(t, spec, nil)
 
 	info, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -856,7 +797,7 @@ func TestProgramAttachToKernel(t *testing.T) {
 				requireTestmod(t)
 			}
 
-			prog, err := NewProgram(&ProgramSpec{
+			_ = mustNewProgram(t, &ProgramSpec{
 				AttachTo:   test.attachTo,
 				AttachType: test.attachType,
 				Instructions: asm.Instructions{
@@ -866,11 +807,7 @@ func TestProgramAttachToKernel(t *testing.T) {
 				License: "GPL",
 				Type:    test.programType,
 				Flags:   test.flags,
-			})
-			if err != nil {
-				t.Fatal("Can't load program:", err)
-			}
-			prog.Close()
+			}, nil)
 		})
 	}
 }
@@ -885,7 +822,7 @@ func TestProgramKernelTypes(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	prog, err := NewProgramWithOptions(&ProgramSpec{
+	_, err = newProgram(t, &ProgramSpec{
 		Type:       Tracing,
 		AttachType: AttachTraceIter,
 		AttachTo:   "bpf_map",
@@ -894,31 +831,18 @@ func TestProgramKernelTypes(t *testing.T) {
 			asm.Return(),
 		},
 		License: "MIT",
-	}, ProgramOptions{
+	}, &ProgramOptions{
 		KernelTypes: btfSpec,
 	})
 	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal("NewProgram with Target:", err)
-	}
-	prog.Close()
+	qt.Assert(t, qt.IsNil(err))
 }
 
 func TestProgramBindMap(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP")
 
-	arr, err := NewMap(&MapSpec{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	if err != nil {
-		t.Errorf("Failed to load map: %v", err)
-	}
-	defer arr.Close()
-
-	prog := mustSocketFilter(t)
+	arr := createMap(t, Array, 2)
+	prog := createBasicProgram(t)
 
 	// The attached map does not contain BTF information. So
 	// the metadata part of the program will be empty. This
@@ -940,11 +864,7 @@ func TestProgramInstructions(t *testing.T) {
 		License: "MIT",
 	}
 
-	prog, err := NewProgram(spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer prog.Close()
+	prog := mustNewProgram(t, spec, nil)
 
 	pi, err := prog.Info()
 	testutils.SkipIfNotSupported(t, err)
@@ -997,7 +917,7 @@ func TestProgramLoadErrors(t *testing.T) {
 
 		t.Run(test.name, func(t *testing.T) {
 			t.Log(progSpec.Instructions)
-			_, err := NewProgramWithOptions(progSpec, ProgramOptions{
+			_, err := newProgram(t, progSpec, &ProgramOptions{
 				KernelTypes: empty,
 			})
 			testutils.SkipIfNotSupported(t, err)
@@ -1044,43 +964,6 @@ func BenchmarkNewProgram(b *testing.B) {
 	}
 }
 
-func createProgramArray(t *testing.T) *Map {
-	t.Helper()
-
-	arr, err := NewMap(&MapSpec{
-		Type:       ProgramArray,
-		KeySize:    4,
-		ValueSize:  4,
-		MaxEntries: 1,
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	return arr
-}
-
-var socketFilterSpec = &ProgramSpec{
-	Name: "test",
-	Type: SocketFilter,
-	Instructions: asm.Instructions{
-		asm.LoadImm(asm.R0, 2, asm.DWord),
-		asm.Return(),
-	},
-	License: "MIT",
-}
-
-func mustSocketFilter(tb testing.TB) *Program {
-	tb.Helper()
-
-	prog, err := NewProgram(socketFilterSpec)
-	if err != nil {
-		tb.Fatal(err)
-	}
-	tb.Cleanup(func() { prog.Close() })
-
-	return prog
-}
-
 // Print the full verifier log when loading a program fails.
 func ExampleVerifierError_retrieveFullLog() {
 	_, err := NewProgram(&ProgramSpec{