mirror of
https://github.com/containerd/containerd.git
synced 2026-11-06 05:17:02 +00:00
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:
244 files changed
+14802
-4769
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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?
|
||||
|
||||
|
||||
+77
-37
@@ -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)
|
||||
|
||||
+7
-1
@@ -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)
|
||||
|
||||
Generated
Vendored
+74
-32
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, ¶ms[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
@@ -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, ¶m.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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,7 +0,0 @@
|
||||
package internal
|
||||
|
||||
import "runtime"
|
||||
|
||||
const (
|
||||
OnLinux = runtime.GOOS == "linux"
|
||||
)
|
||||
+27
-155
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
package platform
|
||||
|
||||
const Native = Linux
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
package platform
|
||||
|
||||
const Native = ""
|
||||
+3
@@ -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
Reference in new issue
Block a user