-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid decoding BTF on the client #2269
Conversation
0a3a817
to
0bf903c
Compare
0bf903c
to
dda2ba0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't run it yet. Only static analysis
dda2ba0
to
749cb67
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I handled the comments. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
// Blob is used to save data to be sent to the client. | ||
// [0] is used for bpf event | ||
// [1] is used for fixed-size members | ||
// [1+] is used for variable size members | ||
Blob [][]byte `json:"blob,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the indexing system (IndexVirtual, IndexEBPF, IndexFixed) confusing. Would the following make the code clearer?
// Blob is used to save data to be sent to the client. | |
// [0] is used for bpf event | |
// [1] is used for fixed-size members | |
// [1+] is used for variable size members | |
Blob [][]byte `json:"blob,omitempty"` | |
// Blobs used to save data to be sent to the client. | |
BpfEvent []byte `json:"bpfEvent,omitempty"` | |
FixedSizedMembers []byte `json:"fixedSizeMembers,omitempty"` | |
VariableSizedMembers [][]byte `json:"fixedSizeMembers,omitempty"` |
// columns.DynamicField | ||
type ColumnDesc struct { | ||
Name string | ||
BlobIndex int // -1: virtual, 0: ebpf, 1: fixed length, 1+ strings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make it clearer to split this into two fields?
BlobIndex int // -1: virtual, 0: ebpf, 1: fixed length, 1+ strings | |
BlobType BlobType // BlobType is an enum defined with iotas | |
StringIndex int // only used when BlobType == BlobTypeString |
f.nextOffset += typ.Size() | ||
|
||
f.setters[name] = func(ev *Event, v T) { | ||
*(*T)(unsafe.Pointer(&ev.Blob[IndexFixed][offset])) = v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To support different endianness between the client and the server:
binary.BigEndian.PutUint64(ev.Blob[IndexFixed][offset:offset+8], (uint64) v)
And fix f.nextOffset += 8
.
Alternatively, use the correct method PutUint16, PutUint32, PutUint64 etc. (even if it does not play as nice with generics)
I wonder if it would be easier to define FixedSizedMembers [][]byte
(i.e. each fixed-sized member would have its own array)
@@ -329,6 +333,10 @@ func verifyGadgetUint64Typedef(t btf.Type) error { | |||
return nil | |||
} | |||
|
|||
func getAsInteger[OT constraints.Integer](data []byte, offset uint32) OT { | |||
return *(*OT)(unsafe.Pointer(&data[offset])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto to support different endianness:
return binary.BigEndian.Uint16(data[offset:xxx])
BlobIndex: index, | ||
} | ||
|
||
f.nextIndex++ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods FactoryAddField
and FactoryAddString
are difficult to use because they need to be called in the exact same order between calculateColumnsForClient
and processEventFunc
. We have to be careful to iterate on the same object. In my previous code, I initially had problems because one was iterating over the btf fields, and the other was iterating over the types defined in the metadata yaml (and they were not in the right order).
So it makes me wonder if it's better to use hash maps keyed by the field name (map[string][]byte
) instead of arrays ([][]byte
).
749cb67
to
badc485
Compare
Signed-off-by: Mauricio Vásquez <mauriciov@microsoft.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mauriciovasquezbernal.
I have 2 remaining questions about nil-dereferencement. After that, it can be merged. The rest of the comments can be handled later.
|
||
fieldSetter := types.GetSetter[string](t.eventFactory, member.Name) | ||
enumSetter := func(ev *types.Event, data []byte) { | ||
val := getter(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if getter
is nil
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as in the other comment, the switch statement is handling all possible values, so getter
won't never be nil.
|
||
var getter func(data []byte) uint64 | ||
typ := simpleTypeFromBTF(member.Type) | ||
switch typ.Kind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can simpleTypeFromBTF
return nil
? In this case, reading typ.Kind
would panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it can return nil
, it's called only for enums and all possibilities are handled in
inspektor-gadget/pkg/gadgets/run/tracer/run.go
Lines 746 to 770 in 2cf1a9e
case *btf.Enum: | |
if typedMember.Signed { | |
switch typedMember.Size { | |
case 1: | |
return &types.Type{Kind: types.KindInt8} | |
case 2: | |
return &types.Type{Kind: types.KindInt16} | |
case 4: | |
return &types.Type{Kind: types.KindInt32} | |
case 8: | |
return &types.Type{Kind: types.KindInt64} | |
} | |
} | |
switch typedMember.Size { | |
case 1: | |
return &types.Type{Kind: types.KindUint8} | |
case 2: | |
return &types.Type{Kind: types.KindUint16} | |
case 4: | |
return &types.Type{Kind: types.KindUint32} | |
case 8: | |
return &types.Type{Kind: types.KindUint64} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think typedMember.Size
is unmarshalled from the ELF file (from the BTF section) and I am not sure if the cilium/ebpf package checks that the size of an enum is one of those values 1,2,4,8. I tried to find that check but I didn't find it.
https://github.com/cilium/ebpf/blob/main/btf/btf_types.go#L165
It should be fine for valid ebpf/ELF files, but we should ensure IG does not panic for invalid or malicious content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Added a check to verify this condition.
badc485
to
2cf1a9e
Compare
This commit changes the logic to avoid the client having to decode BTF information. The whole idea consists on having the "columns descriptors" in the GadgetInfo structure. These descriptors are generated by the server and used by the client to decode the data it receives. The data is sent as a binary blob [][]byte. The first array is the bpf event, the second one are fixed type fields and the other ones are used for strings. This commit adds some helper methods to add fields to the event. This commit is an intermediate step in order to enable support for wasm. The next work is to start using it from operators to decouple them from the tracer implementation. Signed-off-by: Mauricio Vásquez <mauriciov@microsoft.com>
2cf1a9e
to
dcf979c
Compare
This commit changes the logic to avoid the client having to decode BTF information. The whole idea consists on having the "columns descriptors" in the GadgetInfo structure. These descriptors are generated by the server and used by the client to decode the data it receives.
The data is sent as a binary blob [][]byte. The first array is the bpf event, the second one are fixed type fields and the other ones are used for strings. This commit adds some helper methods to add fields to the event.
This commit is an intermediate step in order to enable support for wasm. The next work is to start using it from operators to decouple them from the tracer implementation.
Fixes #2131
I rebased #2204 on top of this in https://github.com/inspektor-gadget/inspektor-gadget/tree/mauricio/experiments/alban_wasm2 and it seems to work fine.
/cc @alban @flyth