Merge pull request #14386 from jterry75/jterry75/update_microsoft_hcsshim_v0_15_0

Bump github.com/Microsoft/hcsshim to v0.15.0
This commit is contained in:
Phil Estes authored and GitHub committed 2026-10-20 20:47:46 +00:00
commit 2c2a63f432
244 files changed
+14802 -4769

No files matched your search

+17 -16
View File
@@ -5,8 +5,8 @@ go 1.27.0
require (
dario.cat/mergo v1.0.2
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
github.com/Microsoft/go-winio v0.6.3
github.com/Microsoft/hcsshim v0.15.0-rc.4
github.com/Microsoft/go-winio v0.6.4
github.com/Microsoft/hcsshim v0.15.0
github.com/checkpoint-restore/checkpointctl v1.6.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containerd/btrfs/v2 v2.0.0
@@ -67,20 +67,20 @@ require (
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
go.etcd.io/bbolt v1.5.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
go.opentelemetry.io/contrib/propagators/envcar v0.71.0
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
go.opentelemetry.io/otel/sdk v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
golang.org/x/mod v0.42.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0
go.opentelemetry.io/contrib/propagators/envcar v0.72.0
go.opentelemetry.io/otel v1.47.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0
go.opentelemetry.io/otel/sdk v1.47.0
go.opentelemetry.io/otel/trace v1.47.0
golang.org/x/mod v0.41.0
golang.org/x/sync v0.24.0
golang.org/x/sys v0.49.0
golang.org/x/time v0.16.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc
google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12
k8s.io/apimachinery v0.37.1
@@ -100,7 +100,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cilium/ebpf v0.17.3 // indirect
github.com/cilium/ebpf v0.22.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/containers/ocicrypt v1.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
@@ -138,7 +138,8 @@ require (
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/otel/log v1.47.0 // indirect
go.opentelemetry.io/otel/metric v1.47.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
@@ -147,7 +148,7 @@ require (
golang.org/x/oauth2 v0.37.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.37.1 // indirect
k8s.io/component-base v0.37.1 // indirect
+40 -38
View File
@@ -6,10 +6,10 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.3 h1:M/52tsoyTfwaxajLnXZm7PqkbMGRH8iSWP+8fcg9MQ4=
github.com/Microsoft/go-winio v0.6.3/go.mod h1:ma0QpxizD4fyyzJ1C7MIblymTD2Nxz5Mho/3CepwIZg=
github.com/Microsoft/hcsshim v0.15.0-rc.4 h1:aZFX4LH0S20Lgjq0wG61StIClj7im4yzrxIClkaR8Z8=
github.com/Microsoft/hcsshim v0.15.0-rc.4/go.mod h1:BA9CBztgu4h/6Jsvo1O1M4qjWw09PoYpaEYgexPE578=
github.com/Microsoft/go-winio v0.6.4 h1:kJF2pamtYCWXf0xAT8sAkwKC0pMl2HYBrRPHAqyUc+g=
github.com/Microsoft/go-winio v0.6.4/go.mod h1:1xl5JXG4ze/m74GH6Dc4Mwxfc3VL5siRVQjIAlHH07U=
github.com/Microsoft/hcsshim v0.15.0 h1:AWiHGf0+9f7cDcfhuU3zXORCAPmIOWzTYfHhcrMcx3U=
github.com/Microsoft/hcsshim v0.15.0/go.mod h1:eStXUkML1Fmkgmwr6XHbYkBC1j7p2FA8ZtA9dyRMbts=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
@@ -28,8 +28,8 @@ github.com/checkpoint-restore/checkpointctl v1.6.0 h1:e+hOhEFiUUYZRCbbfbceQ3lLGQ
github.com/checkpoint-restore/checkpointctl v1.6.0/go.mod h1:LjfSgCtcTMbzwcA7d+vXJ7udJsWC/VRJpKUojTAGeCk=
github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg=
github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk=
github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY=
github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/containerd/btrfs/v2 v2.0.0 h1:FN4wsx7KQrYoLXN7uLP0vBV4oVWHOIKDRQ1G2Z0oL5M=
@@ -146,8 +146,8 @@ github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeI
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
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/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -295,28 +295,30 @@ go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
go.opentelemetry.io/contrib/propagators/envcar v0.71.0 h1:r7TFdi1B3O5ai64tONEuBTyxeQaLKVYf75vLiV7ZsDc=
go.opentelemetry.io/contrib/propagators/envcar v0.71.0/go.mod h1:uAnx0mCFm63a47qI4f/hcoI72gVYrGdxEBL+6c500Ns=
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM=
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 h1:Tq+E65HaNbZTeqcLw6QUhsV+/EIWdkfUH2CQzcrlE5M=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0/go.mod h1:A+EVhDakAj1waJBPhmTszZaSr08ww8MRRKy1nB1rqck=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0 h1:LxwW/9ctSCv+QkE/cLR7M91ZIkXNMqJtEMi1vCw9U8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0/go.mod h1:tOsftB4SslBwwErVEPaenU2RpThXWPIU8DoJHEC4dyw=
go.opentelemetry.io/contrib/propagators/envcar v0.72.0 h1:qQD0vhMfPszrsmKawPJwbj4CvBqswbaotIzvZ1JOHtA=
go.opentelemetry.io/contrib/propagators/envcar v0.72.0/go.mod h1:MQgMBQ6sXaNiOVN4StGJObFWp1RPigcDDASoBc8sHek=
go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 h1:aXZXsZ012wOgVkqaiQv+Z/d8V0xKjmg70F6m4CE94lc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0/go.mod h1:ziu1gUIJhAaxM3EV1e3Ralk5AAyL1UOYz3lJJ4VSI38=
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
@@ -340,8 +342,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -410,15 +412,15 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU=
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE=
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc h1:4bNTbnb44EqGVy9HaQxSY2jnafSUdgV3qHtedvNpDKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+14 -8
View File
@@ -183,11 +183,14 @@ func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error)
func ReadDMVerityInfoReader(r io.Reader) (*VerityInfo, error) {
block := make([]byte, blockSize)
if s, err := r.Read(block); err != nil || s != blockSize {
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSuperBlockReadFailure, err)
// io.Reader is allowed to return fewer bytes than requested without that
// being an error, so read the whole block rather than relying on a single
// Read filling it.
if s, err := io.ReadFull(r, block); err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, fmt.Errorf("unexpected bytes read expected=%d actual=%d: %w", blockSize, s, ErrSuperBlockReadFailure)
}
return nil, fmt.Errorf("unexpected bytes read expected=%d actual=%d: %w", blockSize, s, ErrSuperBlockReadFailure)
return nil, fmt.Errorf("%w: %w", ErrSuperBlockReadFailure, err)
}
dmvSB := &dmveritySuperblock{}
@@ -199,12 +202,15 @@ func ReadDMVerityInfoReader(r io.Reader) (*VerityInfo, error) {
if string(bytes.Trim(dmvSB.Signature[:], "\x00")[:]) != VeritySignature {
return nil, ErrNotVeritySuperBlock
}
if int(dmvSB.SaltSize) > len(dmvSB.Salt) {
return nil, fmt.Errorf("%w: salt size %d exceeds salt capacity %d", ErrSuperBlockParseFailure, dmvSB.SaltSize, len(dmvSB.Salt))
}
if s, err := r.Read(block); err != nil || s != blockSize {
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrRootHashReadFailure, err)
if s, err := io.ReadFull(r, block); err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, fmt.Errorf("unexpected bytes read expected=%d, actual=%d: %w", blockSize, s, ErrRootHashReadFailure)
}
return nil, fmt.Errorf("unexpected bytes read expected=%d, actual=%d: %w", blockSize, s, ErrRootHashReadFailure)
return nil, fmt.Errorf("%w: %w", ErrRootHashReadFailure, err)
}
rootHash := hash2(dmvSB.Salt[:dmvSB.SaltSize], block)
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+17 -3
View File
@@ -194,16 +194,30 @@ func (r *legacyLayerReader) reset() {
func findBackupStreamSize(r io.Reader) (int64, error) {
br := winio.NewBackupStreamReader(r)
var size int64
for {
hdr, err := br.Next()
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
// With no inline data, sparse blocks provide the logical size.
return size, nil
}
return 0, err
}
if hdr.Id == winio.BackupData {
return hdr.Size, nil
switch hdr.Id {
case winio.BackupData:
// Non-sparse files and sparse files with inline data carry their
// logical size in BackupData.
if hdr.Size > 0 || hdr.Attributes&winio.StreamSparseAttributes == 0 {
return hdr.Size, nil
}
// A sparse BackupData stream without inline data is followed by
// sparse blocks that describe its logical size.
case winio.BackupSparseBlock:
// The terminal zero-length block has an offset at logical EOF.
if end := hdr.Offset + hdr.Size; end > size {
size = end
}
}
}
}
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+14
View File
@@ -154,6 +154,20 @@ func CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSi
)(blockCimPath, isSealed, hashSize, signatureSize, fixedHeaderSize, hash, signature)
}
func CimGetVerificationInformation2(blockCimPath string, flags types.CimGetVerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *types.CimSignatureType, hashAlgorithm *types.CimHashAlgorithm) (hr error) {
return pickSupported(
cimwriter.CimGetVerificationInformation2,
cimfs.CimGetVerificationInformation2,
)(blockCimPath, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
}
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *types.CimFsCimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
return pickSupported(
cimwriter.CimQueryBlockInfo,
cimfs.CimQueryBlockInfo,
)(blockCimPath, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
}
func CimMountVerifiedImage(imagePath string, fsName string, flags uint32, volumeID *guid.GUID, hashSize uint16, hash *byte) error {
return cimfs.CimMountVerifiedImage(imagePath, fsName, flags, volumeID, hashSize, hash)
}
+7 -1
View File
@@ -19,6 +19,10 @@ type FsHandle = types.FsHandle
type StreamHandle = types.StreamHandle
type FileMetadata = types.CimFsFileMetadata
type ImagePath = types.CimFsImagePath
type CimInfo = types.CimFsCimInfo
type VerificationInfoFlags = types.CimGetVerificationInfoFlags
type SignatureType = types.CimSignatureType
type HashAlgorithm = types.CimHashAlgorithm
//sys CimMountImage(imagePath string, fsName string, flags uint32, volumeID *GUID) (hr error) = cimfs.CimMountImage?
//sys CimDismountImage(volumeID *GUID) (hr error) = cimfs.CimDismountImage?
@@ -29,7 +33,7 @@ type ImagePath = types.CimFsImagePath
//sys CimCommitImage(cimFSHandle FsHandle) (hr error) = cimfs.CimCommitImage?
//sys CimCreateFile(cimFSHandle FsHandle, path string, file *FileMetadata, cimStreamHandle *StreamHandle) (hr error) = cimfs.CimCreateFile?
//sys CimCloseStream(cimStreamHandle StreamHandle) (hr error) = cimfs.CimCloseStream?
//sys CimCloseStream(cimStreamHandle StreamHandle) = cimfs.CimCloseStream?
//sys CimWriteStream(cimStreamHandle StreamHandle, buffer uintptr, bufferSize uint32) (hr error) = cimfs.CimWriteStream?
//sys CimDeletePath(cimFSHandle FsHandle, path string) (hr error) = cimfs.CimDeletePath?
//sys CimCreateHardLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimfs.CimCreateHardLink?
@@ -41,6 +45,8 @@ type ImagePath = types.CimFsImagePath
//sys CimCreateMergeLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimfs.CimCreateMergeLink?
//sys CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) = cimfs.CimSealImage?
//sys CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSize *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte) (hr error) = cimfs.CimGetVerificationInformation?
//sys CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) = cimfs.CimGetVerificationInformation2?
//sys CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) = cimfs.CimQueryBlockInfo?
//sys CimMountVerifiedImage(imagePath string, fsName string, flags uint32, volumeID *GUID, hashSize uint16, hash *byte) (hr error) = cimfs.CimMountVerifiedImage?
//sys CimMergeMountVerifiedImage(numCimPaths uint32, backingImagePaths *ImagePath, flags uint32, volumeID *GUID, hashSize uint16, hash *byte) (hr error) = cimfs.CimMergeMountVerifiedImage?
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
@@ -39,27 +35,29 @@ func errnoErr(e syscall.Errno) error {
var (
modcimfs = windows.NewLazySystemDLL("cimfs.dll")
procCimAddFsToMergedImage = modcimfs.NewProc("CimAddFsToMergedImage")
procCimAddFsToMergedImage2 = modcimfs.NewProc("CimAddFsToMergedImage2")
procCimCloseImage = modcimfs.NewProc("CimCloseImage")
procCimCloseStream = modcimfs.NewProc("CimCloseStream")
procCimCommitImage = modcimfs.NewProc("CimCommitImage")
procCimCreateAlternateStream = modcimfs.NewProc("CimCreateAlternateStream")
procCimCreateFile = modcimfs.NewProc("CimCreateFile")
procCimCreateHardLink = modcimfs.NewProc("CimCreateHardLink")
procCimCreateImage = modcimfs.NewProc("CimCreateImage")
procCimCreateImage2 = modcimfs.NewProc("CimCreateImage2")
procCimCreateMergeLink = modcimfs.NewProc("CimCreateMergeLink")
procCimDeletePath = modcimfs.NewProc("CimDeletePath")
procCimDismountImage = modcimfs.NewProc("CimDismountImage")
procCimGetVerificationInformation = modcimfs.NewProc("CimGetVerificationInformation")
procCimMergeMountImage = modcimfs.NewProc("CimMergeMountImage")
procCimMergeMountVerifiedImage = modcimfs.NewProc("CimMergeMountVerifiedImage")
procCimMountImage = modcimfs.NewProc("CimMountImage")
procCimMountVerifiedImage = modcimfs.NewProc("CimMountVerifiedImage")
procCimSealImage = modcimfs.NewProc("CimSealImage")
procCimTombstoneFile = modcimfs.NewProc("CimTombstoneFile")
procCimWriteStream = modcimfs.NewProc("CimWriteStream")
procCimAddFsToMergedImage = modcimfs.NewProc("CimAddFsToMergedImage")
procCimAddFsToMergedImage2 = modcimfs.NewProc("CimAddFsToMergedImage2")
procCimCloseImage = modcimfs.NewProc("CimCloseImage")
procCimCloseStream = modcimfs.NewProc("CimCloseStream")
procCimCommitImage = modcimfs.NewProc("CimCommitImage")
procCimCreateAlternateStream = modcimfs.NewProc("CimCreateAlternateStream")
procCimCreateFile = modcimfs.NewProc("CimCreateFile")
procCimCreateHardLink = modcimfs.NewProc("CimCreateHardLink")
procCimCreateImage = modcimfs.NewProc("CimCreateImage")
procCimCreateImage2 = modcimfs.NewProc("CimCreateImage2")
procCimCreateMergeLink = modcimfs.NewProc("CimCreateMergeLink")
procCimDeletePath = modcimfs.NewProc("CimDeletePath")
procCimDismountImage = modcimfs.NewProc("CimDismountImage")
procCimGetVerificationInformation = modcimfs.NewProc("CimGetVerificationInformation")
procCimGetVerificationInformation2 = modcimfs.NewProc("CimGetVerificationInformation2")
procCimMergeMountImage = modcimfs.NewProc("CimMergeMountImage")
procCimMergeMountVerifiedImage = modcimfs.NewProc("CimMergeMountVerifiedImage")
procCimMountImage = modcimfs.NewProc("CimMountImage")
procCimMountVerifiedImage = modcimfs.NewProc("CimMountVerifiedImage")
procCimQueryBlockInfo = modcimfs.NewProc("CimQueryBlockInfo")
procCimSealImage = modcimfs.NewProc("CimSealImage")
procCimTombstoneFile = modcimfs.NewProc("CimTombstoneFile")
procCimWriteStream = modcimfs.NewProc("CimWriteStream")
)
func CimAddFsToMergedImage(cimFSHandle FsHandle, path string) (hr error) {
@@ -119,18 +117,12 @@ func CimCloseImage(cimFSHandle FsHandle) (err error) {
return
}
func CimCloseStream(cimStreamHandle StreamHandle) (hr error) {
hr = procCimCloseStream.Find()
if hr != nil {
func CimCloseStream(cimStreamHandle StreamHandle) (err error) {
err = procCimCloseStream.Find()
if err != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
return
}
@@ -366,6 +358,30 @@ func _CimGetVerificationInformation(blockCimPath *uint16, isSealed *uint32, hash
return
}
func CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
if hr != nil {
return
}
return _CimGetVerificationInformation2(_p0, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
}
func _CimGetVerificationInformation2(blockCimPath *uint16, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
hr = procCimGetVerificationInformation2.Find()
if hr != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimGetVerificationInformation2.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(flags), uintptr(unsafe.Pointer(isSealed)), uintptr(unsafe.Pointer(hashSize)), uintptr(unsafe.Pointer(signatureOffset)), uintptr(unsafe.Pointer(signatureSize)), uintptr(unsafe.Pointer(fixedHeaderSize)), uintptr(unsafe.Pointer(hash)), uintptr(unsafe.Pointer(signature)), uintptr(unsafe.Pointer(signatureType)), uintptr(unsafe.Pointer(hashAlgorithm)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func CimMergeMountImage(numCimPaths uint32, backingImagePaths *ImagePath, flags uint32, volumeID *GUID) (hr error) {
hr = procCimMergeMountImage.Find()
if hr != nil {
@@ -454,6 +470,30 @@ func _CimMountVerifiedImage(imagePath *uint16, fsName *uint16, flags uint32, vol
return
}
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
if hr != nil {
return
}
return _CimQueryBlockInfo(_p0, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
}
func _CimQueryBlockInfo(blockCimPath *uint16, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
hr = procCimQueryBlockInfo.Find()
if hr != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimQueryBlockInfo.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(cimInfoBufferSize), uintptr(unsafe.Pointer(cimInfoBuffer)), uintptr(unsafe.Pointer(requiredBufferSize)), uintptr(unsafe.Pointer(cimCount)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
@@ -17,6 +17,10 @@ type FsHandle = types.FsHandle
type StreamHandle = types.StreamHandle
type FileMetadata = types.CimFsFileMetadata
type ImagePath = types.CimFsImagePath
type CimInfo = types.CimFsCimInfo
type VerificationInfoFlags = types.CimGetVerificationInfoFlags
type SignatureType = types.CimSignatureType
type HashAlgorithm = types.CimHashAlgorithm
//sys CimCreateImage(imagePath string, oldFSName *uint16, newFSName *uint16, cimFSHandle *FsHandle) (hr error) = cimwriter.CimCreateImage?
//sys CimCreateImage2(imagePath string, flags uint32, oldFSName *uint16, newFSName *uint16, cimFSHandle *FsHandle) (hr error) = cimwriter.CimCreateImage2?
@@ -24,7 +28,7 @@ type ImagePath = types.CimFsImagePath
//sys CimCommitImage(cimFSHandle FsHandle) (hr error) = cimwriter.CimCommitImage?
//sys CimCreateFile(cimFSHandle FsHandle, path string, file *FileMetadata, cimStreamHandle *StreamHandle) (hr error) = cimwriter.CimCreateFile?
//sys CimCloseStream(cimStreamHandle StreamHandle) (hr error) = cimwriter.CimCloseStream?
//sys CimCloseStream(cimStreamHandle StreamHandle) = cimwriter.CimCloseStream?
//sys CimWriteStream(cimStreamHandle StreamHandle, buffer uintptr, bufferSize uint32) (hr error) = cimwriter.CimWriteStream?
//sys CimDeletePath(cimFSHandle FsHandle, path string) (hr error) = cimwriter.CimDeletePath?
//sys CimCreateHardLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimwriter.CimCreateHardLink?
@@ -37,6 +41,8 @@ type ImagePath = types.CimFsImagePath
//sys CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) = cimwriter.CimSealImage?
//sys CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSize *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte) (hr error) = cimwriter.CimGetVerificationInformation?
//sys CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) = cimwriter.CimGetVerificationInformation2?
//sys CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) = cimwriter.CimQueryBlockInfo?
var load = sync.OnceValue(func() error {
// Pre-load the DLL with a restricted search path (System32 + application directory only)
@@ -7,18 +7,16 @@ package cimwriter
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -28,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
@@ -37,22 +35,24 @@ func errnoErr(e syscall.Errno) error {
var (
modcimwriter = syscall.NewLazyDLL("cimwriter.dll")
procCimAddFsToMergedImage = modcimwriter.NewProc("CimAddFsToMergedImage")
procCimAddFsToMergedImage2 = modcimwriter.NewProc("CimAddFsToMergedImage2")
procCimCloseImage = modcimwriter.NewProc("CimCloseImage")
procCimCloseStream = modcimwriter.NewProc("CimCloseStream")
procCimCommitImage = modcimwriter.NewProc("CimCommitImage")
procCimCreateAlternateStream = modcimwriter.NewProc("CimCreateAlternateStream")
procCimCreateFile = modcimwriter.NewProc("CimCreateFile")
procCimCreateHardLink = modcimwriter.NewProc("CimCreateHardLink")
procCimCreateImage = modcimwriter.NewProc("CimCreateImage")
procCimCreateImage2 = modcimwriter.NewProc("CimCreateImage2")
procCimCreateMergeLink = modcimwriter.NewProc("CimCreateMergeLink")
procCimDeletePath = modcimwriter.NewProc("CimDeletePath")
procCimGetVerificationInformation = modcimwriter.NewProc("CimGetVerificationInformation")
procCimSealImage = modcimwriter.NewProc("CimSealImage")
procCimTombstoneFile = modcimwriter.NewProc("CimTombstoneFile")
procCimWriteStream = modcimwriter.NewProc("CimWriteStream")
procCimAddFsToMergedImage = modcimwriter.NewProc("CimAddFsToMergedImage")
procCimAddFsToMergedImage2 = modcimwriter.NewProc("CimAddFsToMergedImage2")
procCimCloseImage = modcimwriter.NewProc("CimCloseImage")
procCimCloseStream = modcimwriter.NewProc("CimCloseStream")
procCimCommitImage = modcimwriter.NewProc("CimCommitImage")
procCimCreateAlternateStream = modcimwriter.NewProc("CimCreateAlternateStream")
procCimCreateFile = modcimwriter.NewProc("CimCreateFile")
procCimCreateHardLink = modcimwriter.NewProc("CimCreateHardLink")
procCimCreateImage = modcimwriter.NewProc("CimCreateImage")
procCimCreateImage2 = modcimwriter.NewProc("CimCreateImage2")
procCimCreateMergeLink = modcimwriter.NewProc("CimCreateMergeLink")
procCimDeletePath = modcimwriter.NewProc("CimDeletePath")
procCimGetVerificationInformation = modcimwriter.NewProc("CimGetVerificationInformation")
procCimGetVerificationInformation2 = modcimwriter.NewProc("CimGetVerificationInformation2")
procCimQueryBlockInfo = modcimwriter.NewProc("CimQueryBlockInfo")
procCimSealImage = modcimwriter.NewProc("CimSealImage")
procCimTombstoneFile = modcimwriter.NewProc("CimTombstoneFile")
procCimWriteStream = modcimwriter.NewProc("CimWriteStream")
)
func CimAddFsToMergedImage(cimFSHandle FsHandle, path string) (hr error) {
@@ -112,18 +112,12 @@ func CimCloseImage(cimFSHandle FsHandle) (err error) {
return
}
func CimCloseStream(cimStreamHandle StreamHandle) (hr error) {
hr = procCimCloseStream.Find()
if hr != nil {
func CimCloseStream(cimStreamHandle StreamHandle) (err error) {
err = procCimCloseStream.Find()
if err != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
return
}
@@ -344,6 +338,54 @@ func _CimGetVerificationInformation(blockCimPath *uint16, isSealed *uint32, hash
return
}
func CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
if hr != nil {
return
}
return _CimGetVerificationInformation2(_p0, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
}
func _CimGetVerificationInformation2(blockCimPath *uint16, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
hr = procCimGetVerificationInformation2.Find()
if hr != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimGetVerificationInformation2.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(flags), uintptr(unsafe.Pointer(isSealed)), uintptr(unsafe.Pointer(hashSize)), uintptr(unsafe.Pointer(signatureOffset)), uintptr(unsafe.Pointer(signatureSize)), uintptr(unsafe.Pointer(fixedHeaderSize)), uintptr(unsafe.Pointer(hash)), uintptr(unsafe.Pointer(signature)), uintptr(unsafe.Pointer(signatureType)), uintptr(unsafe.Pointer(hashAlgorithm)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
if hr != nil {
return
}
return _CimQueryBlockInfo(_p0, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
}
func _CimQueryBlockInfo(blockCimPath *uint16, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
hr = procCimQueryBlockInfo.Find()
if hr != nil {
return
}
r0, _, _ := syscall.SyscallN(procCimQueryBlockInfo.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(cimInfoBufferSize), uintptr(unsafe.Pointer(cimInfoBuffer)), uintptr(unsafe.Pointer(requiredBufferSize)), uintptr(unsafe.Pointer(cimCount)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
+27
View File
@@ -11,6 +11,27 @@ import (
type FsHandle uintptr
type StreamHandle uintptr
type CimGetVerificationInfoFlags uint32
const (
CimGetVerificationInfoNone CimGetVerificationInfoFlags = 0
CimGetVerificationInfoFromSkeletonCim CimGetVerificationInfoFlags = 1
)
type CimSignatureType uint32
const (
CimSignatureNone CimSignatureType = 0
CimSignaturePKCS7 CimSignatureType = 1
)
type CimHashAlgorithm uint32
const (
CimHashAlgorithmSHA256 CimHashAlgorithm = 0
CimHashAlgorithmSHA512 CimHashAlgorithm = 1
)
type CimFsFileMetadata struct {
Attributes uint32
FileSize int64
@@ -34,3 +55,9 @@ type CimFsImagePath struct {
ImageDir *uint16
ImageName *uint16
}
type CimFsCimInfo struct {
NextEntryOffset uint32
CimNameLength uint16
CimName [1]uint16
}
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+2 -6
View File
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
errERROR_EINVAL error = syscall.EINVAL
)
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
case windows.ERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
+3
View File
@@ -1 +1,4 @@
# Force line ending normalisation
* text=auto
# Show types.go in the PR diff view by default
internal/sys/types.go linguist-generated=false
+22 -24
View File
@@ -1,31 +1,29 @@
---
version: "2"
linters:
disable-all: true
default: none
enable:
- goimports
- gosimple
- depguard
- govet
- ineffassign
- misspell
- staticcheck
- typecheck
- unused
settings:
depguard:
rules:
no-x-sys-unix:
files:
- '!**/internal/unix/*.go'
- '!**/examples/**/*.go'
- '!**/docs/**/*.go'
deny:
- pkg: golang.org/x/sys/unix
desc: use internal/unix instead
formatters:
enable:
- gofmt
- depguard
linters-settings:
goimports:
# A comma-separated list of prefixes, which, if set, checks import paths
# with the given prefixes are grouped after 3rd-party packages.
# Default: ""
local-prefixes: github.com/cilium/ebpf
depguard:
rules:
no-x-sys-unix:
files:
# Filenames are matched against absolute paths, include **/ at the start.
- '!**/internal/unix/*.go'
- '!**/examples/**/*.go'
- '!**/docs/**/*.go'
deny:
- pkg: golang.org/x/sys/unix
desc: use internal/unix instead
- goimports
settings:
goimports:
local-prefixes:
- github.com/cilium/ebpf
+11 -7
View File
@@ -1,13 +1,17 @@
* @cilium/ebpf-lib-maintainers
features/ @rgo3
link/ @mmat11
/features/ @rgo3
/link/ @mmat11 @rgo3
perf/ @florianl
ringbuf/ @florianl
/perf/ @florianl
/ringbuf/ @florianl
btf/ @dylandreimerink
/btf/ @dylandreimerink
cmd/bpf2go/ @mejedi
/docs/ @cilium/ebpf-go-reviewers @ti-mo
docs/ @ti-mo
# Windows specific code.
/docs/**/windows*.md @cilium/ebpf-go-windows-reviewers
/internal/efw @cilium/ebpf-go-windows-reviewers
windows/ @cilium/ebpf-go-windows-reviewers # Folders
*windows*.go @cilium/ebpf-go-windows-reviewers # Go code
+68 -33
View File
@@ -1,30 +1,51 @@
# The development version of clang is distributed as the 'clang' binary,
# while stable/released versions have a version number attached.
# Pin the default clang to a stable version.
CLANG ?= clang-17
STRIP ?= llvm-strip-17
OBJCOPY ?= llvm-objcopy-17
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
CLANG ?= clang-22
STRIP ?= llvm-strip-22
OBJCOPY ?= llvm-objcopy-22
CFLAGS := -O2 -g -Wall -Werror -mcpu=v2 $(CFLAGS)
CI_KERNEL_URL ?= https://github.com/cilium/ci-kernels/raw/master/
# Obtain an absolute path to the directory of the Makefile.
# Assume the Makefile is in the root of the repository.
REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
UIDGID := $(shell stat -c '%u:%g' ${REPODIR})
# Prefer podman if installed, otherwise use docker.
# Note: Setting the var at runtime will always override.
CONTAINER_ENGINE ?= $(if $(shell command -v podman), podman, docker)
CONTAINER_RUN_ARGS ?= $(if $(filter ${CONTAINER_ENGINE}, podman), --log-driver=none, --user "${UIDGID}")
CONTAINER_ENGINE ?= $(if $(shell command -v podman),podman,docker)
# Configure container runtime arguments based on the container engine.
CONTAINER_RUN_ARGS := \
--env MAKEFLAGS \
--env BPF2GO_CC="$(CLANG)" \
--env BPF2GO_CFLAGS="$(CFLAGS)" \
--env HOME=/tmp \
-v "${REPODIR}":/ebpf -w /ebpf \
-v "$(shell go env GOCACHE)":/tmp/.cache/go-build \
-v "$(shell go env GOPATH)":/go \
-v "$(shell go env GOMODCACHE)":/go/pkg/mod
ifeq ($(CONTAINER_ENGINE), podman)
CONTAINER_RUN_ARGS += --log-driver=none --security-opt label=disable
else
CONTAINER_RUN_ARGS += --user "$(shell stat -c '%u:%g' ${REPODIR})"
endif
IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE)
VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION)
TARGETS_EL := \
testdata/linked1 \
testdata/linked2 \
testdata/linked
TARGETS := \
testdata/loader-clang-11 \
testdata/loader-clang-14 \
testdata/loader-clang-17 \
testdata/loader-$(CLANG) \
testdata/loader_nobtf \
testdata/manyprogs \
testdata/btf_map_init \
testdata/invalid_map \
@@ -46,6 +67,8 @@ TARGETS := \
testdata/constants \
testdata/errors \
testdata/variables \
testdata/arena \
testdata/struct_ops \
btf/testdata/relocs \
btf/testdata/relocs_read \
btf/testdata/relocs_read_tgt \
@@ -53,27 +76,27 @@ TARGETS := \
btf/testdata/tags \
cmd/bpf2go/testdata/minimal
.PHONY: all clean container-all container-shell generate
HEADERS := $(wildcard testdata/*.h)
.DEFAULT_TARGET = container-all
.PHONY: all clean godirs container-all container-shell generate format
.DEFAULT_GOAL = container-all
# Make sure Go-related dirs exist before starting containerized builds,
# otherwise podman will fail to start.
godirs:
mkdir -p $(shell go env GOCACHE) $(shell go env GOPATH) $(shell go env GOMODCACHE)
# Build all ELF binaries using a containerized LLVM toolchain.
container-all:
container-all: godirs
+${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
-v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
--env HOME="/tmp" \
--env BPF2GO_CC="$(CLANG)" \
--env BPF2GO_FLAGS="-fdebug-prefix-map=/ebpf=. $(CFLAGS)" \
"${IMAGE}:${VERSION}" \
make all
$(MAKE) all
# (debug) Drop the user into a shell inside the container as root.
# Set BPF2GO_ envs to make 'make generate' just work.
container-shell:
${CONTAINER_ENGINE} run --rm -ti \
-v "${REPODIR}":/ebpf -w /ebpf \
--env BPF2GO_CC="$(CLANG)" \
--env BPF2GO_FLAGS="-fdebug-prefix-map=/ebpf=. $(CFLAGS)" \
container-shell: godirs
${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
"${IMAGE}:${VERSION}"
clean:
@@ -83,32 +106,44 @@ clean:
format:
find . -type f -name "*.c" | xargs clang-format -i
all: format $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) generate
all: format testdata update-external-deps
ln -srf testdata/loader-$(CLANG)-el.elf testdata/loader-el.elf
ln -srf testdata/loader-$(CLANG)-eb.elf testdata/loader-eb.elf
$(MAKE) generate
generate:
go generate -run "internal/cmd/gentypes" ./...
go generate -skip "internal/cmd/gentypes" ./...
go generate -run "gentypes" ./...
go generate -run "stringer" ./...
go generate -skip "(gentypes|stringer)" ./...
testdata/loader-%-el.elf: testdata/loader.c
testdata: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) $(addsuffix -el.elf,$(TARGETS_EL))
testdata/loader-%-el.elf: testdata/loader.c $(HEADERS)
$* $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
testdata/loader-%-eb.elf: testdata/loader.c
testdata/loader-%-eb.elf: testdata/loader.c $(HEADERS)
$* $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
%-el.elf: %.c
testdata/loader_nobtf-el.elf: testdata/loader.c $(HEADERS)
$(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfel -c $< -o $@
testdata/loader_nobtf-eb.elf: testdata/loader.c $(HEADERS)
$(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfeb -c $< -o $@
%-el.elf: %.c $(HEADERS)
$(CLANG) $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
%-eb.elf : %.c
%-eb.elf: %.c $(HEADERS)
$(CLANG) $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
.PHONY: update-kernel-deps
update-kernel-deps: export KERNEL_VERSION?=6.8
update-kernel-deps:
./testdata/sh/update-kernel-deps.sh
$(MAKE) container-all
testdata/linked-el.elf: testdata/linked1-el.elf testdata/linked2-el.elf
bpftool gen object $@ $^
.PHONY: update-external-deps
update-external-deps:
./scripts/update-kernel-deps.sh
./scripts/update-efw-deps.sh
+5 -2
View File
@@ -59,8 +59,11 @@ This library includes the following packages:
* A version of Go that is [supported by
upstream](https://golang.org/doc/devel/release.html#policy)
* CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed versions
are not supported.
* Linux (amd64, arm64): CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed
versions are not supported.
* Windows (amd64): CI is run against Windows Server 2022. Only the latest eBPF for Windows
release is supported.
* Other architectures are best effort. 32bit arches are not supported.
## License
+1 -1
View File
@@ -1,6 +1,6 @@
package asm
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output alu_string.go -type=Source,Endianness,ALUOp
//go:generate go tool stringer -output alu_string.go -type=Source,Endianness,ALUOp
// Source of ALU / ALU64 / Branch operations
//
+9 -230
View File
@@ -1,239 +1,18 @@
package asm
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output func_string.go -type=BuiltinFunc
import "github.com/cilium/ebpf/internal/platform"
//go:generate go tool stringer -output func_string.go -type=BuiltinFunc
// BuiltinFunc is a built-in eBPF function.
type BuiltinFunc int32
type BuiltinFunc uint32
// eBPF built-in functions
// BuiltinFuncForPlatform returns a platform specific function constant.
//
// You can regenerate this list using the following gawk script:
//
// /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 {
+223
View File
@@ -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) //lint:ignore SA4016 consistency
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)
)
+49 -4
View File
@@ -220,15 +220,60 @@ func _() {
_ = x[FnUserRingbufDrain-209]
_ = x[FnCgrpStorageGet-210]
_ = x[FnCgrpStorageDelete-211]
_ = x[WindowsFnMapLookupElem-268435457]
_ = x[WindowsFnMapUpdateElem-268435458]
_ = x[WindowsFnMapDeleteElem-268435459]
_ = x[WindowsFnMapLookupAndDeleteElem-268435460]
_ = x[WindowsFnTailCall-268435461]
_ = x[WindowsFnGetPrandomU32-268435462]
_ = x[WindowsFnKtimeGetBootNs-268435463]
_ = x[WindowsFnGetSmpProcessorId-268435464]
_ = x[WindowsFnKtimeGetNs-268435465]
_ = x[WindowsFnCsumDiff-268435466]
_ = x[WindowsFnRingbufOutput-268435467]
_ = x[WindowsFnTracePrintk2-268435468]
_ = x[WindowsFnTracePrintk3-268435469]
_ = x[WindowsFnTracePrintk4-268435470]
_ = x[WindowsFnTracePrintk5-268435471]
_ = x[WindowsFnMapPushElem-268435472]
_ = x[WindowsFnMapPopElem-268435473]
_ = x[WindowsFnMapPeekElem-268435474]
_ = x[WindowsFnGetCurrentPidTgid-268435475]
_ = x[WindowsFnGetCurrentLogonId-268435476]
_ = x[WindowsFnIsCurrentAdmin-268435477]
_ = x[WindowsFnMemcpyS-268435478]
_ = x[WindowsFnMemcmpS-268435479]
_ = x[WindowsFnMemset-268435480]
_ = x[WindowsFnMemmoveS-268435481]
_ = x[WindowsFnGetSocketCookie-268435482]
_ = x[WindowsFnStrncpyS-268435483]
_ = x[WindowsFnStrncatS-268435484]
_ = x[WindowsFnStrnlenS-268435485]
_ = x[WindowsFnKtimeGetBootMs-268435486]
_ = x[WindowsFnKtimeGetMs-268435487]
_ = x[WindowsFnPerfEventOutput-268435488]
_ = x[WindowsFnGetCurrentProcessStartKey-268435489]
_ = x[WindowsFnGetCurrentThreadCreateTime-268435490]
}
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete"
const (
_BuiltinFunc_name_0 = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete"
_BuiltinFunc_name_1 = "WindowsFnMapLookupElemWindowsFnMapUpdateElemWindowsFnMapDeleteElemWindowsFnMapLookupAndDeleteElemWindowsFnTailCallWindowsFnGetPrandomU32WindowsFnKtimeGetBootNsWindowsFnGetSmpProcessorIdWindowsFnKtimeGetNsWindowsFnCsumDiffWindowsFnRingbufOutputWindowsFnTracePrintk2WindowsFnTracePrintk3WindowsFnTracePrintk4WindowsFnTracePrintk5WindowsFnMapPushElemWindowsFnMapPopElemWindowsFnMapPeekElemWindowsFnGetCurrentPidTgidWindowsFnGetCurrentLogonIdWindowsFnIsCurrentAdminWindowsFnMemcpySWindowsFnMemcmpSWindowsFnMemsetWindowsFnMemmoveSWindowsFnGetSocketCookieWindowsFnStrncpySWindowsFnStrncatSWindowsFnStrnlenSWindowsFnKtimeGetBootMsWindowsFnKtimeGetMsWindowsFnPerfEventOutputWindowsFnGetCurrentProcessStartKeyWindowsFnGetCurrentThreadCreateTime"
)
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}
var (
_BuiltinFunc_index_0 = [...]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}
_BuiltinFunc_index_1 = [...]uint16{0, 22, 44, 66, 97, 114, 136, 159, 185, 204, 221, 243, 264, 285, 306, 327, 347, 366, 386, 412, 438, 461, 477, 493, 508, 525, 549, 566, 583, 600, 623, 642, 666, 700, 735}
)
func (i BuiltinFunc) String() string {
if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
switch {
case i <= 211:
return _BuiltinFunc_name_0[_BuiltinFunc_index_0[i]:_BuiltinFunc_index_0[i+1]]
case 268435457 <= i && i <= 268435490:
i -= 268435457
return _BuiltinFunc_name_1[_BuiltinFunc_index_1[i]:_BuiltinFunc_index_1[i+1]]
default:
return "BuiltinFunc(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]]
}
+45
View File
@@ -0,0 +1,45 @@
// Code generated by internal/cmd/genwinfunctions.awk; DO NOT EDIT.
package asm
// Code in this file is derived from eBPF for Windows, available under the MIT License.
import "github.com/cilium/ebpf/internal/platform"
// Built-in functions (Windows).
const (
WindowsFnMapLookupElem = BuiltinFunc(platform.WindowsTag | 1)
WindowsFnMapUpdateElem = BuiltinFunc(platform.WindowsTag | 2)
WindowsFnMapDeleteElem = BuiltinFunc(platform.WindowsTag | 3)
WindowsFnMapLookupAndDeleteElem = BuiltinFunc(platform.WindowsTag | 4)
WindowsFnTailCall = BuiltinFunc(platform.WindowsTag | 5)
WindowsFnGetPrandomU32 = BuiltinFunc(platform.WindowsTag | 6)
WindowsFnKtimeGetBootNs = BuiltinFunc(platform.WindowsTag | 7)
WindowsFnGetSmpProcessorId = BuiltinFunc(platform.WindowsTag | 8)
WindowsFnKtimeGetNs = BuiltinFunc(platform.WindowsTag | 9)
WindowsFnCsumDiff = BuiltinFunc(platform.WindowsTag | 10)
WindowsFnRingbufOutput = BuiltinFunc(platform.WindowsTag | 11)
WindowsFnTracePrintk2 = BuiltinFunc(platform.WindowsTag | 12)
WindowsFnTracePrintk3 = BuiltinFunc(platform.WindowsTag | 13)
WindowsFnTracePrintk4 = BuiltinFunc(platform.WindowsTag | 14)
WindowsFnTracePrintk5 = BuiltinFunc(platform.WindowsTag | 15)
WindowsFnMapPushElem = BuiltinFunc(platform.WindowsTag | 16)
WindowsFnMapPopElem = BuiltinFunc(platform.WindowsTag | 17)
WindowsFnMapPeekElem = BuiltinFunc(platform.WindowsTag | 18)
WindowsFnGetCurrentPidTgid = BuiltinFunc(platform.WindowsTag | 19)
WindowsFnGetCurrentLogonId = BuiltinFunc(platform.WindowsTag | 20)
WindowsFnIsCurrentAdmin = BuiltinFunc(platform.WindowsTag | 21)
WindowsFnMemcpyS = BuiltinFunc(platform.WindowsTag | 22)
WindowsFnMemcmpS = BuiltinFunc(platform.WindowsTag | 23)
WindowsFnMemset = BuiltinFunc(platform.WindowsTag | 24)
WindowsFnMemmoveS = BuiltinFunc(platform.WindowsTag | 25)
WindowsFnGetSocketCookie = BuiltinFunc(platform.WindowsTag | 26)
WindowsFnStrncpyS = BuiltinFunc(platform.WindowsTag | 27)
WindowsFnStrncatS = BuiltinFunc(platform.WindowsTag | 28)
WindowsFnStrnlenS = BuiltinFunc(platform.WindowsTag | 29)
WindowsFnKtimeGetBootMs = BuiltinFunc(platform.WindowsTag | 30)
WindowsFnKtimeGetMs = BuiltinFunc(platform.WindowsTag | 31)
WindowsFnPerfEventOutput = BuiltinFunc(platform.WindowsTag | 32)
WindowsFnGetCurrentProcessStartKey = BuiltinFunc(platform.WindowsTag | 33)
WindowsFnGetCurrentThreadCreateTime = BuiltinFunc(platform.WindowsTag | 34)
)
+136 -111
View File
@@ -2,15 +2,19 @@ package asm
import (
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"math"
"sort"
"strings"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
"github.com/cilium/ebpf/internal/sys"
)
@@ -41,11 +45,19 @@ type Instruction struct {
Metadata Metadata
}
// Width returns how many raw BPF instructions the Instruction occupies within
// an instruction stream. For example, an Instruction encoding a 64-bit value
// will typically occupy 2 raw instructions, while a 32-bit constant can be
// encoded in a single raw instruction.
func (ins *Instruction) Width() RawInstructionOffset {
return RawInstructionOffset(ins.OpCode.rawInstructions())
}
// 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 +72,23 @@ 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() {
// Convert to int32 before widening to int64
// to ensure the signed bit is carried over.
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
if ins.IsBuiltinCall() {
if ins.Constant >= 0 {
// Leave negative constants from the instruction stream
// unchanged. These are sometimes used as placeholders for later
// patching.
// This relies on not having a valid platform tag with a high bit set.
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 {
@@ -85,33 +113,35 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
ins.Offset = 0
}
}
} else if ins.OpCode.Class() == StXClass &&
ins.OpCode.Mode() == AtomicMode {
// For atomic ops, part of the opcode is stored in the
// constant field. Shift over 8 bytes so we can OR with the actual opcode and
// apply `atomicMask` to avoid merging unknown bits that may be added in the future.
ins.OpCode |= (OpCode((ins.Constant << 8)) & atomicMask)
}
// Convert to int32 before widening to int64
// to ensure the signed bit is carried over.
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 +163,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 (%s): %w", fn, plat, internal.ErrNotSupportedOnOS)
}
cons = int32(value)
} else if ins.OpCode.Class().IsALU() {
newOffset := int16(0)
switch ins.OpCode.ALUOp() {
case SDiv:
@@ -156,6 +193,9 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
return 0, fmt.Errorf("extended ALU opcodes should have an .Offset of 0: %s", ins)
}
ins.Offset = newOffset
} else if atomic := ins.OpCode.AtomicOp(); atomic != InvalidAtomic {
ins.OpCode = ins.OpCode &^ atomicMask
ins.Constant = int64(atomic >> 8)
}
op, err := ins.OpCode.bpfOpCode()
@@ -203,22 +243,6 @@ func (ins *Instruction) AssociateMap(m FDer) error {
return nil
}
// RewriteMapPtr changes an instruction to use a new map fd.
//
// Returns an error if the instruction doesn't load a map.
//
// Deprecated: use AssociateMap instead. If you cannot provide a Map,
// wrap an fd in a type implementing FDer.
func (ins *Instruction) RewriteMapPtr(fd int) error {
if !ins.IsLoadFromMap() {
return errors.New("not a load from a map")
}
ins.encodeMapFD(fd)
return nil
}
func (ins *Instruction) encodeMapFD(fd int) {
// Preserve the offset value for direct map loads.
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
@@ -226,22 +250,6 @@ func (ins *Instruction) encodeMapFD(fd int) {
ins.Constant = int64(offset | rawFd)
}
// MapPtr returns the map fd for this instruction.
//
// The result is undefined if the instruction is not a load from a map,
// see IsLoadFromMap.
//
// Deprecated: use Map() instead.
func (ins *Instruction) MapPtr() int {
// If there is a map associated with the instruction, return its FD.
if fd := ins.Metadata.Get(mapMeta{}); fd != nil {
return fd.(FDer).FD()
}
// Fall back to the fd stored in the Constant field
return ins.mapFd()
}
// mapFd returns the map file descriptor stored in the 32 least significant
// bits of ins' Constant field.
func (ins *Instruction) mapFd() int {
@@ -367,8 +375,8 @@ func (ins Instruction) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
case MemMode, MemSXMode:
fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant)
case XAddMode:
fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src)
case AtomicMode:
fmt.Fprintf(f, "dst: %s src: %s off: %d", ins.Dst, ins.Src, ins.Offset)
}
case cls.IsALU():
@@ -388,13 +396,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}
case cls.IsJump():
fmt.Fprintf(f, "%v ", op)
switch jop := op.JumpOp(); jop {
case Call:
fmt.Fprintf(f, "%v ", op)
switch ins.Src {
case PseudoCall:
// bpf-to-bpf call
fmt.Fprint(f, ins.Constant)
// bpf-to-bpf call. For JIT-compiled programs, the kernel stores
// the PC offset in the 'off' field (ins.Offset) rather than 'imm'
// (ins.Constant). See kernel bpf_insn_prepare_dump in verifier.c.
if ins.Offset != 0 {
fmt.Fprint(f, ins.Offset)
} else {
fmt.Fprint(f, ins.Constant)
}
case PseudoKfuncCall:
// kfunc call
fmt.Fprintf(f, "Kfunc(%d)", ins.Constant)
@@ -403,13 +417,23 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}
case Ja:
fmt.Fprintf(f, "%v ", op)
if ins.OpCode.Class() == Jump32Class {
fmt.Fprintf(f, "imm: %d", ins.Constant)
} else {
fmt.Fprintf(f, "off: %d", ins.Offset)
}
case JCOND:
switch ins.Src {
case PseudoMayGoto:
fmt.Fprintf(f, "JCond may_goto off: %d", ins.Offset)
default:
fmt.Fprintf(f, "%v", op)
}
default:
fmt.Fprintf(f, "%v ", op)
fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset)
if op.Source() == ImmSource {
fmt.Fprintf(f, "imm: %d", ins.Constant)
@@ -456,13 +480,6 @@ func (ins Instruction) WithSymbol(name string) Instruction {
return ins
}
// Sym creates a symbol.
//
// Deprecated: use WithSymbol instead.
func (ins Instruction) Sym(name string) Instruction {
return ins.WithSymbol(name)
}
// Symbol returns the value ins has been marked with using WithSymbol,
// otherwise returns an empty string. A symbol is often an Instruction
// at the start of a function body.
@@ -529,29 +546,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.
@@ -607,39 +619,6 @@ func (insns Instructions) AssociateMap(symbol string, m FDer) error {
return nil
}
// RewriteMapPtr rewrites all loads of a specific map pointer to a new fd.
//
// Returns ErrUnreferencedSymbol if the symbol isn't used.
//
// Deprecated: use AssociateMap instead.
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
if symbol == "" {
return errors.New("empty symbol")
}
var found bool
for i := range insns {
ins := &insns[i]
if ins.Reference() != symbol {
continue
}
if !ins.IsLoadFromMap() {
return errors.New("not a load from a map")
}
ins.encodeMapFD(fd)
found = true
}
if !found {
return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol)
}
return nil
}
// SymbolOffsets returns the set of symbols and their offset in
// the instructions.
func (insns Instructions) SymbolOffsets() (map[string]int, error) {
@@ -792,18 +771,72 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
// It mirrors bpf_prog_calc_tag in the kernel and so can be compared
// to ProgramInfo.Tag to figure out whether a loaded program matches
// certain instructions.
//
// Deprecated: The value produced by this method no longer matches tags produced
// by the kernel since Linux 6.18. Use [Instructions.HasTag] instead.
func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
// We cannot determine which hashing function to use without probing the kernel.
// So use the legacy SHA-1 implementation and deprecate this method.
return insns.tagSha1(bo)
}
// HasTag returns true if the given tag matches the kernel tag of insns.
func (insns Instructions) HasTag(tag string, bo binary.ByteOrder) (bool, error) {
sha256Tag, err := insns.tagSha256(bo)
if err != nil {
return false, fmt.Errorf("hashing sha256: %w", err)
}
if tag == sha256Tag {
return true, nil
}
sha1Tag, err := insns.tagSha1(bo)
if err != nil {
return false, fmt.Errorf("hashing sha1: %w", err)
}
return tag == sha1Tag, nil
}
// tagSha1 calculates the kernel tag for a series of instructions.
//
// It mirrors bpf_prog_calc_tag in kernels up to v6.18 and can be compared to
// ProgramInfo.Tag to figure out whether a loaded Program matches insns.
func (insns Instructions) tagSha1(bo binary.ByteOrder) (string, error) {
h := sha1.New()
if err := insns.hash(h, bo); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil
}
// tagSha256 calculates the kernel tag for a series of instructions.
//
// It mirrors bpf_prog_calc_tag in the kernel and can be compared to
// ProgramInfo.Tag to figure out whether a loaded Program matches insns.
func (insns Instructions) tagSha256(bo binary.ByteOrder) (string, error) {
h := sha256.New()
if err := insns.hash(h, bo); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil
}
// hash calculates the hash of the instruction stream. Map load instructions
// are zeroed out, since these contain map file descriptors or pointers to
// maps, which will be different from load to load and would make the hash
// non-deterministic.
func (insns Instructions) hash(h hash.Hash, bo binary.ByteOrder) error {
for i, ins := range insns {
if ins.IsLoadFromMap() {
ins.Constant = 0
}
_, err := ins.Marshal(h, bo)
if err != nil {
return "", fmt.Errorf("instruction %d: %w", i, err)
return fmt.Errorf("instruction %d: %w", i, err)
}
}
return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil
return nil
}
// encodeFunctionReferences populates the Offset (or Constant, depending on
@@ -943,11 +976,3 @@ func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, erro
return 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}
// IsUnreferencedSymbol returns true if err was caused by
// an unreferenced symbol.
//
// Deprecated: use errors.Is(err, asm.ErrUnreferencedSymbol).
func IsUnreferencedSymbol(err error) bool {
return errors.Is(err, ErrUnreferencedSymbol)
}
+3 -1
View File
@@ -1,6 +1,6 @@
package asm
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output jump_string.go -type=JumpOp
//go:generate go tool stringer -output jump_string.go -type=JumpOp
// JumpOp affect control flow.
//
@@ -44,6 +44,8 @@ const (
JSLT JumpOp = 0xc0
// JSLE jumps by offset if signed r <= signed imm
JSLE JumpOp = 0xd0
// JCOND is a conditional pseudo jump to encode the may_goto instruction
JCOND JumpOp = 0xe0
)
// Return emits an exit instruction.
+4 -2
View File
@@ -23,9 +23,10 @@ func _() {
_ = x[JLE-176]
_ = x[JSLT-192]
_ = x[JSLE-208]
_ = x[JCOND-224]
}
const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp"
const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEJCONDInvalidJumpOp"
var _JumpOp_map = map[JumpOp]string{
0: _JumpOp_name[0:2],
@@ -42,7 +43,8 @@ var _JumpOp_map = map[JumpOp]string{
176: _JumpOp_name[37:40],
192: _JumpOp_name[40:44],
208: _JumpOp_name[44:48],
255: _JumpOp_name[48:61],
224: _JumpOp_name[48:53],
255: _JumpOp_name[53:66],
}
func (i JumpOp) String() string {
+120 -9
View File
@@ -1,6 +1,8 @@
package asm
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size
import "fmt"
//go:generate go tool stringer -output load_store_string.go -type=Mode,Size
// Mode for load and store operations
//
@@ -26,10 +28,119 @@ const (
MemMode Mode = 0x60
// MemSXMode - load from memory, sign extension
MemSXMode Mode = 0x80
// XAddMode - add atomically across processors.
XAddMode Mode = 0xc0
// AtomicMode - add atomically across processors.
AtomicMode Mode = 0xc0
)
const atomicMask OpCode = 0x0001_ff00
type AtomicOp uint32
const (
InvalidAtomic AtomicOp = 0xffff_ffff
// AddAtomic - add src to memory address dst atomically
AddAtomic AtomicOp = AtomicOp(Add) << 8
// FetchAdd - add src to memory address dst atomically, store result in src
FetchAdd AtomicOp = AddAtomic | fetch
// AndAtomic - bitwise AND src with memory address at dst atomically
AndAtomic AtomicOp = AtomicOp(And) << 8
// FetchAnd - bitwise AND src with memory address at dst atomically, store result in src
FetchAnd AtomicOp = AndAtomic | fetch
// OrAtomic - bitwise OR src with memory address at dst atomically
OrAtomic AtomicOp = AtomicOp(Or) << 8
// FetchOr - bitwise OR src with memory address at dst atomically, store result in src
FetchOr AtomicOp = OrAtomic | fetch
// XorAtomic - bitwise XOR src with memory address at dst atomically
XorAtomic AtomicOp = AtomicOp(Xor) << 8
// FetchXor - bitwise XOR src with memory address at dst atomically, store result in src
FetchXor AtomicOp = XorAtomic | fetch
// Xchg - atomically exchange the old value with the new value
//
// src gets populated with the old value of *(size *)(dst + offset).
Xchg AtomicOp = 0x0000_e000 | fetch
// CmpXchg - atomically compare and exchange the old value with the new value
//
// Compares R0 and *(size *)(dst + offset), writes src to *(size *)(dst + offset) on match.
// R0 gets populated with the old value of *(size *)(dst + offset), even if no exchange occurs.
CmpXchg AtomicOp = 0x0000_f000 | fetch
// fetch modifier for copy-modify-write atomics
fetch AtomicOp = 0x0000_0100
// loadAcquire - atomically load with acquire semantics
loadAcquire AtomicOp = 0x0001_0000
// storeRelease - atomically store with release semantics
storeRelease AtomicOp = 0x0001_1000
)
func (op AtomicOp) String() string {
var name string
switch op {
case AddAtomic, AndAtomic, OrAtomic, XorAtomic:
name = ALUOp(op >> 8).String()
case FetchAdd, FetchAnd, FetchOr, FetchXor:
name = "Fetch" + ALUOp((op^fetch)>>8).String()
case Xchg:
name = "Xchg"
case CmpXchg:
name = "CmpXchg"
case loadAcquire:
name = "LdAcq"
case storeRelease:
name = "StRel"
default:
name = fmt.Sprintf("AtomicOp(%#x)", uint32(op))
}
return name
}
func (op AtomicOp) OpCode(size Size) OpCode {
switch op {
case AddAtomic, AndAtomic, OrAtomic, XorAtomic,
FetchAdd, FetchAnd, FetchOr, FetchXor,
Xchg, CmpXchg:
switch size {
case Byte, Half:
// 8-bit and 16-bit atomic copy-modify-write atomics are not supported
return InvalidOpCode
}
}
return OpCode(StXClass).SetMode(AtomicMode).SetSize(size).SetAtomicOp(op)
}
// Mem emits `*(size *)(dst + offset) (op) src`.
func (op AtomicOp) Mem(dst, src Register, size Size, offset int16) Instruction {
return Instruction{
OpCode: op.OpCode(size),
Dst: dst,
Src: src,
Offset: offset,
}
}
// Emits `lock-acquire dst = *(size *)(src + offset)`.
func LoadAcquire(dst, src Register, size Size, offset int16) Instruction {
return Instruction{
OpCode: loadAcquire.OpCode(size),
Dst: dst,
Src: src,
Offset: offset,
}
}
// Emits `lock-release *(size *)(dst + offset) = src`.
func StoreRelease(dst, src Register, size Size, offset int16) Instruction {
return Instruction{
OpCode: storeRelease.OpCode(size),
Dst: dst,
Src: src,
Offset: offset,
}
}
// Size of load and store operations
//
// msb lsb
@@ -202,6 +313,10 @@ func StoreImmOp(size Size) OpCode {
// StoreImm emits `*(size *)(dst + offset) = value`.
func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
if size == DWord {
return Instruction{OpCode: InvalidOpCode}
}
return Instruction{
OpCode: StoreImmOp(size),
Dst: dst,
@@ -212,14 +327,10 @@ func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
// StoreXAddOp returns the OpCode to atomically add a register to a value in memory.
func StoreXAddOp(size Size) OpCode {
return OpCode(StXClass).SetMode(XAddMode).SetSize(size)
return AddAtomic.OpCode(size)
}
// StoreXAdd atomically adds src to *dst.
func StoreXAdd(dst, src Register, size Size) Instruction {
return Instruction{
OpCode: StoreXAddOp(size),
Dst: dst,
Src: src,
}
return AddAtomic.Mem(dst, src, size, 0)
}
+2 -2
View File
@@ -14,7 +14,7 @@ func _() {
_ = x[IndMode-64]
_ = x[MemMode-96]
_ = x[MemSXMode-128]
_ = x[XAddMode-192]
_ = x[AtomicMode-192]
}
const (
@@ -23,7 +23,7 @@ const (
_Mode_name_2 = "IndMode"
_Mode_name_3 = "MemMode"
_Mode_name_4 = "MemSXMode"
_Mode_name_5 = "XAddMode"
_Mode_name_5 = "AtomicMode"
_Mode_name_6 = "InvalidMode"
)
+4 -4
View File
@@ -7,13 +7,13 @@ type Metadata struct {
type metaElement struct {
next *metaElement
key, value interface{}
key, value any
}
// Find the element containing key.
//
// Returns nil if there is no such element.
func (m *Metadata) find(key interface{}) *metaElement {
func (m *Metadata) find(key any) *metaElement {
for e := m.head; e != nil; e = e.next {
if e.key == key {
return e
@@ -49,7 +49,7 @@ func (m *Metadata) remove(r *metaElement) {
//
// If value is nil, the key is removed. Avoids modifying old metadata by
// copying if necessary.
func (m *Metadata) Set(key, value interface{}) {
func (m *Metadata) Set(key, value any) {
if e := m.find(key); e != nil {
if e.value == value {
// Key is present and the value is the same. Nothing to do.
@@ -72,7 +72,7 @@ func (m *Metadata) Set(key, value interface{}) {
// Get the value of a key.
//
// Returns nil if no value with the given key is present.
func (m *Metadata) Get(key interface{}) interface{} {
func (m *Metadata) Get(key any) any {
if e := m.find(key); e != nil {
return e.value
}
+34 -10
View File
@@ -5,7 +5,7 @@ import (
"strings"
)
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output opcode_string.go -type=Class
//go:generate go tool stringer -output opcode_string.go -type=Class
// Class of operations
//
@@ -71,24 +71,29 @@ func (cls Class) isJumpOrALU() bool {
//
// The encoding varies based on a 3-bit Class:
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// ??? | CLS
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// ??? | CLS
//
// For ALUClass and ALUCLass32:
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// OPC |S| CLS
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | OPC |S| CLS
//
// For LdClass, LdXclass, StClass and StXClass:
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | MDE |SIZ| CLS
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | MDE |SIZ| CLS
//
// For StXClass where MDE == AtomicMode:
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | ATOMIC OP | MDE |SIZ| CLS
//
// For JumpClass, Jump32Class:
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | OPC |S| CLS
type OpCode uint16
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// 0 | OPC |S| CLS
type OpCode uint32
// InvalidOpCode is returned by setters on OpCode
const InvalidOpCode OpCode = 0xffff
@@ -138,6 +143,14 @@ func (op OpCode) Size() Size {
return Size(op & sizeMask)
}
// AtomicOp returns the type of atomic operation.
func (op OpCode) AtomicOp() AtomicOp {
if op.Class() != StXClass || op.Mode() != AtomicMode {
return InvalidAtomic
}
return AtomicOp(op & atomicMask)
}
// Source returns the source for branch and ALU operations.
func (op OpCode) Source() Source {
if !op.Class().isJumpOrALU() || op.ALUOp() == Swap {
@@ -199,6 +212,13 @@ func (op OpCode) SetSize(size Size) OpCode {
return (op & ^sizeMask) | OpCode(size)
}
func (op OpCode) SetAtomicOp(atomic AtomicOp) OpCode {
if op.Class() != StXClass || op.Mode() != AtomicMode || !valid(OpCode(atomic), atomicMask) {
return InvalidOpCode
}
return (op & ^atomicMask) | OpCode(atomic)
}
// SetSource sets the source on jump and ALU operations.
//
// Returns InvalidOpCode if op is of the wrong class.
@@ -247,6 +267,10 @@ func (op OpCode) String() string {
mode := op.Mode()
f.WriteString(strings.TrimSuffix(mode.String(), "Mode"))
if atomic := op.AtomicOp(); atomic != InvalidAtomic {
f.WriteString(strings.TrimSuffix(atomic.String(), "Atomic"))
}
switch op.Size() {
case DWord:
f.WriteString("DW")
+3 -2
View File
@@ -23,8 +23,9 @@ const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassA
var _Class_index = [...]uint8{0, 7, 15, 22, 30, 38, 47, 58, 68}
func (i Class) String() string {
if i >= Class(len(_Class_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_Class_index)-1 {
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Class_name[_Class_index[i]:_Class_index[i+1]]
return _Class_name[_Class_index[idx]:_Class_index[idx+1]]
}
+1
View File
@@ -40,6 +40,7 @@ const (
PseudoCall = R1 // BPF_PSEUDO_CALL
PseudoFunc = R4 // BPF_PSEUDO_FUNC
PseudoKfuncCall = R2 // BPF_PSEUDO_KFUNC_CALL
PseudoMayGoto = R0 // BPF_MAY_GOTO
)
func (r Register) String() string {
+24 -4
View File
@@ -66,15 +66,35 @@ func _() {
_ = x[AttachCgroupUnixGetsockname-53]
_ = x[AttachNetkitPrimary-54]
_ = x[AttachNetkitPeer-55]
_ = x[AttachWindowsXDP-268435457]
_ = x[AttachWindowsBind-268435458]
_ = x[AttachWindowsCGroupInet4Connect-268435459]
_ = x[AttachWindowsCGroupInet6Connect-268435460]
_ = x[AttachWindowsCgroupInet4RecvAccept-268435461]
_ = x[AttachWindowsCgroupInet6RecvAccept-268435462]
_ = x[AttachWindowsCGroupSockOps-268435463]
_ = x[AttachWindowsSample-268435464]
_ = x[AttachWindowsXDPTest-268435465]
}
const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession"
const (
_AttachType_name_0 = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession"
_AttachType_name_1 = "WindowsXDPWindowsBindWindowsCGroupInet4ConnectWindowsCGroupInet6ConnectWindowsCgroupInet4RecvAcceptWindowsCgroupInet6RecvAcceptWindowsCGroupSockOpsWindowsSampleWindowsXDPTest"
)
var _AttachType_index = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804, 822}
var (
_AttachType_index_0 = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804, 822}
_AttachType_index_1 = [...]uint8{0, 10, 21, 46, 71, 99, 127, 147, 160, 174}
)
func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) {
switch {
case i <= 56:
return _AttachType_name_0[_AttachType_index_0[i]:_AttachType_index_0[i+1]]
case 268435457 <= i && i <= 268435465:
i -= 268435457
return _AttachType_name_1[_AttachType_index_1[i]:_AttachType_index_1[i+1]]
default:
return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
}
+170 -327
View File
@@ -1,16 +1,16 @@
package btf
import (
"bufio"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"iter"
"maps"
"math"
"os"
"reflect"
"sync"
"slices"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
@@ -29,154 +29,24 @@ var (
// ID represents the unique ID of a BTF object.
type ID = sys.BTFID
// immutableTypes is a set of types which musn't be changed.
type immutableTypes struct {
// All types contained by the spec, not including types from the base in
// case the spec was parsed from split BTF.
types []Type
// Type IDs indexed by type.
typeIDs map[Type]TypeID
// The ID of the first type in types.
firstTypeID TypeID
// Types indexed by essential name.
// Includes all struct flavors and types with the same name.
namedTypes map[essentialName][]TypeID
// Byte order of the types. This affects things like struct member order
// when using bitfields.
byteOrder binary.ByteOrder
type elfData struct {
sectionSizes map[string]uint32
symbolOffsets map[elfSymbol]uint32
fixups map[Type]bool
}
func (s *immutableTypes) typeByID(id TypeID) (Type, bool) {
if id < s.firstTypeID {
return nil, false
}
index := int(id - s.firstTypeID)
if index >= len(s.types) {
return nil, false
}
return s.types[index], true
}
// mutableTypes is a set of types which may be changed.
type mutableTypes struct {
imm immutableTypes
mu sync.RWMutex // protects copies below
copies map[Type]Type // map[orig]copy
copiedTypeIDs map[Type]TypeID // map[copy]origID
}
// add a type to the set of mutable types.
//
// Copies type and all of its children once. Repeated calls with the same type
// do not copy again.
func (mt *mutableTypes) add(typ Type, typeIDs map[Type]TypeID) Type {
mt.mu.RLock()
cpy, ok := mt.copies[typ]
mt.mu.RUnlock()
if ok {
// Fast path: the type has been copied before.
return cpy
}
// modifyGraphPreorder copies the type graph node by node, so we can't drop
// the lock in between.
mt.mu.Lock()
defer mt.mu.Unlock()
return copyType(typ, typeIDs, mt.copies, mt.copiedTypeIDs)
}
// copy a set of mutable types.
func (mt *mutableTypes) copy() *mutableTypes {
if mt == nil {
return nil
}
// Prevent concurrent modification of mt.copiedTypeIDs.
mt.mu.RLock()
defer mt.mu.RUnlock()
mtCopy := &mutableTypes{
mt.imm,
sync.RWMutex{},
make(map[Type]Type, len(mt.copies)),
make(map[Type]TypeID, len(mt.copiedTypeIDs)),
}
copiesOfCopies := make(map[Type]Type, len(mt.copies))
for orig, copy := range mt.copies {
// NB: We make a copy of copy, not orig, so that changes to mutable types
// are preserved.
copyOfCopy := copyType(copy, mt.copiedTypeIDs, copiesOfCopies, mtCopy.copiedTypeIDs)
mtCopy.copies[orig] = copyOfCopy
}
return mtCopy
}
func (mt *mutableTypes) typeID(typ Type) (TypeID, error) {
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
}
mt.mu.RLock()
defer mt.mu.RUnlock()
id, ok := mt.copiedTypeIDs[typ]
if !ok {
return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound)
}
return id, nil
}
func (mt *mutableTypes) typeByID(id TypeID) (Type, bool) {
immT, ok := mt.imm.typeByID(id)
if !ok {
return nil, false
}
return mt.add(immT, mt.imm.typeIDs), true
}
func (mt *mutableTypes) anyTypesByName(name string) ([]Type, error) {
immTypes := mt.imm.namedTypes[newEssentialName(name)]
if len(immTypes) == 0 {
return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound)
}
// Return a copy to prevent changes to namedTypes.
result := make([]Type, 0, len(immTypes))
for _, id := range immTypes {
immT, ok := mt.imm.typeByID(id)
if !ok {
return nil, fmt.Errorf("no type with ID %d", id)
}
// Match against the full name, not just the essential one
// in case the type being looked up is a struct flavor.
if immT.TypeName() == name {
result = append(result, mt.add(immT, mt.imm.typeIDs))
}
}
return result, nil
type elfSymbol struct {
section string
name string
}
// Spec allows querying a set of Types and loading the set into the
// kernel.
type Spec struct {
*mutableTypes
*decoder
// String table from ELF.
strings *stringTable
// Additional data from ELF, may be nil.
elf *elfData
}
// LoadSpec opens file and calls LoadSpecFromReader on it.
@@ -197,11 +67,12 @@ func LoadSpec(file string) (*Spec, error) {
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
if bo := guessRawBTFByteOrder(rd); bo != nil {
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil)
raw, err := io.ReadAll(io.NewSectionReader(rd, 0, math.MaxInt64))
if err != nil {
return nil, fmt.Errorf("read raw BTF: %w", err)
}
return nil, err
return loadRawSpec(raw, nil)
}
return loadSpecFromELF(file)
@@ -237,13 +108,13 @@ func LoadSpecAndExtInfosFromReader(rd io.ReaderAt) (*Spec, *ExtInfos, error) {
// Some ELF symbols (e.g. in vmlinux) may point to virtual memory that is well
// beyond this range. Since these symbols cannot be described by BTF info,
// ignore them here.
func symbolOffsets(file *internal.SafeELFFile) (map[symbol]uint32, error) {
func symbolOffsets(file *internal.SafeELFFile) (map[elfSymbol]uint32, error) {
symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
}
offsets := make(map[symbol]uint32)
offsets := make(map[elfSymbol]uint32)
for _, sym := range symbols {
if idx := sym.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
// Ignore things like SHN_ABS
@@ -260,7 +131,7 @@ func symbolOffsets(file *internal.SafeELFFile) (map[symbol]uint32, error) {
}
secName := file.Sections[sym.Section].Name
offsets[symbol{secName, sym.Name}] = uint32(sym.Value)
offsets[elfSymbol{secName, sym.Name}] = uint32(sym.Value)
}
return offsets, nil
@@ -298,145 +169,86 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
return nil, err
}
if btfSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed BTF is not supported")
rawBTF, err := btfSection.Data()
if err != nil {
return nil, fmt.Errorf("reading .BTF section: %w", err)
}
spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil)
spec, err := loadRawSpec(rawBTF, nil)
if err != nil {
return nil, err
}
err = fixupDatasec(spec.imm.types, sectionSizes, offsets)
if err != nil {
return nil, err
if spec.decoder.byteOrder != file.ByteOrder {
return nil, fmt.Errorf("BTF byte order %s does not match ELF byte order %s", spec.decoder.byteOrder, file.ByteOrder)
}
spec.elf = &elfData{
sectionSizes,
offsets,
make(map[Type]bool),
}
return spec, nil
}
func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) {
func loadRawSpec(btf []byte, base *Spec) (*Spec, error) {
var (
baseDecoder *decoder
baseStrings *stringTable
firstTypeID TypeID
err error
)
if base != nil {
if base.imm.firstTypeID != 0 {
return nil, fmt.Errorf("can't use split BTF as base")
}
baseDecoder = base.decoder
baseStrings = base.strings
firstTypeID, err = base.nextTypeID()
if err != nil {
return nil, err
}
}
types, rawStrings, err := parseBTF(btf, bo, baseStrings, base)
header, _, bo, err := parseBTFHeader(btf)
if err != nil {
return nil, fmt.Errorf("parsing .BTF header: %v", err)
}
if header.HdrLen > uint32(len(btf)) {
return nil, fmt.Errorf("BTF header length is out of bounds")
}
btf = btf[header.HdrLen:]
if uint64(header.StringOff)+uint64(header.StringLen) > uint64(len(btf)) {
return nil, fmt.Errorf("string table is out of bounds")
}
stringsSection := btf[header.StringOff : header.StringOff+header.StringLen]
rawStrings, err := newStringTable(stringsSection, baseStrings)
if err != nil {
return nil, fmt.Errorf("read string section: %w", err)
}
if uint64(header.TypeOff)+uint64(header.TypeLen) > uint64(len(btf)) {
return nil, fmt.Errorf("types section is out of bounds")
}
typesSection := btf[header.TypeOff : header.TypeOff+header.TypeLen]
decoder, err := newDecoder(typesSection, bo, rawStrings, baseDecoder)
if err != nil {
return nil, err
}
typeIDs, typesByName := indexTypes(types, firstTypeID)
return &Spec{
&mutableTypes{
immutableTypes{
types,
typeIDs,
firstTypeID,
typesByName,
bo,
},
sync.RWMutex{},
make(map[Type]Type),
make(map[Type]TypeID),
},
rawStrings,
}, nil
}
func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentialName][]TypeID) {
namedTypes := 0
for _, typ := range types {
if typ.TypeName() != "" {
// Do a pre-pass to figure out how big types by name has to be.
// Most types have unique names, so it's OK to ignore essentialName
// here.
namedTypes++
}
}
typeIDs := make(map[Type]TypeID, len(types))
typesByName := make(map[essentialName][]TypeID, namedTypes)
for i, typ := range types {
id := firstTypeID + TypeID(i)
typeIDs[typ] = id
if name := newEssentialName(typ.TypeName()); name != "" {
typesByName[name] = append(typesByName[name], id)
}
}
return typeIDs, typesByName
}
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
buf := new(bufio.Reader)
for _, bo := range []binary.ByteOrder{
binary.LittleEndian,
binary.BigEndian,
} {
buf.Reset(io.NewSectionReader(r, 0, math.MaxInt64))
if _, err := parseBTFHeader(buf, bo); err == nil {
return bo
}
}
return nil
}
// parseBTF reads a .BTF section into memory and parses it into a list of
// raw types and a string table.
func parseBTF(btf io.ReaderAt, bo binary.ByteOrder, baseStrings *stringTable, base *Spec) ([]Type, *stringTable, error) {
buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64)
header, err := parseBTFHeader(buf, bo)
if err != nil {
return nil, nil, fmt.Errorf("parsing .BTF header: %v", err)
}
rawStrings, err := readStringTable(io.NewSectionReader(btf, header.stringStart(), int64(header.StringLen)),
baseStrings)
if err != nil {
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
buf.Reset(io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen)))
types, err := readAndInflateTypes(buf, bo, header.TypeLen, rawStrings, base)
if err != nil {
return nil, nil, err
}
return types, rawStrings, nil
}
type symbol struct {
section string
name string
return &Spec{decoder, nil}, nil
}
// fixupDatasec attempts to patch up missing info in Datasecs and its members by
// supplementing them with information from the ELF headers and symbol table.
func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symbol]uint32) error {
for _, typ := range types {
ds, ok := typ.(*Datasec)
if !ok {
continue
func (elf *elfData) fixupDatasec(typ Type) error {
if elf == nil {
return nil
}
if ds, ok := typ.(*Datasec); ok {
if elf.fixups[ds] {
return nil
}
elf.fixups[ds] = true
name := ds.Name
@@ -459,7 +271,7 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb
}
}
continue
return nil
case ".kconfig":
// .kconfig has a size of 0 and has all members' offsets set to 0.
// Fix up all offsets and set the Datasec's size.
@@ -472,21 +284,21 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb
vsi.Type.(*Var).Linkage = GlobalVar
}
continue
return nil
}
if ds.Size != 0 {
continue
return nil
}
ds.Size, ok = sectionSizes[name]
ds.Size, ok = elf.sectionSizes[name]
if !ok {
return fmt.Errorf("data section %s: missing size", name)
}
for i := range ds.Vars {
symName := ds.Vars[i].Type.TypeName()
ds.Vars[i].Offset, ok = offsets[symbol{name, symName}]
ds.Vars[i].Offset, ok = elf.symbolOffsets[elfSymbol{name, symName}]
if !ok {
return fmt.Errorf("data section %s: missing offset for symbol %s", name, symName)
}
@@ -530,36 +342,29 @@ func fixupDatasecLayout(ds *Datasec) error {
return nil
}
// Copy creates a copy of Spec.
// Copy a Spec.
//
// All contained types are duplicated while preserving any modifications made
// to them.
func (s *Spec) Copy() *Spec {
if s == nil {
return nil
}
return &Spec{
s.mutableTypes.copy(),
s.strings,
}
}
type sliceWriter []byte
func (sw sliceWriter) Write(p []byte) (int, error) {
if len(p) != len(sw) {
return 0, errors.New("size doesn't match")
cpy := &Spec{
s.decoder.Copy(),
nil,
}
return copy(sw, p), nil
}
// nextTypeID returns the next unallocated type ID or an error if there are no
// more type IDs.
func (s *Spec) nextTypeID() (TypeID, error) {
id := s.imm.firstTypeID + TypeID(len(s.imm.types))
if id < s.imm.firstTypeID {
return 0, fmt.Errorf("no more type IDs")
if s.elf != nil {
cpy.elf = &elfData{
s.elf.sectionSizes,
s.elf.symbolOffsets,
maps.Clone(s.elf.fixups),
}
}
return id, nil
return cpy
}
// TypeByID returns the BTF Type with the given type ID.
@@ -567,9 +372,13 @@ func (s *Spec) nextTypeID() (TypeID, error) {
// Returns an error wrapping ErrNotFound if a Type with the given ID
// does not exist in the Spec.
func (s *Spec) TypeByID(id TypeID) (Type, error) {
typ, ok := s.typeByID(id)
if !ok {
return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s.imm.firstTypeID, ErrNotFound)
typ, err := s.decoder.TypeByID(id)
if err != nil {
return nil, fmt.Errorf("inflate type: %w", err)
}
if err := s.elf.fixupDatasec(typ); err != nil {
return nil, err
}
return typ, nil
@@ -579,7 +388,7 @@ func (s *Spec) TypeByID(id TypeID) (Type, error) {
//
// Returns an error wrapping [ErrNotFound] if the type isn't part of the Spec.
func (s *Spec) TypeID(typ Type) (TypeID, error) {
return s.mutableTypes.typeID(typ)
return s.decoder.TypeID(typ)
}
// AnyTypesByName returns a list of BTF Types with the given name.
@@ -590,7 +399,25 @@ func (s *Spec) TypeID(typ Type) (TypeID, error) {
//
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
func (s *Spec) AnyTypesByName(name string) ([]Type, error) {
return s.mutableTypes.anyTypesByName(name)
types, err := s.TypesByName(newEssentialName(name))
if err != nil {
return nil, err
}
for i := 0; i < len(types); i++ {
// Match against the full name, not just the essential one
// in case the type being looked up is a struct flavor.
if types[i].TypeName() != name {
types = slices.Delete(types, i, i+1)
continue
}
if err := s.elf.fixupDatasec(types[i]); err != nil {
return nil, err
}
}
return types, nil
}
// AnyTypeByName returns a Type with the given name.
@@ -618,15 +445,18 @@ func (s *Spec) AnyTypeByName(name string) (Type, error) {
//
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
// Returns an error wrapping ErrMultipleTypes if multiple candidates are found.
func (s *Spec) TypeByName(name string, typ interface{}) error {
typeInterface := reflect.TypeOf((*Type)(nil)).Elem()
func (s *Spec) TypeByName(name string, typ any) error {
if err := internal.IsNilPointer(typ); err != nil {
return fmt.Errorf("type argument: %w", err)
}
typeInterface := reflect.TypeFor[Type]()
// typ may be **T or *Type
typValue := reflect.ValueOf(typ)
if typValue.Kind() != reflect.Ptr {
if typValue.Kind() != reflect.Pointer {
return fmt.Errorf("%T is not a pointer", typ)
}
typPtr := typValue.Elem()
if !typPtr.CanSet() {
return fmt.Errorf("%T cannot be set", typ)
@@ -635,6 +465,9 @@ func (s *Spec) TypeByName(name string, typ interface{}) error {
wanted := typPtr.Type()
if wanted == typeInterface {
// This is *Type. Unwrap the value's type.
if typPtr.IsNil() {
return fmt.Errorf("%T points to a nil Type", typ)
}
wanted = typPtr.Elem().Type()
}
@@ -669,45 +502,55 @@ func (s *Spec) TypeByName(name string, typ interface{}) error {
return nil
}
// LoadSplitSpec loads split BTF from the given file.
//
// Types from base are used to resolve references in the split BTF.
// The returned Spec only contains types from the split BTF, not from the base.
func LoadSplitSpec(file string, base *Spec) (*Spec, error) {
fh, err := os.Open(file)
if err != nil {
return nil, err
}
defer fh.Close()
return LoadSplitSpecFromReader(fh, base)
}
// LoadSplitSpecFromReader loads split BTF from a reader.
//
// Types from base are used to resolve references in the split BTF.
// The returned Spec only contains types from the split BTF, not from the base.
func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) {
return loadRawSpec(r, internal.NativeEndian, base)
}
// TypesIterator iterates over types of a given spec.
type TypesIterator struct {
spec *Spec
id TypeID
done bool
// The last visited type in the spec.
Type Type
}
// Iterate returns the types iterator.
func (s *Spec) Iterate() *TypesIterator {
return &TypesIterator{spec: s, id: s.imm.firstTypeID}
}
// Next returns true as long as there are any remaining types.
func (iter *TypesIterator) Next() bool {
if iter.done {
return false
raw, err := io.ReadAll(io.NewSectionReader(r, 0, math.MaxInt64))
if err != nil {
return nil, fmt.Errorf("read raw BTF: %w", err)
}
var ok bool
iter.Type, ok = iter.spec.typeByID(iter.id)
iter.id++
iter.done = !ok
if !iter.done {
// Skip declTags, during unmarshaling declTags become `Tags` fields of other types.
// We keep them in the spec to avoid holes in the ID space, but for the purposes of
// iteration, they are not useful to the user.
if _, ok := iter.Type.(*declTag); ok {
return iter.Next()
return loadRawSpec(raw, base)
}
// All iterates over all types.
func (s *Spec) All() iter.Seq2[Type, error] {
return func(yield func(Type, error) bool) {
for id := s.firstTypeID; ; id++ {
typ, err := s.TypeByID(id)
if errors.Is(err, ErrNotFound) {
return
} else if err != nil {
yield(nil, err)
return
}
// Skip declTags, during unmarshaling declTags become `Tags` fields of other types.
// We keep them in the spec to avoid holes in the ID space, but for the purposes of
// iteration, they are not useful to the user.
if _, ok := typ.(*declTag); ok {
continue
}
if !yield(typ, nil) {
return
}
}
}
return !iter.done
}
+118 -107
View File
@@ -4,13 +4,10 @@ import (
"encoding/binary"
"errors"
"fmt"
"io"
"unsafe"
"github.com/cilium/ebpf/internal"
)
//go:generate go run golang.org/x/tools/cmd/stringer@latest -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind
//go:generate go tool stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind
// btfKind describes a Type.
type btfKind uint8
@@ -87,51 +84,70 @@ type btfHeader struct {
StringLen uint32
}
// typeStart returns the offset from the beginning of the .BTF section
// to the start of its type entries.
func (h *btfHeader) typeStart() int64 {
return int64(h.HdrLen + h.TypeOff)
}
// stringStart returns the offset from the beginning of the .BTF section
// to the start of its string table.
func (h *btfHeader) stringStart() int64 {
return int64(h.HdrLen + h.StringOff)
type btfLayout struct {
Off uint32
Len uint32
}
// parseBTFHeader parses the header of the .BTF section.
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
func parseBTFHeader(buf []byte) (*btfHeader, *btfLayout, binary.ByteOrder, error) {
var header btfHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
var bo binary.ByteOrder
for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} {
n, err := binary.Decode(buf, order, &header)
if err != nil {
return nil, nil, nil, fmt.Errorf("read header: %v", err)
}
if header.Magic != btfMagic {
continue
}
buf = buf[n:]
bo = order
break
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
if bo == nil {
return nil, nil, nil, fmt.Errorf("no valid BTF header")
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header length shorter than btfHeader size")
return nil, nil, nil, errors.New("header length shorter than minimum BTF header size")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
return nil, fmt.Errorf("header padding: %v", err)
if len(buf) < int(remainder) {
return nil, nil, nil, errors.New("header length exceeds available data")
}
return &header, nil
var layout btfLayout
if remainder >= int64(binary.Size(&btfLayout{})) {
n, err := binary.Decode(buf, bo, &layout)
if err != nil {
return nil, nil, nil, fmt.Errorf("read layout offset and length: %v", err)
}
buf = buf[n:]
remainder -= int64(n)
}
for _, b := range buf[:remainder] {
if b != 0 {
return nil, nil, nil, errors.New("header contains non-zero trailer")
}
}
return &header, &layout, bo, nil
}
var btfTypeLen = binary.Size(btfType{})
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
type btfType struct {
NameOff uint32
@@ -268,30 +284,53 @@ func (bt *btfType) SetSize(size uint32) {
bt.SizeType = size
}
func (bt *btfType) Marshal(w io.Writer, bo binary.ByteOrder) error {
buf := make([]byte, unsafe.Sizeof(*bt))
func (bt *btfType) Encode(buf []byte, bo binary.ByteOrder) (int, error) {
if len(buf) < btfTypeSize {
return 0, fmt.Errorf("not enough bytes to marshal btfType")
}
bo.PutUint32(buf[0:], bt.NameOff)
bo.PutUint32(buf[4:], bt.Info)
bo.PutUint32(buf[8:], bt.SizeType)
_, err := w.Write(buf)
return err
return btfTypeSize, nil
}
type rawType struct {
btfType
data interface{}
}
func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error {
if err := rt.btfType.Marshal(w, bo); err != nil {
return err
// DataLen returns the length of additional type specific data in bytes.
func (bt *btfType) DataLen() (int, error) {
switch bt.Kind() {
case kindInt:
return int(unsafe.Sizeof(btfInt{})), nil
case kindPointer:
case kindArray:
return int(unsafe.Sizeof(btfArray{})), nil
case kindStruct:
fallthrough
case kindUnion:
return int(unsafe.Sizeof(btfMember{})) * bt.Vlen(), nil
case kindEnum:
return int(unsafe.Sizeof(btfEnum{})) * bt.Vlen(), nil
case kindForward:
case kindTypedef:
case kindVolatile:
case kindConst:
case kindRestrict:
case kindFunc:
case kindFuncProto:
return int(unsafe.Sizeof(btfParam{})) * bt.Vlen(), nil
case kindVar:
return int(unsafe.Sizeof(btfVariable{})), nil
case kindDatasec:
return int(unsafe.Sizeof(btfVarSecinfo{})) * bt.Vlen(), nil
case kindFloat:
case kindDeclTag:
return int(unsafe.Sizeof(btfDeclTag{})), nil
case kindTypeTag:
case kindEnum64:
return int(unsafe.Sizeof(btfEnum64{})) * bt.Vlen(), nil
default:
return 0, fmt.Errorf("unknown kind: %v", bt.Kind())
}
if rt.data == nil {
return nil
}
return binary.Write(w, bo, rt.data)
return 0, nil
}
// btfInt encodes additional data for integers.
@@ -376,21 +415,15 @@ type btfMember struct {
var btfMemberLen = int(unsafe.Sizeof(btfMember{}))
func unmarshalBtfMembers(members []btfMember, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range members {
if off+btfMemberLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfMember %d", i)
}
members[i].NameOff = bo.Uint32(b[off+0:])
members[i].Type = TypeID(bo.Uint32(b[off+4:]))
members[i].Offset = bo.Uint32(b[off+8:])
off += btfMemberLen
func unmarshalBtfMember(bm *btfMember, b []byte, bo binary.ByteOrder) (int, error) {
if btfMemberLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfMember")
}
return off, nil
bm.NameOff = bo.Uint32(b[0:])
bm.Type = TypeID(bo.Uint32(b[4:]))
bm.Offset = bo.Uint32(b[8:])
return btfMemberLen, nil
}
type btfVarSecinfo struct {
@@ -401,21 +434,15 @@ type btfVarSecinfo struct {
var btfVarSecinfoLen = int(unsafe.Sizeof(btfVarSecinfo{}))
func unmarshalBtfVarSecInfos(secinfos []btfVarSecinfo, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range secinfos {
if off+btfVarSecinfoLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfVarSecinfo %d", i)
}
secinfos[i].Type = TypeID(bo.Uint32(b[off+0:]))
secinfos[i].Offset = bo.Uint32(b[off+4:])
secinfos[i].Size = bo.Uint32(b[off+8:])
off += btfVarSecinfoLen
func unmarshalBtfVarSecInfo(bvsi *btfVarSecinfo, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfVarSecinfoLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfVarSecinfo")
}
return off, nil
bvsi.Type = TypeID(bo.Uint32(b[0:]))
bvsi.Offset = bo.Uint32(b[4:])
bvsi.Size = bo.Uint32(b[8:])
return btfVarSecinfoLen, nil
}
type btfVariable struct {
@@ -440,20 +467,14 @@ type btfEnum struct {
var btfEnumLen = int(unsafe.Sizeof(btfEnum{}))
func unmarshalBtfEnums(enums []btfEnum, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range enums {
if off+btfEnumLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum %d", i)
}
enums[i].NameOff = bo.Uint32(b[off+0:])
enums[i].Val = bo.Uint32(b[off+4:])
off += btfEnumLen
func unmarshalBtfEnum(be *btfEnum, b []byte, bo binary.ByteOrder) (int, error) {
if btfEnumLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum")
}
return off, nil
be.NameOff = bo.Uint32(b[0:])
be.Val = bo.Uint32(b[4:])
return btfEnumLen, nil
}
type btfEnum64 struct {
@@ -464,21 +485,16 @@ type btfEnum64 struct {
var btfEnum64Len = int(unsafe.Sizeof(btfEnum64{}))
func unmarshalBtfEnums64(enums []btfEnum64, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range enums {
if off+btfEnum64Len > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum64 %d", i)
}
enums[i].NameOff = bo.Uint32(b[off+0:])
enums[i].ValLo32 = bo.Uint32(b[off+4:])
enums[i].ValHi32 = bo.Uint32(b[off+8:])
off += btfEnum64Len
func unmarshalBtfEnum64(enum *btfEnum64, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfEnum64Len {
return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum64")
}
return off, nil
enum.NameOff = bo.Uint32(b[0:])
enum.ValLo32 = bo.Uint32(b[4:])
enum.ValHi32 = bo.Uint32(b[8:])
return btfEnum64Len, nil
}
type btfParam struct {
@@ -488,20 +504,15 @@ type btfParam struct {
var btfParamLen = int(unsafe.Sizeof(btfParam{}))
func unmarshalBtfParams(params []btfParam, b []byte, bo binary.ByteOrder) (int, error) {
off := 0
for i := range params {
if off+btfParamLen > len(b) {
return 0, fmt.Errorf("not enough bytes to unmarshal btfParam %d", i)
}
params[i].NameOff = bo.Uint32(b[off+0:])
params[i].Type = TypeID(bo.Uint32(b[off+4:]))
off += btfParamLen
func unmarshalBtfParam(param *btfParam, b []byte, bo binary.ByteOrder) (int, error) {
if len(b) < btfParamLen {
return 0, fmt.Errorf("not enough bytes to unmarshal btfParam")
}
return off, nil
param.NameOff = bo.Uint32(b[0:])
param.Type = TypeID(bo.Uint32(b[4:]))
return btfParamLen, nil
}
type btfDeclTag struct {
+9 -6
View File
@@ -18,10 +18,11 @@ const _FuncLinkage_name = "staticglobalextern"
var _FuncLinkage_index = [...]uint8{0, 6, 12, 18}
func (i FuncLinkage) String() string {
if i < 0 || i >= FuncLinkage(len(_FuncLinkage_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_FuncLinkage_index)-1 {
return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_index[i+1]]
return _FuncLinkage_name[_FuncLinkage_index[idx]:_FuncLinkage_index[idx+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
@@ -37,10 +38,11 @@ const _VarLinkage_name = "staticglobalextern"
var _VarLinkage_index = [...]uint8{0, 6, 12, 18}
func (i VarLinkage) String() string {
if i < 0 || i >= VarLinkage(len(_VarLinkage_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_VarLinkage_index)-1 {
return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+1]]
return _VarLinkage_name[_VarLinkage_index[idx]:_VarLinkage_index[idx+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
@@ -73,8 +75,9 @@ const _btfKind_name = "UnknownIntPointerArrayStructUnionEnumForwardTypedefVolati
var _btfKind_index = [...]uint8{0, 7, 10, 17, 22, 28, 33, 37, 44, 51, 59, 64, 72, 76, 85, 88, 95, 100, 107, 114, 120}
func (i btfKind) String() string {
if i >= btfKind(len(_btfKind_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_btfKind_index)-1 {
return "btfKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _btfKind_name[_btfKind_index[i]:_btfKind_index[i+1]]
return _btfKind_name[_btfKind_index[idx]:_btfKind_index[idx+1]]
}
+15 -12
View File
@@ -6,11 +6,12 @@ import (
"fmt"
"math"
"reflect"
"slices"
"strconv"
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
)
// Code in this file is derived from libbpf, which is available under a BSD
@@ -46,6 +47,10 @@ func (f *COREFixup) String() string {
}
func (f *COREFixup) Apply(ins *asm.Instruction) error {
if !platform.IsLinux {
return fmt.Errorf("CO-RE fixup: %w", internal.ErrNotSupportedOnOS)
}
if f.poison {
// Relocation is poisoned, replace the instruction with an invalid one.
if ins.OpCode.IsDWordLoad() {
@@ -205,8 +210,8 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder,
resolveTargetTypeID := targets[0].TypeID
for _, target := range targets {
if bo != target.imm.byteOrder {
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.imm.byteOrder)
if bo != target.byteOrder {
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.byteOrder)
}
}
@@ -259,16 +264,14 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder,
var targetTypes []Type
for _, target := range targets {
namedTypeIDs := target.imm.namedTypes[essentialName]
targetTypes = slices.Grow(targetTypes, len(namedTypeIDs))
for _, id := range namedTypeIDs {
typ, err := target.TypeByID(id)
if err != nil {
return nil, err
}
targetTypes = append(targetTypes, typ)
namedTypes, err := target.TypesByName(essentialName)
if errors.Is(err, ErrNotFound) {
continue
} else if err != nil {
return nil, err
}
targetTypes = append(targetTypes, namedTypes...)
}
fixups, err := coreCalculateFixups(group.relos, targetTypes, bo, resolveTargetTypeID)
+633
View File
@@ -0,0 +1,633 @@
package btf
import (
"errors"
"fmt"
"hash/maphash"
"slices"
)
// deduper deduplicates BTF types by finding all types in a Type graph that are
// Equivalent and replaces them with a single instance.
//
// See doc comments in types.go to understand the various ways in which Types
// can relate to each other and how they are compared for equality. We separate
// Identity (same memory location), Equivalence (same shape/layout), and
// Compatibility (CO-RE compatible) to be explicit about intent.
//
// This deduper opportunistically uses a combination of Identity and Equivalence
// to find types that can be deduplicated.
type deduper struct {
visited map[Type]struct{}
hashCache map[hashCacheKey]uint64
// Set of types that have been deduplicated.
done map[Type]Type
// Map of hash to types with that hash.
hashed map[uint64][]Type
eqCache map[typKey]bool
seed maphash.Seed
}
func newDeduper() *deduper {
return &deduper{
make(map[Type]struct{}),
make(map[hashCacheKey]uint64),
make(map[Type]Type),
make(map[uint64][]Type),
make(map[typKey]bool),
maphash.MakeSeed(),
}
}
func (d *deduper) deduplicate(t Type) (Type, error) {
// If we have already attempted to deduplicate this exact type, return the
// result.
if done, ok := d.done[t]; ok {
return done, nil
}
// Visit the subtree, if a type has children, attempt to replace it with a
// deduplicated version of those children.
for t := range postorder(t, d.visited) {
for c := range children(t) {
var err error
*c, err = d.hashInsert(*c)
if err != nil {
return nil, err
}
}
}
// Finally, deduplicate the root type itself.
return d.hashInsert(t)
}
// hashInsert attempts to deduplicate t by hashing it and comparing against
// other types with the same hash. Returns the Type to be used as the common
// substitute at this position in the graph.
func (d *deduper) hashInsert(t Type) (Type, error) {
// If we have deduplicated this type before, return the result of that
// deduplication.
if done, ok := d.done[t]; ok {
return done, nil
}
// Compute the hash of this type. Types with the same hash are candidates for
// deduplication.
hash, err := d.hash(t, -1)
if err != nil {
return nil, err
}
// A hash collision is possible, so we need to compare against all candidates
// with the same hash.
for _, candidate := range d.hashed[hash] {
// Pre-size the visited slice, experimentation on VMLinux shows a capacity
// of 16 to give the best performance.
const visitedCapacity = 16
err := d.typesEquivalent(candidate, t, make([]Type, 0, visitedCapacity))
if errors.Is(err, errNotEquivalent) {
continue
}
if err != nil {
return nil, err
}
// Found a Type that's both Equivalent and hashes to the same value, choose
// it as the deduplicated version.
d.done[t] = candidate
return candidate, nil
}
d.hashed[hash] = append(d.hashed[hash], t)
return t, nil
}
// The hash of a Type is the same given its pointer and depth budget.
type hashCacheKey struct {
t Type
depthBudget int
}
// hash computes a hash for t. The produced hash is the same for Types which
// are similar. The hash can collide such that two different Types may produce
// the same hash, so equivalence must be checked explicitly. It will recurse
// into children. The initial call should use a depthBudget of -1.
func (d *deduper) hash(t Type, depthBudget int) (uint64, error) {
if depthBudget == 0 {
return 0, nil
}
h := &maphash.Hash{}
h.SetSeed(d.seed)
switch t := t.(type) {
case *Void:
maphash.WriteComparable(h, kindUnknown)
case *Int:
maphash.WriteComparable(h, kindInt)
maphash.WriteComparable(h, *t)
case *Pointer:
maphash.WriteComparable(h, kindPointer)
// If the depth budget is positive, decrement it every time we follow a
// pointer.
if depthBudget > 0 {
depthBudget--
}
// If this is the first time we are following a pointer, set the depth
// budget. This limits amount of recursion we do when hashing pointers that
// form cycles. This is cheaper than tracking visited types and works
// because hash collisions are allowed.
if depthBudget < 0 {
depthBudget = 1
// Double pointers are common in C. However, with a depth budget of 1, all
// double pointers would hash the same, causing a performance issue when
// checking equivalence. So we give double pointers a bit more budget.
if _, ok := t.Target.(*Pointer); ok {
depthBudget = 2
}
}
sub, err := d.hash(t.Target, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Array:
maphash.WriteComparable(h, kindArray)
maphash.WriteComparable(h, t.Nelems)
sub, err := d.hash(t.Index, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
_, err = d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Struct, *Union:
// Check the cache to avoid recomputing the hash for this type and depth
// budget.
key := hashCacheKey{t, depthBudget}
if cached, ok := d.hashCache[key]; ok {
return cached, nil
}
var members []Member
switch t := t.(type) {
case *Struct:
maphash.WriteComparable(h, kindStruct)
maphash.WriteComparable(h, t.Name)
maphash.WriteComparable(h, t.Size)
members = t.Members
case *Union:
maphash.WriteComparable(h, kindUnion)
maphash.WriteComparable(h, t.Name)
maphash.WriteComparable(h, t.Size)
members = t.Members
}
maphash.WriteComparable(h, len(members))
for _, m := range members {
maphash.WriteComparable(h, m.Name)
maphash.WriteComparable(h, m.Offset)
sub, err := d.hash(m.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
}
sum := h.Sum64()
d.hashCache[key] = sum
return sum, nil
case *Enum:
maphash.WriteComparable(h, kindEnum)
maphash.WriteComparable(h, t.Name)
maphash.WriteComparable(h, t.Size)
maphash.WriteComparable(h, t.Signed)
for _, v := range t.Values {
maphash.WriteComparable(h, v)
}
case *Fwd:
maphash.WriteComparable(h, kindForward)
maphash.WriteComparable(h, *t)
case *Typedef:
maphash.WriteComparable(h, kindTypedef)
maphash.WriteComparable(h, t.Name)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Volatile:
maphash.WriteComparable(h, kindVolatile)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Const:
maphash.WriteComparable(h, kindConst)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Restrict:
maphash.WriteComparable(h, kindRestrict)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Func:
maphash.WriteComparable(h, kindFunc)
maphash.WriteComparable(h, t.Name)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *FuncProto:
// It turns out that pointers to function prototypes are common in C code,
// function pointers. Function prototypes frequently have similar patterns
// of [ptr, ptr] -> int, or [ptr, ptr, ptr] -> int. Causing frequent hash
// collisions, for the default depth budget of 1. So allow one additional
// level of pointers when we encounter a function prototype.
if depthBudget >= 0 {
depthBudget++
}
maphash.WriteComparable(h, kindFuncProto)
for _, p := range t.Params {
maphash.WriteComparable(h, p.Name)
sub, err := d.hash(p.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
}
sub, err := d.hash(t.Return, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Var:
maphash.WriteComparable(h, kindVar)
maphash.WriteComparable(h, t.Name)
maphash.WriteComparable(h, t.Linkage)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Datasec:
maphash.WriteComparable(h, kindDatasec)
maphash.WriteComparable(h, t.Name)
for _, v := range t.Vars {
maphash.WriteComparable(h, v.Offset)
maphash.WriteComparable(h, v.Size)
sub, err := d.hash(v.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
}
case *declTag:
maphash.WriteComparable(h, kindDeclTag)
maphash.WriteComparable(h, t.Value)
maphash.WriteComparable(h, t.Index)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *TypeTag:
maphash.WriteComparable(h, kindTypeTag)
maphash.WriteComparable(h, t.Value)
sub, err := d.hash(t.Type, depthBudget)
if err != nil {
return 0, err
}
maphash.WriteComparable(h, sub)
case *Float:
maphash.WriteComparable(h, kindFloat)
maphash.WriteComparable(h, *t)
default:
return 0, fmt.Errorf("unsupported type for hashing: %T", t)
}
return h.Sum64(), nil
}
type typKey struct {
a Type
b Type
}
var errNotEquivalent = errors.New("types are not equivalent")
// typesEquivalent checks if two types are Equivalent.
func (d *deduper) typesEquivalent(ta, tb Type, visited []Type) error {
// Fast path: if Types are Identical, they are also Equivalent.
if ta == tb {
return nil
}
switch a := ta.(type) {
case *Void:
if _, ok := tb.(*Void); ok {
return nil
}
return errNotEquivalent
case *Int:
b, ok := tb.(*Int)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || a.Size != b.Size || a.Encoding != b.Encoding {
return errNotEquivalent
}
return nil
case *Enum:
b, ok := tb.(*Enum)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || len(a.Values) != len(b.Values) {
return errNotEquivalent
}
for i := range a.Values {
if a.Values[i].Name != b.Values[i].Name || a.Values[i].Value != b.Values[i].Value {
return errNotEquivalent
}
}
return nil
case *Fwd:
b, ok := tb.(*Fwd)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || a.Kind != b.Kind {
return errNotEquivalent
}
return nil
case *Float:
b, ok := tb.(*Float)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || a.Size != b.Size {
return errNotEquivalent
}
return nil
case *Array:
b, ok := tb.(*Array)
if !ok {
return errNotEquivalent
}
if a.Nelems != b.Nelems {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Index, b.Index, visited); err != nil {
return err
}
if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil {
return err
}
return nil
case *Pointer:
b, ok := tb.(*Pointer)
if !ok {
return errNotEquivalent
}
// Detect cycles by tracking visited types. Assume types are Equivalent if
// we have already visited this type in the current Equivalence check.
if slices.Contains(visited, ta) {
return nil
}
visited = append(visited, ta)
return d.typesEquivalent(a.Target, b.Target, visited)
case *Struct, *Union:
// Use a cache to avoid recomputation. We only do this for composite types
// since they are where types fan out the most. For other types, the
// overhead of the lookup and update outweighs performance benefits.
cacheKey := typKey{a: ta, b: tb}
if equal, ok := d.eqCache[cacheKey]; ok {
if equal {
return nil
}
return errNotEquivalent
}
compErr := d.compositeEquivalent(ta, tb, visited)
d.eqCache[cacheKey] = compErr == nil
return compErr
case *Typedef:
b, ok := tb.(*Typedef)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *Volatile:
b, ok := tb.(*Volatile)
if !ok {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *Const:
b, ok := tb.(*Const)
if !ok {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *Restrict:
b, ok := tb.(*Restrict)
if !ok {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *Func:
b, ok := tb.(*Func)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *FuncProto:
b, ok := tb.(*FuncProto)
if !ok {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Return, b.Return, visited); err != nil {
return err
}
if len(a.Params) != len(b.Params) {
return errNotEquivalent
}
for i := range a.Params {
if a.Params[i].Name != b.Params[i].Name {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Params[i].Type, b.Params[i].Type, visited); err != nil {
return err
}
}
return nil
case *Var:
b, ok := tb.(*Var)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil {
return err
}
if a.Linkage != b.Linkage {
return errNotEquivalent
}
return nil
case *Datasec:
b, ok := tb.(*Datasec)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || len(a.Vars) != len(b.Vars) {
return errNotEquivalent
}
for i := range a.Vars {
if a.Vars[i].Offset != b.Vars[i].Offset ||
a.Vars[i].Size != b.Vars[i].Size {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Vars[i].Type, b.Vars[i].Type, visited); err != nil {
return err
}
}
return nil
case *declTag:
b, ok := tb.(*declTag)
if !ok {
return errNotEquivalent
}
if a.Value != b.Value || a.Index != b.Index {
return errNotEquivalent
}
return d.typesEquivalent(a.Type, b.Type, visited)
case *TypeTag:
b, ok := tb.(*TypeTag)
if !ok {
return errNotEquivalent
}
if a.Value != b.Value {
return errNotEquivalent
}
if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil {
return err
}
return nil
default:
return fmt.Errorf("unsupported type for equivalence: %T", a)
}
}
// compositeEquivalent checks if two composite types (Struct or Union) are
// Equivalent.
func (d *deduper) compositeEquivalent(at, bt Type, visited []Type) error {
var ma, mb []Member
switch a := at.(type) {
case *Struct:
b, ok := bt.(*Struct)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) {
return errNotEquivalent
}
ma = a.Members
mb = b.Members
case *Union:
b, ok := bt.(*Union)
if !ok {
return errNotEquivalent
}
if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) {
return errNotEquivalent
}
ma = a.Members
mb = b.Members
}
for i := range ma {
if ma[i].Name != mb[i].Name || ma[i].Offset != mb[i].Offset {
return errNotEquivalent
}
if err := d.typesEquivalent(ma[i].Type, mb[i].Type, visited); err != nil {
return err
}
}
return nil
}
+61 -73
View File
@@ -13,12 +13,23 @@ import (
"github.com/cilium/ebpf/internal"
)
// ExtInfos contains ELF section metadata.
// ExtInfos contains raw, per-section extended BTF metadata from the .BTF.ext
// ELF section.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string]FuncOffsets
lineInfos map[string]LineOffsets
relocationInfos map[string]CORERelocationInfos
Funcs map[string]FuncOffsets
Lines map[string]LineOffsets
CORERelos map[string]CORERelocationOffsets
}
// Section returns the FuncOffsets, LineOffsets and CORERelocationOffsets for
// the given section name. Returns all nils if ExtInfos is nil, or individual
// nils if there is no metadata of that type for the section.
func (ei *ExtInfos) Section(name string) (FuncOffsets, LineOffsets, CORERelocationOffsets) {
if ei == nil {
return nil, nil, nil
}
return ei.Funcs[name], ei.Lines[name], ei.CORERelos[name]
}
// loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF.
@@ -91,7 +102,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}
coreRelos := make(map[string]CORERelocationInfos, len(btfCORERelos))
coreRelos := make(map[string]CORERelocationOffsets, len(btfCORERelos))
for section, brs := range btfCORERelos {
coreRelos[section], err = newRelocationInfos(brs, spec, spec.strings)
if err != nil {
@@ -102,46 +113,6 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
}
type (
funcInfoMeta struct{}
coreRelocationMeta struct{}
)
// Assign per-section metadata from BTF to a section's instructions.
func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
funcInfos := ei.funcInfos[section]
lineInfos := ei.lineInfos[section]
reloInfos := ei.relocationInfos[section]
AssignMetadataToInstructions(insns, funcInfos, lineInfos, reloInfos)
}
// Assign per-instruction metadata to the instructions in insns.
func AssignMetadataToInstructions(
insns asm.Instructions,
funcInfos FuncOffsets,
lineInfos LineOffsets,
reloInfos CORERelocationInfos,
) {
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func)
funcInfos = funcInfos[1:]
}
if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset {
*iter.Ins = iter.Ins.WithSource(lineInfos[0].Line)
lineInfos = lineInfos[1:]
}
if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset {
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos.infos[0].relo)
reloInfos.infos = reloInfos.infos[1:]
}
}
}
// MarshalExtInfos encodes function and line info embedded in insns into kernel
// wire format.
//
@@ -335,8 +306,8 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
return recordSize, nil
}
// FuncOffsets is a sorted slice of FuncOffset.
type FuncOffsets []FuncOffset
// FuncOffsets is a slice of FuncOffsets sorted by offset.
type FuncOffsets = []FuncOffset
// The size of a FuncInfo in BTF wire format.
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
@@ -466,7 +437,7 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
for range recordNum {
if err := binary.Read(r, bo, &fi); err != nil {
return nil, fmt.Errorf("can't read function info: %v", err)
}
@@ -518,8 +489,8 @@ func (li *Line) String() string {
return li.line
}
// LineOffsets contains a sorted list of line infos.
type LineOffsets []LineOffset
// LineOffsets is a slice of LineOffsets sorted by offset.
type LineOffsets = []LineOffset
// LineOffset represents a line info and its raw instruction offset.
type LineOffset struct {
@@ -559,12 +530,12 @@ func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
}
func newLineInfo(li bpfLineInfo, strings *stringTable) (LineOffset, error) {
line, err := strings.Lookup(li.LineOff)
line, err := strings.LookupCached(li.LineOff)
if err != nil {
return LineOffset{}, fmt.Errorf("lookup of line: %w", err)
}
fileName, err := strings.Lookup(li.FileNameOff)
fileName, err := strings.LookupCached(li.FileNameOff)
if err != nil {
return LineOffset{}, fmt.Errorf("lookup of filename: %w", err)
}
@@ -671,9 +642,15 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
}
out := make([]bpfLineInfo, recordNum)
if err := binary.Read(r, bo, out); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
out := make([]bpfLineInfo, 0)
chunk := make([]bpfLineInfo, min(1024, recordNum))
for remaining := recordNum; remaining > 0; {
n := min(uint32(1024), remaining)
if err := binary.Read(r, bo, chunk[:n]); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
out = append(out, chunk[:n]...)
remaining -= n
}
if offsetInBytes {
@@ -713,22 +690,32 @@ func (cr *CORERelocation) String() string {
return fmt.Sprintf("CORERelocation(%s, %s[%s], local_id=%d)", cr.kind, cr.typ, cr.accessor, cr.id)
}
type coreRelocationMeta struct{}
// CORERelocationMetadata returns the CORERelocation associated with ins.
func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation)
return relo
}
// CORERelocationInfos contains a sorted list of co:re relocation infos.
type CORERelocationInfos struct {
infos []coreRelocationInfo
// WithCORERelocationMetadata associates a CORERelocation with ins and returns
// the modified Instruction.
func WithCORERelocationMetadata(ins asm.Instruction, relo *CORERelocation) asm.Instruction {
ins.Metadata.Set(coreRelocationMeta{}, relo)
return ins
}
type coreRelocationInfo struct {
relo *CORERelocation
offset asm.RawInstructionOffset
// CORERelocationOffsets is a slice of CORERelocationOffsets sorted by offset.
type CORERelocationOffsets = []CORERelocationOffset
// CORERelocationOffset represents a CO-RE relocation and an offset at which it
// should be applied.
type CORERelocationOffset struct {
Relo *CORERelocation
Offset asm.RawInstructionOffset
}
func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*coreRelocationInfo, error) {
func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*CORERelocationOffset, error) {
typ, err := spec.TypeByID(relo.TypeID)
if err != nil {
return nil, err
@@ -744,7 +731,7 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
return &coreRelocationInfo{
return &CORERelocationOffset{
&CORERelocation{
typ,
accessor,
@@ -755,20 +742,21 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor
}, nil
}
func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationInfos, error) {
rs := CORERelocationInfos{
infos: make([]coreRelocationInfo, 0, len(brs)),
}
func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationOffsets, error) {
rs := make(CORERelocationOffsets, 0, len(brs))
for _, br := range brs {
relo, err := newRelocationInfo(br, spec, strings)
if err != nil {
return CORERelocationInfos{}, fmt.Errorf("offset %d: %w", br.InsnOff, err)
return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
}
rs.infos = append(rs.infos, *relo)
rs = append(rs, *relo)
}
sort.Slice(rs.infos, func(i, j int) bool {
return rs.infos[i].offset < rs.infos[j].offset
sort.Slice(rs, func(i, j int) bool {
return rs[i].Offset < rs[j].Offset
})
return rs, nil
}
@@ -812,7 +800,7 @@ func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordNum uint32) ([
var out []bpfCORERelo
var relo bpfCORERelo
for i := uint32(0); i < recordNum; i++ {
for range recordNum {
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
}
+2 -2
View File
@@ -135,7 +135,7 @@ var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error {
}, "6.0")
func probeBTF(typ Type) error {
b, err := NewBuilder([]Type{typ})
b, err := NewBuilder([]Type{typ}, nil)
if err != nil {
return err
}
@@ -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)),
})
+4 -4
View File
@@ -56,7 +56,7 @@ func (gf *GoFormatter) enumIdentifier(name, element string) string {
//
// It encodes https://golang.org/ref/spec#Type_declarations:
//
// type foo struct { bar uint32; }
// type foo struct { _ structs.HostLayout; bar uint32; }
// type bar int32
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
if name == "" {
@@ -114,7 +114,7 @@ func (gf *GoFormatter) writeType(typ Type, depth int) error {
//
// It encodes https://golang.org/ref/spec#TypeLit.
//
// struct { bar uint32; }
// struct { _ structs.HostLayout; bar uint32; }
// uint32
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
depth++
@@ -208,7 +208,7 @@ func (gf *GoFormatter) writeIntLit(i *Int) error {
}
func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
gf.w.WriteString("struct { ")
gf.w.WriteString("struct { _ structs.HostLayout; ")
prevOffset := uint32(0)
skippedBitfield := false
@@ -298,7 +298,7 @@ func (gf *GoFormatter) writeStructField(m Member, depth int) error {
}
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
gf.w.WriteString("struct { ")
gf.w.WriteString("struct { _ structs.HostLayout; ")
prevOffset := uint32(0)
for i, vsi := range ds.Vars {
+43 -6
View File
@@ -1,13 +1,13 @@
package btf
import (
"bytes"
"errors"
"fmt"
"math"
"os"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -43,12 +43,16 @@ func NewHandle(b *Builder) (*Handle, error) {
func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
const minLogSize = 64 * 1024
if platform.IsWindows {
return nil, fmt.Errorf("btf: handle: %w", internal.ErrNotSupportedOnOS)
}
if uint64(len(btf)) > math.MaxUint32 {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
Btf: sys.SlicePointer(btf),
BtfSize: uint32(len(btf)),
}
@@ -91,10 +95,14 @@ 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
}
if errors.Is(err, sys.ErrTokenCapabilities) {
return nil, fmt.Errorf("load btf: %w", err)
}
if err := haveBTF(); err != nil {
return nil, err
}
@@ -110,6 +118,10 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
//
// Requires CAP_SYS_ADMIN.
func NewHandleFromID(id ID) (*Handle, error) {
if platform.IsWindows {
return nil, fmt.Errorf("btf: handle: %w", internal.ErrNotSupportedOnOS)
}
fd, err := sys.BtfGetFdById(&sys.BtfGetFdByIdAttr{
Id: uint32(id),
})
@@ -133,7 +145,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
@@ -143,7 +156,7 @@ func (h *Handle) Spec(base *Spec) (*Spec, error) {
return nil, fmt.Errorf("missing base types")
}
return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base)
return loadRawSpec(btfBuffer, base)
}
// Close destroys the handle.
@@ -162,6 +175,24 @@ func (h *Handle) FD() int {
return h.fd.Int()
}
// Clone creates a duplicate of the handle.
//
// Closing the duplicate does not affect the original, and vice versa.
//
// Cloning a nil Handle returns nil.
func (h *Handle) Clone() (*Handle, error) {
if h == nil {
return nil, nil
}
dup, err := h.fd.Dup()
if err != nil {
return nil, fmt.Errorf("can't clone handle: %w", err)
}
return &Handle{dup, h.size, h.needsKernelBase}, nil
}
// Info returns metadata about the handle.
func (h *Handle) Info() (*HandleInfo, error) {
return newHandleInfoFromFD(h.fd)
@@ -204,7 +235,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
}
@@ -242,6 +274,11 @@ type HandleIterator struct {
// Returns true if another BTF object was found. Call [HandleIterator.Err] after
// the function returns false.
func (it *HandleIterator) Next() bool {
if platform.IsWindows {
it.err = fmt.Errorf("btf: %w", internal.ErrNotSupportedOnOS)
return false
}
id := it.ID
for {
attr := &sys.BtfGetNextIdAttr{Id: id}
+177 -76
View File
@@ -5,113 +5,90 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"slices"
"sort"
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/platform"
"github.com/cilium/ebpf/internal/unix"
)
var kernelBTF = struct {
sync.RWMutex
kernel *Spec
modules map[string]*Spec
}{
modules: make(map[string]*Spec),
}
// FlushKernelSpec removes any cached kernel type information.
func FlushKernelSpec() {
kernelBTF.Lock()
defer kernelBTF.Unlock()
kernelBTF.kernel = nil
kernelBTF.modules = make(map[string]*Spec)
}
// LoadKernelSpec returns the current kernel's BTF information.
//
// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system
// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled.
// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file
// system for vmlinux ELFs. Returns an error wrapping [ErrNotSupported] if BTF
// is not enabled.
//
// Consider using [Cache] instead.
func LoadKernelSpec() (*Spec, error) {
kernelBTF.RLock()
spec := kernelBTF.kernel
kernelBTF.RUnlock()
if spec == nil {
kernelBTF.Lock()
defer kernelBTF.Unlock()
spec = kernelBTF.kernel
if platform.IsWindows {
return nil, internal.ErrNotSupportedOnOS
}
if spec != nil {
return spec.Copy(), nil
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()
info, err := fh.Stat()
if err != nil {
return nil, fmt.Errorf("stat vmlinux: %w", err)
}
// NB: It's not safe to mmap arbitrary files because mmap(2) doesn't
// guarantee that changes made after mmap are not visible in the mapping.
//
// This is not a problem for vmlinux, since it is always a read-only file.
raw, err := unix.Mmap(int(fh.Fd()), 0, int(info.Size()), unix.PROT_READ, unix.MAP_PRIVATE)
if err != nil {
return LoadSplitSpecFromReader(fh, nil)
}
spec, err := loadRawSpec(raw, nil)
if err != nil {
_ = unix.Munmap(raw)
return nil, fmt.Errorf("load vmlinux: %w", err)
}
runtime.AddCleanup(spec.decoder.sharedBuf, func(b []byte) {
_ = unix.Munmap(b)
}, raw)
return spec, nil
}
spec, _, err := loadKernelSpec()
file, err := findVMLinux()
if err != nil {
return nil, err
}
defer file.Close()
kernelBTF.kernel = spec
return spec.Copy(), nil
spec, err := LoadSpecFromReader(file)
return spec, err
}
// LoadKernelModuleSpec returns the BTF information for the named kernel module.
//
// Using [Cache.Module] is faster when loading BTF for more than one module.
//
// Defaults to /sys/kernel/btf/<module>.
// Returns an error wrapping ErrNotSupported if BTF is not enabled.
// Returns an error wrapping fs.ErrNotExist if BTF for the specific module doesn't exist.
func LoadKernelModuleSpec(module string) (*Spec, error) {
kernelBTF.RLock()
spec := kernelBTF.modules[module]
kernelBTF.RUnlock()
if spec != nil {
return spec.Copy(), nil
}
base, err := LoadKernelSpec()
if err != nil {
return nil, fmt.Errorf("load kernel spec: %w", err)
}
kernelBTF.Lock()
defer kernelBTF.Unlock()
if spec = kernelBTF.modules[module]; spec != nil {
return spec.Copy(), nil
}
spec, err = loadKernelModuleSpec(module, base)
if err != nil {
return nil, err
}
kernelBTF.modules[module] = spec
return spec.Copy(), nil
}
func loadKernelSpec() (_ *Spec, fallback bool, _ error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()
spec, err := loadRawSpec(fh, internal.NativeEndian, nil)
return spec, false, err
}
file, err := findVMLinux()
if err != nil {
return nil, false, err
}
defer file.Close()
spec, err := LoadSpecFromReader(file)
return spec, true, err
return loadKernelModuleSpec(module, base)
}
func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) {
if platform.IsWindows {
return nil, internal.ErrNotSupportedOnOS
}
dir, file := filepath.Split(module)
if dir != "" || filepath.Ext(file) != "" {
return nil, fmt.Errorf("invalid module name %q", module)
@@ -123,11 +100,15 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) {
}
defer fh.Close()
return loadRawSpec(fh, internal.NativeEndian, base)
return LoadSplitSpecFromReader(fh, base)
}
// findVMLinux scans multiple well-known paths for vmlinux kernel images.
func findVMLinux() (*os.File, error) {
if platform.IsWindows {
return nil, fmt.Errorf("find vmlinux: %w", internal.ErrNotSupportedOnOS)
}
release, err := linux.KernelRelease()
if err != nil {
return nil, err
@@ -155,3 +136,123 @@ func findVMLinux() (*os.File, error) {
return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported)
}
// Cache allows to amortise the cost of decoding BTF across multiple call-sites.
//
// It is safe for concurrent use.
type Cache struct {
mu sync.RWMutex
kernelTypes *Spec
moduleTypes map[string]*Spec
loadedModules []string
}
// NewCache returns an empty Cache.
//
// Pass it to [CollectionOptions] to share kernel BTF across multiple
// Collection loads.
func NewCache() *Cache {
return &Cache{}
}
// Kernel is equivalent to [LoadKernelSpec], except that repeated calls do
// not copy the Spec.
//
// Returns an error wrapping [ErrNotSupported] if BTF is not enabled.
func (c *Cache) Kernel() (*Spec, error) {
c.mu.RLock()
if c.kernelTypes != nil {
spec := c.kernelTypes
c.mu.RUnlock()
return spec, nil
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
return c.kernel()
}
func (c *Cache) kernel() (*Spec, error) {
if c.kernelTypes != nil {
return c.kernelTypes, nil
}
var err error
c.kernelTypes, err = LoadKernelSpec()
return c.kernelTypes, err
}
// Module is equivalent to [LoadKernelModuleSpec], except that repeated calls do
// not copy the spec.
//
// All modules also share the return value of [Kernel] as their base.
func (c *Cache) Module(name string) (*Spec, error) {
c.mu.RLock()
if spec := c.moduleTypes[name]; spec != nil {
c.mu.RUnlock()
return spec, nil
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
if spec := c.moduleTypes[name]; spec != nil {
return spec, nil
}
if c.moduleTypes == nil {
c.moduleTypes = make(map[string]*Spec)
}
base, err := c.kernel()
if err != nil {
return nil, err
}
spec, err := loadKernelModuleSpec(name, base)
if err != nil {
return nil, err
}
c.moduleTypes[name] = spec
return spec, nil
}
// Modules returns a sorted list of all loaded modules.
func (c *Cache) Modules() ([]string, error) {
c.mu.RLock()
if c.loadedModules != nil {
modules := slices.Clone(c.loadedModules)
c.mu.RUnlock()
return modules, nil
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
if c.loadedModules != nil {
return slices.Clone(c.loadedModules), nil
}
btfDir, err := os.Open("/sys/kernel/btf")
if err != nil {
return nil, err
}
defer btfDir.Close()
entries, err := btfDir.Readdirnames(-1)
if err != nil {
return nil, err
}
entries = slices.DeleteFunc(entries, func(s string) bool {
return s == "vmlinux"
})
sort.Strings(entries)
c.loadedModules = entries
return slices.Clone(entries), nil
}
+176 -93
View File
@@ -1,7 +1,6 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
@@ -9,6 +8,7 @@ import (
"math"
"slices"
"sync"
"unsafe"
"github.com/cilium/ebpf/internal"
)
@@ -45,7 +45,6 @@ type encoder struct {
MarshalOptions
pending internal.Deque[Type]
buf *bytes.Buffer
strings *stringTableBuilder
ids map[Type]TypeID
visited map[Type]struct{}
@@ -79,6 +78,13 @@ type Builder struct {
stableIDs map[Type]TypeID
// Explicitly added strings.
strings *stringTableBuilder
// Deduplication data structure.
deduper *deduper
}
type BuilderOptions struct {
// Deduplicate enables type deduplication.
Deduplicate bool
}
// NewBuilder creates a Builder from a list of types.
@@ -86,11 +92,20 @@ type Builder struct {
// It is more efficient than calling [Add] individually.
//
// Returns an error if adding any of the types fails.
func NewBuilder(types []Type) (*Builder, error) {
func NewBuilder(types []Type, opts *BuilderOptions) (*Builder, error) {
if opts == nil {
opts = &BuilderOptions{}
}
b := &Builder{
make([]Type, 0, len(types)),
make(map[Type]TypeID, len(types)),
nil,
nil,
}
if opts.Deduplicate {
b.deduper = newDeduper()
}
for _, typ := range types {
@@ -114,13 +129,25 @@ func (b *Builder) Empty() bool {
//
// See [Type] for details on identity.
func (b *Builder) Add(typ Type) (TypeID, error) {
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
}
if err := internal.IsNilPointer(typ); err != nil {
return 0, fmt.Errorf("invalid type: %w", err)
}
if b.stableIDs == nil {
b.stableIDs = make(map[Type]TypeID)
}
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
if b.deduper != nil {
var err error
typ, err = b.deduper.deduplicate(typ)
if err != nil {
return 0, err
}
}
if ds, ok := typ.(*Datasec); ok {
@@ -145,6 +172,19 @@ func (b *Builder) Add(typ Type) (TypeID, error) {
return id, nil
}
// Spec marshals the Builder's types and returns a new Spec to query them.
//
// The resulting Spec does not share any state with the Builder, subsequent
// additions to the Builder will not affect the Spec.
func (b *Builder) Spec() (*Spec, error) {
buf, err := b.Marshal(make([]byte, 0), nil)
if err != nil {
return nil, err
}
return loadRawSpec(buf, nil)
}
// Marshal encodes all types in the Marshaler into BTF wire format.
//
// opts may be nil.
@@ -166,12 +206,8 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) {
// Reserve space for the BTF header.
buf = slices.Grow(buf, btfHeaderLen)[:btfHeaderLen]
w := internal.NewBuffer(buf)
defer internal.PutBuffer(w)
e := encoder{
MarshalOptions: *opts,
buf: w,
strings: stb,
lastID: TypeID(len(b.types)),
visited: make(map[Type]struct{}, len(b.types)),
@@ -199,15 +235,16 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) {
e.pending.Push(typ)
}
if err := e.deflatePending(); err != nil {
buf, err := e.deflatePending(buf)
if err != nil {
return nil, err
}
length := e.buf.Len()
length := len(buf)
typeLen := uint32(length - btfHeaderLen)
stringLen := e.strings.Length()
buf = e.strings.AppendEncoded(e.buf.Bytes())
buf = e.strings.AppendEncoded(buf)
// Fill out the header, and write it out.
header := &btfHeader{
@@ -221,7 +258,7 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) {
StringLen: uint32(stringLen),
}
err := binary.Write(sliceWriter(buf[:btfHeaderLen]), e.Order, header)
_, err = binary.Encode(buf[:btfHeaderLen], e.Order, header)
if err != nil {
return nil, fmt.Errorf("write header: %v", err)
}
@@ -243,28 +280,27 @@ func (b *Builder) addString(str string) (uint32, error) {
return b.strings.Add(str)
}
func (e *encoder) allocateIDs(root Type) (err error) {
visitInPostorder(root, e.visited, func(typ Type) bool {
func (e *encoder) allocateIDs(root Type) error {
for typ := range postorder(root, e.visited) {
if _, ok := typ.(*Void); ok {
return true
continue
}
if _, ok := e.ids[typ]; ok {
return true
continue
}
id := e.lastID + 1
if id < e.lastID {
err = errors.New("type ID overflow")
return false
return errors.New("type ID overflow")
}
e.pending.Push(typ)
e.ids[typ] = id
e.lastID = id
return true
})
return
}
return nil
}
// id returns the ID for the given type or panics with an error.
@@ -281,7 +317,7 @@ func (e *encoder) id(typ Type) TypeID {
return id
}
func (e *encoder) deflatePending() error {
func (e *encoder) deflatePending(buf []byte) ([]byte, error) {
// Declare root outside of the loop to avoid repeated heap allocations.
var root Type
@@ -289,20 +325,22 @@ func (e *encoder) deflatePending() error {
root = e.pending.Shift()
// Allocate IDs for all children of typ, including transitive dependencies.
if err := e.allocateIDs(root); err != nil {
return err
err := e.allocateIDs(root)
if err != nil {
return nil, err
}
if err := e.deflateType(root); err != nil {
buf, err = e.deflateType(buf, root)
if err != nil {
id := e.ids[root]
return fmt.Errorf("deflate %v with ID %d: %w", root, id, err)
return nil, fmt.Errorf("deflate %v with ID %d: %w", root, id, err)
}
}
return nil
return buf, nil
}
func (e *encoder) deflateType(typ Type) (err error) {
func (e *encoder) deflateType(buf []byte, typ Type) (_ []byte, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
@@ -313,18 +351,22 @@ func (e *encoder) deflateType(typ Type) (err error) {
}
}()
var raw rawType
var raw btfType
raw.NameOff, err = e.strings.Add(typ.TypeName())
if err != nil {
return err
return nil, err
}
// Reserve space for the btfType header.
start := len(buf)
buf = append(buf, make([]byte, unsafe.Sizeof(raw))...)
switch v := typ.(type) {
case *Void:
return errors.New("Void is implicit in BTF wire format")
return nil, errors.New("Void is implicit in BTF wire format")
case *Int:
e.deflateInt(&raw, v)
buf, err = e.deflateInt(buf, &raw, v)
case *Pointer:
raw.SetKind(kindPointer)
@@ -332,25 +374,25 @@ func (e *encoder) deflateType(typ Type) (err error) {
case *Array:
raw.SetKind(kindArray)
raw.data = &btfArray{
buf, err = binary.Append(buf, e.Order, &btfArray{
e.id(v.Type),
e.id(v.Index),
v.Nelems,
}
})
case *Struct:
raw.SetKind(kindStruct)
raw.SetSize(v.Size)
raw.data, err = e.convertMembers(&raw.btfType, v.Members)
buf, err = e.deflateMembers(buf, &raw, v.Members)
case *Union:
err = e.deflateUnion(&raw, v)
buf, err = e.deflateUnion(buf, &raw, v)
case *Enum:
if v.Size == 8 {
err = e.deflateEnum64(&raw, v)
buf, err = e.deflateEnum64(buf, &raw, v)
} else {
err = e.deflateEnum(&raw, v)
buf, err = e.deflateEnum(buf, &raw, v)
}
case *Fwd:
@@ -383,41 +425,46 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.SetKind(kindFuncProto)
raw.SetType(e.id(v.Return))
raw.SetVlen(len(v.Params))
raw.data, err = e.deflateFuncParams(v.Params)
buf, err = e.deflateFuncParams(buf, v.Params)
case *Var:
raw.SetKind(kindVar)
raw.SetType(e.id(v.Type))
raw.data = btfVariable{uint32(v.Linkage)}
buf, err = binary.Append(buf, e.Order, btfVariable{uint32(v.Linkage)})
case *Datasec:
raw.SetKind(kindDatasec)
raw.SetSize(v.Size)
raw.SetVlen(len(v.Vars))
raw.data = e.deflateVarSecinfos(v.Vars)
buf, err = e.deflateVarSecinfos(buf, v.Vars)
case *Float:
raw.SetKind(kindFloat)
raw.SetSize(v.Size)
case *declTag:
err = e.deflateDeclTag(&raw, v)
buf, err = e.deflateDeclTag(buf, &raw, v)
case *TypeTag:
err = e.deflateTypeTag(&raw, v)
default:
return fmt.Errorf("don't know how to deflate %T", v)
return nil, fmt.Errorf("don't know how to deflate %T", v)
}
if err != nil {
return err
return nil, err
}
return raw.Marshal(e.buf, e.Order)
header := buf[start : start+int(unsafe.Sizeof(raw))]
if _, err = raw.Encode(header, e.Order); err != nil {
return nil, err
}
return buf, nil
}
func (e *encoder) deflateInt(raw *rawType, i *Int) {
func (e *encoder) deflateInt(buf []byte, raw *btfType, i *Int) ([]byte, error) {
raw.SetKind(kindInt)
raw.SetSize(i.Size)
@@ -426,40 +473,47 @@ func (e *encoder) deflateInt(raw *rawType, i *Int) {
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(i.Size) * 8)
raw.data = bi
return binary.Append(buf, e.Order, bi)
}
func (e *encoder) deflateDeclTag(raw *rawType, tag *declTag) (err error) {
func (e *encoder) deflateDeclTag(buf []byte, raw *btfType, tag *declTag) ([]byte, error) {
// Replace a decl tag with an integer for compatibility with <5.16 kernels,
// following libbpf behaviour.
if e.ReplaceDeclTags {
typ := &Int{"decl_tag_placeholder", 1, Unsigned}
e.deflateInt(raw, typ)
buf, err := e.deflateInt(buf, raw, typ)
if err != nil {
return nil, err
}
// Add the placeholder type name to the string table. The encoder added the
// original type name before this call.
raw.NameOff, err = e.strings.Add(typ.TypeName())
return
return buf, err
}
var err error
raw.SetKind(kindDeclTag)
raw.SetType(e.id(tag.Type))
raw.data = &btfDeclTag{uint32(tag.Index)}
raw.NameOff, err = e.strings.Add(tag.Value)
return
if err != nil {
return nil, err
}
return binary.Append(buf, e.Order, btfDeclTag{uint32(tag.Index)})
}
func (e *encoder) deflateConst(raw *rawType, c *Const) {
func (e *encoder) deflateConst(raw *btfType, c *Const) {
raw.SetKind(kindConst)
raw.SetType(e.id(c.Type))
}
func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) {
func (e *encoder) deflateTypeTag(raw *btfType, tag *TypeTag) (err error) {
// Replace a type tag with a const qualifier for compatibility with <5.17
// kernels, following libbpf behaviour.
if e.ReplaceTypeTags {
e.deflateConst(raw, &Const{tag.Type})
return
return nil
}
raw.SetKind(kindTypeTag)
@@ -468,16 +522,17 @@ func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) {
return
}
func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) {
func (e *encoder) deflateUnion(buf []byte, raw *btfType, union *Union) ([]byte, error) {
raw.SetKind(kindUnion)
raw.SetSize(union.Size)
raw.data, err = e.convertMembers(&raw.btfType, union.Members)
return
return e.deflateMembers(buf, raw, union.Members)
}
func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember, error) {
bms := make([]btfMember, 0, len(members))
func (e *encoder) deflateMembers(buf []byte, header *btfType, members []Member) ([]byte, error) {
var bm btfMember
isBitfield := false
buf = slices.Grow(buf, len(members)*int(unsafe.Sizeof(bm)))
for _, member := range members {
isBitfield = isBitfield || member.BitfieldSize > 0
@@ -491,30 +546,35 @@ func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember
return nil, err
}
bms = append(bms, btfMember{
bm = btfMember{
nameOff,
e.id(member.Type),
uint32(offset),
})
}
buf, err = binary.Append(buf, e.Order, &bm)
if err != nil {
return nil, err
}
}
header.SetVlen(len(members))
header.SetBitfield(isBitfield)
return bms, nil
return buf, nil
}
func (e *encoder) deflateEnum(raw *rawType, enum *Enum) (err error) {
func (e *encoder) deflateEnum(buf []byte, raw *btfType, enum *Enum) ([]byte, error) {
raw.SetKind(kindEnum)
raw.SetSize(enum.Size)
raw.SetVlen(len(enum.Values))
// Signedness appeared together with ENUM64 support.
raw.SetSigned(enum.Signed && !e.ReplaceEnum64)
raw.data, err = e.deflateEnumValues(enum)
return
return e.deflateEnumValues(buf, enum)
}
func (e *encoder) deflateEnumValues(enum *Enum) ([]btfEnum, error) {
bes := make([]btfEnum, 0, len(enum.Values))
func (e *encoder) deflateEnumValues(buf []byte, enum *Enum) ([]byte, error) {
var be btfEnum
buf = slices.Grow(buf, len(enum.Values)*int(unsafe.Sizeof(be)))
for _, value := range enum.Values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
@@ -531,16 +591,21 @@ func (e *encoder) deflateEnumValues(enum *Enum) ([]btfEnum, error) {
}
}
bes = append(bes, btfEnum{
be = btfEnum{
nameOff,
uint32(value.Value),
})
}
buf, err = binary.Append(buf, e.Order, &be)
if err != nil {
return nil, err
}
}
return bes, nil
return buf, nil
}
func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
func (e *encoder) deflateEnum64(buf []byte, raw *btfType, enum *Enum) ([]byte, error) {
if e.ReplaceEnum64 {
// Replace the ENUM64 with a union of fields with the correct size.
// This matches libbpf behaviour on purpose.
@@ -553,7 +618,7 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
placeholder.Encoding = Signed
}
if err := e.allocateIDs(placeholder); err != nil {
return fmt.Errorf("add enum64 placeholder: %w", err)
return nil, fmt.Errorf("add enum64 placeholder: %w", err)
}
members := make([]Member, 0, len(enum.Values))
@@ -564,61 +629,79 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
})
}
return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members, nil})
return e.deflateUnion(buf, raw, &Union{enum.Name, enum.Size, members, nil})
}
raw.SetKind(kindEnum64)
raw.SetSize(enum.Size)
raw.SetVlen(len(enum.Values))
raw.SetSigned(enum.Signed)
raw.data, err = e.deflateEnum64Values(enum.Values)
return
return e.deflateEnum64Values(buf, enum.Values)
}
func (e *encoder) deflateEnum64Values(values []EnumValue) ([]btfEnum64, error) {
bes := make([]btfEnum64, 0, len(values))
func (e *encoder) deflateEnum64Values(buf []byte, values []EnumValue) ([]byte, error) {
var be btfEnum64
buf = slices.Grow(buf, len(values)*int(unsafe.Sizeof(be)))
for _, value := range values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}
bes = append(bes, btfEnum64{
be = btfEnum64{
nameOff,
uint32(value.Value),
uint32(value.Value >> 32),
})
}
buf, err = binary.Append(buf, e.Order, &be)
if err != nil {
return nil, err
}
}
return bes, nil
return buf, nil
}
func (e *encoder) deflateFuncParams(params []FuncParam) ([]btfParam, error) {
bps := make([]btfParam, 0, len(params))
func (e *encoder) deflateFuncParams(buf []byte, params []FuncParam) ([]byte, error) {
var bp btfParam
buf = slices.Grow(buf, len(params)*int(unsafe.Sizeof(bp)))
for _, param := range params {
nameOff, err := e.strings.Add(param.Name)
if err != nil {
return nil, err
}
bps = append(bps, btfParam{
bp = btfParam{
nameOff,
e.id(param.Type),
})
}
buf, err = binary.Append(buf, e.Order, &bp)
if err != nil {
return nil, err
}
}
return bps, nil
return buf, nil
}
func (e *encoder) deflateVarSecinfos(vars []VarSecinfo) []btfVarSecinfo {
vsis := make([]btfVarSecinfo, 0, len(vars))
func (e *encoder) deflateVarSecinfos(buf []byte, vars []VarSecinfo) ([]byte, error) {
var vsi btfVarSecinfo
var err error
buf = slices.Grow(buf, len(vars)*int(unsafe.Sizeof(vsi)))
for _, v := range vars {
vsis = append(vsis, btfVarSecinfo{
vsi = btfVarSecinfo{
e.id(v.Type),
v.Offset,
v.Size,
})
}
buf, err = binary.Append(buf, e.Order, vsi)
if err != nil {
return nil, err
}
}
return vsis
return buf, nil
}
// MarshalMapKV creates a BTF object containing a map key and value.
+87 -74
View File
@@ -1,21 +1,24 @@
package btf
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"maps"
"slices"
"strings"
"sync"
)
// stringTable contains a sequence of null-terminated strings.
//
// It is safe for concurrent use.
type stringTable struct {
base *stringTable
offsets []uint32
prevIdx int
strings []string
base *stringTable
bytes []byte
mu sync.Mutex
cache map[uint32]string
}
// sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc.
@@ -25,93 +28,103 @@ type sizedReader interface {
}
func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) {
bytes := make([]byte, r.Size())
if _, err := io.ReadFull(r, bytes); err != nil {
return nil, err
}
return newStringTable(bytes, base)
}
func newStringTable(bytes []byte, base *stringTable) (*stringTable, error) {
// When parsing split BTF's string table, the first entry offset is derived
// from the last entry offset of the base BTF.
firstStringOffset := uint32(0)
if base != nil {
idx := len(base.offsets) - 1
firstStringOffset = base.offsets[idx] + uint32(len(base.strings[idx])) + 1
firstStringOffset = uint32(len(base.bytes))
}
// Derived from vmlinux BTF.
const averageStringLength = 16
if len(bytes) > 0 {
if bytes[len(bytes)-1] != 0 {
return nil, errors.New("string table isn't null terminated")
}
n := int(r.Size() / averageStringLength)
offsets := make([]uint32, 0, n)
strings := make([]string, 0, n)
offset := firstStringOffset
scanner := bufio.NewScanner(r)
scanner.Split(splitNull)
for scanner.Scan() {
str := scanner.Text()
offsets = append(offsets, offset)
strings = append(strings, str)
offset += uint32(len(str)) + 1
}
if err := scanner.Err(); err != nil {
return nil, err
}
if len(strings) == 0 {
return nil, errors.New("string table is empty")
}
if firstStringOffset == 0 && strings[0] != "" {
return nil, errors.New("first item in string table is non-empty")
}
return &stringTable{base, offsets, 0, strings}, nil
}
func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) {
i := bytes.IndexByte(data, 0)
if i == -1 {
if atEOF && len(data) > 0 {
return 0, nil, errors.New("string table isn't null terminated")
if firstStringOffset == 0 && bytes[0] != 0 {
return nil, errors.New("first item in string table is non-empty")
}
return 0, nil, nil
}
return i + 1, data[:i], nil
return &stringTable{base: base, bytes: bytes}, nil
}
func (st *stringTable) Lookup(offset uint32) (string, error) {
if st.base != nil && offset <= st.base.offsets[len(st.base.offsets)-1] {
return st.base.lookup(offset)
}
return st.lookup(offset)
}
func (st *stringTable) lookup(offset uint32) (string, error) {
// Fast path: zero offset is the empty string, looked up frequently.
if offset == 0 && st.base == nil {
if offset == 0 {
return "", nil
}
// Accesses tend to be globally increasing, so check if the next string is
// the one we want. This skips the binary search in about 50% of cases.
if st.prevIdx+1 < len(st.offsets) && st.offsets[st.prevIdx+1] == offset {
st.prevIdx++
return st.strings[st.prevIdx], nil
}
i, found := slices.BinarySearch(st.offsets, offset)
if !found {
return "", fmt.Errorf("offset %d isn't start of a string", offset)
}
// Set the new increment index, but only if its greater than the current.
if i > st.prevIdx+1 {
st.prevIdx = i
}
return st.strings[i], nil
b, err := st.lookupSlow(offset)
return string(b), err
}
// Num returns the number of strings in the table.
func (st *stringTable) Num() int {
return len(st.strings)
func (st *stringTable) LookupBytes(offset uint32) ([]byte, error) {
// Fast path: zero offset is the empty string, looked up frequently.
if offset == 0 {
return nil, nil
}
return st.lookupSlow(offset)
}
func (st *stringTable) lookupSlow(offset uint32) ([]byte, error) {
if st.base != nil {
n := uint32(len(st.base.bytes))
if offset < n {
return st.base.lookupSlow(offset)
}
offset -= n
}
if offset > uint32(len(st.bytes)) {
return nil, fmt.Errorf("offset %d is out of bounds of string table", offset)
}
if offset > 0 && st.bytes[offset-1] != 0 {
return nil, fmt.Errorf("offset %d is not the beginning of a string", offset)
}
i := bytes.IndexByte(st.bytes[offset:], 0)
if i == -1 {
return nil, fmt.Errorf("string at offset %d is not null terminated", offset)
}
return st.bytes[offset : offset+uint32(i)], nil
}
// LookupCache returns the string at the given offset, caching the result
// for future lookups.
func (cst *stringTable) LookupCached(offset uint32) (string, error) {
// Fast path: zero offset is the empty string, looked up frequently.
if offset == 0 {
return "", nil
}
cst.mu.Lock()
defer cst.mu.Unlock()
if str, ok := cst.cache[offset]; ok {
return str, nil
}
str, err := cst.Lookup(offset)
if err != nil {
return "", err
}
if cst.cache == nil {
cst.cache = make(map[uint32]string)
}
cst.cache[offset] = str
return str, nil
}
// stringTableBuilder builds BTF string tables.
+124 -124
View File
@@ -2,16 +2,21 @@ package btf
import (
"fmt"
"iter"
)
// Functions to traverse a cyclic graph of types. The below was very useful:
// https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/#post-order-and-reverse-post-order
// Visit all types reachable from root in postorder.
//
// Traversal stops if yield returns false.
//
// Returns false if traversal was aborted.
// postorder yields all types reachable from root in post order.
func postorder(root Type, visited map[Type]struct{}) iter.Seq[Type] {
return func(yield func(Type) bool) {
visitInPostorder(root, visited, yield)
}
}
// visitInPostorder is a separate function to avoid arguments escaping
// to the heap. Don't change the setup without re-running the benchmarks.
func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type) bool) bool {
if _, ok := visited[root]; ok {
return true
@@ -21,139 +26,134 @@ func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type)
}
visited[root] = struct{}{}
cont := children(root, func(child *Type) bool {
return visitInPostorder(*child, visited, yield)
})
if !cont {
return false
for child := range children(root) {
if !visitInPostorder(*child, visited, yield) {
return false
}
}
return yield(root)
}
// children calls yield on each child of typ.
//
// Traversal stops if yield returns false.
//
// Returns false if traversal was aborted.
func children(typ Type, yield func(child *Type) bool) bool {
// Explicitly type switch on the most common types to allow the inliner to
// do its work. This avoids allocating intermediate slices from walk() on
// the heap.
var tags []string
switch v := typ.(type) {
case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
// No children to traverse.
// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
// during unmarshaling.
case *Pointer:
if !yield(&v.Target) {
return false
}
case *Array:
if !yield(&v.Index) {
return false
}
if !yield(&v.Type) {
return false
}
case *Struct:
for i := range v.Members {
if !yield(&v.Members[i].Type) {
return false
// children yields all direct descendants of typ.
func children(typ Type) iter.Seq[*Type] {
return func(yield func(*Type) bool) {
// Explicitly type switch on the most common types to allow the inliner to
// do its work. This avoids allocating intermediate slices from walk() on
// the heap.
var tags []string
switch v := typ.(type) {
case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
// No children to traverse.
// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
// during unmarshaling.
case *Pointer:
if !yield(&v.Target) {
return
}
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
case *Array:
if !yield(&v.Index) {
return
}
if !yield(&v.Type) {
return
}
case *Struct:
for i := range v.Members {
if !yield(&v.Members[i].Type) {
return
}
}
}
tags = v.Tags
case *Union:
for i := range v.Members {
if !yield(&v.Members[i].Type) {
return false
}
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
}
}
}
tags = v.Tags
case *Typedef:
if !yield(&v.Type) {
return false
}
tags = v.Tags
case *Volatile:
if !yield(&v.Type) {
return false
}
case *Const:
if !yield(&v.Type) {
return false
}
case *Restrict:
if !yield(&v.Type) {
return false
}
case *Func:
if !yield(&v.Type) {
return false
}
if fp, ok := v.Type.(*FuncProto); ok {
for i := range fp.Params {
if len(v.ParamTags) <= i {
continue
}
for _, t := range v.ParamTags[i] {
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
return
}
}
}
tags = v.Tags
case *Union:
for i := range v.Members {
if !yield(&v.Members[i].Type) {
return
}
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return
}
}
}
tags = v.Tags
case *Typedef:
if !yield(&v.Type) {
return
}
tags = v.Tags
case *Volatile:
if !yield(&v.Type) {
return
}
case *Const:
if !yield(&v.Type) {
return
}
case *Restrict:
if !yield(&v.Type) {
return
}
case *Func:
if !yield(&v.Type) {
return
}
if fp, ok := v.Type.(*FuncProto); ok {
for i := range fp.Params {
if len(v.ParamTags) <= i {
continue
}
for _, t := range v.ParamTags[i] {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return
}
}
}
}
tags = v.Tags
case *FuncProto:
if !yield(&v.Return) {
return
}
for i := range v.Params {
if !yield(&v.Params[i].Type) {
return
}
}
case *Var:
if !yield(&v.Type) {
return
}
tags = v.Tags
case *Datasec:
for i := range v.Vars {
if !yield(&v.Vars[i].Type) {
return
}
}
case *TypeTag:
if !yield(&v.Type) {
return
}
case *cycle:
// cycle has children, but we ignore them deliberately.
default:
panic(fmt.Sprintf("don't know how to walk Type %T", v))
}
tags = v.Tags
case *FuncProto:
if !yield(&v.Return) {
return false
}
for i := range v.Params {
if !yield(&v.Params[i].Type) {
return false
for _, t := range tags {
var tag Type = &declTag{typ, t, -1}
if !yield(&tag) {
return
}
}
case *Var:
if !yield(&v.Type) {
return false
}
tags = v.Tags
case *Datasec:
for i := range v.Vars {
if !yield(&v.Vars[i].Type) {
return false
}
}
case *TypeTag:
if !yield(&v.Type) {
return false
}
case *cycle:
// cycle has children, but we ignore them deliberately.
default:
panic(fmt.Sprintf("don't know how to walk Type %T", v))
}
for _, t := range tags {
var tag Type = &declTag{typ, t, -1}
if !yield(&tag) {
return false
}
}
return true
}
+60 -518
View File
@@ -1,12 +1,10 @@
package btf
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"slices"
"strings"
"github.com/cilium/ebpf/asm"
@@ -23,15 +21,25 @@ type TypeID = sys.TypeID
// Type represents a type described by BTF.
//
// Identity of Type follows the [Go specification]: two Types are considered
// equal if they have the same concrete type and the same dynamic value, aka
// they point at the same location in memory. This means that the following
// Types are considered distinct even though they have the same "shape".
// A Type has three properties where compared to other Types.
//
// Identity: follows the [Go specification], two Types are considered identical
// if they have the same concrete type and the same dynamic value, aka they point
// at the same location in memory. This means that the following Types are
// considered distinct even though they have the same "shape".
//
// a := &Int{Size: 1}
// b := &Int{Size: 1}
// a != b
//
// Equivalence: two Types are considered equivalent if they have the same shape
// and thus are functionally interchangeable, even if they are located at different
// memory addresses. The above two Int types are equivalent.
//
// Compatibility: two Types are considered compatible according to the rules of CO-RE
// see [coreAreTypesCompatible] for details. This is a non-commutative property,
// so A may be compatible with B, but B not compatible with A.
//
// [Go specification]: https://go.dev/ref/spec#Comparison_operators
type Type interface {
// Type can be formatted using the %s and %v verbs. %s outputs only the
@@ -52,7 +60,7 @@ type Type interface {
// Make a copy of the type, without copying Type members.
copy() Type
// New implementations must update walkType.
// New implementations must update children, deduper.typeHash, and typesEquivalent.
}
var (
@@ -172,8 +180,15 @@ type Struct struct {
Tags []string
}
// Format supports the width option to %v to limit or extend the number of
// struct member names printed (default 5).
//
// For example, %1v will print only the first member name followed by '...':
//
// Struct:"struct"[fields=3 fieldNames=[a ...]]
func (s *Struct) Format(fs fmt.State, verb rune) {
formatType(fs, verb, s, "fields=", len(s.Members))
max, _ := fs.Width()
formatType(fs, verb, s, "fields=", len(s.Members), "fieldNames=", memberNames(s.Members, max))
}
func (s *Struct) TypeName() string { return s.Name }
@@ -200,8 +215,15 @@ type Union struct {
Tags []string
}
// Format supports the width option to %v to limit or extend the number of
// union member names printed (default 5).
//
// For example, %1v will print only the first member name followed by '...':
//
// Union:"union"[fields=3 fieldNames=[a ...]]
func (u *Union) Format(fs fmt.State, verb rune) {
formatType(fs, verb, u, "fields=", len(u.Members))
max, _ := fs.Width()
formatType(fs, verb, u, "fields=", len(u.Members), "fieldNames=", memberNames(u.Members, max))
}
func (u *Union) TypeName() string { return u.Name }
@@ -219,6 +241,29 @@ func (u *Union) members() []Member {
return u.Members
}
// memberNames returns the names of members, or its index in the list of members
// if the name is empty.
//
// Returns up to max entries (default 5), followed by '...'.
func memberNames(members []Member, max int) []string {
if max <= 0 {
max = 5
}
names := make([]string, min(len(members), max+1))
for i, m := range members {
if i >= max {
names[i] = "..."
break
}
if m.Name == "" {
names[i] = fmt.Sprintf("<%d>", i)
continue
}
names[i] = m.Name
}
return names
}
func copyMembers(orig []Member) []Member {
cpy := make([]Member, len(orig))
copy(cpy, orig)
@@ -430,6 +475,8 @@ type Func struct {
ParamTags [][]string
}
type funcInfoMeta struct{}
func FuncMetadata(ins *asm.Instruction) *Func {
fn, _ := ins.Metadata.Get(funcInfoMeta{}).(*Func)
return fn
@@ -646,7 +693,7 @@ func Sizeof(typ Type) (int, error) {
elem int64
)
for i := 0; i < maxResolveDepth; i++ {
for range maxResolveDepth {
switch v := typ.(type) {
case *Array:
if n > 0 && int64(v.Nelems) > math.MaxInt64/n {
@@ -740,520 +787,15 @@ func copyType(typ Type, ids map[Type]TypeID, copies map[Type]Type, copiedIDs map
copiedIDs[cpy] = id
}
children(cpy, func(child *Type) bool {
for child := range children(cpy) {
*child = copyType(*child, ids, copies, copiedIDs)
return true
})
}
return cpy
}
type typeDeque = internal.Deque[*Type]
// readAndInflateTypes reads the raw btf type info and turns it into a graph
// of Types connected via pointers.
//
// If base is provided, then the types are considered to be of a split BTF
// (e.g., a kernel module).
//
// Returns a slice of types indexed by TypeID. Since BTF ignores compilation
// units, multiple types may share the same name. A Type may form a cyclic graph
// by pointing at itself.
func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawStrings *stringTable, base *Spec) ([]Type, error) {
// because of the interleaving between types and struct members it is difficult to
// precompute the numbers of raw types this will parse
// this "guess" is a good first estimation
sizeOfbtfType := uintptr(btfTypeLen)
tyMaxCount := uintptr(typeLen) / sizeOfbtfType / 2
types := make([]Type, 0, tyMaxCount)
// Void is defined to always be type ID 0, and is thus omitted from BTF.
types = append(types, (*Void)(nil))
firstTypeID := TypeID(0)
if base != nil {
var err error
firstTypeID, err = base.nextTypeID()
if err != nil {
return nil, err
}
// Split BTF doesn't contain Void.
types = types[:0]
}
type fixupDef struct {
id TypeID
typ *Type
}
var fixups []fixupDef
fixup := func(id TypeID, typ *Type) {
if id < firstTypeID {
if baseType, err := base.TypeByID(id); err == nil {
*typ = baseType
return
}
}
idx := int(id - firstTypeID)
if idx < len(types) {
// We've already inflated this type, fix it up immediately.
*typ = types[idx]
return
}
fixups = append(fixups, fixupDef{id, typ})
}
type bitfieldFixupDef struct {
id TypeID
m *Member
}
var (
legacyBitfields = make(map[TypeID][2]Bits) // offset, size
bitfieldFixups []bitfieldFixupDef
)
convertMembers := func(raw []btfMember, kindFlag bool) ([]Member, error) {
// NB: The fixup below relies on pre-allocating this array to
// work, since otherwise append might re-allocate members.
members := make([]Member, 0, len(raw))
for i, btfMember := range raw {
name, err := rawStrings.Lookup(btfMember.NameOff)
if err != nil {
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
}
members = append(members, Member{
Name: name,
Offset: Bits(btfMember.Offset),
})
m := &members[i]
fixup(raw[i].Type, &m.Type)
if kindFlag {
m.BitfieldSize = Bits(btfMember.Offset >> 24)
m.Offset &= 0xffffff
// We ignore legacy bitfield definitions if the current composite
// is a new-style bitfield. This is kind of safe since offset and
// size on the type of the member must be zero if kindFlat is set
// according to spec.
continue
}
// This may be a legacy bitfield, try to fix it up.
data, ok := legacyBitfields[raw[i].Type]
if ok {
// Bingo!
m.Offset += data[0]
m.BitfieldSize = data[1]
continue
}
if m.Type != nil {
// We couldn't find a legacy bitfield, but we know that the member's
// type has already been inflated. Hence we know that it can't be
// a legacy bitfield and there is nothing left to do.
continue
}
// We don't have fixup data, and the type we're pointing
// at hasn't been inflated yet. No choice but to defer
// the fixup.
bitfieldFixups = append(bitfieldFixups, bitfieldFixupDef{
raw[i].Type,
m,
})
}
return members, nil
}
var (
buf = make([]byte, 1024)
header btfType
bInt btfInt
bArr btfArray
bMembers []btfMember
bEnums []btfEnum
bParams []btfParam
bVariable btfVariable
bSecInfos []btfVarSecinfo
bDeclTag btfDeclTag
bEnums64 []btfEnum64
)
var declTags []*declTag
for {
var (
id = firstTypeID + TypeID(len(types))
typ Type
)
if _, err := io.ReadFull(r, buf[:btfTypeLen]); err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
}
if _, err := unmarshalBtfType(&header, buf[:btfTypeLen], bo); err != nil {
return nil, fmt.Errorf("can't unmarshal type info for id %v: %v", id, err)
}
if id < firstTypeID {
return nil, fmt.Errorf("no more type IDs")
}
name, err := rawStrings.Lookup(header.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for type id %d: %w", id, err)
}
switch header.Kind() {
case kindInt:
size := header.Size()
buf = buf[:btfIntLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfInt, id: %d: %w", id, err)
}
if _, err := unmarshalBtfInt(&bInt, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfInt, id: %d: %w", id, err)
}
if bInt.Offset() > 0 || bInt.Bits().Bytes() != size {
legacyBitfields[id] = [2]Bits{bInt.Offset(), bInt.Bits()}
}
typ = &Int{name, header.Size(), bInt.Encoding()}
case kindPointer:
ptr := &Pointer{nil}
fixup(header.Type(), &ptr.Target)
typ = ptr
case kindArray:
buf = buf[:btfArrayLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfArray, id: %d: %w", id, err)
}
if _, err := unmarshalBtfArray(&bArr, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfArray, id: %d: %w", id, err)
}
arr := &Array{nil, nil, bArr.Nelems}
fixup(bArr.IndexType, &arr.Index)
fixup(bArr.Type, &arr.Type)
typ = arr
case kindStruct:
vlen := header.Vlen()
bMembers = slices.Grow(bMembers[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfMemberLen)[:vlen*btfMemberLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfMembers, id: %d: %w", id, err)
}
if _, err := unmarshalBtfMembers(bMembers, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfMembers, id: %d: %w", id, err)
}
members, err := convertMembers(bMembers, header.Bitfield())
if err != nil {
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
}
typ = &Struct{name, header.Size(), members, nil}
case kindUnion:
vlen := header.Vlen()
bMembers = slices.Grow(bMembers[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfMemberLen)[:vlen*btfMemberLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfMembers, id: %d: %w", id, err)
}
if _, err := unmarshalBtfMembers(bMembers, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfMembers, id: %d: %w", id, err)
}
members, err := convertMembers(bMembers, header.Bitfield())
if err != nil {
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
}
typ = &Union{name, header.Size(), members, nil}
case kindEnum:
vlen := header.Vlen()
bEnums = slices.Grow(bEnums[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfEnumLen)[:vlen*btfEnumLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfEnums, id: %d: %w", id, err)
}
if _, err := unmarshalBtfEnums(bEnums, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfEnums, id: %d: %w", id, err)
}
vals := make([]EnumValue, 0, vlen)
signed := header.Signed()
for i, btfVal := range bEnums {
name, err := rawStrings.Lookup(btfVal.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for enum value %d: %s", i, err)
}
value := uint64(btfVal.Val)
if signed {
// Sign extend values to 64 bit.
value = uint64(int32(btfVal.Val))
}
vals = append(vals, EnumValue{name, value})
}
typ = &Enum{name, header.Size(), signed, vals}
case kindForward:
typ = &Fwd{name, header.FwdKind()}
case kindTypedef:
typedef := &Typedef{name, nil, nil}
fixup(header.Type(), &typedef.Type)
typ = typedef
case kindVolatile:
volatile := &Volatile{nil}
fixup(header.Type(), &volatile.Type)
typ = volatile
case kindConst:
cnst := &Const{nil}
fixup(header.Type(), &cnst.Type)
typ = cnst
case kindRestrict:
restrict := &Restrict{nil}
fixup(header.Type(), &restrict.Type)
typ = restrict
case kindFunc:
fn := &Func{name, nil, header.Linkage(), nil, nil}
fixup(header.Type(), &fn.Type)
typ = fn
case kindFuncProto:
vlen := header.Vlen()
bParams = slices.Grow(bParams[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfParamLen)[:vlen*btfParamLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfParams, id: %d: %w", id, err)
}
if _, err := unmarshalBtfParams(bParams, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfParams, id: %d: %w", id, err)
}
params := make([]FuncParam, 0, vlen)
for i, param := range bParams {
name, err := rawStrings.Lookup(param.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err)
}
params = append(params, FuncParam{
Name: name,
})
}
for i := range params {
fixup(bParams[i].Type, &params[i].Type)
}
fp := &FuncProto{nil, params}
fixup(header.Type(), &fp.Return)
typ = fp
case kindVar:
buf = buf[:btfVariableLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err)
}
if _, err := unmarshalBtfVariable(&bVariable, buf, bo); err != nil {
return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err)
}
v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil}
fixup(header.Type(), &v.Type)
typ = v
case kindDatasec:
vlen := header.Vlen()
bSecInfos = slices.Grow(bSecInfos[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfVarSecinfoLen)[:vlen*btfVarSecinfoLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfVarSecInfos, id: %d: %w", id, err)
}
if _, err := unmarshalBtfVarSecInfos(bSecInfos, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfVarSecInfos, id: %d: %w", id, err)
}
vars := make([]VarSecinfo, 0, vlen)
for _, btfVar := range bSecInfos {
vars = append(vars, VarSecinfo{
Offset: btfVar.Offset,
Size: btfVar.Size,
})
}
for i := range vars {
fixup(bSecInfos[i].Type, &vars[i].Type)
}
typ = &Datasec{name, header.Size(), vars}
case kindFloat:
typ = &Float{name, header.Size()}
case kindDeclTag:
buf = buf[:btfDeclTagLen]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err)
}
if _, err := unmarshalBtfDeclTag(&bDeclTag, buf, bo); err != nil {
return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err)
}
btfIndex := bDeclTag.ComponentIdx
if uint64(btfIndex) > math.MaxInt {
return nil, fmt.Errorf("type id %d: index exceeds int", id)
}
dt := &declTag{nil, name, int(int32(btfIndex))}
fixup(header.Type(), &dt.Type)
typ = dt
declTags = append(declTags, dt)
case kindTypeTag:
tt := &TypeTag{nil, name}
fixup(header.Type(), &tt.Type)
typ = tt
case kindEnum64:
vlen := header.Vlen()
bEnums64 = slices.Grow(bEnums64[:0], vlen)[:vlen]
buf = slices.Grow(buf[:0], vlen*btfEnum64Len)[:vlen*btfEnum64Len]
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("can't read btfEnum64s, id: %d: %w", id, err)
}
if _, err := unmarshalBtfEnums64(bEnums64, buf, bo); err != nil {
return nil, fmt.Errorf("can't unmarshal btfEnum64s, id: %d: %w", id, err)
}
vals := make([]EnumValue, 0, vlen)
for i, btfVal := range bEnums64 {
name, err := rawStrings.Lookup(btfVal.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for enum64 value %d: %s", i, err)
}
value := (uint64(btfVal.ValHi32) << 32) | uint64(btfVal.ValLo32)
vals = append(vals, EnumValue{name, value})
}
typ = &Enum{name, header.Size(), header.Signed(), vals}
default:
return nil, fmt.Errorf("type id %d: unknown kind: %v", id, header.Kind())
}
types = append(types, typ)
}
for _, fixup := range fixups {
if fixup.id < firstTypeID {
return nil, fmt.Errorf("fixup for base type id %d is not expected", fixup.id)
}
idx := int(fixup.id - firstTypeID)
if idx >= len(types) {
return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id)
}
*fixup.typ = types[idx]
}
for _, bitfieldFixup := range bitfieldFixups {
if bitfieldFixup.id < firstTypeID {
return nil, fmt.Errorf("bitfield fixup from split to base types is not expected")
}
data, ok := legacyBitfields[bitfieldFixup.id]
if ok {
// This is indeed a legacy bitfield, fix it up.
bitfieldFixup.m.Offset += data[0]
bitfieldFixup.m.BitfieldSize = data[1]
}
}
for _, dt := range declTags {
switch t := dt.Type.(type) {
case *Var:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case *Typedef:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case composite:
if dt.Index >= 0 {
members := t.members()
if dt.Index >= len(members) {
return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t)
}
members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value)
continue
}
if dt.Index == -1 {
switch t2 := t.(type) {
case *Struct:
t2.Tags = append(t2.Tags, dt.Value)
case *Union:
t2.Tags = append(t2.Tags, dt.Value)
}
continue
}
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
case *Func:
fp, ok := t.Type.(*FuncProto)
if !ok {
return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type)
}
// Ensure the number of argument tag lists equals the number of arguments
if len(t.ParamTags) == 0 {
t.ParamTags = make([][]string, len(fp.Params))
}
if dt.Index >= 0 {
if dt.Index >= len(fp.Params) {
return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t)
}
t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value)
continue
}
if dt.Index == -1 {
t.Tags = append(t.Tags, dt.Value)
continue
}
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
default:
return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t)
}
}
return types, nil
}
// essentialName represents the name of a BTF type stripped of any flavor
// suffixes after a ___ delimiter.
type essentialName string
@@ -1347,7 +889,7 @@ type formattableType interface {
// Handles cyclical types by only printing cycles up to a certain depth. Elements
// in extra are separated by spaces unless the preceding element is a string
// ending in '='.
func formatType(f fmt.State, verb rune, t formattableType, extra ...interface{}) {
func formatType(f fmt.State, verb rune, t formattableType, extra ...any) {
if verb != 'v' && verb != 's' {
fmt.Fprintf(f, "{UNRECOGNIZED: %c}", verb)
return
+764
View File
@@ -0,0 +1,764 @@
package btf
import (
"bytes"
"encoding/binary"
"fmt"
"hash/maphash"
"io"
"iter"
"maps"
"math"
"slices"
"sync"
)
// sharedBuf is a buffer which may be shared between multiple decoders.
//
// It must not be modified. Some sharedBuf may be backed by an mmap-ed file, in
// which case the sharedBuf has a finalizer. sharedBuf must therefore always be
// passed as a pointer.
type sharedBuf struct {
raw []byte
}
type decoder struct {
// Immutable fields, may be shared.
base *decoder
byteOrder binary.ByteOrder
*sharedBuf
strings *stringTable
// The ID for offsets[0].
firstTypeID TypeID
// Map from TypeID to offset of the marshaled data in raw. Contains an entry
// for each TypeID, including 0 aka Void. The offset for Void is invalid.
offsets []int
declTags map[TypeID][]TypeID
// An index from essentialName to TypeID.
namedTypes *fuzzyStringIndex
// Protection for mutable fields below.
mu sync.Mutex
types map[TypeID]Type
typeIDs map[Type]TypeID
legacyBitfields map[TypeID][2]Bits // offset, size
}
func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *decoder) (*decoder, error) {
firstTypeID := TypeID(0)
if base != nil {
if base.byteOrder != bo {
return nil, fmt.Errorf("can't use %v base with %v split BTF", base.byteOrder, bo)
}
if base.firstTypeID != 0 {
return nil, fmt.Errorf("can't use split BTF as base")
}
firstTypeID = TypeID(len(base.offsets))
}
var header btfType
var numTypes, numDeclTags, numNamedTypes int
for _, err := range allBtfTypeOffsets(raw, bo, &header) {
if err != nil {
return nil, err
}
numTypes++
if header.Kind() == kindDeclTag {
numDeclTags++
}
if header.NameOff != 0 {
numNamedTypes++
}
}
if firstTypeID == 0 {
// Allocate an extra slot for Void so we don't have to deal with
// constant off by one issues.
numTypes++
}
offsets := make([]int, 0, numTypes)
declTags := make(map[TypeID][]TypeID, numDeclTags)
namedTypes := newFuzzyStringIndex(numNamedTypes)
if firstTypeID == 0 {
// Add a sentinel for Void.
offsets = append(offsets, math.MaxInt)
}
id := firstTypeID + TypeID(len(offsets))
for offset := range allBtfTypeOffsets(raw, bo, &header) {
if id < firstTypeID {
return nil, fmt.Errorf("no more type IDs")
}
offsets = append(offsets, offset)
if header.Kind() == kindDeclTag {
declTags[header.Type()] = append(declTags[header.Type()], id)
}
// Build named type index.
name, err := strings.LookupBytes(header.NameOff)
if err != nil {
return nil, fmt.Errorf("lookup type name for id %v: %w", id, err)
}
if len(name) > 0 {
if i := bytes.Index(name, []byte("___")); i != -1 {
// Flavours are rare. It's cheaper to find the first index for some
// reason.
i = bytes.LastIndex(name, []byte("___"))
name = name[:i]
}
namedTypes.Add(name, id)
}
id++
}
namedTypes.Build()
return &decoder{
base,
bo,
&sharedBuf{raw},
strings,
firstTypeID,
offsets,
declTags,
namedTypes,
sync.Mutex{},
make(map[TypeID]Type),
make(map[Type]TypeID),
make(map[TypeID][2]Bits),
}, nil
}
func allBtfTypeOffsets(buf []byte, bo binary.ByteOrder, header *btfType) iter.Seq2[int, error] {
return func(yield func(int, error) bool) {
for offset := 0; offset < len(buf); {
start := offset
n, err := unmarshalBtfType(header, buf[offset:], bo)
if err != nil {
yield(-1, fmt.Errorf("unmarshal type header: %w", err))
return
}
offset += n
n, err = header.DataLen()
if err != nil {
yield(-1, err)
return
}
offset += n
if offset > len(buf) {
yield(-1, fmt.Errorf("auxiliary type data: %w", io.ErrUnexpectedEOF))
return
}
if !yield(start, nil) {
return
}
}
}
}
// Copy performs a deep copy of a decoder and its base.
func (d *decoder) Copy() *decoder {
if d == nil {
return nil
}
return d.copy(nil)
}
func (d *decoder) copy(copiedTypes map[Type]Type) *decoder {
if d == nil {
return nil
}
d.mu.Lock()
defer d.mu.Unlock()
if copiedTypes == nil {
copiedTypes = make(map[Type]Type, len(d.types))
}
types := make(map[TypeID]Type, len(d.types))
typeIDs := make(map[Type]TypeID, len(d.typeIDs))
for id, typ := range d.types {
types[id] = copyType(typ, d.typeIDs, copiedTypes, typeIDs)
}
return &decoder{
d.base.copy(copiedTypes),
d.byteOrder,
d.sharedBuf,
d.strings,
d.firstTypeID,
d.offsets,
d.declTags,
d.namedTypes,
sync.Mutex{},
types,
typeIDs,
maps.Clone(d.legacyBitfields),
}
}
// TypeID returns the ID for a Type previously obtained via [TypeByID].
func (d *decoder) TypeID(typ Type) (TypeID, error) {
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
}
d.mu.Lock()
defer d.mu.Unlock()
id, ok := d.typeIDs[typ]
if !ok {
return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound)
}
return id, nil
}
// TypesByName returns all types which have the given essential name.
//
// Returns ErrNotFound if no matching Type exists.
func (d *decoder) TypesByName(name essentialName) ([]Type, error) {
var types []Type
for id := range d.namedTypes.Find(string(name)) {
typ, err := d.TypeByID(id)
if err != nil {
return nil, err
}
if newEssentialName(typ.TypeName()) == name {
// Deal with hash collisions by checking against the name.
types = append(types, typ)
}
}
if len(types) == 0 {
// Return an unwrapped error because this is on the hot path
// for CO-RE.
return nil, ErrNotFound
}
return types, nil
}
// TypeByID decodes a type and any of its descendants.
func (d *decoder) TypeByID(id TypeID) (Type, error) {
d.mu.Lock()
defer d.mu.Unlock()
return d.inflateType(id)
}
func (d *decoder) inflateType(id TypeID) (typ Type, err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
// err is the return value of the enclosing function, even if an explicit
// return is used.
// See https://go.dev/ref/spec#Defer_statements
if err != nil {
// Remove partially inflated type so that d.types only contains
// fully inflated ones.
delete(d.types, id)
} else {
// Populate reverse index.
d.typeIDs[typ] = id
}
}()
if id < d.firstTypeID {
return d.base.inflateType(id)
}
if id == 0 {
// Void is defined to always be type ID 0, and is thus omitted from BTF.
// Fast-path because it is looked up frequently.
return (*Void)(nil), nil
}
if typ, ok := d.types[id]; ok {
return typ, nil
}
fixup := func(id TypeID, typ *Type) {
fixup, err := d.inflateType(id)
if err != nil {
panic(err)
}
*typ = fixup
}
convertMembers := func(header *btfType, buf []byte) ([]Member, error) {
var bm btfMember
members := make([]Member, 0, header.Vlen())
for i := range header.Vlen() {
n, err := unmarshalBtfMember(&bm, buf, d.byteOrder)
if err != nil {
return nil, fmt.Errorf("unmarshal member: %w", err)
}
buf = buf[n:]
name, err := d.strings.Lookup(bm.NameOff)
if err != nil {
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
}
members = append(members, Member{
Name: name,
Offset: Bits(bm.Offset),
})
m := &members[i]
fixup(bm.Type, &m.Type)
if header.Bitfield() {
m.BitfieldSize = Bits(bm.Offset >> 24)
m.Offset &= 0xffffff
// We ignore legacy bitfield definitions if the current composite
// is a new-style bitfield. This is kind of safe since offset and
// size on the type of the member must be zero if kindFlat is set
// according to spec.
continue
}
// This may be a legacy bitfield, try to fix it up.
data, ok := d.legacyBitfields[bm.Type]
if ok {
// Bingo!
m.Offset += data[0]
m.BitfieldSize = data[1]
continue
}
}
return members, nil
}
idx := int(id - d.firstTypeID)
if idx >= len(d.offsets) {
return nil, fmt.Errorf("type id %v: %w", id, ErrNotFound)
}
offset := d.offsets[idx]
if offset >= len(d.raw) {
return nil, fmt.Errorf("offset out of bounds")
}
var (
header btfType
bInt btfInt
bArr btfArray
bVariable btfVariable
bDeclTag btfDeclTag
pos = d.raw[offset:]
)
{
if n, err := unmarshalBtfType(&header, pos, d.byteOrder); err != nil {
return nil, fmt.Errorf("can't unmarshal type info for id %v: %v", id, err)
} else {
pos = pos[n:]
}
name, err := d.strings.Lookup(header.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for type id %d: %w", id, err)
}
switch header.Kind() {
case kindInt:
size := header.Size()
if _, err := unmarshalBtfInt(&bInt, pos, d.byteOrder); err != nil {
return nil, fmt.Errorf("can't unmarshal btfInt, id: %d: %w", id, err)
}
if bInt.Offset() > 0 || bInt.Bits().Bytes() != size {
d.legacyBitfields[id] = [2]Bits{bInt.Offset(), bInt.Bits()}
}
typ = &Int{name, header.Size(), bInt.Encoding()}
d.types[id] = typ
case kindPointer:
ptr := &Pointer{nil}
d.types[id] = ptr
fixup(header.Type(), &ptr.Target)
typ = ptr
case kindArray:
if _, err := unmarshalBtfArray(&bArr, pos, d.byteOrder); err != nil {
return nil, fmt.Errorf("can't unmarshal btfArray, id: %d: %w", id, err)
}
arr := &Array{nil, nil, bArr.Nelems}
d.types[id] = arr
fixup(bArr.IndexType, &arr.Index)
fixup(bArr.Type, &arr.Type)
typ = arr
case kindStruct:
str := &Struct{name, header.Size(), nil, nil}
d.types[id] = str
typ = str
str.Members, err = convertMembers(&header, pos)
if err != nil {
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
}
case kindUnion:
uni := &Union{name, header.Size(), nil, nil}
d.types[id] = uni
typ = uni
uni.Members, err = convertMembers(&header, pos)
if err != nil {
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
}
case kindEnum:
enum := &Enum{name, header.Size(), header.Signed(), nil}
d.types[id] = enum
typ = enum
var be btfEnum
enum.Values = make([]EnumValue, 0, header.Vlen())
for i := range header.Vlen() {
n, err := unmarshalBtfEnum(&be, pos, d.byteOrder)
if err != nil {
return nil, fmt.Errorf("unmarshal btfEnum %d, id: %d: %w", i, id, err)
}
pos = pos[n:]
name, err := d.strings.Lookup(be.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for enum value %d: %s", i, err)
}
value := uint64(be.Val)
if enum.Signed {
// Sign extend values to 64 bit.
value = uint64(int32(be.Val))
}
enum.Values = append(enum.Values, EnumValue{name, value})
}
case kindForward:
typ = &Fwd{name, header.FwdKind()}
d.types[id] = typ
case kindTypedef:
typedef := &Typedef{name, nil, nil}
d.types[id] = typedef
fixup(header.Type(), &typedef.Type)
typ = typedef
case kindVolatile:
volatile := &Volatile{nil}
d.types[id] = volatile
fixup(header.Type(), &volatile.Type)
typ = volatile
case kindConst:
cnst := &Const{nil}
d.types[id] = cnst
fixup(header.Type(), &cnst.Type)
typ = cnst
case kindRestrict:
restrict := &Restrict{nil}
d.types[id] = restrict
fixup(header.Type(), &restrict.Type)
typ = restrict
case kindFunc:
fn := &Func{name, nil, header.Linkage(), nil, nil}
d.types[id] = fn
fixup(header.Type(), &fn.Type)
typ = fn
case kindFuncProto:
fp := &FuncProto{}
d.types[id] = fp
params := make([]FuncParam, 0, header.Vlen())
var bParam btfParam
for i := range header.Vlen() {
n, err := unmarshalBtfParam(&bParam, pos, d.byteOrder)
if err != nil {
return nil, fmt.Errorf("can't unmarshal btfParam %d, id: %d: %w", i, id, err)
}
pos = pos[n:]
name, err := d.strings.Lookup(bParam.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err)
}
param := FuncParam{Name: name}
fixup(bParam.Type, &param.Type)
params = append(params, param)
}
fixup(header.Type(), &fp.Return)
fp.Params = params
typ = fp
case kindVar:
if _, err := unmarshalBtfVariable(&bVariable, pos, d.byteOrder); err != nil {
return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err)
}
v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil}
d.types[id] = v
fixup(header.Type(), &v.Type)
typ = v
case kindDatasec:
ds := &Datasec{name, header.Size(), nil}
d.types[id] = ds
vlen := header.Vlen()
vars := make([]VarSecinfo, 0, vlen)
var bSecInfo btfVarSecinfo
for i := range vlen {
n, err := unmarshalBtfVarSecInfo(&bSecInfo, pos, d.byteOrder)
if err != nil {
return nil, fmt.Errorf("can't unmarshal btfVarSecinfo %d, id: %d: %w", i, id, err)
}
pos = pos[n:]
vs := VarSecinfo{
Offset: bSecInfo.Offset,
Size: bSecInfo.Size,
}
fixup(bSecInfo.Type, &vs.Type)
vars = append(vars, vs)
}
ds.Vars = vars
typ = ds
case kindFloat:
typ = &Float{name, header.Size()}
d.types[id] = typ
case kindDeclTag:
if _, err := unmarshalBtfDeclTag(&bDeclTag, pos, d.byteOrder); err != nil {
return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err)
}
btfIndex := bDeclTag.ComponentIdx
if uint64(btfIndex) > math.MaxInt {
return nil, fmt.Errorf("type id %d: index exceeds int", id)
}
dt := &declTag{nil, name, int(int32(btfIndex))}
d.types[id] = dt
fixup(header.Type(), &dt.Type)
typ = dt
case kindTypeTag:
tt := &TypeTag{nil, name}
d.types[id] = tt
fixup(header.Type(), &tt.Type)
typ = tt
case kindEnum64:
enum := &Enum{name, header.Size(), header.Signed(), nil}
d.types[id] = enum
typ = enum
enum.Values = make([]EnumValue, 0, header.Vlen())
var bEnum64 btfEnum64
for i := range header.Vlen() {
n, err := unmarshalBtfEnum64(&bEnum64, pos, d.byteOrder)
if err != nil {
return nil, fmt.Errorf("can't unmarshal btfEnum64 %d, id: %d: %w", i, id, err)
}
pos = pos[n:]
name, err := d.strings.Lookup(bEnum64.NameOff)
if err != nil {
return nil, fmt.Errorf("get name for enum64 value %d: %s", i, err)
}
value := (uint64(bEnum64.ValHi32) << 32) | uint64(bEnum64.ValLo32)
enum.Values = append(enum.Values, EnumValue{name, value})
}
default:
return nil, fmt.Errorf("type id %d: unknown kind: %v", id, header.Kind())
}
}
for _, tagID := range d.declTags[id] {
dtType, err := d.inflateType(tagID)
if err != nil {
return nil, err
}
dt, ok := dtType.(*declTag)
if !ok {
return nil, fmt.Errorf("type id %v: not a declTag", tagID)
}
switch t := typ.(type) {
case *Var:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case *Typedef:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case composite:
if dt.Index >= 0 {
members := t.members()
if dt.Index >= len(members) {
return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t)
}
members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value)
} else if dt.Index == -1 {
switch t2 := t.(type) {
case *Struct:
t2.Tags = append(t2.Tags, dt.Value)
case *Union:
t2.Tags = append(t2.Tags, dt.Value)
}
} else {
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
}
case *Func:
fp, ok := t.Type.(*FuncProto)
if !ok {
return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type)
}
// Ensure the number of argument tag lists equals the number of arguments
if len(t.ParamTags) == 0 {
t.ParamTags = make([][]string, len(fp.Params))
}
if dt.Index >= 0 {
if dt.Index >= len(fp.Params) {
return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t)
}
t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value)
} else if dt.Index == -1 {
t.Tags = append(t.Tags, dt.Value)
} else {
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
}
default:
return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t)
}
}
return typ, nil
}
// An index from string to TypeID.
//
// Fuzzy because it may return false positive matches.
type fuzzyStringIndex struct {
seed maphash.Seed
entries []fuzzyStringIndexEntry
}
func newFuzzyStringIndex(capacity int) *fuzzyStringIndex {
return &fuzzyStringIndex{
maphash.MakeSeed(),
make([]fuzzyStringIndexEntry, 0, capacity),
}
}
// Add a string to the index.
//
// Calling the method with identical arguments will create duplicate entries.
func (idx *fuzzyStringIndex) Add(name []byte, id TypeID) {
hash := uint32(maphash.Bytes(idx.seed, name))
idx.entries = append(idx.entries, newFuzzyStringIndexEntry(hash, id))
}
// Build the index.
//
// Must be called after [Add] and before [Match].
func (idx *fuzzyStringIndex) Build() {
slices.Sort(idx.entries)
}
// Find TypeIDs which may match the name.
//
// May return false positives, but is guaranteed to not have false negatives.
//
// You must call [Build] at least once before calling this method.
func (idx *fuzzyStringIndex) Find(name string) iter.Seq[TypeID] {
return func(yield func(TypeID) bool) {
hash := uint32(maphash.String(idx.seed, name))
// We match only on the first 32 bits here, so ignore found.
i, _ := slices.BinarySearch(idx.entries, fuzzyStringIndexEntry(hash)<<32)
for i := i; i < len(idx.entries); i++ {
if idx.entries[i].hash() != hash {
break
}
if !yield(idx.entries[i].id()) {
return
}
}
}
}
// Tuple mapping the hash of an essential name to a type.
//
// Encoded in an uint64 so that it implements cmp.Ordered.
type fuzzyStringIndexEntry uint64
func newFuzzyStringIndexEntry(hash uint32, id TypeID) fuzzyStringIndexEntry {
return fuzzyStringIndexEntry(hash)<<32 | fuzzyStringIndexEntry(id)
}
func (e fuzzyStringIndexEntry) hash() uint32 {
return uint32(e >> 32)
}
func (e fuzzyStringIndexEntry) id() TypeID {
return TypeID(e)
}
+185 -161
View File
@@ -4,15 +4,18 @@ import (
"encoding/binary"
"errors"
"fmt"
"path/filepath"
"reflect"
"runtime"
"slices"
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/kconfig"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/platform"
"github.com/cilium/ebpf/internal/sys"
)
@@ -33,6 +36,12 @@ type CollectionOptions struct {
// The given Maps are Clone()d before being used in the Collection, so the
// caller can Close() them freely when they are no longer needed.
MapReplacements map[string]*Map
// Cache amortises the cost of decoding kernel BTF across multiple
// Collection loads. Callers loading more than one Collection should
// allocate a Cache via [btf.NewCache] and share it across loads.
// If nil, a fresh cache is allocated per load and discarded.
Cache *btf.Cache
}
// CollectionSpec describes a collection.
@@ -61,117 +70,34 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
}
cpy := CollectionSpec{
Maps: make(map[string]*MapSpec, len(cs.Maps)),
Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
Maps: copyMapOfSpecs(cs.Maps),
Programs: copyMapOfSpecs(cs.Programs),
Variables: make(map[string]*VariableSpec, len(cs.Variables)),
ByteOrder: cs.ByteOrder,
Types: cs.Types.Copy(),
}
for name, spec := range cs.Maps {
cpy.Maps[name] = spec.Copy()
}
for name, spec := range cs.Programs {
cpy.Programs[name] = spec.Copy()
}
for name, spec := range cs.Variables {
cpy.Variables[name] = spec.copy(&cpy)
cpy.Variables[name] = spec.Copy()
}
if cs.Variables == nil {
cpy.Variables = nil
}
return &cpy
}
// RewriteMaps replaces all references to specific maps.
//
// Use this function to use pre-existing maps instead of creating new ones
// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
//
// Returns an error if a named map isn't used in at least one program.
//
// Deprecated: Pass CollectionOptions.MapReplacements when loading the Collection
// instead.
func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
for symbol, m := range maps {
// have we seen a program that uses this symbol / map
seen := false
for progName, progSpec := range cs.Programs {
err := progSpec.Instructions.AssociateMap(symbol, m)
switch {
case err == nil:
seen = true
case errors.Is(err, asm.ErrUnreferencedSymbol):
// Not all programs need to use the map
default:
return fmt.Errorf("program %s: %w", progName, err)
}
}
if !seen {
return fmt.Errorf("map %s not referenced by any programs", symbol)
}
// Prevent NewCollection from creating rewritten maps
delete(cs.Maps, symbol)
func copyMapOfSpecs[T interface{ Copy() T }](m map[string]T) map[string]T {
if m == nil {
return nil
}
return nil
}
// MissingConstantsError is returned by [CollectionSpec.RewriteConstants].
type MissingConstantsError struct {
// The constants missing from .rodata.
Constants []string
}
func (m *MissingConstantsError) Error() string {
return fmt.Sprintf("some constants are missing from .rodata: %s", strings.Join(m.Constants, ", "))
}
// RewriteConstants replaces the value of multiple constants.
//
// The constant must be defined like so in the C program:
//
// volatile const type foobar;
// volatile const type foobar = default;
//
// Replacement values must be of the same length as the C sizeof(type).
// If necessary, they are marshalled according to the same rules as
// map values.
//
// From Linux 5.5 the verifier will use constants to eliminate dead code.
//
// Returns an error wrapping [MissingConstantsError] if a constant doesn't exist.
//
// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead.
// RewriteConstants is now a wrapper around the VariableSpec API.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
var missing []string
for n, c := range consts {
v, ok := cs.Variables[n]
if !ok {
missing = append(missing, n)
continue
}
if !v.Constant() {
return fmt.Errorf("variable %s is not a constant", n)
}
if err := v.Set(c); err != nil {
return fmt.Errorf("rewriting constant %s: %w", n, err)
}
cpy := make(map[string]T, len(m))
for k, v := range m {
cpy[k] = v.Copy()
}
if len(missing) != 0 {
return fmt.Errorf("rewrite constants: %w", &MissingConstantsError{Constants: missing})
}
return nil
return cpy
}
// Assign the contents of a CollectionSpec to a struct.
@@ -195,22 +121,22 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
//
// Returns an error if any of the eBPF objects can't be found, or
// if the same Spec is assigned multiple times.
func (cs *CollectionSpec) Assign(to interface{}) error {
getValue := func(typ reflect.Type, name string) (interface{}, error) {
func (cs *CollectionSpec) Assign(to any) error {
getValue := func(typ reflect.Type, name string) (any, error) {
switch typ {
case reflect.TypeOf((*ProgramSpec)(nil)):
case reflect.TypeFor[*ProgramSpec]():
if p := cs.Programs[name]; p != nil {
return p, nil
}
return nil, fmt.Errorf("missing program %q", name)
case reflect.TypeOf((*MapSpec)(nil)):
case reflect.TypeFor[*MapSpec]():
if m := cs.Maps[name]; m != nil {
return m, nil
}
return nil, fmt.Errorf("missing map %q", name)
case reflect.TypeOf((*VariableSpec)(nil)):
case reflect.TypeFor[*VariableSpec]():
if v := cs.Variables[name]; v != nil {
return v, nil
}
@@ -251,7 +177,7 @@ func (cs *CollectionSpec) Assign(to interface{}) error {
//
// Returns an error if any of the fields can't be found, or
// if the same Map or Program is assigned multiple times.
func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
func (cs *CollectionSpec) LoadAndAssign(to any, opts *CollectionOptions) error {
loader, err := newCollectionLoader(cs, opts)
if err != nil {
return err
@@ -263,18 +189,18 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
assignedProgs := make(map[string]bool)
assignedVars := make(map[string]bool)
getValue := func(typ reflect.Type, name string) (interface{}, error) {
getValue := func(typ reflect.Type, name string) (any, error) {
switch typ {
case reflect.TypeOf((*Program)(nil)):
case reflect.TypeFor[*Program]():
assignedProgs[name] = true
return loader.loadProgram(name)
case reflect.TypeOf((*Map)(nil)):
case reflect.TypeFor[*Map]():
assignedMaps[name] = true
return loader.loadMap(name)
case reflect.TypeOf((*Variable)(nil)):
case reflect.TypeFor[*Variable]():
assignedVars[name] = true
return loader.loadVariable(name)
@@ -285,18 +211,17 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
// Load the Maps and Programs requested by the annotated struct.
if err := assignValues(to, getValue); err != nil {
return err
return fmt.Errorf("assign values: %w", err)
}
// Populate the requested maps. Has a chance of lazy-loading other dependent maps.
if err := loader.populateDeferredMaps(); err != nil {
return err
return fmt.Errorf("populate deferred maps: %w", err)
}
// Evaluate the loader's objects after all (lazy)loading has taken place.
for n, m := range loader.maps {
switch m.typ {
case ProgramArray:
if m.typ.canStoreProgram() {
// Require all lazy-loaded ProgramArrays to be assigned to the given object.
// The kernel empties a ProgramArray once the last user space reference
// to it closes, which leads to failed tail calls. Combined with the library
@@ -403,6 +328,7 @@ type collectionLoader struct {
maps map[string]*Map
programs map[string]*Program
vars map[string]*Variable
types *btf.Cache
}
func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) {
@@ -421,12 +347,18 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
return nil, fmt.Errorf("populating kallsyms caches: %w", err)
}
cache := opts.Cache
if cache == nil {
cache = btf.NewCache()
}
return &collectionLoader{
coll,
opts,
make(map[string]*Map),
make(map[string]*Program),
make(map[string]*Variable),
cache,
}, nil
}
@@ -434,20 +366,6 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
// during individual program loading. Since we have less context available
// at those stages, we batch the lookups here instead to avoid redundant work.
func populateKallsyms(progs map[string]*ProgramSpec) error {
// Look up associated kernel modules for all symbols referenced by
// ProgramSpec.AttachTo for program types that support attaching to kmods.
mods := make(map[string]string)
for _, p := range progs {
if p.AttachTo != "" && p.targetsKernelModule() {
mods[p.AttachTo] = ""
}
}
if len(mods) != 0 {
if err := kallsyms.AssignModules(mods); err != nil {
return fmt.Errorf("getting modules from kallsyms: %w", err)
}
}
// Look up addresses of all kernel symbols referenced by all programs.
addrs := make(map[string]uint64)
for _, p := range progs {
@@ -515,7 +433,11 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
return m, nil
}
m, err := newMapWithOptions(mapSpec, cl.opts.Maps)
if err := mapSpec.updateDataSection(cl.coll.Variables, mapName); err != nil {
return nil, fmt.Errorf("assembling contents of map %s: %w", mapName, err)
}
m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.types)
if err != nil {
return nil, fmt.Errorf("map %s: %w", mapName, err)
}
@@ -525,6 +447,7 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
// that need to be finalized before invoking the verifier.
if !mapSpec.Type.canStoreMapOrProgram() {
if err := m.finalize(mapSpec); err != nil {
_ = m.Close()
return nil, fmt.Errorf("finalizing map %s: %w", mapName, err)
}
}
@@ -577,12 +500,13 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
}
}
prog, err := newProgramWithOptions(progSpec, cl.opts.Programs)
prog, err := newProgramWithOptions(progSpec, cl.opts.Programs, cl.types)
if err != nil {
return nil, fmt.Errorf("program %s: %w", progName, err)
}
cl.programs[progName] = prog
return prog, nil
}
@@ -596,19 +520,7 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
return nil, fmt.Errorf("unknown variable %s", varName)
}
// Get the key of the VariableSpec's MapSpec in the CollectionSpec.
var mapName string
for n, ms := range cl.coll.Maps {
if ms == varSpec.m {
mapName = n
break
}
}
if mapName == "" {
return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name)
}
m, err := cl.loadMap(mapName)
m, err := cl.loadMap(varSpec.SectionName)
if err != nil {
return nil, fmt.Errorf("variable %s: %w", varName, err)
}
@@ -618,16 +530,21 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
// emit a Variable with a nil Memory. This keeps Collection{Spec}.Variables
// consistent across systems with different feature sets without breaking
// LoadAndAssign.
mm, err := m.Memory()
var mm *Memory
if unsafeMemory {
mm, err = m.unsafeMemory()
} else {
mm, err = m.Memory()
}
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err)
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, varSpec.SectionName, err)
}
v, err := newVariable(
varSpec.name,
varSpec.offset,
varSpec.size,
varSpec.t,
varSpec.Name,
varSpec.Offset,
varSpec.Size(),
varSpec.Type,
mm,
)
if err != nil {
@@ -685,6 +602,13 @@ func (cl *collectionLoader) populateDeferredMaps() error {
}
}
if mapSpec.Type == StructOpsMap {
// populate StructOps data into `kernVData`
if err := cl.populateStructOps(m, mapSpec); err != nil {
return err
}
}
// Populate and freeze the map if specified.
if err := m.finalize(mapSpec); err != nil {
return fmt.Errorf("populating map %s: %w", mapName, err)
@@ -694,6 +618,97 @@ func (cl *collectionLoader) populateDeferredMaps() error {
return nil
}
// populateStructOps translates the user struct bytes into the kernel value struct
// layout for a struct_ops map and writes the result back to mapSpec.Contents[0].
func (cl *collectionLoader) populateStructOps(m *Map, mapSpec *MapSpec) error {
userType, ok := btf.As[*btf.Struct](mapSpec.Value)
if !ok {
return fmt.Errorf("value should be a *Struct")
}
userData, err := mapSpec.dataSection()
if err != nil {
return fmt.Errorf("getting data section: %w", err)
}
if len(userData) < int(userType.Size) {
return fmt.Errorf("user data too short: have %d, need at least %d", len(userData), userType.Size)
}
vType, _, module, err := structOpsFindTarget(userType, cl.types)
if err != nil {
return fmt.Errorf("struct_ops value type %q: %w", userType.Name, err)
}
defer module.Close()
// Find the inner ops struct embedded in the value struct.
kType, kTypeOff, err := structOpsFindInnerType(vType)
if err != nil {
return err
}
kernVData := make([]byte, int(vType.Size))
for _, m := range userType.Members {
i := slices.IndexFunc(kType.Members, func(km btf.Member) bool {
return km.Name == m.Name
})
// Allow field to not exist in target as long as the source is zero.
if i == -1 {
mSize, err := btf.Sizeof(m.Type)
if err != nil {
return fmt.Errorf("sizeof(user.%s): %w", m.Name, err)
}
srcOff := int(m.Offset.Bytes())
if srcOff < 0 || srcOff+mSize > len(userData) {
return fmt.Errorf("member %q: userdata is too small", m.Name)
}
// let fail if the field in type user type is missing in type kern type
if !structOpsIsMemZeroed(userData[srcOff : srcOff+mSize]) {
return fmt.Errorf("%s doesn't exist in %s, but it has non-zero value", m.Name, kType.Name)
}
continue
}
km := kType.Members[i]
switch btf.UnderlyingType(m.Type).(type) {
case *btf.Pointer:
// If this is a pointer → resolve struct_ops program.
psKey := kType.Name + ":" + m.Name
for k, ps := range cl.coll.Programs {
if ps.AttachTo == psKey {
p, ok := cl.programs[k]
if !ok || p == nil {
return nil
}
if err := structOpsPopulateValue(km, kernVData[kTypeOff:], p); err != nil {
return err
}
}
}
default:
// Otherwise → memcpy the field contents.
if err := structOpsCopyMember(m, km, userData, kernVData[kTypeOff:]); err != nil {
return fmt.Errorf("field %s: %w", kType.Name, err)
}
}
}
// Populate the map explicitly and keep a reference on cl.programs.
// This is necessary because we may inline fds into kernVData which
// may become invalid if the GC frees them.
if err := m.Put(uint32(0), kernVData); err != nil {
return err
}
mapSpec.Contents = nil
runtime.KeepAlive(cl.programs)
return nil
}
// resolveKconfig resolves all variables declared in .kconfig and populates
// m.Contents. Does nothing if the given m.Contents is non-empty.
func resolveKconfig(m *MapSpec) error {
@@ -702,6 +717,10 @@ func resolveKconfig(m *MapSpec) error {
return errors.New("map value is not a Datasec")
}
if platform.IsWindows {
return fmt.Errorf(".kconfig: %w", internal.ErrNotSupportedOnOS)
}
type configInfo struct {
offset uint32
size uint32
@@ -794,6 +813,13 @@ func resolveKconfig(m *MapSpec) error {
// Omitting Collection.Close() during application shutdown is an error.
// See the package documentation for details around Map and Program lifecycle.
func LoadCollection(file string) (*Collection, error) {
if platform.IsWindows {
// This mirrors a check in efW.
if ext := filepath.Ext(file); ext == ".sys" {
return loadCollectionFromNativeImage(file)
}
}
spec, err := LoadCollectionSpec(file)
if err != nil {
return nil, err
@@ -823,31 +849,31 @@ func LoadCollection(file string) (*Collection, error) {
//
// Ownership and Close()ing responsibility is transferred to `to`
// for any successful assigns. On error `to` is left in an undefined state.
func (coll *Collection) Assign(to interface{}) error {
func (coll *Collection) Assign(to any) error {
assignedMaps := make(map[string]bool)
assignedProgs := make(map[string]bool)
assignedVars := make(map[string]bool)
// Assign() only transfers already-loaded Maps and Programs. No extra
// loading is done.
getValue := func(typ reflect.Type, name string) (interface{}, error) {
getValue := func(typ reflect.Type, name string) (any, error) {
switch typ {
case reflect.TypeOf((*Program)(nil)):
case reflect.TypeFor[*Program]():
if p := coll.Programs[name]; p != nil {
assignedProgs[name] = true
return p, nil
}
return nil, fmt.Errorf("missing program %q", name)
case reflect.TypeOf((*Map)(nil)):
case reflect.TypeFor[*Map]():
if m := coll.Maps[name]; m != nil {
assignedMaps[name] = true
return m, nil
}
return nil, fmt.Errorf("missing map %q", name)
case reflect.TypeOf((*Variable)(nil)):
case reflect.TypeFor[*Variable]():
if v := coll.Variables[name]; v != nil {
assignedVars[name] = true
return v, nil
@@ -860,7 +886,7 @@ func (coll *Collection) Assign(to interface{}) error {
}
if err := assignValues(to, getValue); err != nil {
return err
return fmt.Errorf("assign values: %w", err)
}
// Finalize ownership transfer
@@ -948,7 +974,7 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc
// to a struct, attempt to gather its fields as well.
var v reflect.Value
switch field.Type.Kind() {
case reflect.Ptr:
case reflect.Pointer:
if field.Type.Elem().Kind() != reflect.Struct {
continue
}
@@ -983,16 +1009,14 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc
//
// getValue is called for every tagged field of 'to' and must return the value
// to be assigned to the field with the given typ and name.
func assignValues(to interface{},
getValue func(typ reflect.Type, name string) (interface{}, error)) error {
toValue := reflect.ValueOf(to)
if toValue.Type().Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer to struct", to)
func assignValues(to any, getValue func(typ reflect.Type, name string) (any, error)) error {
if err := internal.IsNilPointer(to); err != nil {
return err
}
if toValue.IsNil() {
return fmt.Errorf("nil pointer to %T", to)
toValue := reflect.ValueOf(to)
if toValue.Type().Kind() != reflect.Pointer {
return fmt.Errorf("%T is not a pointer to struct", to)
}
fields, err := ebpfFields(toValue.Elem(), nil)
+9
View File
@@ -0,0 +1,9 @@
//go:build !windows
package ebpf
import "github.com/cilium/ebpf/internal"
func loadCollectionFromNativeImage(_ string) (*Collection, error) {
return nil, internal.ErrNotSupportedOnOS
}
+136
View File
@@ -0,0 +1,136 @@
package ebpf
import (
"errors"
"fmt"
"unsafe"
"github.com/cilium/ebpf/internal/efw"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
func loadCollectionFromNativeImage(file string) (_ *Collection, err error) {
mapFds := make([]efw.FD, 32)
programFds := make([]efw.FD, 32)
var maps map[string]*Map
var programs map[string]*Program
defer func() {
if err == nil {
return
}
for _, fd := range append(mapFds, programFds...) {
// efW never uses fd 0.
if fd != 0 {
_ = efw.EbpfCloseFd(int(fd))
}
}
for _, m := range maps {
_ = m.Close()
}
for _, p := range programs {
_ = p.Close()
}
}()
nMaps, nPrograms, err := efw.EbpfObjectLoadNativeFds(file, mapFds, programFds)
if errors.Is(err, efw.EBPF_NO_MEMORY) && (nMaps > len(mapFds) || nPrograms > len(programFds)) {
mapFds = make([]efw.FD, nMaps)
programFds = make([]efw.FD, nPrograms)
nMaps, nPrograms, err = efw.EbpfObjectLoadNativeFds(file, mapFds, programFds)
}
if err != nil {
return nil, err
}
mapFds = mapFds[:nMaps]
programFds = programFds[:nPrograms]
// The maximum length of a name is only 16 bytes on Linux, longer names
// are truncated. This is not a problem when loading from an ELF, since
// we get the full object name from the symbol table.
// When loading a native image we do not have this luxury. Use an efW native
// API to retrieve up to 64 bytes of the object name.
maps = make(map[string]*Map, len(mapFds))
for _, raw := range mapFds {
fd, err := sys.NewFD(int(raw))
if err != nil {
return nil, err
}
m, mapErr := newMapFromFD(fd)
if mapErr != nil {
_ = fd.Close()
return nil, mapErr
}
var efwMapInfo efw.BpfMapInfo
size := uint32(unsafe.Sizeof(efwMapInfo))
_, err = efw.EbpfObjectGetInfoByFd(m.FD(), unsafe.Pointer(&efwMapInfo), &size)
if err != nil {
_ = m.Close()
return nil, err
}
if size >= uint32(unsafe.Offsetof(efwMapInfo.Name)+unsafe.Sizeof(efwMapInfo.Name)) {
m.name = unix.ByteSliceToString(efwMapInfo.Name[:])
}
if m.name == "" {
_ = m.Close()
return nil, fmt.Errorf("unnamed map")
}
if _, ok := maps[m.name]; ok {
return nil, fmt.Errorf("duplicate map with the same name: %s", m.name)
}
maps[m.name] = m
}
programs = make(map[string]*Program, len(programFds))
for _, raw := range programFds {
fd, err := sys.NewFD(int(raw))
if err != nil {
return nil, err
}
program, err := newProgramFromFD(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
var efwProgInfo efw.BpfProgInfo
size := uint32(unsafe.Sizeof(efwProgInfo))
_, err = efw.EbpfObjectGetInfoByFd(program.FD(), unsafe.Pointer(&efwProgInfo), &size)
if err != nil {
_ = program.Close()
return nil, err
}
if size >= uint32(unsafe.Offsetof(efwProgInfo.Name)+unsafe.Sizeof(efwProgInfo.Name)) {
program.name = unix.ByteSliceToString(efwProgInfo.Name[:])
}
if program.name == "" {
_ = program.Close()
return nil, fmt.Errorf("unnamed program")
}
if _, ok := programs[program.name]; ok {
_ = program.Close()
return nil, fmt.Errorf("duplicate program with the same name: %s", program.name)
}
programs[program.name] = program
}
return &Collection{programs, maps, nil}, nil
}
-49
View File
@@ -1,16 +1,5 @@
package ebpf
import (
"fmt"
"os"
"strings"
"sync"
)
var possibleCPU = sync.OnceValues(func() (int, error) {
return parseCPUsFromFile("/sys/devices/system/cpu/possible")
})
// PossibleCPU returns the max number of CPUs a system may possibly have
// Logical CPU numbers must be of the form 0-n
func PossibleCPU() (int, error) {
@@ -26,41 +15,3 @@ func MustPossibleCPU() int {
}
return cpus
}
func parseCPUsFromFile(path string) (int, error) {
spec, err := os.ReadFile(path)
if err != nil {
return 0, err
}
n, err := parseCPUs(string(spec))
if err != nil {
return 0, fmt.Errorf("can't parse %s: %v", path, err)
}
return n, nil
}
// parseCPUs parses the number of cpus from a string produced
// by bitmap_list_string() in the Linux kernel.
// Multiple ranges are rejected, since they can't be unified
// into a single number.
// This is the format of /sys/devices/system/cpu/possible, it
// is not suitable for /sys/devices/system/cpu/online, etc.
func parseCPUs(spec string) (int, error) {
if strings.Trim(spec, "\n") == "0" {
return 1, nil
}
var low, high int
n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
if n != 2 || err != nil {
return 0, fmt.Errorf("invalid format: %s", spec)
}
if low != 0 {
return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
}
// cpus is 0 indexed
return high + 1, nil
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !windows
package ebpf
import (
"sync"
"github.com/cilium/ebpf/internal/linux"
)
var possibleCPU = sync.OnceValues(func() (int, error) {
return linux.ParseCPUsFromFile("/sys/devices/system/cpu/possible")
})
+11
View File
@@ -0,0 +1,11 @@
package ebpf
import (
"sync"
"golang.org/x/sys/windows"
)
var possibleCPU = sync.OnceValues(func() (int, error) {
return int(windows.GetMaximumProcessorCount(windows.ALL_PROCESSOR_GROUPS)), nil
})
+460 -206
View File
@@ -8,13 +8,17 @@ import (
"errors"
"fmt"
"io"
"iter"
"maps"
"math"
"os"
"slices"
"strings"
"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"
)
@@ -79,8 +83,8 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
// Checks if the ELF file is for BPF data.
// Old LLVM versions set e_machine to EM_NONE.
if f.File.Machine != elf.EM_NONE && f.File.Machine != elf.EM_BPF {
return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.File.Machine)
if f.Machine != elf.EM_NONE && f.Machine != elf.EM_BPF {
return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.Machine)
}
var (
@@ -114,8 +118,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
case sec.Type == elf.SHT_REL:
// Store relocations under the section index of the target
relSections[elf.SectionIndex(sec.Info)] = sec
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
sections[idx] = newElfSection(sec, programSection)
case sec.Type == elf.SHT_PROGBITS && sec.Size > 0:
if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 {
sections[idx] = newElfSection(sec, programSection)
} else if sec.Name == structOpsLinkSec {
// classification based on sec names so that struct_ops-specific
// sections (.struct_ops.link) is correctly recognized
// as non-executable PROGBITS, allowing value placement and link metadata to be loaded.
sections[idx] = newElfSection(sec, structOpsSection)
} else if sec.Name == structOpsSec {
return nil, fmt.Errorf("section %q: got '.struct_ops' section: %w", sec.Name, ErrNotSupported)
}
}
}
@@ -184,7 +197,18 @@ 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
// assiociate members in structs with ProgramSpecs using relo
if err := ec.associateStructOpsRelocs(progs); err != nil {
return nil, fmt.Errorf("load struct_ops: %w", err)
}
return &CollectionSpec{
ec.maps,
progs,
ec.vars,
btfSpec,
ec.ByteOrder,
}, nil
}
func loadLicense(sec *elf.Section) (string, error) {
@@ -231,6 +255,7 @@ const (
btfMapSection
programSection
dataSection
structOpsSection
)
type elfSection struct {
@@ -255,6 +280,17 @@ func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection {
}
}
// symbolsSorted returns the section's symbols sorted by offset.
func (es *elfSection) symbolsSorted() iter.Seq2[uint64, elf.Symbol] {
return func(yield func(uint64, elf.Symbol) bool) {
for _, off := range slices.Sorted(maps.Keys(es.symbols)) {
if !yield(off, es.symbols[off]) {
return
}
}
}
}
// assignSymbols takes a list of symbols and assigns them to their
// respective sections, indexed by name.
func (ec *elfCode) assignSymbols(symbols []elf.Symbol) {
@@ -282,10 +318,11 @@ func (ec *elfCode) assignSymbols(symbols []elf.Symbol) {
if symType != elf.STT_NOTYPE && symType != elf.STT_FUNC {
continue
}
// LLVM emits LBB_ (Local Basic Block) symbols that seem to be jump
// targets within sections, but BPF has no use for them.
if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL &&
strings.HasPrefix(symbol.Name, "LBB") {
// Program sections may contain NOTYPE symbols with local scope, these are
// usually labels for jumps. We do not care for these for the purposes of
// linking and they may overlap with function symbols.
if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL {
continue
}
// Only collect symbols that occur in program/maps/data sections.
@@ -345,6 +382,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
if sec.ReaderAt == nil {
return nil, fmt.Errorf("compressed program section is not supported")
}
funcs, err := ec.loadFunctions(sec)
if err != nil {
return nil, fmt.Errorf("section %v: %w", sec.Name, err)
@@ -397,46 +438,106 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
// be narrowed down to STT_NOTYPE (emitted by clang <8) or STT_FUNC.
//
// The resulting map is indexed by function name.
func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructions, error) {
r := bufio.NewReader(section.Open())
func (ec *elfCode) loadFunctions(sec *elfSection) (map[string]asm.Instructions, error) {
progs := make(map[string]asm.Instructions)
// Decode the section's instruction stream.
insns := make(asm.Instructions, 0, section.Size/asm.InstructionSize)
if err := insns.Unmarshal(r, ec.ByteOrder); err != nil {
return nil, fmt.Errorf("decoding instructions for section %s: %w", section.Name, err)
}
if len(insns) == 0 {
return nil, fmt.Errorf("no instructions found in section %s", section.Name)
}
// Pull out ExtInfos once per section to avoid map lookups on every
// instruction.
fo, lo, ro := ec.extInfo.Section(sec.Name)
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
offset := iter.Offset.Bytes()
// Raw instruction count since start of the section. ExtInfos point at raw
// insn offsets and ignore the gaps between symbols in case of linked objects.
// We need to count them, we can't obtain this info by any other means.
var raw asm.RawInstructionOffset
// Tag Symbol Instructions.
if sym, ok := section.symbols[offset]; ok {
*ins = ins.WithSymbol(sym.Name)
// Sort symbols by offset so we can track instructions by their raw offsets.
for _, sym := range sec.symbolsSorted() {
if progs[sym.Name] != nil {
return nil, fmt.Errorf("duplicate symbol %s in section %s", sym.Name, sec.Name)
}
// Apply any relocations for the current instruction.
// If no relocation is present, resolve any section-relative function calls.
if rel, ok := section.relocations[offset]; ok {
if err := ec.relocateInstruction(ins, rel); err != nil {
return nil, fmt.Errorf("offset %d: relocating instruction: %w", offset, err)
}
} else {
if err := referenceRelativeJump(ins, offset, section.symbols); err != nil {
return nil, fmt.Errorf("offset %d: resolving relative jump: %w", offset, err)
}
if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 {
return nil, fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name)
}
if sym.Value+sym.Size > sec.Size {
return nil, fmt.Errorf("symbol %s: size goes out of bounds of section %s", sym.Name, sec.Name)
}
// Decode the symbol's instruction stream, limited to its size.
sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size))
insns := make(asm.Instructions, 0, sym.Size/asm.InstructionSize)
insns, err := asm.AppendInstructions(insns, sr, ec.ByteOrder, platform.Linux)
if err != nil {
return nil, fmt.Errorf("decoding instructions for symbol %s in section %s: %w", sym.Name, sec.Name, err)
}
if len(insns) == 0 {
return nil, fmt.Errorf("no instructions found for symbol %s in section %s", sym.Name, sec.Name)
}
// Mark the first instruction as the start of a function.
insns[0] = insns[0].WithSymbol(sym.Name)
iter := insns.Iterate()
for iter.Next() {
// Global byte offset of the instruction within the ELF section.
offset := sym.Value + iter.Offset.Bytes()
// Apply any relocations for the current instruction. If no relocation is
// present, resolve any section-relative function calls.
if rel, ok := sec.relocations[offset]; ok {
if err := ec.relocateInstruction(iter.Ins, rel); err != nil {
return nil, fmt.Errorf("offset %d in section %s: relocating instruction: %w", offset, sec.Name, err)
}
} else {
if err := referenceRelativeJump(iter.Ins, offset, sec.symbols); err != nil {
return nil, fmt.Errorf("offset %d in section %s: resolving relative jump: %w", offset, sec.Name, err)
}
}
assignMetadata(iter.Ins, raw, &fo, &lo, &ro)
raw += iter.Ins.Width()
}
// Emit the program's instructions.
progs[sym.Name] = insns
}
if ec.extInfo != nil {
ec.extInfo.Assign(insns, section.Name)
return progs, nil
}
// take pops and returns the first item in q if it matches the given predicate
// f. Otherwise, it returns nil.
func take[T any](q *[]T, f func(T) bool) *T {
if q == nil || len(*q) == 0 {
return nil
}
return splitSymbols(insns)
out := (*q)[0]
if f(out) {
*q = (*q)[1:]
return &out
}
return nil
}
// Tag the instruction with any ExtInfo metadata that's pointing at the given
// raw instruction.
func assignMetadata(ins *asm.Instruction, raw asm.RawInstructionOffset,
fo *btf.FuncOffsets, lo *btf.LineOffsets, ro *btf.CORERelocationOffsets) {
if f := take(fo, func(f btf.FuncOffset) bool { return f.Offset == raw }); f != nil {
*ins = btf.WithFuncMetadata(*ins, f.Func)
}
if l := take(lo, func(l btf.LineOffset) bool { return l.Offset == raw }); l != nil {
*ins = ins.WithSource(l.Line)
}
if r := take(ro, func(r btf.CORERelocationOffset) bool { return r.Offset == raw }); r != nil {
*ins = btf.WithCORERelocationMetadata(*ins, r.Relo)
}
}
// referenceRelativeJump turns a relative jump to another bpf subprogram within
@@ -564,7 +665,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
return fmt.Errorf("call: %s: %w: %s", name, errUnsupportedBinding, bind)
}
@@ -701,69 +802,102 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
return nil
}
// loadMaps iterates over all ELF sections marked as map sections (like .maps)
// and parses each symbol into a MapSpec.
func (ec *elfCode) loadMaps() error {
for _, sec := range ec.sections {
if sec.kind != mapSection {
continue
}
nSym := len(sec.symbols)
if nSym == 0 {
if len(sec.symbols) == 0 {
return fmt.Errorf("section %v: no symbols", sec.Name)
}
if sec.Size%uint64(nSym) != 0 {
return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name)
if sec.ReaderAt == nil {
return fmt.Errorf("compressed map section is not supported")
}
var (
r = bufio.NewReader(sec.Open())
size = sec.Size / uint64(nSym)
)
for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size {
mapSym, ok := sec.symbols[offset]
if !ok {
return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
vars, err := ec.sectionVars(ec.btf, sec.Name)
if err != nil {
return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err)
}
for _, sym := range sec.symbols {
name := sym.Name
if ec.maps[name] != nil {
return fmt.Errorf("duplicate symbol %s in section %s", name, sec.Name)
}
mapName := mapSym.Name
if ec.maps[mapName] != nil {
return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 {
return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name)
}
if sym.Value+sym.Size > sec.Size {
return fmt.Errorf("symbol %s: size goes out of bounds of section %s", name, sec.Name)
}
lr := io.LimitReader(r, int64(size))
sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size))
spec := MapSpec{
Name: SanitizeName(mapName, -1),
Name: sanitizeName(name, -1),
}
switch {
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
return fmt.Errorf("map %s: missing type", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
return fmt.Errorf("map %s: missing key size", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
return fmt.Errorf("map %s: missing value size", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
return fmt.Errorf("map %s: missing max entries", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
return fmt.Errorf("map %s: missing flags", mapName)
case binary.Read(sr, ec.ByteOrder, &spec.Type) != nil:
return fmt.Errorf("map %s: missing type", name)
case binary.Read(sr, ec.ByteOrder, &spec.KeySize) != nil:
return fmt.Errorf("map %s: missing key size", name)
case binary.Read(sr, ec.ByteOrder, &spec.ValueSize) != nil:
return fmt.Errorf("map %s: missing value size", name)
case binary.Read(sr, ec.ByteOrder, &spec.MaxEntries) != nil:
return fmt.Errorf("map %s: missing max entries", name)
case binary.Read(sr, ec.ByteOrder, &spec.Flags) != nil:
return fmt.Errorf("map %s: missing flags", name)
}
extra, err := io.ReadAll(lr)
extra, err := io.ReadAll(sr)
if err != nil {
return fmt.Errorf("map %s: reading map tail: %w", mapName, err)
return fmt.Errorf("map %s: reading map tail: %w", name, err)
}
if len(extra) > 0 {
spec.Extra = bytes.NewReader(extra)
}
ec.maps[mapName] = &spec
if v, ok := vars[name]; ok {
spec.Tags = slices.Clone(v.Tags)
}
ec.maps[name] = &spec
}
}
return nil
}
// sectionVars looks up the BTF Datasec for the given section name and returns a
// map of variable names to their btf.Var definitions.
func (ec *elfCode) sectionVars(spec *btf.Spec, sec string) (map[string]*btf.Var, error) {
vars := make(map[string]*btf.Var)
if spec == nil {
return vars, nil
}
var ds *btf.Datasec
if err := ec.btf.TypeByName(sec, &ds); err != nil {
return vars, nil
}
for _, vsi := range ds.Vars {
v, ok := btf.As[*btf.Var](vsi.Type)
if !ok {
return nil, fmt.Errorf("btf.VarSecInfo doesn't point to a *btf.Var: %T", vsi.Type)
}
vars[string(v.Name)] = v
}
return vars, nil
}
// loadBTFMaps iterates over all ELF sections marked as BTF map sections
// (like .maps) and parses them into MapSpecs. Dump the .maps section and
// any relocations with `readelf -x .maps -r <elf_file>`.
@@ -777,32 +911,47 @@ func (ec *elfCode) loadBTFMaps() error {
return fmt.Errorf("missing BTF")
}
// Each section must appear as a DataSec in the ELF's BTF blob.
var ds *btf.Datasec
if err := ec.btf.TypeByName(sec.Name, &ds); err != nil {
return fmt.Errorf("cannot find section '%s' in BTF: %w", sec.Name, err)
if sec.ReaderAt == nil {
return fmt.Errorf("compressed BTF map section is not supported")
}
// Open a Reader to the ELF's raw section bytes so we can assert that all
// of them are zero on a per-map (per-Var) basis. For now, the section's
// sole purpose is to receive relocations, so all must be zero.
rs := sec.Open()
vars, err := ec.sectionVars(ec.btf, sec.Name)
if err != nil {
return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err)
}
for _, vs := range ds.Vars {
// BPF maps are declared as and assigned to global variables,
// so iterate over each Var in the DataSec and validate their types.
v, ok := vs.Type.(*btf.Var)
if len(vars) != len(sec.symbols) {
return fmt.Errorf("section %v: contains %d symbols but %d btf.Vars", sec.Name, len(sec.symbols), len(vars))
}
syms := make(map[string]elf.Symbol)
for _, sym := range sec.symbols {
syms[sym.Name] = sym
}
for _, v := range vars {
name := v.Name
// Find the ELF symbol corresponding to this Var.
sym, ok := syms[name]
if !ok {
return fmt.Errorf("section %v: unexpected type %s", sec.Name, vs.Type)
return fmt.Errorf("section %v: missing symbol for map %s", sec.Name, name)
}
name := string(v.Name)
if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 {
return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name)
}
if sym.Value+sym.Size > sec.Size {
return fmt.Errorf("section %v: symbol %s: size goes out of bounds of section", sec.Name, name)
}
sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size))
// The BTF metadata for each Var contains the full length of the map
// declaration, so read the corresponding amount of bytes from the ELF.
// This way, we can pinpoint which map declaration contains unexpected
// (and therefore unsupported) data.
_, err := io.Copy(internal.DiscardZeroes{}, io.LimitReader(rs, int64(vs.Size)))
if err != nil {
if _, err = io.Copy(internal.DiscardZeroes{}, sr); err != nil {
return fmt.Errorf("section %v: map %s: initializing BTF map definitions: %w", sec.Name, name, internal.ErrNotSupported)
}
@@ -816,22 +965,12 @@ func (ec *elfCode) loadBTFMaps() error {
return fmt.Errorf("expected struct, got %s", v.Type)
}
mapSpec, err := mapSpecFromBTF(sec, &vs, mapStruct, ec.btf, name, false)
spec, err := mapSpecFromBTF(sec, sym, v, mapStruct, ec.btf, name, false)
if err != nil {
return fmt.Errorf("map %v: %w", name, err)
}
ec.maps[name] = mapSpec
}
// Drain the ELF section reader to make sure all bytes are accounted for
// with BTF metadata.
i, err := io.Copy(io.Discard, rs)
if err != nil {
return fmt.Errorf("section %v: unexpected error reading remainder of ELF section: %w", sec.Name, err)
}
if i > 0 {
return fmt.Errorf("section %v: %d unexpected remaining bytes in ELF section, invalid BTF?", sec.Name, i)
ec.maps[name] = spec
}
}
@@ -841,13 +980,14 @@ func (ec *elfCode) loadBTFMaps() error {
// mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing
// a BTF map definition. The name and spec arguments will be copied to the
// resulting MapSpec, and inner must be true on any recursive invocations.
func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) {
func mapSpecFromBTF(es *elfSection, sym elf.Symbol, v *btf.Var, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) {
var (
key, value btf.Type
keySize, valueSize uint32
keySize, valueSize uint64
mapType MapType
flags, maxEntries uint32
flags, maxEntries uint64
pinType PinType
mapExtra uint64
innerMapSpec *MapSpec
contents []MapKV
err error
@@ -891,7 +1031,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
return nil, fmt.Errorf("can't get size of BTF key: %w", err)
}
keySize = uint32(size)
keySize = uint64(size)
case "value":
if valueSize != 0 {
@@ -910,7 +1050,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
return nil, fmt.Errorf("can't get size of BTF value: %w", err)
}
valueSize = uint32(size)
valueSize = uint64(size)
case "key_size":
// Key needs to be nil and keySize needs to be 0 for key_size to be
@@ -984,7 +1124,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
// on kernels 5.2 and up)
// Pass the BTF spec from the parent object, since both parent and
// child must be created from the same BTF blob (on kernels that support BTF).
innerMapSpec, err = mapSpecFromBTF(es, vs, t, spec, name+"_inner", true)
innerMapSpec, err = mapSpecFromBTF(es, sym, v, t, spec, name+"_inner", true)
if err != nil {
return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err)
}
@@ -1000,13 +1140,16 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
return nil, fmt.Errorf("unsupported value type %q in 'values' field", t)
}
contents, err = resolveBTFValuesContents(es, vs, member)
contents, err = resolveBTFValuesContents(es, sym, member)
if err != nil {
return nil, fmt.Errorf("resolving values contents: %w", err)
}
case "map_extra":
return nil, fmt.Errorf("BTF map definition: field %s: %w", member.Name, ErrNotSupported)
mapExtra, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("resolving map_extra: %w", err)
}
default:
return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
@@ -1021,34 +1164,47 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
}
return &MapSpec{
Name: SanitizeName(name, -1),
Name: sanitizeName(name, -1),
Type: MapType(mapType),
KeySize: keySize,
ValueSize: valueSize,
MaxEntries: maxEntries,
Flags: flags,
KeySize: uint32(keySize),
ValueSize: uint32(valueSize),
MaxEntries: uint32(maxEntries),
Flags: uint32(flags),
Key: key,
Value: value,
Pinning: pinType,
InnerMap: innerMapSpec,
Contents: contents,
Tags: slices.Clone(v.Tags),
MapExtra: mapExtra,
}, nil
}
// uintFromBTF resolves the __uint macro, which is a pointer to a sized
// array, e.g. for int (*foo)[10], this function will return 10.
func uintFromBTF(typ btf.Type) (uint32, error) {
ptr, ok := typ.(*btf.Pointer)
if !ok {
return 0, fmt.Errorf("not a pointer: %v", typ)
}
// uintFromBTF resolves the __uint and __ulong macros.
//
// __uint emits a pointer to a sized array. For int (*foo)[10], this function
// will return 10.
//
// __ulong emits an enum with a single value that can represent a 64-bit
// integer. The first (and only) enum value is returned.
func uintFromBTF(typ btf.Type) (uint64, error) {
switch t := typ.(type) {
case *btf.Pointer:
arr, ok := t.Target.(*btf.Array)
if !ok {
return 0, fmt.Errorf("not a pointer to array: %v", typ)
}
return uint64(arr.Nelems), nil
arr, ok := ptr.Target.(*btf.Array)
if !ok {
return 0, fmt.Errorf("not a pointer to array: %v", typ)
}
case *btf.Enum:
if len(t.Values) == 0 {
return 0, errors.New("enum has no values")
}
return t.Values[0].Value, nil
return arr.Nelems, nil
default:
return 0, fmt.Errorf("not a pointer or enum: %v", typ)
}
}
// resolveBTFArrayMacro resolves the __array macro, which declares an array
@@ -1068,58 +1224,74 @@ func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) {
return ptr.Target, nil
}
// resolveBTFValuesContents resolves relocations into ELF sections belonging
// to btf.VarSecinfo's. This can be used on the 'values' member in BTF map
// definitions to extract static declarations of map contents.
func resolveBTFValuesContents(es *elfSection, vs *btf.VarSecinfo, member btf.Member) ([]MapKV, error) {
// The elements of a .values pointer array are not encoded in BTF.
// Instead, relocations are generated into each array index.
// However, it's possible to leave certain array indices empty, so all
// indices' offsets need to be checked for emitted relocations.
// valuesRelocations returns an iterator over the relocations in the ELF section
// corresponding to the elements of a .values array in a BTF map definition. Each
// iteration yields the array index and the symbol referenced by the relocation
// at that index. Empty indices are skipped.
func valuesRelocations(es *elfSection, sym elf.Symbol, member btf.Member) iter.Seq2[uint32, elf.Symbol] {
// The elements of a .values pointer array are not encoded in BTF itself.
// Instead, each array index receives a relocation pointing at a symbol
// (map/prog) in another section. However, it's possible to leave certain
// array indices empty, so all indices' offsets need to be checked for emitted
// relocations.
// The offset of the 'values' member within the _struct_ (in bits)
// is the starting point of the array. Convert to bytes. Add VarSecinfo
// offset to get the absolute position in the ELF blob.
start := member.Offset.Bytes() + vs.Offset
// 'values' is encoded in BTF as a zero (variable) length struct
// member, and its contents run until the end of the VarSecinfo.
// Add VarSecinfo offset to get the absolute position in the ELF blob.
end := vs.Size + vs.Offset
// The size of an address in this section. This determines the width of
// an index in the array.
align := uint32(es.SectionHeader.Addralign)
// Absolute offset of the .values member within the section.
start := sym.Value + uint64(member.Offset.Bytes())
// Check if variable-length section is aligned.
if (end-start)%align != 0 {
return nil, errors.New("unaligned static values section")
}
// .values is a variable-length struct member, so its contents run until the
// end of the symbol. The symbol offset + size is the absolute offset of the
// end of the array in the section.
end := sym.Value + sym.Size
// The size of an address in this section. This determines the width of an
// index in the array.
align := es.Addralign
// Amount of elements in the .values array.
elems := (end - start) / align
if elems == 0 {
return nil, nil
return func(yield func(uint32, elf.Symbol) bool) {
for i := range uint32(elems) {
// off increases by align on each iteration, starting at .values.
off := start + (uint64(i) * align)
r, ok := es.relocations[off]
if !ok {
continue
}
if !yield(i, r) {
return
}
}
}
contents := make([]MapKV, 0, elems)
}
// k is the array index, off is its corresponding ELF section offset.
for k, off := uint32(0), start; k < elems; k, off = k+1, off+align {
r, ok := es.relocations[uint64(off)]
if !ok {
continue
}
// resolveBTFValuesContents looks up the symbols referenced by the relocations
// in a .values array and returns them as MapKV pairs, where the key is the
// array index and the value is the symbol name. Empty indices are skipped.
func resolveBTFValuesContents(es *elfSection, sym elf.Symbol, member btf.Member) ([]MapKV, error) {
var contents []MapKV
// Relocation exists for the current offset in the ELF section.
// Emit a value stub based on the type of relocation to be replaced by
// a real fd later in the pipeline before populating the map.
// Map keys are encoded in MapKV entries, so empty array indices are
// skipped here.
switch t := elf.ST_TYPE(r.Info); t {
if member.Offset.Bytes() > uint32(sym.Size) {
return nil, fmt.Errorf("member offset %d exceeds symbol size %d", member.Offset.Bytes(), sym.Size)
}
if es.Addralign == 0 {
return nil, fmt.Errorf("section has no address alignment, can't resolve .values contents")
}
for i, sym := range valuesRelocations(es, sym, member) {
// Emit a value stub based on the type of relocation to be replaced by a
// real fd later in the pipeline before populating the Map.
switch t := elf.ST_TYPE(sym.Info); t {
case elf.STT_FUNC:
contents = append(contents, MapKV{uint32(k), r.Name})
contents = append(contents, MapKV{i, sym.Name})
case elf.STT_OBJECT:
contents = append(contents, MapKV{uint32(k), r.Name})
contents = append(contents, MapKV{i, sym.Name})
default:
return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, r.Name)
return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, sym.Name)
}
}
@@ -1148,7 +1320,7 @@ func (ec *elfCode) loadDataSections() error {
}
mapSpec := &MapSpec{
Name: SanitizeName(sec.Name, -1),
Name: sanitizeName(sec.Name, -1),
Type: Array,
KeySize: 4,
ValueSize: uint32(sec.Size),
@@ -1159,14 +1331,15 @@ func (ec *elfCode) loadDataSections() error {
mapSpec.Flags = sys.BPF_F_RDONLY_PROG
}
var data []byte
switch sec.Type {
// Only open the section if we know there's actual data to be read.
case elf.SHT_PROGBITS:
data, err := sec.Data()
var err error
data, err = sec.Data()
if err != nil {
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
}
mapSpec.Contents = []MapKV{{uint32(0), data}}
case elf.SHT_NOBITS:
// NOBITS sections like .bss contain only zeroes and are not allocated in
@@ -1174,12 +1347,14 @@ func (ec *elfCode) loadDataSections() error {
// them. Don't attempt reading zeroes from the ELF, instead allocate the
// zeroed memory to support getting and setting VariableSpecs for sections
// like .bss.
mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}}
data = make([]byte, sec.Size)
default:
return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type)
}
mapSpec.Contents = []MapKV{{uint32(0), data}}
for off, sym := range sec.symbols {
// Skip symbols marked with the 'hidden' attribute.
if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN ||
@@ -1208,11 +1383,23 @@ func (ec *elfCode) loadDataSections() error {
continue
}
if off+sym.Size > uint64(len(data)) {
return fmt.Errorf("data section %s: variable %s exceeds section bounds", sec.Name, sym.Name)
}
if off > math.MaxUint32 {
return fmt.Errorf("data section %s: variable %s offset %d exceeds maximum", sec.Name, sym.Name, off)
}
if sym.Size > math.MaxUint32 {
return fmt.Errorf("data section %s: variable %s size %d exceeds maximum", sec.Name, sym.Name, sym.Size)
}
ec.vars[sym.Name] = &VariableSpec{
name: sym.Name,
offset: off,
size: sym.Size,
m: mapSpec,
SectionName: sec.Name,
Name: sym.Name,
Offset: uint32(off),
Value: slices.Clone(data[off : off+sym.Size]),
}
}
@@ -1243,17 +1430,17 @@ func (ec *elfCode) loadDataSections() error {
continue
}
if uint64(v.Offset) != ev.offset {
return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.offset)
if v.Offset != ev.Offset {
return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.Offset)
}
if uint64(v.Size) != ev.size {
return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.size)
if v.Size != ev.Size() {
return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.Size())
}
// Decouple the Var in the VariableSpec from the underlying DataSec in
// the MapSpec to avoid modifications from affecting map loads later on.
ev.t = btf.Copy(vt).(*btf.Var)
ev.Type = btf.Copy(vt).(*btf.Var)
}
}
}
@@ -1328,6 +1515,95 @@ func (ec *elfCode) loadKsymsSection() error {
return nil
}
// associateStructOpsRelocs handles `.struct_ops.link`
// and associates the target function with the correct struct member in the map.
func (ec *elfCode) associateStructOpsRelocs(progs map[string]*ProgramSpec) error {
for _, sec := range ec.sections {
if sec.kind != structOpsSection {
continue
}
userData, err := sec.Data()
if err != nil {
return fmt.Errorf("failed to read section data: %w", err)
}
if ec.btf == nil {
return fmt.Errorf("struct_ops section %s: missing BTF", sec.Name)
}
// Resolve the BTF datasec describing variables in this section.
var ds *btf.Datasec
if err := ec.btf.TypeByName(sec.Name, &ds); err != nil {
return fmt.Errorf("datasec %s: %w", sec.Name, err)
}
// Set flags for .struct_ops.link (BPF_F_LINK).
flags := uint32(0)
if sec.Name == structOpsLinkSec {
flags = sys.BPF_F_LINK
}
for _, vsi := range ds.Vars {
userSt, baseOff, err := ec.createStructOpsMap(vsi, userData, flags)
if err != nil {
return err
}
if err := structOpsSetAttachTo(sec, baseOff, userSt, progs); err != nil {
return err
}
}
}
return nil
}
// createStructOpsMap() creates and registers a MapSpec for a struct_ops
func (ec *elfCode) createStructOpsMap(vsi btf.VarSecinfo, userData []byte, flags uint32) (*btf.Struct, uint32, error) {
varType, ok := btf.As[*btf.Var](vsi.Type)
if !ok {
return nil, 0, fmt.Errorf("vsi: expect var, got %T", vsi.Type)
}
mapName := varType.Name
userSt, ok := btf.As[*btf.Struct](varType.Type)
if !ok {
return nil, 0, fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type)
}
userSize := userSt.Size
baseOff := vsi.Offset
if uint64(baseOff)+uint64(userSize) > uint64(len(userData)) {
return nil, 0, fmt.Errorf("%s exceeds section", mapName)
}
// Register the MapSpec for this struct_ops instance if doesn't exist
if _, exists := ec.maps[mapName]; exists {
return nil, 0, fmt.Errorf("struct_ops map %s: already exists", mapName)
}
ec.maps[mapName] = &MapSpec{
Name: mapName,
Type: StructOpsMap,
Key: &btf.Int{Size: 4},
KeySize: structOpsKeySize,
ValueSize: userSize, // length of the user-struct type
Value: userSt,
Flags: flags,
MaxEntries: 1,
Contents: []MapKV{
{
Key: uint32(0),
Value: append([]byte(nil), userData[baseOff:baseOff+userSize]...),
},
},
}
return userSt, baseOff, nil
}
type libbpfElfSectionDef struct {
pattern string
programType sys.ProgType
@@ -1348,29 +1624,9 @@ const (
_SEC_XDP_FRAGS
_SEC_USDT
// Ignore any present extra in order to preserve backwards compatibility
// with earlier versions of the library.
ignoreExtra
_SEC_ATTACHABLE_OPT = _SEC_ATTACHABLE | _SEC_EXP_ATTACH_OPT
)
func init() {
// Compatibility with older versions of the library.
// We prepend libbpf definitions since they contain a prefix match
// for "xdp".
elfSectionDefs = append([]libbpfElfSectionDef{
{"xdp.frags/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP, _SEC_XDP_FRAGS | ignoreExtra},
{"xdp.frags_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, _SEC_XDP_FRAGS},
{"xdp_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, 0},
{"xdp.frags_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, _SEC_XDP_FRAGS},
{"xdp_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, 0},
// This has been in the library since the beginning of time. Not sure
// where it came from.
{"seccomp", sys.BPF_PROG_TYPE_SOCKET_FILTER, 0, _SEC_NONE},
}, elfSectionDefs...)
}
func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
// Skip optional program marking for now.
sectionName = strings.TrimPrefix(sectionName, "?")
@@ -1391,15 +1647,13 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
if t.flags&_SEC_XDP_FRAGS > 0 {
flags |= sys.BPF_F_XDP_HAS_FRAGS
}
if t.flags&_SEC_EXP_ATTACH_OPT > 0 {
if programType == XDP {
// The library doesn't yet have code to fallback to not specifying
// attach type. Only do this for XDP since we've enforced correct
// attach type for all other program types.
attachType = AttachNone
}
}
if t.flags&ignoreExtra > 0 {
// The libbpf documentation on program types states: 'The struct_ops attach
// format supports struct_ops[.s]/<name> convention, but name is ignored and
// it is recommended to just use plain SEC("struct_ops[.s]").'
//
// Ignore any extra for struct_ops to match libbpf behaviour.
if programType == StructOps {
extra = ""
}
+4
View File
@@ -21,8 +21,10 @@ var elfSectionDefs = []libbpfElfSectionDef{
{"kprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_SESSION, _SEC_NONE},
{"uprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
{"uretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
{"uprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_NONE},
{"uprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE},
{"uretprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE},
{"uprobe.session.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_SLEEPABLE},
{"ksyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE},
{"kretsyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE},
{"usdt+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_USDT},
@@ -49,6 +51,8 @@ var elfSectionDefs = []libbpfElfSectionDef{
{"fentry.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FENTRY, _SEC_ATTACH_BTF | _SEC_SLEEPABLE},
{"fmod_ret.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_MODIFY_RETURN, _SEC_ATTACH_BTF | _SEC_SLEEPABLE},
{"fexit.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FEXIT, _SEC_ATTACH_BTF | _SEC_SLEEPABLE},
{"fsession+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF},
{"fsession.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF | _SEC_SLEEPABLE},
{"freplace+", sys.BPF_PROG_TYPE_EXT, 0, _SEC_ATTACH_BTF},
{"lsm+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF},
{"lsm.s+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF | _SEC_SLEEPABLE},
+19
View File
@@ -0,0 +1,19 @@
// Package features allows probing for BPF features available to the calling process.
//
// In general, the error return values from feature probes in this package
// all have the following semantics unless otherwise specified:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that resource
// creation may succeed despite an error being returned. For example, some
// map and program types cannot reliably be probed and will return an
// inconclusive error.
//
// As a rule, only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached by the library and persist throughout any changes
// to the process' environment, like capability changes.
package features
+157
View File
@@ -0,0 +1,157 @@
package features
import (
"errors"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// HaveBPFLinkUprobeMulti probes the running kernel if uprobe_multi link is supported.
//
// See the package documentation for the meaning of the error return value.
func HaveBPFLinkUprobeMulti() error {
return haveBPFLinkUprobeMulti()
}
var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_upm_link",
Type: ebpf.Kprobe,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
AttachType: ebpf.AttachTraceUprobeMulti,
License: "MIT",
})
if errors.Is(err, unix.E2BIG) {
// Kernel doesn't support AttachType field.
return ebpf.ErrNotSupported
}
if err != nil {
return err
}
defer prog.Close()
// We try to create uprobe multi link on '/' path which results in
// error with -EBADF in case uprobe multi link is supported.
fd, err := sys.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_UPROBE_MULTI,
Path: sys.NewStringPointer("/"),
Offsets: sys.SlicePointer([]uint64{0}),
Count: 1,
})
switch {
case errors.Is(err, unix.EBADF):
return nil
case errors.Is(err, unix.EINVAL):
return ebpf.ErrNotSupported
case err != nil:
return err
}
// should not happen
fd.Close()
return errors.New("successfully attached uprobe_multi to /, kernel bug?")
}, "6.6")
// HaveBPFLinkKprobeMulti probes the running kernel if kprobe_multi link is supported.
//
// See the package documentation for the meaning of the error return value.
func HaveBPFLinkKprobeMulti() error {
return haveBPFLinkKprobeMulti()
}
var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_kpm_link",
Type: ebpf.Kprobe,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
AttachType: ebpf.AttachTraceKprobeMulti,
License: "MIT",
})
if errors.Is(err, unix.E2BIG) {
// Kernel doesn't support AttachType field.
return ebpf.ErrNotSupported
}
if err != nil {
return err
}
defer prog.Close()
fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_KPROBE_MULTI,
Count: 1,
Syms: sys.NewStringSlicePointer([]string{"vprintk"}),
})
switch {
case errors.Is(err, unix.EINVAL):
return ebpf.ErrNotSupported
// If CONFIG_FPROBE isn't set.
case errors.Is(err, unix.EOPNOTSUPP):
return ebpf.ErrNotSupported
case err != nil:
return err
}
fd.Close()
return nil
}, "5.18")
// HaveBPFLinkKprobeSession probes the running kernel if kprobe_session link is supported.
//
// See the package documentation for the meaning of the error return value.
func HaveBPFLinkKprobeSession() error {
return haveBPFLinkKprobeSession()
}
var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_kps_link",
Type: ebpf.Kprobe,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
AttachType: ebpf.AttachTraceKprobeSession,
License: "MIT",
})
if errors.Is(err, unix.E2BIG) {
// Kernel doesn't support AttachType field.
return ebpf.ErrNotSupported
}
if err != nil {
return err
}
defer prog.Close()
fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_KPROBE_SESSION,
Count: 1,
Syms: sys.NewStringSlicePointer([]string{"vprintk"}),
})
switch {
case errors.Is(err, unix.EINVAL):
return ebpf.ErrNotSupported
// If CONFIG_FPROBE isn't set.
case errors.Is(err, unix.EOPNOTSUPP):
return ebpf.ErrNotSupported
case err != nil:
return err
}
fd.Close()
return nil
}, "6.10")
+362
View File
@@ -0,0 +1,362 @@
package features
import (
"errors"
"fmt"
"os"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// HaveMapType probes the running kernel for the availability of the specified map type.
//
// See the package documentation for the meaning of the error return value.
func HaveMapType(mt ebpf.MapType) error {
return haveMapTypeMatrix.Result(mt)
}
func probeCgroupStorageMap(mt sys.MapType) error {
// keySize needs to be sizeof(struct{u32 + u64}) = 12 (+ padding = 16)
// by using unsafe.Sizeof(int) we are making sure that this works on 32bit and 64bit archs
return createMap(&sys.MapCreateAttr{
MapType: mt,
ValueSize: 4,
KeySize: uint32(8 + unsafe.Sizeof(int(0))),
MaxEntries: 0,
})
}
func probeStorageMap(mt sys.MapType) error {
// maxEntries needs to be 0
// BPF_F_NO_PREALLOC needs to be set
// btf* fields need to be set
// see alloc_check for local_storage map types
err := createMap(&sys.MapCreateAttr{
MapType: mt,
KeySize: 4,
ValueSize: 4,
MaxEntries: 0,
MapFlags: sys.BPF_F_NO_PREALLOC,
BtfKeyTypeId: 1,
BtfValueTypeId: 1,
BtfFd: ^uint32(0),
})
if errors.Is(err, unix.EBADF) {
// Triggered by BtfFd.
return nil
}
return err
}
func probeNestedMap(mt sys.MapType) error {
// assign invalid innerMapFd to pass validation check
// will return EBADF
err := probeMap(&sys.MapCreateAttr{
MapType: mt,
InnerMapFd: ^uint32(0),
})
if errors.Is(err, unix.EBADF) {
return nil
}
return err
}
func probeMap(attr *sys.MapCreateAttr) error {
if attr.KeySize == 0 {
attr.KeySize = 4
}
if attr.ValueSize == 0 {
attr.ValueSize = 4
}
attr.MaxEntries = 1
return createMap(attr)
}
func createMap(attr *sys.MapCreateAttr) error {
fd, err := sys.MapCreate(attr)
if err == nil {
fd.Close()
return nil
}
switch {
// EINVAL occurs when attempting to create a map with an unknown type.
// E2BIG occurs when MapCreateAttr contains non-zero bytes past the end
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
return ebpf.ErrNotSupported
}
return err
}
var haveMapTypeMatrix = internal.FeatureMatrix[ebpf.MapType]{
ebpf.Hash: {Version: "3.19"},
ebpf.Array: {Version: "3.19"},
ebpf.ProgramArray: {Version: "4.2"},
ebpf.PerfEventArray: {Version: "4.3"},
ebpf.PerCPUHash: {Version: "4.6"},
ebpf.PerCPUArray: {Version: "4.6"},
ebpf.StackTrace: {
Version: "4.6",
Fn: func() error {
return probeMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_STACK_TRACE,
ValueSize: 8, // sizeof(uint64)
})
},
},
ebpf.CGroupArray: {Version: "4.8"},
ebpf.LRUHash: {Version: "4.10"},
ebpf.LRUCPUHash: {Version: "4.10"},
ebpf.LPMTrie: {
Version: "4.11",
Fn: func() error {
// keySize and valueSize need to be sizeof(struct{u32 + u8}) + 1 + padding = 8
// BPF_F_NO_PREALLOC needs to be set
return probeMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_LPM_TRIE,
KeySize: 8,
ValueSize: 8,
MapFlags: sys.BPF_F_NO_PREALLOC,
})
},
},
ebpf.ArrayOfMaps: {
Version: "4.12",
Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_ARRAY_OF_MAPS) },
},
ebpf.HashOfMaps: {
Version: "4.12",
Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_HASH_OF_MAPS) },
},
ebpf.DevMap: {Version: "4.14"},
ebpf.SockMap: {Version: "4.14"},
ebpf.CPUMap: {Version: "4.15"},
ebpf.XSKMap: {Version: "4.18"},
ebpf.SockHash: {Version: "4.18"},
ebpf.CGroupStorage: {
Version: "4.19",
Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_CGROUP_STORAGE) },
},
ebpf.ReusePortSockArray: {Version: "4.19"},
ebpf.PerCPUCGroupStorage: {
Version: "4.20",
Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) },
},
ebpf.Queue: {
Version: "4.20",
Fn: func() error {
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_QUEUE,
KeySize: 0,
ValueSize: 4,
MaxEntries: 1,
})
},
},
ebpf.Stack: {
Version: "4.20",
Fn: func() error {
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_STACK,
KeySize: 0,
ValueSize: 4,
MaxEntries: 1,
})
},
},
ebpf.SkStorage: {
Version: "5.2",
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_SK_STORAGE) },
},
ebpf.DevMapHash: {Version: "5.4"},
ebpf.StructOpsMap: {
Version: "5.6",
Fn: func() error {
// StructOps requires setting a vmlinux type id, but id 1 will always
// resolve to some type of integer. This will cause ENOTSUPP.
err := probeMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_STRUCT_OPS,
BtfVmlinuxValueTypeId: 1,
})
if errors.Is(err, sys.ENOTSUPP) {
// ENOTSUPP means the map type is at least known to the kernel.
return nil
}
return err
},
},
ebpf.RingBuf: {
Version: "5.8",
Fn: func() error {
// keySize and valueSize need to be 0
// maxEntries needs to be power of 2 and PAGE_ALIGNED
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_RINGBUF,
KeySize: 0,
ValueSize: 0,
MaxEntries: uint32(os.Getpagesize()),
})
},
},
ebpf.InodeStorage: {
Version: "5.10",
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_INODE_STORAGE) },
},
ebpf.TaskStorage: {
Version: "5.11",
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_TASK_STORAGE) },
},
ebpf.BloomFilter: {
Version: "5.16",
Fn: func() error {
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_BLOOM_FILTER,
KeySize: 0,
ValueSize: 4,
MaxEntries: 1,
})
},
},
ebpf.UserRingbuf: {
Version: "6.1",
Fn: func() error {
// keySize and valueSize need to be 0
// maxEntries needs to be power of 2 and PAGE_ALIGNED
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_USER_RINGBUF,
KeySize: 0,
ValueSize: 0,
MaxEntries: uint32(os.Getpagesize()),
})
},
},
ebpf.CgroupStorage: {
Version: "6.2",
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_CGRP_STORAGE) },
},
ebpf.Arena: {
Version: "6.9",
Fn: func() error {
return createMap(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_ARENA,
KeySize: 0,
ValueSize: 0,
MaxEntries: 1, // one page
MapExtra: 0, // can mmap() at any address
MapFlags: sys.BPF_F_MMAPABLE,
})
},
},
}
func init() {
for mt, ft := range haveMapTypeMatrix {
ft.Name = mt.String()
if ft.Fn == nil {
// Avoid referring to the loop variable in the closure.
mt := sys.MapType(mt)
ft.Fn = func() error { return probeMap(&sys.MapCreateAttr{MapType: mt}) }
}
}
}
// MapFlags document which flags may be feature probed.
type MapFlags uint32
// Flags which may be feature probed.
const (
BPF_F_NO_PREALLOC = sys.BPF_F_NO_PREALLOC
BPF_F_RDONLY_PROG = sys.BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG = sys.BPF_F_WRONLY_PROG
BPF_F_MMAPABLE = sys.BPF_F_MMAPABLE
BPF_F_INNER_MAP = sys.BPF_F_INNER_MAP
)
// HaveMapFlag probes the running kernel for the availability of the specified map flag.
//
// Returns an error if flag is not one of the flags declared in this package.
// See the package documentation for the meaning of the error return value.
func HaveMapFlag(flag MapFlags) (err error) {
return haveMapFlagsMatrix.Result(flag)
}
func probeMapFlag(attr *sys.MapCreateAttr) error {
// For now, we do not check if the map type is supported because we only support
// probing for flags defined on arrays and hashes that are always supported.
// In the future, if we allow probing on flags defined on newer types, checking for map type
// support will be required.
if attr.MapType == sys.BPF_MAP_TYPE_UNSPEC {
attr.MapType = sys.BPF_MAP_TYPE_ARRAY
}
attr.KeySize = 4
attr.ValueSize = 4
attr.MaxEntries = 1
fd, err := sys.MapCreate(attr)
if err == nil {
fd.Close()
} else if errors.Is(err, unix.EINVAL) {
// EINVAL occurs when attempting to create a map with an unknown type or an unknown flag.
err = ebpf.ErrNotSupported
}
return err
}
var haveMapFlagsMatrix = internal.FeatureMatrix[MapFlags]{
BPF_F_NO_PREALLOC: {
Version: "4.6",
Fn: func() error {
return probeMapFlag(&sys.MapCreateAttr{
MapType: sys.BPF_MAP_TYPE_HASH,
MapFlags: BPF_F_NO_PREALLOC,
})
},
},
BPF_F_RDONLY_PROG: {
Version: "5.2",
Fn: func() error {
return probeMapFlag(&sys.MapCreateAttr{
MapFlags: BPF_F_RDONLY_PROG,
})
},
},
BPF_F_WRONLY_PROG: {
Version: "5.2",
Fn: func() error {
return probeMapFlag(&sys.MapCreateAttr{
MapFlags: BPF_F_WRONLY_PROG,
})
},
},
BPF_F_MMAPABLE: {
Version: "5.5",
Fn: func() error {
return probeMapFlag(&sys.MapCreateAttr{
MapFlags: BPF_F_MMAPABLE,
})
},
},
BPF_F_INNER_MAP: {
Version: "5.10",
Fn: func() error {
return probeMapFlag(&sys.MapCreateAttr{
MapFlags: BPF_F_INNER_MAP,
})
},
},
}
func init() {
for mf, ft := range haveMapFlagsMatrix {
ft.Name = fmt.Sprint(mf)
}
}
+135
View File
@@ -0,0 +1,135 @@
package features
import (
"errors"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
)
// HaveLargeInstructions probes the running kernel if more than 4096 instructions
// per program are supported.
//
// Upstream commit c04c0d2b968a ("bpf: increase complexity limit and maximum program size").
//
// See the package documentation for the meaning of the error return value.
func HaveLargeInstructions() error {
return haveLargeInstructions()
}
var haveLargeInstructions = internal.NewFeatureTest(">4096 instructions", func() error {
const maxInsns = 4096
insns := make(asm.Instructions, maxInsns, maxInsns+1)
for i := range insns {
insns[i] = asm.Mov.Imm(asm.R0, 1)
}
insns = append(insns, asm.Return())
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: insns,
})
}, "5.2")
// HaveBoundedLoops probes the running kernel if bounded loops are supported.
//
// Upstream commit 2589726d12a1 ("bpf: introduce bounded loops").
//
// See the package documentation for the meaning of the error return value.
func HaveBoundedLoops() error {
return haveBoundedLoops()
}
var haveBoundedLoops = internal.NewFeatureTest("bounded loops", func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 10),
asm.Sub.Imm(asm.R0, 1).WithSymbol("loop"),
asm.JNE.Imm(asm.R0, 0, "loop"),
asm.Return(),
},
})
}, "5.3")
// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported.
//
// Upstream commit 92b31a9af73b ("bpf: add BPF_J{LT,LE,SLT,SLE} instructions").
//
// See the package documentation for the meaning of the error return value.
func HaveV2ISA() error {
return haveV2ISA()
}
var haveV2ISA = internal.NewFeatureTest("v2 ISA", func() error {
err := probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.JLT.Imm(asm.R0, 0, "exit"),
asm.Mov.Imm(asm.R0, 1),
asm.Return().WithSymbol("exit"),
},
})
// This sometimes bubbles up from the JIT on aarch64.
if errors.Is(err, sys.ENOTSUPP) {
return ebpf.ErrNotSupported
}
return err
}, "4.14")
// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported.
//
// Upstream commit 092ed0968bb6 ("bpf: verifier support JMP32").
//
// See the package documentation for the meaning of the error return value.
func HaveV3ISA() error {
return haveV3ISA()
}
var haveV3ISA = internal.NewFeatureTest("v3 ISA", func() error {
err := probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.JLT.Imm32(asm.R0, 0, "exit"),
asm.Mov.Imm(asm.R0, 1),
asm.Return().WithSymbol("exit"),
},
})
// This sometimes bubbles up from the JIT on aarch64.
if errors.Is(err, sys.ENOTSUPP) {
return ebpf.ErrNotSupported
}
return err
}, "5.1")
// HaveV4ISA probes the running kernel if instructions of the v4 ISA are supported.
//
// Upstream commit 1f9a1ea821ff ("bpf: Support new sign-extension load insns").
//
// See the package documentation for the meaning of the error return value.
func HaveV4ISA() error {
return haveV4ISA()
}
var haveV4ISA = internal.NewFeatureTest("v4 ISA", func() error {
err := probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.JEq.Imm(asm.R0, 1, "error"),
asm.LongJump("exit"),
asm.Mov.Imm(asm.R0, 1).WithSymbol("error"),
asm.Return().WithSymbol("exit"),
},
})
// This sometimes bubbles up from the JIT on aarch64.
if errors.Is(err, sys.ENOTSUPP) {
return ebpf.ErrNotSupported
}
return err
}, "6.6")
+332
View File
@@ -0,0 +1,332 @@
package features
import (
"errors"
"fmt"
"slices"
"strings"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// HaveProgramType probes the running kernel for the availability of the specified program type.
//
// See the package documentation for the meaning of the error return value.
func HaveProgramType(pt ebpf.ProgramType) (err error) {
return haveProgramTypeMatrix.Result(pt)
}
func probeProgram(spec *ebpf.ProgramSpec) error {
if spec.Instructions == nil {
spec.Instructions = asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
}
}
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
LogDisabled: true,
})
if err == nil {
prog.Close()
}
switch {
// EINVAL occurs when attempting to create a program with an unknown type.
// E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given prog type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = ebpf.ErrNotSupported
}
return err
}
var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{
ebpf.SocketFilter: {Version: "3.19"},
ebpf.Kprobe: {Version: "4.1"},
ebpf.SchedCLS: {Version: "4.1"},
ebpf.SchedACT: {Version: "4.1"},
ebpf.TracePoint: {Version: "4.7"},
ebpf.XDP: {Version: "4.8"},
ebpf.PerfEvent: {Version: "4.9"},
ebpf.CGroupSKB: {Version: "4.10"},
ebpf.CGroupSock: {Version: "4.10"},
ebpf.LWTIn: {Version: "4.10"},
ebpf.LWTOut: {Version: "4.10"},
ebpf.LWTXmit: {Version: "4.10"},
ebpf.SockOps: {Version: "4.13"},
ebpf.SkSKB: {Version: "4.14"},
ebpf.CGroupDevice: {Version: "4.15"},
ebpf.SkMsg: {Version: "4.17"},
ebpf.RawTracepoint: {Version: "4.17"},
ebpf.CGroupSockAddr: {
Version: "4.17",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSockAddr,
AttachType: ebpf.AttachCGroupInet4Connect,
})
},
},
ebpf.LWTSeg6Local: {Version: "4.18"},
ebpf.LircMode2: {Version: "4.18"},
ebpf.SkReuseport: {Version: "4.19"},
ebpf.FlowDissector: {Version: "4.20"},
ebpf.CGroupSysctl: {Version: "5.2"},
ebpf.RawTracepointWritable: {Version: "5.2"},
ebpf.CGroupSockopt: {
Version: "5.3",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSockopt,
AttachType: ebpf.AttachCGroupGetsockopt,
})
},
},
ebpf.Tracing: {
Version: "5.5",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.Tracing,
AttachType: ebpf.AttachTraceFEntry,
AttachTo: "bpf_init",
})
},
},
ebpf.StructOps: {
Version: "5.6",
Fn: func() error {
err := probeProgram(&ebpf.ProgramSpec{
Type: ebpf.StructOps,
License: "GPL",
})
if errors.Is(err, sys.ENOTSUPP) {
// ENOTSUPP means the program type is at least known to the kernel.
return nil
}
return err
},
},
ebpf.Extension: {
Version: "5.6",
Fn: func() error {
// create btf.Func to add to first ins of target and extension so both progs are btf powered
btfFn := btf.Func{
Name: "a",
Type: &btf.FuncProto{
Return: &btf.Int{},
Params: []btf.FuncParam{
{Name: "ctx", Type: &btf.Pointer{Target: &btf.Struct{Name: "xdp_md"}}},
},
},
Linkage: btf.GlobalFunc,
}
insns := asm.Instructions{
btf.WithFuncMetadata(asm.Mov.Imm(asm.R0, 0), &btfFn),
asm.Return(),
}
// create target prog
prog, err := ebpf.NewProgramWithOptions(
&ebpf.ProgramSpec{
Type: ebpf.XDP,
Instructions: insns,
},
ebpf.ProgramOptions{
LogDisabled: true,
},
)
if err != nil {
return err
}
defer prog.Close()
// probe for Extension prog with target
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.Extension,
Instructions: insns,
AttachTarget: prog,
AttachTo: btfFn.Name,
})
},
},
ebpf.LSM: {
Version: "5.7",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.LSM,
AttachType: ebpf.AttachLSMMac,
AttachTo: "file_mprotect",
License: "GPL",
})
},
},
ebpf.SkLookup: {
Version: "5.9",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SkLookup,
AttachType: ebpf.AttachSkLookup,
})
},
},
ebpf.Syscall: {
Version: "5.14",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.Syscall,
Flags: sys.BPF_F_SLEEPABLE,
})
},
},
ebpf.Netfilter: {
Version: "6.4",
Fn: func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.Netfilter,
AttachType: ebpf.AttachNetfilter,
})
},
},
}
func init() {
for key, ft := range haveProgramTypeMatrix {
ft.Name = key.String()
if ft.Fn == nil {
key := key // avoid the dreaded loop variable problem
ft.Fn = func() error { return probeProgram(&ebpf.ProgramSpec{Type: key}) }
}
}
}
type helperKey struct {
typ ebpf.ProgramType
helper asm.BuiltinFunc
}
var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest {
return &internal.FeatureTest{
Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ),
Fn: func() error {
return haveProgramHelper(key.typ, key.helper)
},
}
})
// HaveProgramHelper probes the running kernel for the availability of the specified helper
// function to a specified program type.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned.
// Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
return helperCache.Result(helperKey{pt, helper})
}
func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
if ok := helperProbeNotImplemented(pt); ok {
return fmt.Errorf("no feature probe for %v/%v", pt, helper)
}
if err := HaveProgramType(pt); err != nil {
return err
}
spec := &ebpf.ProgramSpec{
Type: pt,
Instructions: asm.Instructions{
helper.Call(),
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "GPL",
}
switch pt {
case ebpf.CGroupSockAddr:
spec.AttachType = ebpf.AttachCGroupInet4Connect
case ebpf.CGroupSockopt:
spec.AttachType = ebpf.AttachCGroupGetsockopt
case ebpf.SkLookup:
spec.AttachType = ebpf.AttachSkLookup
case ebpf.Syscall:
spec.Flags = sys.BPF_F_SLEEPABLE
case ebpf.Netfilter:
spec.AttachType = ebpf.AttachNetfilter
}
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
LogLevel: 1,
})
if err == nil {
prog.Close()
}
var verr *ebpf.VerifierError
if !errors.As(err, &verr) {
return err
}
helperTag := fmt.Sprintf("#%d", helper)
switch {
// EACCES occurs when attempting to create a program probe with a helper
// while the register args when calling this helper aren't set up properly.
// We interpret this as the helper being available, because the verifier
// returns EINVAL if the helper is not supported by the running kernel.
case errors.Is(err, unix.EACCES):
err = nil
// EINVAL occurs when attempting to create a program with an unknown helper.
case errors.Is(err, unix.EINVAL):
// https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10663
if logContainsAll(verr.Log, "invalid func", helperTag) {
return ebpf.ErrNotSupported
}
// https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10668
wrongProgramType := logContainsAll(verr.Log, "program of this type cannot use helper", helperTag)
// https://github.com/torvalds/linux/blob/59b418c7063d30e0a3e1f592d47df096db83185c/kernel/bpf/verifier.c#L10204
// 4.9 doesn't include # in verifier output.
wrongProgramType = wrongProgramType || logContainsAll(verr.Log, "unknown func")
if wrongProgramType {
return fmt.Errorf("program of this type cannot use helper: %w", ebpf.ErrNotSupported)
}
}
return err
}
func logContainsAll(log []string, needles ...string) bool {
first := max(len(log)-5, 0) // Check last 5 lines.
return slices.ContainsFunc(log[first:], func(line string) bool {
for _, needle := range needles {
if !strings.Contains(line, needle) {
return false
}
}
return true
})
}
func helperProbeNotImplemented(pt ebpf.ProgramType) bool {
switch pt {
case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing:
return true
}
return false
}
+18
View File
@@ -0,0 +1,18 @@
package features
import "github.com/cilium/ebpf/internal/linux"
// LinuxVersionCode returns the version of the currently running kernel
// as defined in the LINUX_VERSION_CODE compile-time macro. It is represented
// in the format described by the KERNEL_VERSION macro from linux/version.h.
//
// Do not use the version to make assumptions about the presence of certain
// kernel features, always prefer feature probes in this package. Some
// distributions backport or disable eBPF features.
func LinuxVersionCode() (uint32, error) {
v, err := linux.KernelVersion()
if err != nil {
return 0, err
}
return v.Kernel(), nil
}
+318 -121
View File
@@ -9,13 +9,12 @@ import (
"io"
"os"
"reflect"
"strings"
"syscall"
"time"
"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"
)
@@ -60,6 +59,36 @@ type MapInfo struct {
frozen bool
}
// minimalMapInfoFromFd queries the minimum information needed to create a Map
// based on a file descriptor. This requires the map type, key/value sizes,
// maxentries and flags.
//
// Does not fall back to fdinfo since the version gap between fdinfo (4.10) and
// [sys.ObjInfo] (4.13) is small and both kernels are EOL since at least Nov
// 2017.
//
// Requires at least Linux 4.13.
func minimalMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
var info sys.MapInfo
if err := sys.ObjInfo(fd, &info); err != nil {
return nil, fmt.Errorf("getting object info: %w", err)
}
typ, err := MapTypeForPlatform(platform.Native, info.Type)
if err != nil {
return nil, fmt.Errorf("map type: %w", err)
}
return &MapInfo{
Type: typ,
KeySize: info.KeySize,
ValueSize: info.ValueSize,
MaxEntries: info.MaxEntries,
Flags: uint32(info.MapFlags),
Name: unix.ByteSliceToString(info.Name[:]),
}, nil
}
// newMapInfoFromFd queries map information about the given fd. [sys.ObjInfo] is
// attempted first, supplementing any missing values with information from
// /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as ErrNotSupported
@@ -74,8 +103,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 +139,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]any{
"map_type": &mapType,
"map_id": &mi.id,
"key_size": &mi.KeySize,
"value_size": &mi.ValueSize,
@@ -116,6 +151,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.
@@ -168,15 +215,41 @@ func (mi *MapInfo) Frozen() bool {
return mi.frozen
}
// programStats holds statistics of a program.
type programStats struct {
// Total accumulated runtime of the program ins ns.
runtime time.Duration
// Total number of times the program was called.
runCount uint64
// Total number of times the programm was NOT called.
// Added in commit 9ed9e9ba2337 ("bpf: Count the number of times recursion was prevented").
recursionMisses uint64
// ProgramStats contains runtime statistics for a single [Program], returned by
// [Program.Stats].
//
// Will contain mostly zero values if the collection of statistics is not
// enabled, see [EnableStats].
type ProgramStats struct {
// Total accumulated runtime of the Program.
//
// Requires at least Linux 5.8.
Runtime time.Duration
// Total number of times the Program has executed.
//
// Requires at least Linux 5.8.
RunCount uint64
// Total number of times the program was not executed due to recursion. This
// can happen when another bpf program is already running on the cpu, when bpf
// program execution is interrupted, for example.
//
// Requires at least Linux 5.12.
RecursionMisses uint64
}
func newProgramStatsFromFd(fd *sys.FD) (*ProgramStats, error) {
var info sys.ProgInfo
if err := sys.ObjInfo(fd, &info); err != nil {
return nil, fmt.Errorf("getting program info: %w", err)
}
return &ProgramStats{
Runtime: time.Duration(info.RunTimeNs),
RunCount: info.RunCnt,
RecursionMisses: info.RecursionMisses,
}, nil
}
// programJitedInfo holds information about JITed info of a program.
@@ -213,7 +286,8 @@ type programJitedInfo struct {
numFuncLens uint32
}
// ProgramInfo describes a program.
// ProgramInfo describes a Program's immutable metadata. For runtime statistics,
// see [ProgramStats].
type ProgramInfo struct {
Type ProgramType
id ProgramID
@@ -225,11 +299,13 @@ type ProgramInfo struct {
createdByUID uint32
haveCreatedByUID bool
btf btf.ID
stats *programStats
loadTime time.Duration
restricted bool
maps []MapID
insns []byte
numInsns uint32
jitedSize uint32
verifiedInstructions uint32
@@ -239,32 +315,81 @@ type ProgramInfo struct {
numLineInfos uint32
funcInfos []byte
numFuncInfos uint32
memlock uint64
}
// minimalProgramFromFd queries the minimum information needed to create a
// Program based on a file descriptor, requiring at least the program type.
//
// Does not fall back to fdinfo since the version gap between fdinfo (4.10) and
// [sys.ObjInfo] (4.13) is small and both kernels are EOL since at least Nov
// 2017.
//
// Requires at least Linux 4.13.
func minimalProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
var info sys.ProgInfo
if err := sys.ObjInfo(fd, &info); err != nil {
return nil, fmt.Errorf("getting object info: %w", err)
}
typ, err := ProgramTypeForPlatform(platform.Native, info.Type)
if err != nil {
return nil, fmt.Errorf("program type: %w", err)
}
return &ProgramInfo{
Type: typ,
Name: unix.ByteSliceToString(info.Name[:]),
}, nil
}
// newProgramInfoFromFd queries program information about the given fd.
//
// [sys.ObjInfo] is attempted first, supplementing any missing values with
// information from /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as
// ErrNotSupported from reading fdinfo (indicating the file exists, but no
// fields of interest were found). If both fail, an error is always returned.
func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
var info sys.ProgInfo
err := sys.ObjInfo(fd, &info)
if errors.Is(err, syscall.EINVAL) {
return newProgramInfoFromProc(fd)
err1 := sys.ObjInfo(fd, &info)
// EINVAL means the kernel doesn't support BPF_OBJ_GET_INFO_BY_FD. Continue
// with fdinfo if that's the case.
if err1 != nil && !errors.Is(err1, unix.EINVAL) {
return nil, fmt.Errorf("getting object info: %w", err1)
}
typ, err := ProgramTypeForPlatform(platform.Native, info.Type)
if err != nil {
return nil, err
return nil, fmt.Errorf("program type: %w", err)
}
pi := ProgramInfo{
Type: ProgramType(info.Type),
id: ProgramID(info.Id),
Tag: hex.EncodeToString(info.Tag[:]),
Name: unix.ByteSliceToString(info.Name[:]),
btf: btf.ID(info.BtfId),
stats: &programStats{
runtime: time.Duration(info.RunTimeNs),
runCount: info.RunCnt,
recursionMisses: info.RecursionMisses,
},
Type: typ,
id: ProgramID(info.Id),
Tag: hex.EncodeToString(info.Tag[:]),
Name: unix.ByteSliceToString(info.Name[:]),
btf: btf.ID(info.BtfId),
jitedSize: info.JitedProgLen,
loadTime: time.Duration(info.LoadTime),
verifiedInstructions: info.VerifiedInsns,
numInsns: info.XlatedProgLen,
}
// Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields
// like memlock that is not present in OBJ_INFO.
err2 := readProgramInfoFromProc(fd, &pi)
if err2 != nil && !errors.Is(err2, ErrNotSupported) {
return nil, fmt.Errorf("getting map info from fdinfo: %w", err2)
}
if err1 != nil && err2 != nil {
return nil, fmt.Errorf("ObjInfo and fdinfo both failed: objinfo: %w, fdinfo: %w", err1, err2)
}
if platform.IsWindows && info.Tag == [8]uint8{} {
// Windows doesn't support the tag field, clear it for now.
pi.Tag = ""
}
// Start with a clean struct for the second call, otherwise we may get EFAULT.
@@ -275,7 +400,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.
@@ -286,21 +411,32 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
}
// createdByUID and NrMapIds were introduced in the same kernel version.
if pi.maps != nil {
if pi.maps != nil && platform.IsLinux {
pi.createdByUID = info.CreatedByUid
pi.haveCreatedByUID = true
}
if info.XlatedProgLen > 0 {
pi.insns = make([]byte, info.XlatedProgLen)
info2.XlatedProgLen = info.XlatedProgLen
info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
makeSecondCall = true
var info3 sys.ProgInfo
info3.XlatedProgLen = info.XlatedProgLen
info3.XlatedProgInsns = sys.SlicePointer(pi.insns)
// When kernel.kptr_restrict and net.core.bpf_jit_harden are both set, it causes the
// syscall to abort when trying to readback xlated instructions, skipping other info
// as well. So request xlated instructions separately.
if err := sys.ObjInfo(fd, &info3); err != nil {
return nil, err
}
if info3.XlatedProgInsns.IsNil() {
pi.restricted = true
pi.insns = nil
}
}
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 +445,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 +457,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 +465,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 +473,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 +481,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
}
@@ -354,28 +490,76 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
}
if info.JitedProgLen > 0 && info2.JitedProgInsns.IsNil() {
// JIT information is not available due to kernel.kptr_restrict
pi.jitedInfo.lineInfos = nil
pi.jitedInfo.ksyms = nil
pi.jitedInfo.insns = nil
pi.jitedInfo.funcLens = nil
}
}
if len(pi.Name) == len(info.Name)-1 { // Possibly truncated, check BTF info for full name
name, err := readNameFromFunc(&pi)
if err == nil {
pi.Name = name
} // If an error occurs, keep the truncated name, which is better than none
}
return &pi, nil
}
func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
var info ProgramInfo
err := scanFdInfo(fd, map[string]interface{}{
"prog_type": &info.Type,
"prog_tag": &info.Tag,
func readNameFromFunc(pi *ProgramInfo) (string, error) {
if pi.numFuncInfos == 0 {
return "", errors.New("no function info")
}
spec, err := pi.btfSpec()
if err != nil {
return "", err
}
funcInfos, err := btf.LoadFuncInfos(
bytes.NewReader(pi.funcInfos),
internal.NativeEndian,
pi.numFuncInfos,
spec,
)
if err != nil {
return "", err
}
for _, funcInfo := range funcInfos {
if funcInfo.Offset == 0 { // Information about the whole program
return funcInfo.Func.Name, nil
}
}
return "", errors.New("no function info about program")
}
func readProgramInfoFromProc(fd *sys.FD, pi *ProgramInfo) error {
var progType uint32
err := scanFdInfo(fd, map[string]any{
"prog_type": &progType,
"prog_tag": &pi.Tag,
"memlock": &pi.memlock,
})
if errors.Is(err, ErrNotSupported) {
return nil, &internal.UnsupportedFeatureError{
if errors.Is(err, ErrNotSupported) && !errors.Is(err, internal.ErrNotSupportedOnOS) {
return &internal.UnsupportedFeatureError{
Name: "reading program info from /proc/self/fdinfo",
MinimumVersion: internal.Version{4, 10, 0},
}
}
if err != nil {
return nil, err
return err
}
return &info, nil
pi.Type, err = ProgramTypeForPlatform(platform.Linux, progType)
if err != nil {
return fmt.Errorf("program type: %w", err)
}
return nil
}
// ID returns the program ID.
@@ -408,38 +592,6 @@ func (pi *ProgramInfo) BTFID() (btf.ID, bool) {
return pi.btf, pi.btf > 0
}
// RunCount returns the total number of times the program was called.
//
// Can return 0 if the collection of statistics is not enabled. See EnableStats().
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) RunCount() (uint64, bool) {
if pi.stats != nil {
return pi.stats.runCount, true
}
return 0, false
}
// Runtime returns the total accumulated runtime of the program.
//
// Can return 0 if the collection of statistics is not enabled. See EnableStats().
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
if pi.stats != nil {
return pi.stats.runtime, true
}
return time.Duration(0), false
}
// RecursionMisses returns the total number of times the program was NOT called.
// This can happen when another bpf program is already running on the cpu, which
// is likely to happen for example when you interrupt bpf program execution.
func (pi *ProgramInfo) RecursionMisses() (uint64, bool) {
if pi.stats != nil {
return pi.stats.recursionMisses, true
}
return 0, false
}
// btfSpec returns the BTF spec associated with the program.
func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) {
id, ok := pi.BTFID()
@@ -461,10 +613,17 @@ func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) {
return spec, nil
}
// ErrRestrictedKernel is returned when kernel address information is restricted
// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls.
var ErrRestrictedKernel = internal.ErrRestrictedKernel
// LineInfos returns the BTF line information of the program.
//
// Available from 5.0.
//
// Returns an error wrapping [ErrRestrictedKernel] if line infos are restricted
// by sysctls.
//
// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
// ErrNotSupported if the program was created without BTF or if the kernel
// doesn't support the field.
@@ -504,9 +663,20 @@ func (pi *ProgramInfo) LineInfos() (btf.LineOffsets, error) {
// this metadata requires CAP_SYS_ADMIN or equivalent. If capability is
// unavailable, the instructions will be returned without metadata.
//
// Returns an error wrapping [ErrRestrictedKernel] if instructions are
// restricted by sysctls.
//
// Available from 4.13. Requires CAP_BPF or equivalent for plain instructions.
// Requires CAP_SYS_ADMIN for instructions with metadata.
func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
if platform.IsWindows && len(pi.insns) == 0 {
return nil, fmt.Errorf("read instructions: %w", internal.ErrNotSupportedOnOS)
}
if pi.restricted {
return nil, fmt.Errorf("instructions: %w", ErrRestrictedKernel)
}
// If the calling process is not BPF-capable or if the kernel doesn't
// support getting xlated instructions, the field will be zero.
if len(pi.insns) == 0 {
@@ -514,8 +684,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)
}
@@ -538,27 +708,20 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
return nil, fmt.Errorf("unable to get BTF spec: %w", err)
}
lineInfos, err := btf.LoadLineInfos(
bytes.NewReader(pi.lineInfos),
internal.NativeEndian,
pi.numLineInfos,
spec,
)
lineInfos, err := btf.LoadLineInfos(bytes.NewReader(pi.lineInfos), internal.NativeEndian, pi.numLineInfos, spec)
if err != nil {
return nil, fmt.Errorf("parse line info: %w", err)
}
funcInfos, err := btf.LoadFuncInfos(
bytes.NewReader(pi.funcInfos),
internal.NativeEndian,
pi.numFuncInfos,
spec,
)
funcInfos, err := btf.LoadFuncInfos(bytes.NewReader(pi.funcInfos), internal.NativeEndian, pi.numFuncInfos, spec)
if err != nil {
return nil, fmt.Errorf("parse func info: %w", err)
}
btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{})
iter := insns.Iterate()
for iter.Next() {
assignMetadata(iter.Ins, iter.Offset, &funcInfos, &lineInfos, nil)
}
}
}
@@ -572,8 +735,12 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
return insns, nil
}
// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the
// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled.
// JitedSize returns the size of the program's JIT-compiled machine code in
// bytes, which is the actual code executed on the host's CPU. This field
// requires the BPF JIT compiler to be enabled.
//
// Returns an error wrapping [ErrRestrictedKernel] if jited program size is
// restricted by sysctls.
//
// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
func (pi *ProgramInfo) JitedSize() (uint32, error) {
@@ -583,16 +750,15 @@ func (pi *ProgramInfo) JitedSize() (uint32, error) {
return pi.jitedSize, nil
}
// TranslatedSize returns the size of the program's translated instructions in bytes, after it has
// been verified and rewritten by the kernel.
// TranslatedSize returns the size of the program's translated instructions in
// bytes, after it has been verified and rewritten by the kernel.
//
// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
func (pi *ProgramInfo) TranslatedSize() (int, error) {
insns := len(pi.insns)
if insns == 0 {
if pi.numInsns == 0 {
return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}
return insns, nil
return int(pi.numInsns), nil
}
// MapIDs returns the maps related to the program.
@@ -683,6 +849,9 @@ func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) {
//
// Available from 5.0.
//
// Returns an error wrapping [ErrRestrictedKernel] if function information is
// restricted by sysctls.
//
// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
// ErrNotSupported if the program was created without BTF or if the kernel
// doesn't support the field.
@@ -704,7 +873,20 @@ func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) {
)
}
func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
// ProgramInfo returns an approximate number of bytes allocated to this program.
//
// Available from 4.10.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) Memlock() (uint64, bool) {
return pi.memlock, pi.memlock > 0
}
func scanFdInfo(fd *sys.FD, fields map[string]any) error {
if platform.IsWindows {
return fmt.Errorf("read fdinfo: %w", internal.ErrNotSupportedOnOS)
}
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
if err != nil {
return err
@@ -717,29 +899,35 @@ func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
return nil
}
func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
func scanFdInfoReader(r io.Reader, fields map[string]any) error {
var (
scanner = bufio.NewScanner(r)
scanned int
reader bytes.Reader
)
for scanner.Scan() {
parts := strings.SplitN(scanner.Text(), "\t", 2)
if len(parts) != 2 {
key, rest, found := bytes.Cut(scanner.Bytes(), []byte(":"))
if !found {
// Line doesn't contain a colon, skip.
continue
}
name := strings.TrimSuffix(parts[0], ":")
field, ok := fields[string(name)]
field, ok := fields[string(key)]
if !ok {
continue
}
// If field already contains a non-zero value, don't overwrite it with fdinfo.
if zero(field) {
if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
return fmt.Errorf("can't parse field %s: %v", name, err)
}
if !zero(field) {
scanned++
continue
}
// Cut the \t following the : as well as any potential trailing whitespace.
rest = bytes.TrimSpace(rest)
reader.Reset(rest)
if n, err := fmt.Fscan(&reader, field); err != nil || n != 1 {
return fmt.Errorf("can't parse field %s: %v", key, err)
}
scanned++
@@ -768,12 +956,16 @@ func zero(arg any) bool {
return v.IsZero()
}
// EnableStats starts the measuring of the runtime
// and run counts of eBPF programs.
// EnableStats starts collecting runtime statistics of eBPF programs, like the
// amount of program executions and the cumulative runtime.
//
// Collecting statistics can have an impact on the performance.
// Specify a BPF_STATS_* constant to select which statistics to collect, like
// [unix.BPF_STATS_RUN_TIME]. Closing the returned [io.Closer] will stop
// collecting statistics.
//
// Requires at least 5.8.
// Collecting statistics may have a performance impact.
//
// Requires at least Linux 5.8.
func EnableStats(which uint32) (io.Closer, error) {
fd, err := sys.EnableStats(&sys.EnableStatsAttr{
Type: which,
@@ -785,6 +977,11 @@ func EnableStats(which uint32) (io.Closer, error) {
}
var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", func() error {
if platform.IsWindows {
// We only support efW versions which have this feature, no need to probe.
return nil
}
prog, err := progLoad(asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
@@ -809,4 +1006,4 @@ var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", f
}
return err
}, "4.15")
}, "4.15", "windows:0.21.0")
-31
View File
@@ -1,31 +0,0 @@
package internal
import (
"bytes"
"sync"
)
var bytesBufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// NewBuffer retrieves a [bytes.Buffer] from a pool an re-initialises it.
//
// The returned buffer should be passed to [PutBuffer].
func NewBuffer(buf []byte) *bytes.Buffer {
wr := bytesBufferPool.Get().(*bytes.Buffer)
// Reinitialize the Buffer with a new backing slice since it is returned to
// the caller by wr.Bytes() below. Pooling is faster despite calling
// NewBuffer. The pooled alloc is still reused, it only needs to be zeroed.
*wr = *bytes.NewBuffer(buf)
return wr
}
// PutBuffer releases a buffer to the pool.
func PutBuffer(buf *bytes.Buffer) {
// Release reference to the backing buffer.
*buf = *bytes.NewBuffer(nil)
bytesBufferPool.Put(buf)
}
+1 -4
View File
@@ -75,10 +75,7 @@ func (dq *Deque[T]) Grow(n int) {
// Round up to the new power of two which is at least 8.
// See https://jameshfisher.com/2018/03/30/round-up-power-2/
capacity := 1 << (64 - bits.LeadingZeros64(need-1))
if capacity < 8 {
capacity = 8
}
capacity := max(1<<(64-bits.LeadingZeros64(need-1)), 8)
elems := make([]T, have, capacity)
pivot := dq.read & dq.mask
+65
View File
@@ -0,0 +1,65 @@
//go:build windows
package efw
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
/*
Converts an attach type enum into a GUID.
ebpf_result_t ebpf_get_ebpf_attach_type(
bpf_attach_type_t bpf_attach_type,
_Out_ ebpf_attach_type_t* ebpf_attach_type_t *ebpf_attach_type)
*/
var ebpfGetEbpfAttachTypeProc = newProc("ebpf_get_ebpf_attach_type")
func EbpfGetEbpfAttachType(attachType uint32) (windows.GUID, error) {
addr, err := ebpfGetEbpfAttachTypeProc.Find()
if err != nil {
return windows.GUID{}, err
}
var attachTypeGUID windows.GUID
err = errorResult(syscall.SyscallN(addr,
uintptr(attachType),
uintptr(unsafe.Pointer(&attachTypeGUID)),
))
return attachTypeGUID, err
}
/*
Retrieve a program type given a GUID.
bpf_prog_type_t ebpf_get_bpf_program_type(_In_ const ebpf_program_type_t* program_type)
*/
var ebpfGetBpfProgramTypeProc = newProc("ebpf_get_bpf_program_type")
func EbpfGetBpfProgramType(programType windows.GUID) (uint32, error) {
addr, err := ebpfGetBpfProgramTypeProc.Find()
if err != nil {
return 0, err
}
return uint32Result(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&programType)))), nil
}
/*
Retrieve an attach type given a GUID.
bpf_attach_type_t ebpf_get_bpf_attach_type(_In_ const ebpf_attach_type_t* ebpf_attach_type)
*/
var ebpfGetBpfAttachTypeProc = newProc("ebpf_get_bpf_attach_type")
func EbpfGetBpfAttachType(attachType windows.GUID) (uint32, error) {
addr, err := ebpfGetBpfAttachTypeProc.Find()
if err != nil {
return 0, err
}
return uint32Result(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&attachType)))), nil
}
+155
View File
@@ -0,0 +1,155 @@
//go:build windows
package efw
import (
"errors"
"fmt"
"os"
"syscall"
"testing"
"golang.org/x/sys/windows"
)
func init() {
if !testing.Testing() {
return
}
if isDebuggerPresent() {
return
}
if err := configureCRTErrorReporting(); err != nil {
fmt.Fprintln(os.Stderr, "WARNING: Could not configure CRT error reporting, tests may hang:", err)
}
}
var errErrorReportingAlreadyConfigured = errors.New("error reporting already configured")
// Configure built-in error reporting of the C runtime library.
//
// The C runtime emits assertion failures into a graphical message box by default.
// This causes a hang in CI environments. This function configures the CRT to
// log to stderr instead.
func configureCRTErrorReporting() error {
const ucrtDebug = "ucrtbased.dll"
// Constants from crtdbg.h
//
// See https://doxygen.reactos.org/da/d40/crt_2crtdbg_8h_source.html
const (
_CRT_ERROR = 1
_CRT_ASSERT = 2
_CRTDBG_MODE_FILE = 0x1
_CRTDBG_MODE_WNDW = 0x4
_CRTDBG_HFILE_ERROR = -2
_CRTDBG_FILE_STDERR = -4
)
// Load the efW API to trigger loading the CRT. This may fail, in which case
// we can't figure out which CRT is being used.
// In that case we rely on the error bubbling up via some other path.
_ = module.Load()
ucrtHandle, err := syscall.UTF16PtrFromString(ucrtDebug)
if err != nil {
return err
}
var handle windows.Handle
err = windows.GetModuleHandleEx(0, ucrtHandle, &handle)
if errors.Is(err, windows.ERROR_MOD_NOT_FOUND) {
// Loading the ebpf api did not pull in the debug UCRT, so there is
// nothing to configure.
return nil
} else if err != nil {
return err
}
defer windows.FreeLibrary(handle)
setReportModeAddr, err := windows.GetProcAddress(handle, "_CrtSetReportMode")
if err != nil {
return err
}
setReportMode := func(reportType int, reportMode int) (int, error) {
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportmode?view=msvc-170
r1, _, err := syscall.SyscallN(setReportModeAddr, uintptr(reportType), uintptr(reportMode))
if int(r1) == -1 {
return 0, fmt.Errorf("set report mode for type %d: %w", reportType, err)
}
return int(r1), nil
}
setReportFileAddr, err := windows.GetProcAddress(handle, "_CrtSetReportFile")
if err != nil {
return err
}
setReportFile := func(reportType int, reportFile int) (int, error) {
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportfile?view=msvc-170
r1, _, err := syscall.SyscallN(setReportFileAddr, uintptr(reportType), uintptr(reportFile))
if int(r1) == _CRTDBG_HFILE_ERROR {
return 0, fmt.Errorf("set report file for type %d: %w", reportType, err)
}
return int(r1), nil
}
reportToFile := func(reportType, defaultMode int) error {
oldMode, err := setReportMode(reportType, _CRTDBG_MODE_FILE)
if err != nil {
return err
}
if oldMode != defaultMode {
// Attempt to restore old mode if it was different from the expected default.
_, _ = setReportMode(reportType, oldMode)
return errErrorReportingAlreadyConfigured
}
oldFile, err := setReportFile(reportType, _CRTDBG_FILE_STDERR)
if err != nil {
return err
}
if oldFile != -1 {
// Attempt to restore old file if it was different from the expected default.
_, _ = setReportFile(reportType, oldFile)
return errErrorReportingAlreadyConfigured
}
return nil
}
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportmode?view=msvc-170#remarks
// for defaults.
if err := reportToFile(_CRT_ASSERT, _CRTDBG_MODE_WNDW); err != nil {
return err
}
if err := reportToFile(_CRT_ERROR, _CRTDBG_MODE_WNDW); err != nil {
return err
}
return nil
}
// isDebuggerPresent returns true if the current process is being debugged.
//
// See https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent
func isDebuggerPresent() bool {
kernel32Handle, err := windows.LoadLibrary("kernel32.dll")
if err != nil {
return false
}
isDebuggerPresentAddr, err := windows.GetProcAddress(kernel32Handle, "IsDebuggerPresent")
if err != nil {
return false
}
r1, _, _ := syscall.SyscallN(isDebuggerPresentAddr)
return r1 != 0
}
+34
View File
@@ -0,0 +1,34 @@
//go:build windows
package efw
import (
"syscall"
"unsafe"
)
// ebpf_result_t ebpf_close_fd(fd_t fd)
var ebpfCloseFdProc = newProc("ebpf_close_fd")
func EbpfCloseFd(fd int) error {
addr, err := ebpfCloseFdProc.Find()
if err != nil {
return err
}
return errorResult(syscall.SyscallN(addr, uintptr(fd)))
}
// ebpf_result_t ebpf_duplicate_fd(fd_t fd, _Out_ fd_t* dup)
var ebpfDuplicateFdProc = newProc("ebpf_duplicate_fd")
func EbpfDuplicateFd(fd int) (int, error) {
addr, err := ebpfDuplicateFdProc.Find()
if err != nil {
return -1, err
}
var dup FD
err = errorResult(syscall.SyscallN(addr, uintptr(fd), uintptr(unsafe.Pointer(&dup))))
return int(dup), err
}
+109
View File
@@ -0,0 +1,109 @@
//go:build windows
package efw
import (
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
/*
ebpf_ring_buffer_map_map_buffer(
fd_t map_fd,
_Outptr_result_maybenull_ void** consumer,
_Outptr_result_maybenull_ const void** producer,
_Outptr_result_buffer_maybenull_(*data_size) const uint8_t** data,
_Out_ size_t* data_size) EBPF_NO_EXCEPT;
*/
var ebpfRingBufferMapMapBufferProc = newProc("ebpf_ring_buffer_map_map_buffer")
func EbpfRingBufferMapMapBuffer(mapFd int) (consumer, producer, data *uint8, dataLen Size, _ error) {
addr, err := ebpfRingBufferMapMapBufferProc.Find()
if err != nil {
return nil, nil, nil, 0, err
}
err = errorResult(syscall.SyscallN(addr,
uintptr(mapFd),
uintptr(unsafe.Pointer(&consumer)),
uintptr(unsafe.Pointer(&producer)),
uintptr(unsafe.Pointer(&data)),
uintptr(unsafe.Pointer(&dataLen)),
))
if err != nil {
return nil, nil, nil, 0, err
}
return consumer, producer, data, dataLen, nil
}
/*
ebpf_ring_buffer_map_unmap_buffer(
fd_t map_fd, _In_ void* consumer, _In_ const void* producer, _In_ const void* data) EBPF_NO_EXCEPT;
*/
var ebpfRingBufferMapUnmapBufferProc = newProc("ebpf_ring_buffer_map_unmap_buffer")
func EbpfRingBufferMapUnmapBuffer(mapFd int, consumer, producer, data *uint8) error {
addr, err := ebpfRingBufferMapUnmapBufferProc.Find()
if err != nil {
return err
}
return errorResult(syscall.SyscallN(addr,
uintptr(mapFd),
uintptr(unsafe.Pointer(consumer)),
uintptr(unsafe.Pointer(producer)),
uintptr(unsafe.Pointer(data)),
))
}
/*
ebpf_result_t ebpf_map_set_wait_handle(
fd_t map_fd,
uint64_t index,
ebpf_handle_t handle)
*/
var ebpfMapSetWaitHandleProc = newProc("ebpf_map_set_wait_handle")
func EbpfMapSetWaitHandle(mapFd int, index uint64, handle windows.Handle) error {
addr, err := ebpfMapSetWaitHandleProc.Find()
if err != nil {
return err
}
return errorResult(syscall.SyscallN(addr,
uintptr(mapFd),
uintptr(index),
uintptr(handle),
))
}
/*
ebpf_result_t ebpf_ring_buffer_map_write(
fd_t ring_buffer_map_fd,
const void* data,
size_t data_length)
*/
var ebpfRingBufferMapWriteProc = newProc("ebpf_ring_buffer_map_write")
func EbpfRingBufferMapWrite(ringBufferMapFd int, data []byte) error {
addr, err := ebpfRingBufferMapWriteProc.Find()
if err != nil {
return err
}
err = errorResult(syscall.SyscallN(addr,
uintptr(ringBufferMapFd),
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
))
runtime.KeepAlive(data)
return err
}
+36
View File
@@ -0,0 +1,36 @@
//go:build windows
// Package efw contains support code for eBPF for Windows.
package efw
import (
"golang.org/x/sys/windows"
)
// module is the global handle for the eBPF for Windows user-space API.
var module = windows.NewLazyDLL("ebpfapi.dll")
// FD is the equivalent of fd_t.
//
// See https://github.com/microsoft/ebpf-for-windows/blob/54632eb360c560ebef2f173be1a4a4625d540744/include/ebpf_api.h#L24
type FD int32
// Size is the equivalent of size_t.
//
// This is correct on amd64 and arm64 according to tests on godbolt.org.
type Size uint64
// Int is the equivalent of int on MSVC (am64, arm64) and MinGW (gcc, clang).
type Int int32
// ObjectType is the equivalent of ebpf_object_type_t.
//
// See https://github.com/microsoft/ebpf-for-windows/blob/44f5de09ec0f3f7ad176c00a290c1cb7106cdd5e/include/ebpf_core_structs.h#L41
type ObjectType uint32
const (
EBPF_OBJECT_UNKNOWN ObjectType = iota
EBPF_OBJECT_MAP
EBPF_OBJECT_LINK
EBPF_OBJECT_PROGRAM
)
+44
View File
@@ -0,0 +1,44 @@
//go:build windows
package efw
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
/*
ebpf_result_t ebpf_object_load_native_by_fds(
_In_z_ const char* file_name,
_Inout_ size_t* count_of_maps,
_Out_writes_opt_(count_of_maps) fd_t* map_fds,
_Inout_ size_t* count_of_programs,
_Out_writes_opt_(count_of_programs) fd_t* program_fds)
*/
var ebpfObjectLoadNativeByFdsProc = newProc("ebpf_object_load_native_by_fds")
func EbpfObjectLoadNativeFds(fileName string, mapFds []FD, programFds []FD) (int, int, error) {
addr, err := ebpfObjectLoadNativeByFdsProc.Find()
if err != nil {
return 0, 0, err
}
fileBytes, err := windows.ByteSliceFromString(fileName)
if err != nil {
return 0, 0, err
}
countOfMaps := Size(len(mapFds))
countOfPrograms := Size(len(programFds))
err = errorResult(syscall.SyscallN(addr,
uintptr(unsafe.Pointer(&fileBytes[0])),
uintptr(unsafe.Pointer(&countOfMaps)),
uintptr(unsafe.Pointer(&mapFds[0])),
uintptr(unsafe.Pointer(&countOfPrograms)),
uintptr(unsafe.Pointer(&programFds[0])),
))
return int(countOfMaps), int(countOfPrograms), err
}
+117
View File
@@ -0,0 +1,117 @@
//go:build windows
package efw
import (
"syscall"
"unsafe"
"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.
ebpf_result_t ebpf_object_get_info_by_fd(
fd_t bpf_fd,
_Inout_updates_bytes_to_opt_(*info_size, *info_size) void* info,
_Inout_opt_ uint32_t* info_size,
_Out_opt_ ebpf_object_type_t* type)
*/
var ebpfObjectGetInfoByFdProc = newProc("ebpf_object_get_info_by_fd")
func EbpfObjectGetInfoByFd(fd int, info unsafe.Pointer, info_size *uint32) (ObjectType, error) {
addr, err := ebpfObjectGetInfoByFdProc.Find()
if err != nil {
return 0, err
}
var objectType ObjectType
err = errorResult(syscall.SyscallN(addr,
uintptr(fd),
uintptr(info),
uintptr(unsafe.Pointer(info_size)),
uintptr(unsafe.Pointer(&objectType)),
))
return objectType, err
}
// ebpf_result_t ebpf_object_unpin(_In_z_ const char* path)
var ebpfObjectUnpinProc = newProc("ebpf_object_unpin")
func EbpfObjectUnpin(path string) error {
addr, err := ebpfObjectUnpinProc.Find()
if err != nil {
return err
}
pathBytes, err := windows.ByteSliceFromString(path)
if err != nil {
return err
}
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
}
/*
Canonicalize a path using filesystem canonicalization rules.
_Must_inspect_result_ ebpf_result_t
ebpf_canonicalize_pin_path(_Out_writes_(output_size) char* output, size_t output_size, _In_z_ const char* input)
*/
var ebpfCanonicalizePinPath = newProc("ebpf_canonicalize_pin_path")
func EbpfCanonicalizePinPath(input string) (string, error) {
addr, err := ebpfCanonicalizePinPath.Find()
if err != nil {
return "", err
}
inputBytes, err := windows.ByteSliceFromString(input)
if err != nil {
return "", err
}
output := make([]byte, _EBPF_MAX_PIN_PATH_LENGTH)
err = errorResult(syscall.SyscallN(addr,
uintptr(unsafe.Pointer(&output[0])),
uintptr(len(output)),
uintptr(unsafe.Pointer(&inputBytes[0])),
))
return windows.ByteSliceToString(output), err
}
+50
View File
@@ -0,0 +1,50 @@
//go:build windows
package efw
import (
"errors"
"fmt"
"syscall"
"golang.org/x/sys/windows"
)
/*
The BPF syscall wrapper which is ABI compatible with Linux.
int bpf(int cmd, union bpf_attr* attr, unsigned int size)
*/
var BPF = newProc("bpf")
type proc struct {
proc *windows.LazyProc
}
func newProc(name string) proc {
return proc{module.NewProc(name)}
}
func (p proc) Find() (uintptr, error) {
if err := p.proc.Find(); err != nil {
if errors.Is(err, windows.ERROR_MOD_NOT_FOUND) {
return 0, fmt.Errorf("load %s: not found", module.Name)
}
return 0, err
}
return p.proc.Addr(), nil
}
// uint32Result wraps a function which returns a uint32_t.
func uint32Result(r1, _ uintptr, _ syscall.Errno) uint32 {
return uint32(r1)
}
// errorResult wraps a function which returns ebpf_result_t.
func errorResult(r1, _ uintptr, errNo syscall.Errno) error {
err := resultToError(Result(r1))
if err != nil && errNo != 0 {
return fmt.Errorf("%w (errno: %v)", err, errNo)
}
return err
}
+39
View File
@@ -0,0 +1,39 @@
//go:build windows
package efw
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
/*
Attach a program.
ebpf_result_t ebpf_program_attach_by_fds(
fd_t program_fd,
_In_opt_ const ebpf_attach_type_t* attach_type,
_In_reads_bytes_opt_(attach_parameters_size) void* attach_parameters,
size_t attach_parameters_size,
_Out_ fd_t* link)
*/
var ebpfProgramAttachByFdsProc = newProc("ebpf_program_attach_by_fds")
func EbpfProgramAttachFds(fd int, attachType windows.GUID, params unsafe.Pointer, params_size uintptr) (int, error) {
addr, err := ebpfProgramAttachByFdsProc.Find()
if err != nil {
return 0, err
}
var link FD
err = errorResult(syscall.SyscallN(addr,
uintptr(fd),
uintptr(unsafe.Pointer(&attachType)),
uintptr(params),
params_size,
uintptr(unsafe.Pointer(&link)),
))
return int(link), err
}
+57
View File
@@ -0,0 +1,57 @@
//go:build windows
package efw
// See https://github.com/microsoft/ebpf-for-windows/blob/main/include/ebpf_result.h
type Result int32
//go:generate go tool stringer -tags windows -output result_string_windows.go -type=Result
const (
EBPF_SUCCESS Result = iota
EBPF_VERIFICATION_FAILED
EBPF_JIT_COMPILATION_FAILED
EBPF_PROGRAM_LOAD_FAILED
EBPF_INVALID_FD
EBPF_INVALID_OBJECT
EBPF_INVALID_ARGUMENT
EBPF_OBJECT_NOT_FOUND
EBPF_OBJECT_ALREADY_EXISTS
EBPF_FILE_NOT_FOUND
EBPF_ALREADY_PINNED
EBPF_NOT_PINNED
EBPF_NO_MEMORY
EBPF_PROGRAM_TOO_LARGE
EBPF_RPC_EXCEPTION
EBPF_ALREADY_INITIALIZED
EBPF_ELF_PARSING_FAILED
EBPF_FAILED
EBPF_OPERATION_NOT_SUPPORTED
EBPF_KEY_NOT_FOUND
EBPF_ACCESS_DENIED
EBPF_BLOCKED_BY_POLICY
EBPF_ARITHMETIC_OVERFLOW
EBPF_EXTENSION_FAILED_TO_LOAD
EBPF_INSUFFICIENT_BUFFER
EBPF_NO_MORE_KEYS
EBPF_KEY_ALREADY_EXISTS
EBPF_NO_MORE_TAIL_CALLS
EBPF_PENDING
EBPF_OUT_OF_SPACE
EBPF_CANCELED
EBPF_INVALID_POINTER
EBPF_TIMEOUT
EBPF_STALE_ID
EBPF_INVALID_STATE
)
func (r Result) Error() string {
return r.String()
}
func resultToError(res Result) error {
if res == EBPF_SUCCESS {
return nil
}
return res
}
+57
View File
@@ -0,0 +1,57 @@
// Code generated by "stringer -tags windows -output result_string_windows.go -type=Result"; DO NOT EDIT.
package efw
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[EBPF_SUCCESS-0]
_ = x[EBPF_VERIFICATION_FAILED-1]
_ = x[EBPF_JIT_COMPILATION_FAILED-2]
_ = x[EBPF_PROGRAM_LOAD_FAILED-3]
_ = x[EBPF_INVALID_FD-4]
_ = x[EBPF_INVALID_OBJECT-5]
_ = x[EBPF_INVALID_ARGUMENT-6]
_ = x[EBPF_OBJECT_NOT_FOUND-7]
_ = x[EBPF_OBJECT_ALREADY_EXISTS-8]
_ = x[EBPF_FILE_NOT_FOUND-9]
_ = x[EBPF_ALREADY_PINNED-10]
_ = x[EBPF_NOT_PINNED-11]
_ = x[EBPF_NO_MEMORY-12]
_ = x[EBPF_PROGRAM_TOO_LARGE-13]
_ = x[EBPF_RPC_EXCEPTION-14]
_ = x[EBPF_ALREADY_INITIALIZED-15]
_ = x[EBPF_ELF_PARSING_FAILED-16]
_ = x[EBPF_FAILED-17]
_ = x[EBPF_OPERATION_NOT_SUPPORTED-18]
_ = x[EBPF_KEY_NOT_FOUND-19]
_ = x[EBPF_ACCESS_DENIED-20]
_ = x[EBPF_BLOCKED_BY_POLICY-21]
_ = x[EBPF_ARITHMETIC_OVERFLOW-22]
_ = x[EBPF_EXTENSION_FAILED_TO_LOAD-23]
_ = x[EBPF_INSUFFICIENT_BUFFER-24]
_ = x[EBPF_NO_MORE_KEYS-25]
_ = x[EBPF_KEY_ALREADY_EXISTS-26]
_ = x[EBPF_NO_MORE_TAIL_CALLS-27]
_ = x[EBPF_PENDING-28]
_ = x[EBPF_OUT_OF_SPACE-29]
_ = x[EBPF_CANCELED-30]
_ = x[EBPF_INVALID_POINTER-31]
_ = x[EBPF_TIMEOUT-32]
_ = x[EBPF_STALE_ID-33]
_ = x[EBPF_INVALID_STATE-34]
}
const _Result_name = "EBPF_SUCCESSEBPF_VERIFICATION_FAILEDEBPF_JIT_COMPILATION_FAILEDEBPF_PROGRAM_LOAD_FAILEDEBPF_INVALID_FDEBPF_INVALID_OBJECTEBPF_INVALID_ARGUMENTEBPF_OBJECT_NOT_FOUNDEBPF_OBJECT_ALREADY_EXISTSEBPF_FILE_NOT_FOUNDEBPF_ALREADY_PINNEDEBPF_NOT_PINNEDEBPF_NO_MEMORYEBPF_PROGRAM_TOO_LARGEEBPF_RPC_EXCEPTIONEBPF_ALREADY_INITIALIZEDEBPF_ELF_PARSING_FAILEDEBPF_FAILEDEBPF_OPERATION_NOT_SUPPORTEDEBPF_KEY_NOT_FOUNDEBPF_ACCESS_DENIEDEBPF_BLOCKED_BY_POLICYEBPF_ARITHMETIC_OVERFLOWEBPF_EXTENSION_FAILED_TO_LOADEBPF_INSUFFICIENT_BUFFEREBPF_NO_MORE_KEYSEBPF_KEY_ALREADY_EXISTSEBPF_NO_MORE_TAIL_CALLSEBPF_PENDINGEBPF_OUT_OF_SPACEEBPF_CANCELEDEBPF_INVALID_POINTEREBPF_TIMEOUTEBPF_STALE_IDEBPF_INVALID_STATE"
var _Result_index = [...]uint16{0, 12, 36, 63, 87, 102, 121, 142, 163, 189, 208, 227, 242, 256, 278, 296, 320, 343, 354, 382, 400, 418, 440, 464, 493, 517, 534, 557, 580, 592, 609, 622, 642, 654, 667, 685}
func (i Result) String() string {
if i < 0 || i >= Result(len(_Result_index)-1) {
return "Result(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Result_name[_Result_index[i]:_Result_index[i+1]]
}
+36
View File
@@ -0,0 +1,36 @@
//go:build windows
package efw
import "golang.org/x/sys/windows"
// https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L366
const _BPF_OBJ_NAME_LEN = 64
// See https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L372-L386
type BpfMapInfo struct {
_ uint32 ///< Map ID.
_ uint32 ///< Type of map.
_ uint32 ///< Size in bytes of a map key.
_ uint32 ///< Size in bytes of a map value.
_ uint32 ///< Maximum number of entries allowed in the map.
Name [_BPF_OBJ_NAME_LEN]byte ///< Null-terminated map name.
_ uint32 ///< Map flags.
_ uint32 ///< ID of inner map template.
_ uint32 ///< Number of pinned paths.
}
// See https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L396-L410
type BpfProgInfo struct {
_ uint32 ///< Program ID.
_ uint32 ///< Program type, if a cross-platform type.
_ uint32 ///< Number of maps associated with this program.
_ uintptr ///< Pointer to caller-allocated array to fill map IDs into.
Name [_BPF_OBJ_NAME_LEN]byte ///< Null-terminated map name.
_ windows.GUID ///< Program type UUID.
_ windows.GUID ///< Attach type UUID.
_ uint32 ///< Number of pinned paths.
_ uint32 ///< Number of attached links.
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || ppc64le || riscv64
//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || ppc64le || riscv64 || wasm
package internal
+26 -26
View File
@@ -4,8 +4,9 @@ import (
"errors"
"fmt"
"runtime"
"strings"
"sync"
"github.com/cilium/ebpf/internal/platform"
)
// ErrNotSupported indicates that a feature is not supported.
@@ -15,6 +16,10 @@ var ErrNotSupported = errors.New("not supported")
// operating system.
var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS)
// ErrRestrictedKernel is returned when kernel address information is restricted
// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls.
var ErrRestrictedKernel = errors.New("restricted by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls")
// UnsupportedFeatureError is returned by FeatureTest() functions.
type UnsupportedFeatureError struct {
// The minimum version required for this feature.
@@ -74,33 +79,12 @@ type FeatureTestFn func() error
// Linux. Returns [ErrNotSupportedOnOS] if there is no version specified for the
// current OS.
func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() error {
const nativePrefix = runtime.GOOS + ":"
if len(versions) == 0 {
return func() error {
return fmt.Errorf("feature test %q: no versions specified", name)
}
version, err := platform.SelectVersion(versions)
if err != nil {
return func() error { return err }
}
ft := &FeatureTest{
Name: name,
Fn: fn,
}
for _, version := range versions {
if strings.HasPrefix(version, nativePrefix) {
ft.Version = strings.TrimPrefix(version, nativePrefix)
break
}
if OnLinux && !strings.ContainsRune(version, ':') {
// Allow version numbers without a GOOS prefix on Linux.
ft.Version = version
break
}
}
if ft.Version == "" {
if version == "" {
return func() error {
// We don't return an UnsupportedFeatureError here, since that will
// trigger version checks which don't make sense.
@@ -108,6 +92,12 @@ func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() er
}
}
ft := &FeatureTest{
Name: name,
Version: version,
Fn: fn,
}
return ft.execute
}
@@ -175,12 +165,18 @@ type FeatureMatrix[K comparable] map[K]*FeatureTest
// Result returns the outcome of the feature test for the given key.
//
// It's safe to call this function concurrently.
//
// Always returns [ErrNotSupportedOnOS] on Windows.
func (fm FeatureMatrix[K]) Result(key K) error {
ft, ok := fm[key]
if !ok {
return fmt.Errorf("no feature probe for %v", key)
}
if platform.IsWindows {
return fmt.Errorf("%s: %w", ft.Name, ErrNotSupportedOnOS)
}
return ft.execute()
}
@@ -201,6 +197,10 @@ func NewFeatureCache[K comparable](newTest func(K) *FeatureTest) *FeatureCache[K
}
func (fc *FeatureCache[K]) Result(key K) error {
if platform.IsWindows {
return fmt.Errorf("feature probe for %v: %w", key, ErrNotSupportedOnOS)
}
// NB: Executing the feature test happens without fc.mu taken.
return fc.retrieve(key).execute()
}
-7
View File
@@ -1,7 +0,0 @@
package internal
import "runtime"
const (
OnLinux = runtime.GOOS == "linux"
)
+27 -155
View File
@@ -1,165 +1,21 @@
package kallsyms
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
)
var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name")
var symAddrs cache[string, uint64]
var symModules cache[string, string]
// Module returns the kernel module providing the given symbol in the kernel, if
// any. Returns an empty string and no error if the symbol is not present in the
// kernel. Only function symbols are considered. Returns an error if multiple
// symbols with the same name were found.
//
// Consider [AssignModules] if you need to resolve multiple symbols, as it will
// only perform one iteration over /proc/kallsyms.
func Module(name string) (string, error) {
if name == "" {
return "", nil
}
if mod, ok := symModules.Load(name); ok {
return mod, nil
}
request := map[string]string{name: ""}
if err := AssignModules(request); err != nil {
return "", err
}
return request[name], nil
}
// AssignModules looks up the kernel module providing each given symbol, if any,
// and assigns them to their corresponding values in the symbols map. Only
// function symbols are considered. Results of all lookups are cached,
// successful or otherwise.
//
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// symbols with a given name were found.
func AssignModules(symbols map[string]string) error {
if !internal.OnLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
if len(symbols) == 0 {
return nil
}
// Attempt to fetch symbols from cache.
request := make(map[string]string)
for name := range symbols {
if mod, ok := symModules.Load(name); ok {
symbols[name] = mod
continue
}
// Mark the symbol to be read from /proc/kallsyms.
request[name] = ""
}
if len(request) == 0 {
// All symbols satisfied from cache.
return nil
}
f, err := os.Open("/proc/kallsyms")
if err != nil {
return err
}
defer f.Close()
if err := assignModules(f, request); err != nil {
return fmt.Errorf("assigning symbol modules: %w", err)
}
// Update the cache with the new symbols. Cache all requested symbols, even if
// they're missing or don't belong to a module.
for name, mod := range request {
symModules.Store(name, mod)
symbols[name] = mod
}
return nil
}
// assignModules assigns kernel symbol modules read from f to values requested
// by symbols. Always scans the whole input to make sure the user didn't request
// an ambiguous symbol.
func assignModules(f io.Reader, symbols map[string]string) error {
if len(symbols) == 0 {
return nil
}
found := make(map[string]struct{})
r := newReader(f)
for r.Line() {
// Only look for function symbols in the kernel's text section (tT).
s, err, skip := parseSymbol(r, []rune{'t', 'T'})
if err != nil {
return fmt.Errorf("parsing kallsyms line: %w", err)
}
if skip {
continue
}
if _, requested := symbols[s.name]; !requested {
continue
}
if _, ok := found[s.name]; ok {
// We've already seen this symbol. Return an error to avoid silently
// attaching to a symbol in the wrong module. libbpf also rejects
// referring to ambiguous symbols.
//
// We can't simply check if we already have a value for the given symbol,
// since many won't have an associated kernel module.
return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w",
s.name, s.addr, s.mod, errAmbiguousKsym)
}
symbols[s.name] = s.mod
found[s.name] = struct{}{}
}
if err := r.Err(); err != nil {
return fmt.Errorf("reading kallsyms: %w", err)
}
return nil
}
// Address returns the address of the given symbol in the kernel. Returns 0 and
// no error if the symbol is not present. Returns an error if multiple addresses
// were found for a symbol.
//
// Consider [AssignAddresses] if you need to resolve multiple symbols, as it
// will only perform one iteration over /proc/kallsyms.
func Address(symbol string) (uint64, error) {
if symbol == "" {
return 0, nil
}
if addr, ok := symAddrs.Load(symbol); ok {
return addr, nil
}
request := map[string]uint64{symbol: 0}
if err := AssignAddresses(request); err != nil {
return 0, err
}
return request[symbol], nil
}
// AssignAddresses looks up the addresses of the requested symbols in the kernel
// and assigns them to their corresponding values in the symbols map. Results
@@ -168,7 +24,7 @@ func Address(symbol string) (uint64, error) {
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// addresses were found for a symbol.
func AssignAddresses(symbols map[string]uint64) error {
if !internal.OnLinux {
if !platform.IsLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
@@ -229,7 +85,7 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error {
continue
}
existing, requested := symbols[s.name]
existing, requested := symbols[string(s.name)]
if existing != 0 {
// Multiple addresses for a symbol have been found. Return a friendly
// error to avoid silently attaching to the wrong symbol. libbpf also
@@ -237,7 +93,20 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error {
return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym)
}
if requested {
symbols[s.name] = s.addr
// Reading a symbol with a zero address is a strong indication that
// kptr_restrict is set and the process doesn't have CAP_SYSLOG, or
// kptr_restrict is set to 2 (never show addresses).
//
// When running the kernel with KASLR disabled (like CI kernels running in
// microVMs), kallsyms will display many absolute symbols at address 0.
// This memory is unlikely to contain anything useful, and production
// machines are unlikely to run without KASLR.
//
// Return a helpful error instead of silently returning zero addresses.
if s.addr == 0 {
return fmt.Errorf("symbol %s: %w", s.name, internal.ErrRestrictedKernel)
}
symbols[string(s.name)] = s.addr
}
}
if err := r.Err(); err != nil {
@@ -249,15 +118,18 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error {
type ksym struct {
addr uint64
name string
mod string
name []byte
mod []byte
}
// parseSymbol parses a line from /proc/kallsyms into an address, type, name and
// module. Skip will be true if the symbol doesn't match any of the given symbol
// types. See `man 1 nm` for all available types.
//
// Example line: `ffffffffc1682010 T nf_nat_init [nf_nat]`
// Only yields symbols whose type is contained in types. An empty value for types
// disables this filtering.
//
// Example line: `ffffffffc1682010 T nf_nat_init\t[nf_nat]`
func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) {
for i := 0; r.Word(); i++ {
switch i {
@@ -275,13 +147,13 @@ func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) {
}
// Name of the symbol.
case 2:
s.name = r.Text()
s.name = r.Bytes()
// Kernel module the symbol is provided by.
case 3:
s.mod = strings.Trim(r.Text(), "[]")
s.mod = bytes.Trim(r.Bytes(), "[]")
// Ignore any future fields.
default:
break
return
}
}
+9 -38
View File
@@ -2,9 +2,8 @@ package kallsyms
import (
"bufio"
"bytes"
"io"
"unicode"
"unicode/utf8"
)
// reader is a line and word-oriented reader built for reading /proc/kallsyms.
@@ -71,46 +70,18 @@ func (r *reader) Line() bool {
// Returns true if a word is found and Word should be called again. Returns
// false when all words on the line have been read.
func (r *reader) Word() bool {
if len(r.line) == 0 {
line := bytes.TrimSpace(r.line)
if len(line) == 0 {
return false
}
// Find next word start, skipping leading spaces.
start := 0
for width := 0; start < len(r.line); start += width {
var c rune
c, width = utf8.DecodeRune(r.line[start:])
if !unicode.IsSpace(c) {
break
}
var found bool
r.word, r.line, found = bytes.Cut(line, []byte{' '})
if !found {
r.word, r.line, _ = bytes.Cut(line, []byte{'\t'})
}
// Whitespace scanning reached the end of the line due to trailing whitespace,
// meaning there are no more words to read
if start == len(r.line) {
return false
}
// Find next word end.
for width, i := 0, start; i < len(r.line); i += width {
var c rune
c, width = utf8.DecodeRune(r.line[i:])
if unicode.IsSpace(c) {
r.word = r.line[start:i]
r.line = r.line[i:]
return true
}
}
// The line contains data, but no end-of-word boundary was found. This is the
// last, unterminated word in the line.
if len(r.line) > start {
r.word = r.line[start:]
r.line = nil
return true
}
return false
return true
}
func (r *reader) Err() error {
+2 -2
View File
@@ -183,8 +183,8 @@ func putValueString(data []byte, typ btf.Type, value string) error {
// Any Int, which is not bool, of one byte could be used to store char:
// https://github.com/torvalds/linux/blob/1a5304fecee5/tools/lib/bpf/libbpf.c#L3637-L3638
if contentType.Size != 1 && contentType.Encoding != btf.Bool {
return fmt.Errorf("cannot add string value, expected array of btf.Int of size 1, got array of btf.Int of size: %v", contentType.Size)
if contentType.Size != 1 || contentType.Encoding == btf.Bool {
return fmt.Errorf("cannot add string value, expected array of non-bool btf.Int of size 1, got array of btf.Int with encoding %v and size %v", contentType.Encoding, contentType.Size)
}
if !strings.HasPrefix(value, `"`) || !strings.HasSuffix(value, `"`) {
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"io"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
"github.com/cilium/ebpf/internal/unix"
)
@@ -46,7 +47,7 @@ func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
}
func newAuxvRuntimeReader() (auxvPairReader, error) {
if !internal.OnLinux {
if !platform.IsLinux {
return nil, fmt.Errorf("read auxv from runtime: %w", internal.ErrNotSupportedOnOS)
}
+45
View File
@@ -0,0 +1,45 @@
package linux
import (
"fmt"
"os"
"strings"
)
func ParseCPUsFromFile(path string) (int, error) {
spec, err := os.ReadFile(path)
if err != nil {
return 0, err
}
n, err := parseCPUs(string(spec))
if err != nil {
return 0, fmt.Errorf("can't parse %s: %v", path, err)
}
return n, nil
}
// parseCPUs parses the number of cpus from a string produced
// by bitmap_list_string() in the Linux kernel.
// Multiple ranges are rejected, since they can't be unified
// into a single number.
// This is the format of /sys/devices/system/cpu/possible, it
// is not suitable for /sys/devices/system/cpu/online, etc.
func parseCPUs(spec string) (int, error) {
if strings.Trim(spec, "\n") == "0" {
return 1, nil
}
var low, high int
n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
if n != 2 || err != nil {
return 0, fmt.Errorf("invalid format: %s", spec)
}
if low != 0 {
return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
}
// cpus is 0 indexed
return high + 1, nil
}
+150
View File
@@ -0,0 +1,150 @@
// Package mountinfo parses /proc/self/mountinfo to discover filesystem
// mounts visible in the calling process's mount namespace.
package mountinfo
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"slices"
"strings"
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/platform"
)
// Entry represents one parsed line from /proc/self/mountinfo.
type Entry struct {
// MountPoint is the absolute path where the filesystem is mounted,
// with octal escapes (\040, \011, \012, \134) decoded.
MountPoint string
// Root is the path within the source filesystem that's exposed at
// MountPoint, octal-decoded. It is "/" for a full filesystem mount and
// some other path (e.g. "/events") when a subdirectory of the source
// filesystem has been bind-mounted onto MountPoint. Callers that want
// to use MountPoint as the root of a filesystem should reject entries
// where Root != "/".
Root string
// FSType is the filesystem type, e.g. "bpf", "tracefs", "debugfs".
FSType string
}
// Read parses /proc/self/mountinfo and returns one Entry per mount line.
// The result is cached for the lifetime of the process; mounts that appear
// or disappear after the first call are not reflected.
//
// On non-Linux, returns an error wrapping [internal.ErrNotSupportedOnOS].
func Read() ([]Entry, error) {
return readOnce()
}
// FindByFSType returns the mount points for filesystems of the given type,
// in order of appearance, with duplicates removed.
func FindByFSType(fstype string) ([]string, error) {
entries, err := Read()
if err != nil {
return nil, err
}
return filterByFSType(entries, fstype), nil
}
func filterByFSType(entries []Entry, fstype string) []string {
var mounts []string
for _, e := range entries {
if e.FSType != fstype {
continue
}
// Number of matching mounts is expected to be very low; linear
// search is faster and more cache-friendly than a map at this
// scale, and avoids the allocations a map would incur.
if !slices.Contains(mounts, e.MountPoint) {
mounts = append(mounts, e.MountPoint)
}
}
return mounts
}
// parseEntries parses mountinfo data read from r.
func parseEntries(r io.Reader) ([]Entry, error) {
var entries []Entry
// Format of /proc/self/mountinfo:
//
// {id} {parent id} {major:minor} {root} {mount point} {mount options} [optional fields...] - {filesystem type} {source} {superblock options}
br := bufio.NewReader(r)
for {
line, err := br.ReadString('\n')
if line != "" {
line = strings.TrimRight(line, "\n")
if line != "" {
entry, perr := parseLine(line)
if perr != nil {
return nil, perr
}
entries = append(entries, entry)
}
}
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("read mountinfo: %w", err)
}
}
return entries, nil
}
func parseLine(line string) (Entry, error) {
firstHalfStr, secondHalfStr, ok := strings.Cut(line, " - ")
if !ok {
return Entry{}, fmt.Errorf("invalid mountinfo line, missing dash: %q", line)
}
secondHalf := strings.Fields(strings.TrimSpace(secondHalfStr))
if len(secondHalf) == 0 {
return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields after dash: %q", line)
}
firstHalf := strings.Fields(strings.TrimSpace(firstHalfStr))
if len(firstHalf) < 6 {
return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields: %q", line)
}
return Entry{
Root: unescape(firstHalf[3]),
MountPoint: unescape(firstHalf[4]),
FSType: secondHalf[0],
}, nil
}
// show_mountinfo in the kernel has the escape set of ' \t\n\\'. Instead of
// a full octal unescaper, only replace these specific characters.
var unescaper = strings.NewReplacer(
`\040`, " ",
`\011`, "\t",
`\012`, "\n",
`\134`, `\`,
)
func unescape(s string) string {
return unescaper.Replace(s)
}
var readOnce = sync.OnceValues(func() ([]Entry, error) {
if !platform.IsLinux {
return nil, fmt.Errorf("mountinfo: %w", internal.ErrNotSupportedOnOS)
}
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, fmt.Errorf("open mountinfo: %w", err)
}
defer f.Close()
return parseEntries(f)
})
+24
View File
@@ -0,0 +1,24 @@
package internal
import (
"fmt"
"reflect"
)
// IsNilPointer returns an error if i is a nil interface or a nil pointer.
// Otherwise, it returns nil.
func IsNilPointer(i any) error {
if i == nil {
return fmt.Errorf("nil interface")
}
v := reflect.ValueOf(i)
if v.Kind() != reflect.Pointer {
return nil
}
if v.IsNil() {
return fmt.Errorf("nil %T", i)
}
return nil
}
+7 -2
View File
@@ -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
}
+62
View File
@@ -0,0 +1,62 @@
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
WindowsTag
)
const (
platformMax = 1<<3 - 1 // most not exceed 3 bits to avoid setting the high bit
platformShift = 28
platformMask = platformMax << platformShift
)
func tagForPlatform(platform string) (uint32, error) {
switch platform {
case Linux:
return LinuxTag, nil
case Windows:
return WindowsTag, 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
case WindowsTag:
return Windows
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
}
+42
View File
@@ -0,0 +1,42 @@
package platform
import (
"errors"
"runtime"
"strings"
)
const (
Linux = "linux"
Windows = "windows"
)
const (
IsLinux = runtime.GOOS == "linux"
IsWindows = runtime.GOOS == "windows"
)
// SelectVersion extracts the platform-appropriate version from a list of strings like
// `linux:6.1` or `windows:0.20.0`.
//
// Returns an empty string and nil if no version matched or an error if no strings were passed.
func SelectVersion(versions []string) (string, error) {
const prefix = runtime.GOOS + ":"
if len(versions) == 0 {
return "", errors.New("no versions specified")
}
for _, version := range versions {
if after, ok := strings.CutPrefix(version, prefix); ok {
return after, nil
}
if IsLinux && !strings.ContainsRune(version, ':') {
// Allow version numbers without a GOOS prefix on Linux.
return version, nil
}
}
return "", nil
}
+3
View File
@@ -0,0 +1,3 @@
package platform
const Native = Linux
+5
View File
@@ -0,0 +1,5 @@
//go:build !linux && !windows
package platform
const Native = ""
+3
View File
@@ -0,0 +1,3 @@
package platform
const Native = Windows
Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff. Show more