Repository: cilium/ebpf
Pull request #1670 (2025-02-06 11:42:47)

sys: add type safe Pointer wrapper
Add TypedPointer which is a type safe wrapper around Pointer. This relieves users of the generated bindings from knowing which types are expected. This should make it less likely that we cause a hard to debug panic or crash due to the kernel overwriting runtime memory.

sys.Pointer still exists because there are some cases where we legitimately don't know what type to point at, for example the link info APIs. Map APIs are also unchanged for now because they don't benefit much from converting to TypedPointer[byte].

lmb: The breaking change is due to replacing ebpf.MapID and ProgramID with a type alias.
kolyshkin: I like the approach!

It will be better to separate the actual overrun fix from the new type system.

I've also updated the fix in #1660 to not break the existing API nor return truncated pointers, PTAL @lmb 
ti-mo: @lmb Looks like this implements https://github.com/cilium/ebpf/issues/914?
lmb: > I still see a bunch of `sys.Pointer` uses in the map API; I'd argue those should all be `sys.UnsafePointer`s instead, to be converted to typed pointers before returning.

Discussed face to face: UnsafePointer is a constructor, not a type. We still need `sys.Pointer` to account for endianness differences on 32 bit arches.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fa7079d..c1c62a2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -202,7 +202,6 @@ jobs:
           - "5.4"
           - "4.19"
           - "4.14"
-          - "4.9"
     steps:
       - uses: actions/checkout@v4
 
diff --git a/btf/feature.go b/btf/feature.go
index e71c707..5b427f5 100644
--- a/btf/feature.go
+++ b/btf/feature.go
@@ -146,7 +146,7 @@ func probeBTF(typ Type) error {
 	}
 
 	fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
-		Btf:     sys.NewSlicePointer(buf),
+		Btf:     sys.SlicePointer(buf),
 		BtfSize: uint32(len(buf)),
 	})
 
diff --git a/btf/handle.go b/btf/handle.go
index 0e02a3b..2e483e9 100644
--- a/btf/handle.go
+++ b/btf/handle.go
@@ -53,7 +53,7 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
 	}
 
 	attr := &sys.BtfLoadAttr{
-		Btf:     sys.NewSlicePointer(btf),
+		Btf:     sys.SlicePointer(btf),
 		BtfSize: uint32(len(btf)),
 	}
 
@@ -96,7 +96,7 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
 
 		logBuf = make([]byte, logSize)
 		attr.BtfLogSize = logSize
-		attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
+		attr.BtfLogBuf = sys.SlicePointer(logBuf)
 		attr.BtfLogLevel = 1
 	}
 
@@ -142,7 +142,8 @@ func NewHandleFromID(id ID) (*Handle, error) {
 func (h *Handle) Spec(base *Spec) (*Spec, error) {
 	var btfInfo sys.BtfInfo
 	btfBuffer := make([]byte, h.size)
-	btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
+	btfInfo.Btf = sys.SlicePointer(btfBuffer)
+	btfInfo.BtfSize = uint32(len(btfBuffer))
 
 	if err := sys.ObjInfo(h.fd, &btfInfo); err != nil {
 		return nil, err
@@ -213,7 +214,8 @@ func newHandleInfoFromFD(fd *sys.FD) (*HandleInfo, error) {
 	btfInfo.BtfSize = 0
 
 	nameBuffer := make([]byte, btfInfo.NameLen)
-	btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
+	btfInfo.Name = sys.SlicePointer(nameBuffer)
+	btfInfo.NameLen = uint32(len(nameBuffer))
 	if err := sys.ObjInfo(fd, &btfInfo); err != nil {
 		return nil, err
 	}
diff --git a/info.go b/info.go
index 676e8fa..c5f47e1 100644
--- a/info.go
+++ b/info.go
@@ -275,7 +275,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 	if info.NrMapIds > 0 {
 		pi.maps = make([]MapID, info.NrMapIds)
 		info2.NrMapIds = info.NrMapIds
-		info2.MapIds = sys.NewSlicePointer(pi.maps)
+		info2.MapIds = sys.SlicePointer(pi.maps)
 		makeSecondCall = true
 	} else if haveProgramInfoMapIDs() == nil {
 		// This program really has no associated maps.
@@ -294,13 +294,13 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 	if info.XlatedProgLen > 0 {
 		pi.insns = make([]byte, info.XlatedProgLen)
 		info2.XlatedProgLen = info.XlatedProgLen
-		info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
+		info2.XlatedProgInsns = sys.SlicePointer(pi.insns)
 		makeSecondCall = true
 	}
 
 	if info.NrLineInfo > 0 {
 		pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo)
-		info2.LineInfo = sys.NewSlicePointer(pi.lineInfos)
+		info2.LineInfo = sys.SlicePointer(pi.lineInfos)
 		info2.LineInfoRecSize = btf.LineInfoSize
 		info2.NrLineInfo = info.NrLineInfo
 		pi.numLineInfos = info.NrLineInfo
@@ -309,7 +309,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 
 	if info.NrFuncInfo > 0 {
 		pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo)
-		info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos)
+		info2.FuncInfo = sys.SlicePointer(pi.funcInfos)
 		info2.FuncInfoRecSize = btf.FuncInfoSize
 		info2.NrFuncInfo = info.NrFuncInfo
 		pi.numFuncInfos = info.NrFuncInfo
@@ -321,7 +321,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		pi.jitedInfo.numInsns = info.JitedProgLen
 		pi.jitedInfo.insns = make([]byte, info.JitedProgLen)
 		info2.JitedProgLen = info.JitedProgLen
-		info2.JitedProgInsns = sys.NewSlicePointer(pi.jitedInfo.insns)
+		info2.JitedProgInsns = sys.SlicePointer(pi.jitedInfo.insns)
 		makeSecondCall = true
 	}
 
@@ -329,7 +329,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		pi.jitedInfo.numFuncLens = info.NrJitedFuncLens
 		pi.jitedInfo.funcLens = make([]uint32, info.NrJitedFuncLens)
 		info2.NrJitedFuncLens = info.NrJitedFuncLens
-		info2.JitedFuncLens = sys.NewSlicePointer(pi.jitedInfo.funcLens)
+		info2.JitedFuncLens = sys.SlicePointer(pi.jitedInfo.funcLens)
 		makeSecondCall = true
 	}
 
@@ -337,7 +337,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		pi.jitedInfo.numLineInfos = info.NrJitedLineInfo
 		pi.jitedInfo.lineInfos = make([]uint64, info.NrJitedLineInfo)
 		info2.NrJitedLineInfo = info.NrJitedLineInfo
-		info2.JitedLineInfo = sys.NewSlicePointer(pi.jitedInfo.lineInfos)
+		info2.JitedLineInfo = sys.SlicePointer(pi.jitedInfo.lineInfos)
 		info2.JitedLineInfoRecSize = info.JitedLineInfoRecSize
 		makeSecondCall = true
 	}
@@ -345,7 +345,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 	if info.NrJitedKsyms > 0 {
 		pi.jitedInfo.numKsyms = info.NrJitedKsyms
 		pi.jitedInfo.ksyms = make([]uint64, info.NrJitedKsyms)
-		info2.JitedKsyms = sys.NewSlicePointer(pi.jitedInfo.ksyms)
+		info2.JitedKsyms = sys.SlicePointer(pi.jitedInfo.ksyms)
 		info2.NrJitedKsyms = info.NrJitedKsyms
 		makeSecondCall = true
 	}
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index fdd5d6f..10047b2 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -70,20 +70,46 @@ func run(args []string) error {
 
 func generateTypes(spec *btf.Spec) ([]byte, error) {
 	objName := &btf.Array{Nelems: 16, Type: &btf.Int{Encoding: btf.Char, Size: 1}}
+	mapID := &btf.Int{Size: 4}
+	progID := &btf.Int{Size: 4}
 	linkID := &btf.Int{Size: 4}
 	btfID := &btf.Int{Size: 4}
 	typeID := &btf.Int{Size: 4}
-	pointer := &btf.Int{Size: 8}
 	logLevel := &btf.Int{Size: 4}
 
+	rawPointer := &btf.Pointer{}
+	stringPointer := &btf.Pointer{}
+	stringSlicePointer := &btf.Pointer{}
+	bytePtr := &btf.Pointer{}
+	int32Ptr := &btf.Pointer{}
+	uint32Ptr := &btf.Pointer{}
+	uint64Ptr := &btf.Pointer{}
+	uintptrPointer := &btf.Pointer{}
+	mapIDPtr := &btf.Pointer{}
+	progIDPtr := &btf.Pointer{}
+	linkIDPtr := &btf.Pointer{}
+
 	gf := &btf.GoFormatter{
 		Names: map[btf.Type]string{
 			objName:  internal.GoTypeName(sys.ObjName{}),
+			mapID:    internal.GoTypeName(sys.MapID(0)),
+			progID:   internal.GoTypeName(sys.ProgramID(0)),
 			linkID:   internal.GoTypeName(sys.LinkID(0)),
 			btfID:    internal.GoTypeName(sys.BTFID(0)),
 			typeID:   internal.GoTypeName(sys.TypeID(0)),
-			pointer:  internal.GoTypeName(sys.Pointer{}),
 			logLevel: internal.GoTypeName(sys.LogLevel(0)),
+
+			rawPointer:         internal.GoTypeName(sys.Pointer{}),
+			stringPointer:      internal.GoTypeName(sys.StringPointer{}),
+			stringSlicePointer: internal.GoTypeName(sys.StringSlicePointer{}),
+			bytePtr:            internal.GoTypeName(sys.TypedPointer[byte]{}),
+			int32Ptr:           internal.GoTypeName(sys.TypedPointer[int32]{}),
+			uint32Ptr:          internal.GoTypeName(sys.TypedPointer[uint32]{}),
+			uint64Ptr:          internal.GoTypeName(sys.TypedPointer[uint64]{}),
+			uintptrPointer:     internal.GoTypeName(sys.TypedPointer[uintptr]{}),
+			mapIDPtr:           internal.GoTypeName(sys.TypedPointer[sys.MapID]{}),
+			progIDPtr:          internal.GoTypeName(sys.TypedPointer[sys.ProgramID]{}),
+			linkIDPtr:          internal.GoTypeName(sys.TypedPointer[sys.LinkID]{}),
 		},
 		Identifier: internal.Identifier,
 		EnumIdentifier: func(name, element string) string {
@@ -199,14 +225,14 @@ import (
 			"ProgInfo", "bpf_prog_info",
 			[]patch{
 				replace(objName, "name"),
-				replace(pointer, "jited_prog_insns"),
-				replace(pointer, "xlated_prog_insns"),
-				replace(pointer, "map_ids"),
-				replace(pointer, "line_info"),
-				replace(pointer, "jited_line_info"),
-				replace(pointer, "jited_ksyms"),
-				replace(pointer, "jited_func_lens"),
-				replace(pointer, "func_info"),
+				replace(bytePtr, "jited_prog_insns"),
+				replace(bytePtr, "xlated_prog_insns"),
+				replace(mapIDPtr, "map_ids"),
+				replace(bytePtr, "line_info"),
+				replace(uint64Ptr, "jited_line_info"),
+				replace(uint64Ptr, "jited_ksyms"),
+				replace(uint32Ptr, "jited_func_lens"),
+				replace(bytePtr, "func_info"),
 				replace(btfID, "btf_id", "attach_btf_obj_id"),
 				replace(typeID, "attach_btf_id"),
 			},
@@ -214,6 +240,7 @@ import (
 		{
 			"MapInfo", "bpf_map_info",
 			[]patch{
+				replace(mapID, "id"),
 				replace(objName, "name"),
 				replace(typeID, "btf_vmlinux_value_type_id", "btf_key_type_id", "btf_value_type_id"),
 			},
@@ -221,7 +248,7 @@ import (
 		{
 			"BtfInfo", "bpf_btf_info",
 			[]patch{
-				replace(pointer, "btf", "name"),
+				replace(bytePtr, "btf", "name"),
 				replace(btfID, "id"),
 			},
 		},
@@ -282,24 +309,24 @@ import (
 		},
 		{
 			"MapLookupElem", retError, "map_elem", "BPF_MAP_LOOKUP_ELEM",
-			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
+			[]patch{choose(2, "value"), replace(rawPointer, "key", "value")},
 		},
 		{
 			"MapLookupAndDeleteElem", retError, "map_elem", "BPF_MAP_LOOKUP_AND_DELETE_ELEM",
-			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
+			[]patch{choose(2, "value"), replace(rawPointer, "key", "value")},
 		},
 		{
 			"MapUpdateElem", retError, "map_elem", "BPF_MAP_UPDATE_ELEM",
-			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
+			[]patch{choose(2, "value"), replace(rawPointer, "key", "value")},
 		},
 		{
 			"MapDeleteElem", retError, "map_elem", "BPF_MAP_DELETE_ELEM",
-			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
+			[]patch{choose(2, "value"), replace(rawPointer, "key", "value")},
 		},
 		{
 			"MapGetNextKey", retError, "map_elem", "BPF_MAP_GET_NEXT_KEY",
 			[]patch{
-				choose(2, "next_key"), replace(pointer, "key", "next_key"),
+				choose(2, "next_key"), replace(rawPointer, "key", "next_key"),
 				truncateAfter("next_key"),
 			},
 		},
@@ -309,19 +336,19 @@ import (
 		},
 		{
 			"MapLookupBatch", retError, "map_elem_batch", "BPF_MAP_LOOKUP_BATCH",
-			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
+			[]patch{replace(rawPointer, "in_batch", "out_batch", "keys", "values")},
 		},
 		{
 			"MapLookupAndDeleteBatch", retError, "map_elem_batch", "BPF_MAP_LOOKUP_AND_DELETE_BATCH",
-			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
+			[]patch{replace(rawPointer, "in_batch", "out_batch", "keys", "values")},
 		},
 		{
 			"MapUpdateBatch", retError, "map_elem_batch", "BPF_MAP_UPDATE_BATCH",
-			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
+			[]patch{replace(rawPointer, "in_batch", "out_batch", "keys", "values")},
 		},
 		{
 			"MapDeleteBatch", retError, "map_elem_batch", "BPF_MAP_DELETE_BATCH",
-			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
+			[]patch{replace(rawPointer, "in_batch", "out_batch", "keys", "values")},
 		},
 		{
 			"ProgLoad", retFd, "prog_load", "BPF_PROG_LOAD",
@@ -330,15 +357,15 @@ import (
 				replace(enumTypes["ProgType"], "prog_type"),
 				replace(enumTypes["AttachType"], "expected_attach_type"),
 				replace(logLevel, "log_level"),
-				replace(pointer,
-					"insns",
-					"license",
-					"log_buf",
+				replace(bytePtr, "insns"),
+				replace(stringPointer, "license"),
+				replace(bytePtr, "log_buf"),
+				replace(bytePtr,
 					"func_info",
 					"line_info",
-					"fd_array",
 					"core_relos",
 				),
+				replace(int32Ptr, "fd_array"),
 				replace(typeID, "attach_btf_id"),
 				choose(20, "attach_btf_obj_fd"),
 			},
@@ -349,11 +376,11 @@ import (
 		},
 		{
 			"ObjPin", retError, "obj_pin", "BPF_OBJ_PIN",
-			[]patch{replace(pointer, "pathname")},
+			[]patch{replace(stringPointer, "pathname")},
 		},
 		{
 			"ObjGet", retFd, "obj_pin", "BPF_OBJ_GET",
-			[]patch{replace(pointer, "pathname")},
+			[]patch{replace(stringPointer, "pathname")},
 		},
 		{
 			"ProgAttach", retError, "prog_attach", "BPF_PROG_ATTACH",
@@ -375,7 +402,7 @@ import (
 		},
 		{
 			"ProgRun", retError, "prog_run", "BPF_PROG_TEST_RUN",
-			[]patch{replace(pointer, "data_in", "data_out", "ctx_in", "ctx_out")},
+			[]patch{replace(bytePtr, "data_in", "data_out", "ctx_in", "ctx_out")},
 		},
 		{
 			"ProgGetNextId", retError, "obj_next_id", "BPF_PROG_GET_NEXT_ID",
@@ -427,15 +454,15 @@ import (
 		},
 		{
 			"ObjGetInfoByFd", retError, "info_by_fd", "BPF_OBJ_GET_INFO_BY_FD",
-			[]patch{replace(pointer, "info")},
+			[]patch{replace(rawPointer, "info")},
 		},
 		{
 			"RawTracepointOpen", retFd, "raw_tracepoint_open", "BPF_RAW_TRACEPOINT_OPEN",
-			[]patch{replace(pointer, "name")},
+			[]patch{replace(stringPointer, "name")},
 		},
 		{
 			"BtfLoad", retFd, "btf_load", "BPF_BTF_LOAD",
-			[]patch{replace(pointer, "btf", "btf_log_buf")},
+			[]patch{replace(bytePtr, "btf", "btf_log_buf")},
 		},
 		{
 			"LinkCreate", retFd, "link_create", "BPF_LINK_CREATE",
@@ -451,7 +478,7 @@ import (
 				chooseNth(4, 1),
 				replace(enumTypes["AttachType"], "attach_type"),
 				flattenAnon,
-				replace(pointer, "iter_info"),
+				replace(rawPointer, "iter_info"),
 			},
 		},
 		{
@@ -471,9 +498,9 @@ import (
 					return rename("flags", "kprobe_multi_flags")(m.Type.(*btf.Struct))
 				}, "kprobe_multi"),
 				flattenAnon,
-				replace(pointer, "cookies"),
-				replace(pointer, "addrs"),
-				replace(pointer, "syms"),
+				replace(uint64Ptr, "cookies"),
+				replace(uintptrPointer, "addrs"),
+				replace(stringSlicePointer, "syms"),
 				rename("cnt", "count"),
 			},
 		},
@@ -517,10 +544,10 @@ import (
 					return rename("flags", "uprobe_multi_flags")(m.Type.(*btf.Struct))
 				}, "uprobe_multi"),
 				flattenAnon,
-				replace(pointer, "path"),
-				replace(pointer, "offsets"),
-				replace(pointer, "ref_ctr_offsets"),
-				replace(pointer, "cookies"),
+				replace(stringPointer, "path"),
+				replace(uint64Ptr, "offsets"),
+				replace(uint64Ptr, "ref_ctr_offsets"),
+				replace(uint64Ptr, "cookies"),
 				rename("cnt", "count"),
 			},
 		},
@@ -551,8 +578,8 @@ import (
 			"ProgQuery", retError, "prog_query", "BPF_PROG_QUERY",
 			[]patch{
 				replace(enumTypes["AttachType"], "attach_type"),
-				replace(pointer, "prog_ids", "prog_attach_flags"),
-				replace(pointer, "link_ids", "link_attach_flags"),
+				replace(progIDPtr, "prog_ids", "prog_attach_flags"),
+				replace(linkIDPtr, "link_ids", "link_attach_flags"),
 				flattenAnon,
 				rename("prog_cnt", "count"),
 				rename("target_fd", "target_fd_or_ifindex"),
@@ -630,7 +657,7 @@ import (
 			[]patch{
 				choose(3, "iter"),
 				flattenAnon,
-				replace(pointer, "target_name"),
+				replace(bytePtr, "target_name"),
 				truncateAfter("target_name_len"),
 			},
 		},
@@ -643,7 +670,7 @@ import (
 		{"RawTracepointLinkInfo",
 			[]patch{choose(3, "raw_tracepoint"),
 				flattenAnon,
-				replace(pointer, "tp_name"),
+				replace(bytePtr, "tp_name"),
 			},
 		},
 		{"TracingLinkInfo",
@@ -683,7 +710,8 @@ import (
 			[]patch{
 				choose(3, "kprobe_multi"),
 				flattenAnon,
-				replace(pointer, "addrs"),
+				replace(uint64Ptr, "addrs"),
+				replace(uint64Ptr, "cookies"),
 			},
 		},
 		{"PerfEventLinkInfo",
@@ -703,7 +731,7 @@ import (
 				replace(enumTypes["PerfEventType"], "perf_event_type"),
 				choose(4, "kprobe"),
 				flattenAnon,
-				replace(pointer, "func_name"),
+				replace(bytePtr, "func_name"),
 			},
 		},
 	}
diff --git a/internal/efw/object.go b/internal/efw/object.go
index 0695fe7..732d097 100644
--- a/internal/efw/object.go
+++ b/internal/efw/object.go
@@ -9,6 +9,9 @@ import (
 	"golang.org/x/sys/windows"
 )
 
+// https://github.com/microsoft/ebpf-for-windows/blob/9d9003c39c3fd75be5225ac0fce30077d6bf0604/include/ebpf_core_structs.h#L15
+const _EBPF_MAX_PIN_PATH_LENGTH = 256
+
 /*
 Retrieve object info and type from a fd.
 
@@ -52,3 +55,35 @@ func EbpfObjectUnpin(path string) error {
 
 	return errorResult(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&pathBytes[0]))))
 }
+
+/*
+Retrieve the next pinned object path.
+
+	ebpf_result_t ebpf_get_next_pinned_object_path(
+		_In_opt_z_ const char* start_path,
+		_Out_writes_z_(next_path_len) char* next_path,
+		size_t next_path_len,
+		_Inout_opt_ ebpf_object_type_t* type)
+*/
+var ebpfGetNextPinnedObjectPath = newProc("ebpf_get_next_pinned_object_path")
+
+func EbpfGetNextPinnedObjectPath(startPath string, objectType ObjectType) (string, ObjectType, error) {
+	addr, err := ebpfGetNextPinnedObjectPath.Find()
+	if err != nil {
+		return "", 0, err
+	}
+
+	ptr, err := windows.BytePtrFromString(startPath)
+	if err != nil {
+		return "", 0, err
+	}
+
+	tmp := make([]byte, _EBPF_MAX_PIN_PATH_LENGTH)
+	err = errorResult(syscall.SyscallN(addr,
+		uintptr(unsafe.Pointer(ptr)),
+		uintptr(unsafe.Pointer(&tmp[0])),
+		uintptr(len(tmp)),
+		uintptr(unsafe.Pointer(&objectType)),
+	))
+	return windows.ByteSliceToString(tmp), objectType, err
+}
diff --git a/internal/output.go b/internal/output.go
index dd6e6cb..bcbb681 100644
--- a/internal/output.go
+++ b/internal/output.go
@@ -92,6 +92,11 @@ func GoTypeName(t any) string {
 	for rT.Kind() == reflect.Pointer {
 		rT = rT.Elem()
 	}
-	// Doesn't return the correct Name for generic types due to https://github.com/golang/go/issues/55924
-	return rT.Name()
+
+	name := rT.Name()
+	if pkgPath := rT.PkgPath(); pkgPath != "" {
+		name = strings.ReplaceAll(name, pkgPath+".", "")
+	}
+
+	return name
 }
diff --git a/internal/sys/ptr.go b/internal/sys/ptr.go
index 5443dd1..173665c 100644
--- a/internal/sys/ptr.go
+++ b/internal/sys/ptr.go
@@ -6,13 +6,13 @@ import (
 	"github.com/cilium/ebpf/internal/unix"
 )
 
-// NewPointer creates a 64-bit pointer from an unsafe Pointer.
-func NewPointer(ptr unsafe.Pointer) Pointer {
+// UnsafePointer creates a 64-bit pointer from an unsafe Pointer.
+func UnsafePointer(ptr unsafe.Pointer) Pointer {
 	return Pointer{ptr: ptr}
 }
 
-// NewSlicePointer creates a 64-bit pointer from a slice.
-func NewSlicePointer[T comparable](buf []T) Pointer {
+// UnsafeSlicePointer creates an untyped [Pointer] from a slice.
+func UnsafeSlicePointer[T comparable](buf []T) Pointer {
 	if len(buf) == 0 {
 		return Pointer{}
 	}
@@ -20,21 +20,39 @@ func NewSlicePointer[T comparable](buf []T) Pointer {
 	return Pointer{ptr: unsafe.Pointer(unsafe.SliceData(buf))}
 }
 
-// NewSlicePointerLen creates a 64-bit pointer from a byte slice.
+// TypedPointer points to typed memory.
 //
-// Useful to assign both the pointer and the length in one go.
-func NewSlicePointerLen(buf []byte) (Pointer, uint32) {
-	return NewSlicePointer(buf), uint32(len(buf))
+// It is like a *T except that it accounts for the BPF syscall interface.
+type TypedPointer[T any] struct {
+	_   [0]*T // prevent TypedPointer[a] to be convertible to TypedPointer[b]
+	ptr Pointer
 }
 
-// NewStringPointer creates a 64-bit pointer from a string.
-func NewStringPointer(str string) Pointer {
+// SlicePointer creates a [TypedPointer] from a slice.
+func SlicePointer[T comparable](s []T) TypedPointer[T] {
+	return TypedPointer[T]{ptr: UnsafeSlicePointer(s)}
+}
+
+// StringPointer points to a null-terminated string.
+type StringPointer struct {
+	_   [0]string
+	ptr Pointer
+}
+
+// NewStringPointer creates a [StringPointer] from a string.
+func NewStringPointer(str string) StringPointer {
 	slice, err := unix.ByteSliceFromString(str)
 	if err != nil {
-		return Pointer{}
+		return StringPointer{}
 	}
 
-	return Pointer{ptr: unsafe.Pointer(&slice[0])}
+	return StringPointer{ptr: Pointer{ptr: unsafe.Pointer(&slice[0])}}
+}
+
+// StringSlicePointer points to a slice of [StringPointer].
+type StringSlicePointer struct {
+	_   [0][]string
+	ptr Pointer
 }
 
 // NewStringSlicePointer allocates an array of Pointers to each string in the
@@ -42,11 +60,11 @@ func NewStringPointer(str string) Pointer {
 // resulting array.
 //
 // Use this function to pass arrays of strings as syscall arguments.
-func NewStringSlicePointer(strings []string) Pointer {
-	sp := make([]Pointer, 0, len(strings))
+func NewStringSlicePointer(strings []string) StringSlicePointer {
+	sp := make([]StringPointer, 0, len(strings))
 	for _, s := range strings {
 		sp = append(sp, NewStringPointer(s))
 	}
 
-	return Pointer{ptr: unsafe.Pointer(&sp[0])}
+	return StringSlicePointer{ptr: Pointer{ptr: unsafe.Pointer(&sp[0])}}
 }
diff --git a/internal/sys/syscall.go b/internal/sys/syscall.go
index 1093abf..f2fffd2 100644
--- a/internal/sys/syscall.go
+++ b/internal/sys/syscall.go
@@ -94,7 +94,7 @@ func ObjInfo(fd *FD, info Info) error {
 	err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{
 		BpfFd:   fd.Uint(),
 		InfoLen: len,
-		Info:    NewPointer(ptr),
+		Info:    UnsafePointer(ptr),
 	})
 	runtime.KeepAlive(fd)
 	return err
@@ -120,6 +120,12 @@ const (
 	BPF_LOG_STATS
 )
 
+// MapID uniquely identifies a bpf_map.
+type MapID uint32
+
+// ProgramID uniquely identifies a bpf_map.
+type ProgramID uint32
+
 // LinkID uniquely identifies a bpf_link.
 type LinkID uint32
 
diff --git a/internal/sys/types.go b/internal/sys/types.go
index 61dafa2..a76d4a5 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -691,10 +691,10 @@ const (
 
 type BtfInfo struct {
 	structs.HostLayout
-	Btf       Pointer
+	Btf       TypedPointer[uint8]
 	BtfSize   uint32
 	Id        BTFID
-	Name      Pointer
+	Name      TypedPointer[uint8]
 	NameLen   uint32
 	KernelBtf uint32
 }
@@ -725,7 +725,7 @@ type LinkInfo struct {
 type MapInfo struct {
 	structs.HostLayout
 	Type                  uint32
-	Id                    uint32
+	Id                    MapID
 	KeySize               uint32
 	ValueSize             uint32
 	MaxEntries            uint32
@@ -749,12 +749,12 @@ type ProgInfo struct {
 	Tag                  [8]uint8
 	JitedProgLen         uint32
 	XlatedProgLen        uint32
-	JitedProgInsns       Pointer
-	XlatedProgInsns      Pointer
+	JitedProgInsns       TypedPointer[uint8]
+	XlatedProgInsns      TypedPointer[uint8]
 	LoadTime             uint64
 	CreatedByUid         uint32
 	NrMapIds             uint32
-	MapIds               Pointer
+	MapIds               TypedPointer[MapID]
 	Name                 ObjName
 	Ifindex              uint32
 	_                    [4]byte /* unsupported bitfield */
@@ -762,15 +762,15 @@ type ProgInfo struct {
 	NetnsIno             uint64
 	NrJitedKsyms         uint32
 	NrJitedFuncLens      uint32
-	JitedKsyms           Pointer
-	JitedFuncLens        Pointer
+	JitedKsyms           TypedPointer[uint64]
+	JitedFuncLens        TypedPointer[uint32]
 	BtfId                BTFID
 	FuncInfoRecSize      uint32
-	FuncInfo             Pointer
+	FuncInfo             TypedPointer[uint8]
 	NrFuncInfo           uint32
 	NrLineInfo           uint32
-	LineInfo             Pointer
-	JitedLineInfo        Pointer
+	LineInfo             TypedPointer[uint8]
+	JitedLineInfo        TypedPointer[uint64]
 	NrJitedLineInfo      uint32
 	LineInfoRecSize      uint32
 	JitedLineInfoRecSize uint32
@@ -837,8 +837,8 @@ func BtfGetNextId(attr *BtfGetNextIdAttr) error {
 
 type BtfLoadAttr struct {
 	structs.HostLayout
-	Btf            Pointer
-	BtfLogBuf      Pointer
+	Btf            TypedPointer[uint8]
+	BtfLogBuf      TypedPointer[uint8]
 	BtfSize        uint32
 	BtfLogSize     uint32
 	BtfLogLevel    uint32
@@ -927,9 +927,9 @@ type LinkCreateKprobeMultiAttr struct {
 	Flags            uint32
 	KprobeMultiFlags uint32
 	Count            uint32
-	Syms             Pointer
-	Addrs            Pointer
-	Cookies          Pointer
+	Syms             StringSlicePointer
+	Addrs            TypedPointer[uintptr]
+	Cookies          TypedPointer[uint64]
 	_                [16]byte
 }
 
@@ -1046,10 +1046,10 @@ type LinkCreateUprobeMultiAttr struct {
 	TargetFd         uint32
 	AttachType       AttachType
 	Flags            uint32
-	Path             Pointer
-	Offsets          Pointer
-	RefCtrOffsets    Pointer
-	Cookies          Pointer
+	Path             StringPointer
+	Offsets          TypedPointer[uint64]
+	RefCtrOffsets    TypedPointer[uint64]
+	Cookies          TypedPointer[uint64]
 	Count            uint32
 	UprobeMultiFlags uint32
 	Pid              uint32
@@ -1302,7 +1302,7 @@ func MapUpdateElem(attr *MapUpdateElemAttr) error {
 
 type ObjGetAttr struct {
 	structs.HostLayout
-	Pathname  Pointer
+	Pathname  StringPointer
 	BpfFd     uint32
 	FileFlags uint32
 	PathFd    int32
@@ -1331,7 +1331,7 @@ func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
 
 type ObjPinAttr struct {
 	structs.HostLayout
-	Pathname  Pointer
+	Pathname  StringPointer
 	BpfFd     uint32
 	FileFlags uint32
 	PathFd    int32
@@ -1415,11 +1415,11 @@ type ProgLoadAttr struct {
 	structs.HostLayout
 	ProgType           ProgType
 	InsnCnt            uint32
-	Insns              Pointer
-	License            Pointer
+	Insns              TypedPointer[uint8]
+	License            StringPointer
 	LogLevel           LogLevel
 	LogSize            uint32
-	LogBuf             Pointer
+	LogBuf             TypedPointer[uint8]
 	KernVersion        uint32
 	ProgFlags          uint32
 	ProgName           ObjName
@@ -1427,16 +1427,16 @@ type ProgLoadAttr struct {
 	ExpectedAttachType AttachType
 	ProgBtfFd          uint32
 	FuncInfoRecSize    uint32
-	FuncInfo           Pointer
+	FuncInfo           TypedPointer[uint8]
 	FuncInfoCnt        uint32
 	LineInfoRecSize    uint32
-	LineInfo           Pointer
+	LineInfo           TypedPointer[uint8]
 	LineInfoCnt        uint32
 	AttachBtfId        TypeID
 	AttachBtfObjFd     uint32
 	CoreReloCnt        uint32
-	FdArray            Pointer
-	CoreRelos          Pointer
+	FdArray            TypedPointer[int32]
+	CoreRelos          TypedPointer[uint8]
 	CoreReloRecSize    uint32
 	LogTrueSize        uint32
 	ProgTokenFd        int32
@@ -1457,12 +1457,12 @@ type ProgQueryAttr struct {
 	AttachType        AttachType
 	QueryFlags        uint32
 	AttachFlags       uint32
-	ProgIds           Pointer
+	ProgIds           TypedPointer[ProgramID]
 	Count             uint32
 	_                 [4]byte
-	ProgAttachFlags   Pointer
-	LinkIds           Pointer
-	LinkAttachFlags   Pointer
+	ProgAttachFlags   TypedPointer[ProgramID]
+	LinkIds           TypedPointer[LinkID]
+	LinkAttachFlags   TypedPointer[LinkID]
 	Revision          uint64
 }
 
@@ -1477,14 +1477,14 @@ type ProgRunAttr struct {
 	Retval      uint32
 	DataSizeIn  uint32
 	DataSizeOut uint32
-	DataIn      Pointer
-	DataOut     Pointer
+	DataIn      TypedPointer[uint8]
+	DataOut     TypedPointer[uint8]
 	Repeat      uint32
 	Duration    uint32
 	CtxSizeIn   uint32
 	CtxSizeOut  uint32
-	CtxIn       Pointer
-	CtxOut      Pointer
+	CtxIn       TypedPointer[uint8]
+	CtxOut      TypedPointer[uint8]
 	Flags       uint32
 	Cpu         uint32
 	BatchSize   uint32
@@ -1498,7 +1498,7 @@ func ProgRun(attr *ProgRunAttr) error {
 
 type RawTracepointOpenAttr struct {
 	structs.HostLayout
-	Name   Pointer
+	Name   StringPointer
 	ProgFd uint32
 	_      [4]byte
 	Cookie uint64
@@ -1529,7 +1529,7 @@ type IterLinkInfo struct {
 	Id            LinkID
 	ProgId        uint32
 	_             [4]byte
-	TargetName    Pointer
+	TargetName    TypedPointer[uint8]
 	TargetNameLen uint32
 }
 
@@ -1541,7 +1541,7 @@ type KprobeLinkInfo struct {
 	_             [4]byte
 	PerfEventType PerfEventType
 	_             [4]byte
-	FuncName      Pointer
+	FuncName      TypedPointer[uint8]
 	NameLen       uint32
 	Offset        uint32
 	Addr          uint64
@@ -1555,11 +1555,11 @@ type KprobeMultiLinkInfo struct {
 	Id      LinkID
 	ProgId  uint32
 	_       [4]byte
-	Addrs   Pointer
+	Addrs   TypedPointer[uint64]
 	Count   uint32
 	Flags   uint32
 	Missed  uint64
-	Cookies uint64
+	Cookies TypedPointer[uint64]
 	_       [16]byte
 }
 
@@ -1613,7 +1613,7 @@ type RawTracepointLinkInfo struct {
 	Id        LinkID
 	ProgId    uint32
 	_         [4]byte
-	TpName    Pointer
+	TpName    TypedPointer[uint8]
 	TpNameLen uint32
 	_         [36]byte
 }
diff --git a/internal/sysenc/buffer.go b/internal/sysenc/buffer.go
index 1b4f052..62e483a 100644
--- a/internal/sysenc/buffer.go
+++ b/internal/sysenc/buffer.go
@@ -63,7 +63,7 @@ func (b Buffer) AppendTo(dst []byte) []byte {
 func (b Buffer) Pointer() sys.Pointer {
 	// NB: This deliberately ignores b.length to support zero-copy
 	// marshaling / unmarshaling using unsafe.Pointer.
-	return sys.NewPointer(b.ptr)
+	return sys.UnsafePointer(b.ptr)
 }
 
 // Unmarshal the buffer into the provided value.
diff --git a/link/iter.go b/link/iter.go
index 2ba601b..ecd2b3a 100644
--- a/link/iter.go
+++ b/link/iter.go
@@ -42,7 +42,7 @@ func AttachIter(opts IterOptions) (*Iter, error) {
 	attr := sys.LinkCreateIterAttr{
 		ProgFd:      uint32(progFd),
 		AttachType:  sys.AttachType(ebpf.AttachTraceIter),
-		IterInfo:    sys.NewPointer(unsafe.Pointer(&info)),
+		IterInfo:    sys.UnsafePointer(unsafe.Pointer(&info)),
 		IterInfoLen: uint32(unsafe.Sizeof(info)),
 	}
 
diff --git a/link/kprobe_multi.go b/link/kprobe_multi.go
index f19f9f4..9511991 100644
--- a/link/kprobe_multi.go
+++ b/link/kprobe_multi.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"unsafe"
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/asm"
@@ -108,11 +107,11 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin
 
 	case addrs != 0:
 		attr.Count = addrs
-		attr.Addrs = sys.NewPointer(unsafe.Pointer(&opts.Addresses[0]))
+		attr.Addrs = sys.SlicePointer(opts.Addresses)
 	}
 
 	if cookies != 0 {
-		attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0]))
+		attr.Cookies = sys.SlicePointer(opts.Cookies)
 	}
 
 	fd, err := sys.LinkCreateKprobeMulti(attr)
diff --git a/link/query.go b/link/query.go
index fe534f8..714359b 100644
--- a/link/query.go
+++ b/link/query.go
@@ -2,7 +2,6 @@ package link
 
 import (
 	"fmt"
-	"unsafe"
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/internal/sys"
@@ -84,13 +83,13 @@ func QueryPrograms(opts QueryOptions) (*QueryResult, error) {
 		AttachType:        sys.AttachType(opts.Attach),
 		QueryFlags:        opts.QueryFlags,
 		Count:             count,
-		ProgIds:           sys.NewPointer(unsafe.Pointer(&progIds[0])),
+		ProgIds:           sys.SlicePointer(progIds),
 	}
 
 	var linkIds []ID
 	if haveLinkIDs {
 		linkIds = make([]ID, count)
-		attr.LinkIds = sys.NewPointer(unsafe.Pointer(&linkIds[0]))
+		attr.LinkIds = sys.SlicePointer(linkIds)
 	}
 
 	if err := sys.ProgQuery(&attr); err != nil {
diff --git a/link/uprobe_multi.go b/link/uprobe_multi.go
index 49dc18b..3779602 100644
--- a/link/uprobe_multi.go
+++ b/link/uprobe_multi.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"unsafe"
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/asm"
@@ -84,15 +83,15 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up
 		AttachType:       sys.BPF_TRACE_UPROBE_MULTI,
 		UprobeMultiFlags: flags,
 		Count:            uint32(addrs),
-		Offsets:          sys.NewPointer(unsafe.Pointer(&addresses[0])),
+		Offsets:          sys.SlicePointer(addresses),
 		Pid:              opts.PID,
 	}
 
 	if refCtrOffsets != 0 {
-		attr.RefCtrOffsets = sys.NewPointer(unsafe.Pointer(&opts.RefCtrOffsets[0]))
+		attr.RefCtrOffsets = sys.SlicePointer(opts.RefCtrOffsets)
 	}
 	if cookies != 0 {
-		attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0]))
+		attr.Cookies = sys.SlicePointer(opts.Cookies)
 	}
 
 	fd, err := sys.LinkCreateUprobeMulti(attr)
@@ -201,7 +200,7 @@ var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", fu
 		ProgFd:     uint32(prog.FD()),
 		AttachType: sys.BPF_TRACE_UPROBE_MULTI,
 		Path:       sys.NewStringPointer("/"),
-		Offsets:    sys.NewPointer(unsafe.Pointer(&[]uint64{0})),
+		Offsets:    sys.SlicePointer([]uint64{0}),
 		Count:      1,
 	})
 	switch {
diff --git a/map.go b/map.go
index e5d8bd7..e206b9e 100644
--- a/map.go
+++ b/map.go
@@ -45,7 +45,7 @@ type MapOptions struct {
 }
 
 // MapID represents the unique ID of an eBPF map
-type MapID uint32
+type MapID = sys.MapID
 
 // MapSpec defines a Map.
 type MapSpec struct {
@@ -739,7 +739,7 @@ func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLooku
 // Returns a nil value if a key doesn't exist.
 func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
 	valueBytes := make([]byte, m.fullValueSize)
-	valuePtr := sys.NewSlicePointer(valueBytes)
+	valuePtr := sys.UnsafeSlicePointer(valueBytes)
 
 	err := m.lookup(key, valuePtr, 0)
 	if errors.Is(err, ErrKeyNotExist) {
@@ -755,7 +755,7 @@ func (m *Map) lookupPerCPU(key, valueOut any, flags MapLookupFlags) error {
 		return err
 	}
 	valueBytes := make([]byte, m.fullValueSize)
-	if err := m.lookup(key, sys.NewSlicePointer(valueBytes), flags); err != nil {
+	if err := m.lookup(key, sys.UnsafeSlicePointer(valueBytes), flags); err != nil {
 		return err
 	}
 	return unmarshalPerCPUValue(slice, int(m.valueSize), valueBytes)
@@ -789,7 +789,7 @@ func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) err
 		return err
 	}
 	valueBytes := make([]byte, m.fullValueSize)
-	if err := m.lookupAndDelete(key, sys.NewSlicePointer(valueBytes), flags); err != nil {
+	if err := m.lookupAndDelete(key, sys.UnsafeSlicePointer(valueBytes), flags); err != nil {
 		return err
 	}
 	return unmarshalPerCPUValue(slice, int(m.valueSize), valueBytes)
@@ -966,7 +966,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
 // Returns nil if there are no more keys.
 func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
 	nextKey := make([]byte, m.keySize)
-	nextKeyPtr := sys.NewSlicePointer(nextKey)
+	nextKeyPtr := sys.UnsafeSlicePointer(nextKey)
 
 	err := m.nextKey(key, nextKeyPtr)
 	if errors.Is(err, ErrKeyNotExist) {
@@ -1006,7 +1006,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error {
 			}
 
 			// Retry the syscall with a valid non-existing key.
-			attr.Key = sys.NewSlicePointer(guessKey)
+			attr.Key = sys.UnsafeSlicePointer(guessKey)
 			if err = sys.MapGetNextKey(&attr); err == nil {
 				return nil
 			}
@@ -1032,7 +1032,7 @@ func (m *Map) guessNonExistentKey() ([]byte, error) {
 	if err != nil {
 		return nil, err
 	}
-	valuePtr := sys.NewSlicePointer(page)
+	valuePtr := sys.UnsafeSlicePointer(page)
 
 	randKey := make([]byte, int(m.keySize))
 
@@ -1159,7 +1159,7 @@ func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, va
 	}
 
 	valueBuf := make([]byte, count*int(m.fullValueSize))
-	valuePtr := sys.NewSlicePointer(valueBuf)
+	valuePtr := sys.UnsafeSlicePointer(valueBuf)
 
 	n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valuePtr, opts)
 	if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) {
@@ -1211,8 +1211,8 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key
 		Keys:     keyBuf.Pointer(),
 		Values:   valuePtr,
 		Count:    uint32(count),
-		InBatch:  sys.NewSlicePointer(inBatch),
-		OutBatch: sys.NewSlicePointer(cursor.opaque),
+		InBatch:  sys.UnsafeSlicePointer(inBatch),
+		OutBatch: sys.UnsafeSlicePointer(cursor.opaque),
 	}
 
 	if opts != nil {
@@ -1294,7 +1294,7 @@ func (m *Map) batchUpdatePerCPU(keys, values any, opts *BatchOptions) (int, erro
 		return 0, err
 	}
 
-	return m.batchUpdate(count, keys, sys.NewSlicePointer(valueBuf), opts)
+	return m.batchUpdate(count, keys, sys.UnsafeSlicePointer(valueBuf), opts)
 }
 
 // BatchDelete batch deletes entries in the map by keys.
@@ -1483,7 +1483,7 @@ func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) {
 	if data == nil {
 		if m.keySize == 0 {
 			// Queues have a key length of zero, so passing nil here is valid.
-			return sys.NewPointer(nil), nil
+			return sys.UnsafePointer(nil), nil
 		}
 		return sys.Pointer{}, errors.New("can't use nil as key of map")
 	}
@@ -1518,7 +1518,7 @@ func (m *Map) marshalValue(data interface{}) (sys.Pointer, error) {
 		return sys.Pointer{}, err
 	}
 
-	return sys.NewSlicePointer(buf), nil
+	return sys.UnsafeSlicePointer(buf), nil
 }
 
 func (m *Map) unmarshalValue(value any, buf sysenc.Buffer) error {
diff --git a/marshalers.go b/marshalers.go
index 57a0a8e..d4e719c 100644
--- a/marshalers.go
+++ b/marshalers.go
@@ -20,7 +20,7 @@ import (
 // unsafe.Pointer.
 func marshalMapSyscallInput(data any, length int) (sys.Pointer, error) {
 	if ptr, ok := data.(unsafe.Pointer); ok {
-		return sys.NewPointer(ptr), nil
+		return sys.UnsafePointer(ptr), nil
 	}
 
 	buf, err := sysenc.Marshal(data, length)
@@ -96,7 +96,7 @@ func marshalPerCPUValue(slice any, elemLength int) (sys.Pointer, error) {
 		return sys.Pointer{}, err
 	}
 
-	return sys.NewSlicePointer(buf), nil
+	return sys.UnsafeSlicePointer(buf), nil
 }
 
 // marshalBatchPerCPUValue encodes a batch-sized slice of slices containing
diff --git a/prog.go b/prog.go
index 8fcae6a..9bcf1c2 100644
--- a/prog.go
+++ b/prog.go
@@ -10,7 +10,6 @@ import (
 	"runtime"
 	"strings"
 	"time"
-	"unsafe"
 
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/btf"
@@ -38,7 +37,7 @@ var errBadRelocation = errors.New("bad CO-RE relocation")
 var errUnknownKfunc = errors.New("unknown kfunc")
 
 // ProgramID represents the unique ID of an eBPF program.
-type ProgramID uint32
+type ProgramID = sys.ProgramID
 
 const (
 	// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
@@ -333,11 +332,11 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 
 		attr.FuncInfoRecSize = btf.FuncInfoSize
 		attr.FuncInfoCnt = uint32(len(fib)) / btf.FuncInfoSize
-		attr.FuncInfo = sys.NewSlicePointer(fib)
+		attr.FuncInfo = sys.SlicePointer(fib)
 
 		attr.LineInfoRecSize = btf.LineInfoSize
 		attr.LineInfoCnt = uint32(len(lib)) / btf.LineInfoSize
-		attr.LineInfo = sys.NewSlicePointer(lib)
+		attr.LineInfo = sys.SlicePointer(lib)
 	}
 
 	if !b.Empty() {
@@ -372,7 +371,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 
 	if len(handles) > 0 {
 		fdArray := handles.fdArray()
-		attr.FdArray = sys.NewPointer(unsafe.Pointer(&fdArray[0]))
+		attr.FdArray = sys.SlicePointer(fdArray)
 	}
 
 	buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
@@ -382,7 +381,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 	}
 
 	bytecode := buf.Bytes()
-	attr.Insns = sys.NewSlicePointer(bytecode)
+	attr.Insns = sys.SlicePointer(bytecode)
 	attr.InsnCnt = uint32(len(bytecode) / asm.InstructionSize)
 
 	if spec.AttachTarget != nil {
@@ -417,7 +416,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 		logBuf = make([]byte, logSize)
 		attr.LogLevel = opts.LogLevel
 		attr.LogSize = uint32(len(logBuf))
-		attr.LogBuf = sys.NewSlicePointer(logBuf)
+		attr.LogBuf = sys.SlicePointer(logBuf)
 	}
 
 	for {
@@ -461,7 +460,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
 
 		logBuf = make([]byte, logSize)
 		attr.LogSize = logSize
-		attr.LogBuf = sys.NewSlicePointer(logBuf)
+		attr.LogBuf = sys.SlicePointer(logBuf)
 	}
 
 	end := bytes.IndexByte(logBuf, 0)
@@ -778,7 +777,7 @@ var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error {
 	attr := sys.ProgRunAttr{
 		ProgFd:     uint32(prog.FD()),
 		DataSizeIn: uint32(len(in)),
-		DataIn:     sys.NewSlicePointer(in),
+		DataIn:     sys.SlicePointer(in),
 	}
 
 	err = sys.ProgRun(&attr)
@@ -829,13 +828,13 @@ func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) {
 		ProgFd:      p.fd.Uint(),
 		DataSizeIn:  uint32(len(opts.Data)),
 		DataSizeOut: uint32(len(opts.DataOut)),
-		DataIn:      sys.NewSlicePointer(opts.Data),
-		DataOut:     sys.NewSlicePointer(opts.DataOut),
+		DataIn:      sys.SlicePointer(opts.Data),
+		DataOut:     sys.SlicePointer(opts.DataOut),
 		Repeat:      uint32(opts.Repeat),
 		CtxSizeIn:   uint32(len(ctxBytes)),
 		CtxSizeOut:  uint32(len(ctxOut)),
-		CtxIn:       sys.NewSlicePointer(ctxBytes),
-		CtxOut:      sys.NewSlicePointer(ctxOut),
+		CtxIn:       sys.SlicePointer(ctxBytes),
+		CtxOut:      sys.SlicePointer(ctxOut),
 		Flags:       opts.Flags,
 		Cpu:         opts.CPU,
 	}
diff --git a/syscalls.go b/syscalls.go
index 25c84c3..f057478 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -56,7 +56,7 @@ func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD,
 	return sys.ProgLoad(&sys.ProgLoadAttr{
 		ProgType: sys.ProgType(typ),
 		License:  sys.NewStringPointer(license),
-		Insns:    sys.NewSlicePointer(bytecode),
+		Insns:    sys.SlicePointer(bytecode),
 		InsnCnt:  uint32(len(bytecode) / asm.InstructionSize),
 	})
 }
@@ -337,7 +337,7 @@ var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", func() er
 	_, err := sys.ProgLoad(&sys.ProgLoadAttr{
 		ProgType:    sys.ProgType(SocketFilter),
 		License:     sys.NewStringPointer("MIT"),
-		Insns:       sys.NewSlicePointer(bytecode),
+		Insns:       sys.SlicePointer(bytecode),
 		InsnCnt:     uint32(len(bytecode) / asm.InstructionSize),
 		FuncInfoCnt: 1,
 		ProgBtfFd:   math.MaxUint32,


Test patch:
diff --git a/internal/output_test.go b/internal/output_test.go
index b2e6fca..a5ea90f 100644
--- a/internal/output_test.go
+++ b/internal/output_test.go
@@ -29,13 +29,14 @@ func TestIdentifier(t *testing.T) {
 	}
 }
 
+type foo struct{}
+
 func TestGoTypeName(t *testing.T) {
-	type foo struct{}
 	type bar[T any] struct{}
 	qt.Assert(t, qt.Equals(GoTypeName(foo{}), "foo"))
 	qt.Assert(t, qt.Equals(GoTypeName(new(foo)), "foo"))
 	qt.Assert(t, qt.Equals(GoTypeName(new(*foo)), "foo"))
 	qt.Assert(t, qt.Equals(GoTypeName(bar[int]{}), "bar[int]"))
-	// Broken in the stdlib, see GoTypeName for details.
-	// qt.Assert(t, GoTypeName(bar[qt.C]{}), qt.Equals, "bar[quicktest.C]")
+	qt.Assert(t, qt.Equals(GoTypeName(bar[foo]{}), "bar[foo]"))
+	qt.Assert(t, qt.Equals(GoTypeName(bar[testing.T]{}), "bar[testing.T]"))
 }
diff --git a/internal/sys/ptr_test.go b/internal/sys/ptr_test.go
new file mode 100644
index 0000000..50736bf
--- /dev/null
+++ b/internal/sys/ptr_test.go
@@ -0,0 +1,31 @@
+package sys
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+	"unsafe"
+
+	"github.com/go-quicktest/qt"
+)
+
+func TestTypedPointer(t *testing.T) {
+	ptrs := []any{
+		TypedPointer[uint32]{},
+		TypedPointer[complex128]{},
+		StringPointer{},
+		StringSlicePointer{},
+	}
+
+	for i, a := range ptrs {
+		qt.Assert(t, qt.Equals(unsafe.Alignof(a), unsafe.Alignof(unsafe.Pointer(nil))))
+
+		for _, b := range ptrs[i+1:] {
+			t.Run(fmt.Sprintf("%T %T", a, b), func(t *testing.T) {
+				typeOfA := reflect.TypeOf(a)
+				typeOfB := reflect.TypeOf(b)
+				qt.Assert(t, qt.IsFalse(typeOfA.ConvertibleTo(typeOfB)))
+			})
+		}
+	}
+}
diff --git a/internal/sysenc/buffer_test.go b/internal/sysenc/buffer_test.go
index 5918198..6be9acf 100644
--- a/internal/sysenc/buffer_test.go
+++ b/internal/sysenc/buffer_test.go
@@ -23,6 +23,6 @@ func TestUnsafeBuffer(t *testing.T) {
 	buf := sysenc.UnsafeBuffer(ptr)
 
 	qt.Assert(t, qt.Equals(buf.CopyTo(make([]byte, 1)), 0))
-	qt.Assert(t, qt.Equals(buf.Pointer(), sys.NewPointer(ptr)))
+	qt.Assert(t, qt.Equals(buf.Pointer(), sys.UnsafePointer(ptr)))
 	qt.Assert(t, qt.IsNil(buf.Unmarshal(new(uint16))))
 }
diff --git a/internal/testutils/bpffs.go b/internal/testutils/bpffs_other.go
similarity index 95%
rename from internal/testutils/bpffs.go
rename to internal/testutils/bpffs_other.go
index 7df15a3..776ba54 100644
--- a/internal/testutils/bpffs.go
+++ b/internal/testutils/bpffs_other.go
@@ -1,3 +1,5 @@
+//go:build !windows
+
 package testutils
 
 import (
diff --git a/internal/testutils/bpffs_windows.go b/internal/testutils/bpffs_windows.go
new file mode 100644
index 0000000..29cc977
--- /dev/null
+++ b/internal/testutils/bpffs_windows.go
@@ -0,0 +1,44 @@
+package testutils
+
+import (
+	"errors"
+	"math/rand"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+
+	"github.com/cilium/ebpf/internal/efw"
+	"github.com/go-quicktest/qt"
+)
+
+// TempBPFFS creates a random prefix to use when pinning on Windows.
+func TempBPFFS(tb testing.TB) string {
+	tb.Helper()
+
+	path := filepath.Join("ebpf-go-test", strconv.Itoa(rand.Int()))
+	tb.Cleanup(func() {
+		tb.Helper()
+
+		cursor := path
+		for {
+			next, _, err := efw.EbpfGetNextPinnedObjectPath(cursor, efw.EBPF_OBJECT_UNKNOWN)
+			if errors.Is(err, efw.EBPF_NO_MORE_KEYS) {
+				break
+			}
+			qt.Assert(tb, qt.IsNil(err))
+
+			if !strings.HasPrefix(next, path) {
+				break
+			}
+
+			if err := efw.EbpfObjectUnpin(next); err != nil {
+				tb.Errorf("Failed to unpin %s: %s", next, err)
+			}
+
+			cursor = next
+		}
+	})
+
+	return path
+}
diff --git a/internal/testutils/bpffs_windows_test.go b/internal/testutils/bpffs_windows_test.go
new file mode 100644
index 0000000..f776f06
--- /dev/null
+++ b/internal/testutils/bpffs_windows_test.go
@@ -0,0 +1,72 @@
+package testutils_test
+
+import (
+	"bytes"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/sys"
+	"github.com/cilium/ebpf/internal/testutils"
+	"github.com/go-quicktest/qt"
+)
+
+func TestTempBPFFS(t *testing.T) {
+	var progPath, mapPath string
+	t.Run("pin", func(t *testing.T) {
+		tmp := testutils.TempBPFFS(t)
+		progPath = filepath.Join(tmp, "prog")
+		mapPath = filepath.Join(tmp, "map")
+
+		var buffer bytes.Buffer
+		insns := asm.Instructions{
+			asm.LoadImm(asm.R0, 0, asm.DWord),
+			asm.Return(),
+		}
+		err := insns.Marshal(&buffer, internal.NativeEndian)
+		qt.Assert(t, qt.IsNil(err))
+
+		progFd, err := sys.ProgLoad(&sys.ProgLoadAttr{
+			ProgType: 998, // XDP_TEST
+			License:  sys.NewStringPointer(""),
+			InsnCnt:  uint32(buffer.Len() / asm.InstructionSize),
+			Insns:    sys.NewSlicePointer(buffer.Bytes()),
+		})
+		qt.Assert(t, qt.IsNil(err))
+		defer progFd.Close()
+
+		err = sys.ObjPin(&sys.ObjPinAttr{
+			BpfFd:    progFd.Uint(),
+			Pathname: sys.NewStringPointer(progPath),
+		})
+		qt.Assert(t, qt.IsNil(err))
+
+		mapFd, err := sys.MapCreate(&sys.MapCreateAttr{
+			MapType:    2, // ARRAY
+			KeySize:    4,
+			ValueSize:  4,
+			MaxEntries: 1,
+		})
+		qt.Assert(t, qt.IsNil(err))
+		defer mapFd.Close()
+
+		err = sys.ObjPin(&sys.ObjPinAttr{
+			BpfFd:    progFd.Uint(),
+			Pathname: sys.NewStringPointer(mapPath),
+		})
+		qt.Assert(t, qt.IsNil(err))
+		qt.Assert(t, qt.IsNil(mapFd.Close()))
+	})
+
+	_, err := sys.ObjGet(&sys.ObjGetAttr{
+		Pathname: sys.NewStringPointer(progPath),
+	})
+	qt.Assert(t, qt.ErrorIs(err, os.ErrNotExist))
+
+	_, err = sys.ObjGet(&sys.ObjGetAttr{
+		Pathname: sys.NewStringPointer(mapPath),
+	})
+	qt.Assert(t, qt.ErrorIs(err, os.ErrNotExist))
+}