Thread (61 messages) 61 messages, 10 authors, 2023-10-19

Re: bpf indirect calls

From: Toke Høiland-Jørgensen <hidden>
Date: 2019-10-20 10:58:47
Also in: bpf

Alexei Starovoitov [off-list ref] writes:
On Wed, Oct 16, 2019 at 03:51:52PM +0200, Toke Høiland-Jørgensen wrote:
quoted
Alexei Starovoitov [off-list ref] writes:
quoted
On Mon, Oct 14, 2019 at 02:35:45PM +0200, Toke Høiland-Jørgensen wrote:
quoted
Alexei Starovoitov [off-list ref] writes:
quoted
On Wed, Oct 09, 2019 at 10:03:43AM +0200, Toke Høiland-Jørgensen wrote:
quoted
Alexei Starovoitov [off-list ref] writes:
quoted
Please implement proper indirect calls and jumps.
I am still not convinced this will actually solve our problem; but OK, I
can give it a shot.
If you're not convinced let's talk about it first.

Indirect calls is a building block for debugpoints.
Let's not call them tracepoints, because Linus banned any discusion
that includes that name.
The debugpoints is a way for BPF program to insert points in its
code to let external facility to do tracing and debugging.

void (*debugpoint1)(struct xdp_buff *, int code);
void (*debugpoint2)(struct xdp_buff *);
void (*debugpoint3)(int len);
So how would these work? Similar to global variables (i.e., the loader
creates a single-entry PROG_ARRAY map for each one)? Presumably with
some BTF to validate the argument types?

So what would it take to actually support this? It doesn't quite sound
trivial to add?
Depends on definition of 'trivial' :)
Well, I don't know... :)
quoted
The kernel has a luxury of waiting until clean solution is implemented
instead of resorting to hacks.
It would be helpful if you could give an opinion on what specific
features are missing in the kernel to support these indirect calls. A
few high-level sentences is fine (e.g., "the verifier needs to be able
to do X, and llvm/libbpf needs to have support for Y")... I'm trying to
gauge whether this is something it would even make sense for me to poke
into, or if I'm better off waiting for someone who actually knows what
they are doing to work on this :)
I have to reveal a secret first...
llvm supports indirect calls since 2017 ;)

It can compile the following:
struct trace_kfree_skb {
	struct sk_buff *skb;
	void *location;
};

typedef void (*fn)(struct sk_buff *skb);
static fn func;

SEC("tp_btf/kfree_skb")
int trace_kfree_skb(struct trace_kfree_skb *ctx)
{
	struct sk_buff *skb = ctx->skb;
	fn f = *(volatile fn *)&func;

	if (f)
		f(skb);
	return 0;
}

into proper BPF assembly:
; 	struct sk_buff *skb = ctx->skb;
       0:	79 11 00 00 00 00 00 00	r1 = *(u64 *)(r1 + 0)
; 	fn f = *(volatile fn *)&func;
       1:	18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00	r2 = 0 ll
       3:	79 22 00 00 00 00 00 00	r2 = *(u64 *)(r2 + 0)
; 	if (f)
       4:	15 02 01 00 00 00 00 00	if r2 == 0 goto +1 <LBB0_2>
; 		f(skb);
       5:	8d 00 00 00 02 00 00 00	callx 2
0000000000000030 LBB0_2:
; 	return 0;
       6:	b7 00 00 00 00 00 00 00	r0 = 0
       7:	95 00 00 00 00 00 00 00	exit

Indirect call is encoded as JMP|CALL|X
Normal call is JMP|CALL|K
Right, cool! So this would be handled like regular (data) global
variables, with libbpf populating a map to store the value behind the
scenes? 
What's left to do is to teach the verifier to parse BTF of global data.
Then teach it to recognize that r2 at insn 1 is PTR_TO_BTF_ID
where btf_id is DATASEC '.bss'
Then load r2+0 is also PTR_TO_BTF_ID where btf_id is VAR 'func'.
New bool flag to reg_state is needed to tell whether if(rX==NULL) check
was completed.
Then at insn 5 the verifier will see that R2 is PTR_TO_BTF_ID and !NULL
and it's a pointer to a function.
Depending on function prototype the verifier would need to check that
R1's type match to arg1 of func proto.
For simplicity we don't need to deal with pointers to stack,
pointers to map, etc. Only PTR_TO_BTF_ID where btf_id is a kernel
data structure or scalar is enough to get a lot of mileage out of
this indirect call feature.
OK, so this means that explicit map lookups could be added later? I.e.,
this:

int trace_kfree_skb(struct trace_kfree_skb *ctx)
{
	struct sk_buff *skb = ctx->skb;
        u32 key = 0;
	fn f;

        f = bpf_map_lookup_elem(&fpointer_map, &key);
        if (f)
          f(skb);
}

would need some more work, right?
That's mostly it.

Few other safety checks would be needed to make sure that writes
into 'r2+0' are also of correct type.
We also need partial map_update bpf_sys command to populate
function pointer with another bpf program that has matching
function proto.

I think it's not trivial verifier work, but not hard either.
I'm happy to do it as soon as I find time to work on it.
Great! I think it's probably more productive for everyone involved if I
just wait for you to get around to this, rather than try my own hand at
this. I'll go hash out the userspace management semantics of chain calls
in the meantime (using my kernel support patch), since that will surely
be needed anyway.

-Toke
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help