Skip to content
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

Merged
merged 2 commits into from
Dec 13, 2023

Conversation

mauriciovasquezbernal
Copy link
Member

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

Copy link
Member

@burak-ok burak-ok left a 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

pkg/gadgets/run/types/types.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
pkg/gadgets/run/tracer/run.go Show resolved Hide resolved
pkg/gadgets/run/tracer/tracer.go Outdated Show resolved Hide resolved
Copy link
Member Author

@mauriciovasquezbernal mauriciovasquezbernal left a 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!

pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/types.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/factory.go Outdated Show resolved Hide resolved
Copy link
Member

@alban alban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment on lines +59 to +63
// 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"`
Copy link
Member

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?

Suggested change
// 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"`

pkg/gadgets/run/types/types.go Outdated Show resolved Hide resolved
pkg/gadgets/run/types/types.go Outdated Show resolved Hide resolved
// columns.DynamicField
type ColumnDesc struct {
Name string
BlobIndex int // -1: virtual, 0: ebpf, 1: fixed length, 1+ strings
Copy link
Member

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?

Suggested change
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
Copy link
Member

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]))
Copy link
Member

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++
Copy link
Member

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).

Signed-off-by: Mauricio Vásquez <mauriciov@microsoft.com>
@mauriciovasquezbernal
Copy link
Member Author

I handled some of the comments. I'll ignore other comments as this is a temporal solution to unblock #2204. @flyth is working in a refactoring that will change this code later on, so it doesn't make sense to expend a lot of time on it.

Copy link
Member

@alban alban left a 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)
Copy link
Member

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?

Copy link
Member Author

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 {
Copy link
Member

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.

Copy link
Member Author

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

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}
}
}

Copy link
Member

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.

Copy link
Member Author

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.

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>
@mauriciovasquezbernal mauriciovasquezbernal merged commit bfda803 into main Dec 13, 2023
50 checks passed
@mauriciovasquezbernal mauriciovasquezbernal deleted the mauricio/columns-server-pr branch December 13, 2023 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RFE] Update GadgetInfo() rpc to avoid handling BTF/Wasm client-side
3 participants