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

CO-RE: bitfield support #384

Closed
Tracked by #114
lmb opened this issue Aug 23, 2021 · 1 comment
Closed
Tracked by #114

CO-RE: bitfield support #384

lmb opened this issue Aug 23, 2021 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@lmb
Copy link
Collaborator

lmb commented Aug 23, 2021

Here is my current understanding how CO-RE relocations for bitfields work.

// excerpt from `bpf_core_read.h`

enum bpf_field_info_kind {
	BPF_FIELD_BYTE_OFFSET = 0,	/* field byte offset */
	BPF_FIELD_BYTE_SIZE = 1,
	BPF_FIELD_EXISTS = 2,		/* field existence in target kernel */
	BPF_FIELD_SIGNED = 3,
	BPF_FIELD_LSHIFT_U64 = 4,
	BPF_FIELD_RSHIFT_U64 = 5,
};

#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld)			      \
	bpf_probe_read_kernel(						      \
			(void *)dst,				      \
			__CORE_RELO(src, fld, BYTE_SIZE),		      \
			(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#else
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld)			      \
	bpf_probe_read_kernel(						      \
			(void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
			__CORE_RELO(src, fld, BYTE_SIZE),		      \
			(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#endif

#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({			      \
	unsigned long long val = 0;					      \
									      \
	__CORE_BITFIELD_PROBE_READ(&val, s, field);			      \
	val <<= __CORE_RELO(s, field, LSHIFT_U64);			      \
	if (__CORE_RELO(s, field, SIGNED))				      \
		val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64);  \
	else								      \
		val = val >> __CORE_RELO(s, field, RSHIFT_U64);		      \
	val;								      \
})

BYTE_SIZE and BYTE_OFFSET are synthesised by libbpf according to the following
logic:

func libbpf(byteSize, bitOffset, bitfieldSize int) (size int, offset int) {
	byteOff := bitOffset / 8 / byteSize * byteSize
	for (bitOffset + bitfieldSize - byteOff*8) > (byteSize * 8) {
		if byteSize >= 8 {
			return -1, -1
		}
		byteSize *= 2
		byteOff = bitOffset / 8 / byteSize * byteSize
	}
	return byteSize, byteOff
}

For some combination of inputs this results in large reads:

byteSize=4 bitOffset=7 bitfieldSize=6
size=4 offset=0 // vs. size=2 offset=0

This is problematic for packed structs. With size and offset, a read into 64 bit dst is done:

&dst +0 +1 +2 +3 +4 +5 +6 +7 lsh direction
LE u6 src[0] 0 0 0 0 0 0 0 ->
LE u11 src[0] src[1] 0 0 0 0 0 0 ->
BE u6 0 0 0 0 0 0 0 src[0] <-
BE u11 0 0 0 0 0 0 src[0] src[1] <-

The first read byte may contain a prefix of undesired bits, the last one a suffix:

src[0] 0 1 2 3 4 5 6 7 src[1] ...
u6 ? x x x x x x ? ? ...
u11 ? ? x x x x x x x ...

To get rid of them, CO-RE first does a left shift by LSHIFT_U64 bits. On little
endian this removes the suffix in the last read byte, on big endian the prefix
in the first one. On both endians this moves the most significant bit of the
field to the most signficiand bit in dst.

Now all that is left is performing sign extension by right shifting by RSHIFT_U64
bits. Since our value is MSB-aligned this is simply 64 - field_bits.

@ti-mo
Copy link
Collaborator

ti-mo commented Mar 29, 2022

Merged in #573.

@ti-mo ti-mo closed this as completed Mar 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants