Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Tue, 2 Jul 2024 12:53:20 -0400 Steven Rostedt wrote: > On Wed, 3 Jul 2024 00:19:05 +0900 > Masami Hiramatsu (Google) wrote: > > > > BTW, is this (batched register/unregister APIs) something you'd like > > > to use from the tracefs-based (or whatever it's called, I mean non-BPF > > > ones) uprobes as well? Or there is just no way to even specify a batch > > > of uprobes? Just curious if you had any plans for this. > > > > No, because current tracefs dynamic event interface is not designed for > > batched registration. I think we can expand it to pass wildcard symbols > > (for kprobe and fprobe) or list of addresses (for uprobes) for uprobe. > > Um, that maybe another good idea. > > I don't see why not. The wild cards were added to the kernel > specifically for the tracefs interface (set_ftrace_filter). Sorry for mislead you, I meant current "dynamic_events" interface does not support the wildcard places. And I agree that we can update it to support something like p:multi_uprobe 0x1234,0x2234,0x3234@/bin/foo $arg1 $arg2 $arg3 (note: kernel does not read the symbols in user binary) Thank you, -- Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Tue, Jul 2, 2024 at 9:53 AM Steven Rostedt wrote: > > On Wed, 3 Jul 2024 00:19:05 +0900 > Masami Hiramatsu (Google) wrote: > > > > BTW, is this (batched register/unregister APIs) something you'd like > > > to use from the tracefs-based (or whatever it's called, I mean non-BPF > > > ones) uprobes as well? Or there is just no way to even specify a batch > > > of uprobes? Just curious if you had any plans for this. > > > > No, because current tracefs dynamic event interface is not designed for > > batched registration. I think we can expand it to pass wildcard symbols > > (for kprobe and fprobe) or list of addresses (for uprobes) for uprobe. > > Um, that maybe another good idea. > > I don't see why not. The wild cards were added to the kernel > specifically for the tracefs interface (set_ftrace_filter). Nice, I'd be happy to adjust batch API to work for that use case as well (when we get there). > > -- Steve
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Wed, 3 Jul 2024 00:19:05 +0900 Masami Hiramatsu (Google) wrote: > > BTW, is this (batched register/unregister APIs) something you'd like > > to use from the tracefs-based (or whatever it's called, I mean non-BPF > > ones) uprobes as well? Or there is just no way to even specify a batch > > of uprobes? Just curious if you had any plans for this. > > No, because current tracefs dynamic event interface is not designed for > batched registration. I think we can expand it to pass wildcard symbols > (for kprobe and fprobe) or list of addresses (for uprobes) for uprobe. > Um, that maybe another good idea. I don't see why not. The wild cards were added to the kernel specifically for the tracefs interface (set_ftrace_filter). -- Steve
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, 1 Jul 2024 18:34:55 -0700 Andrii Nakryiko wrote: > > > How about this? I'll keep the existing get_uprobe_consumer(idx, ctx) > > > contract, which works for the only user right now, BPF multi-uprobes. > > > When it's time to add another consumer that works with a linked list, > > > we can add another more complicated contract that would do > > > iterator-style callbacks. This would be used by linked list users, and > > > we can transparently implement existing uprobe_register_batch() > > > contract on top of if by implementing a trivial iterator wrapper on > > > top of get_uprobe_consumer(idx, ctx) approach. > > > > Agreed, anyway as far as it uses an array of uprobe_consumer, it works. > > When we need to register list of the structure, we may be possible to > > allocate an array or introduce new function. > > > > Cool, glad we agree. What you propose above with start + next + ctx > seems like a way forward if we need this. > > BTW, is this (batched register/unregister APIs) something you'd like > to use from the tracefs-based (or whatever it's called, I mean non-BPF > ones) uprobes as well? Or there is just no way to even specify a batch > of uprobes? Just curious if you had any plans for this. No, because current tracefs dynamic event interface is not designed for batched registration. I think we can expand it to pass wildcard symbols (for kprobe and fprobe) or list of addresses (for uprobes) for uprobe. Um, that maybe another good idea. Thank you! -- Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, Jul 1, 2024 at 6:01 PM Masami Hiramatsu wrote: > > On Mon, 1 Jul 2024 15:15:56 -0700 > Andrii Nakryiko wrote: > > > On Mon, Jul 1, 2024 at 10:55 AM Andrii Nakryiko > > wrote: > > > > > > On Sat, Jun 29, 2024 at 4:30 PM Masami Hiramatsu > > > wrote: > > > > > > > > On Fri, 28 Jun 2024 09:34:26 -0700 > > > > Andrii Nakryiko wrote: > > > > > > > > > On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu > > > > > wrote: > > > > > > > > > > > > On Thu, 27 Jun 2024 09:47:10 -0700 > > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > > > > > > wrote: > > > > > > > > > > > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t > > > > > > > > > offset, > > > > > > > > > - loff_t ref_ctr_offset, struct > > > > > > > > > uprobe_consumer *uc) > > > > > > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > > > > > > + uprobe_consumer_fn > > > > > > > > > get_uprobe_consumer, void *ctx) > > > > > > > > > > > > > > > > Is this interface just for avoiding memory allocation? Can't we > > > > > > > > just > > > > > > > > allocate a temporary array of *uprobe_consumer instead? > > > > > > > > > > > > > > Yes, exactly, to avoid the need for allocating another array that > > > > > > > would just contain pointers to uprobe_consumer. Consumers would > > > > > > > never > > > > > > > just have an array of `struct uprobe_consumer *`, because > > > > > > > uprobe_consumer struct is embedded in some other struct, so the > > > > > > > array > > > > > > > interface isn't the most convenient. > > > > > > > > > > > > OK, I understand it. > > > > > > > > > > > > > > > > > > > > If you feel strongly, I can do an array, but this necessitates > > > > > > > allocating an extra array *and keeping it* for the entire > > > > > > > duration of > > > > > > > BPF multi-uprobe link (attachment) existence, so it feels like a > > > > > > > waste. This is because we don't want to do anything that can fail > > > > > > > in > > > > > > > the detachment logic (so no temporary array allocation there). > > > > > > > > > > > > No need to change it, that sounds reasonable. > > > > > > > > > > > > > > > > Great, thanks. > > > > > > > > > > > > > > > > > > > Anyways, let me know how you feel about keeping this callback. > > > > > > > > > > > > IMHO, maybe the interface function is better to change to > > > > > > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > > > > > > side uses a linked list of structure, index access will need to > > > > > > follow the list every time. > > > > > > > > > > This would be problematic. Note how we call get_uprobe_consumer(i, > > > > > ctx) with i going from 0 to N in multiple independent loops. So if we > > > > > are only allowed to ask for the next consumer, then > > > > > uprobe_register_batch and uprobe_unregister_batch would need to build > > > > > its own internal index and remember ith instance. Which again means > > > > > more allocations and possibly failing uprobe_unregister_batch(), which > > > > > isn't great. > > > > > > > > No, I think we can use a cursor variable as; > > > > > > > > int uprobe_register_batch(struct inode *inode, > > > > uprobe_consumer_fn get_uprobe_consumer, void *ctx) > > > > { > > > > void *cur = ctx; > > > > > > > > while ((uc = get_uprobe_consumer()) != NULL) { > > > > ... > > > > } > > > > > > > > cur = ctx; > > > > while ((uc = get_uprobe_consumer()) != NULL) { > > > > ... > > > > } > > > > } > > > > > > > > This can also remove the cnt. > > > > > > Ok, if you prefer this I'll switch. It's a bit more cumbersome to use > > > for callers, but we have one right now, and might have another one, so > > > not a big deal. > > > > > > > Actually, now that I started implementing this, I really-really don't > > like it. In the example above you assume by storing and reusing > > original ctx value you will reset iteration to the very beginning. > > This is not how it works in practice though. Ctx is most probably a > > pointer to some struct somewhere with iteration state (e.g., array of > > all uprobes + current index), and so get_uprobe_consumer() doesn't > > update `void *ctx` itself, it updates the state of that struct. > > Yeah, that should be noted so that if the get_uprobe_consumer() is > called with original `ctx` value, it should return the same. > Ah, and I found we need to pass both ctx and pos... > >while ((uc = get_uprobe_consumer(, ctx)) != NULL) { > ... > } > > Usually it is enough to pass the cursor as similar to the other > for_each_* macros. For example, struct foo has .list and .uc, then > > struct uprobe_consumer *get_uprobe_consumer_foo(void **pos, void *head) > { >
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, 1 Jul 2024 15:15:56 -0700 Andrii Nakryiko wrote: > On Mon, Jul 1, 2024 at 10:55 AM Andrii Nakryiko > wrote: > > > > On Sat, Jun 29, 2024 at 4:30 PM Masami Hiramatsu > > wrote: > > > > > > On Fri, 28 Jun 2024 09:34:26 -0700 > > > Andrii Nakryiko wrote: > > > > > > > On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu > > > > wrote: > > > > > > > > > > On Thu, 27 Jun 2024 09:47:10 -0700 > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > > > > > wrote: > > > > > > > > > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t > > > > > > > > offset, > > > > > > > > - loff_t ref_ctr_offset, struct > > > > > > > > uprobe_consumer *uc) > > > > > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > > > > > + uprobe_consumer_fn get_uprobe_consumer, > > > > > > > > void *ctx) > > > > > > > > > > > > > > Is this interface just for avoiding memory allocation? Can't we > > > > > > > just > > > > > > > allocate a temporary array of *uprobe_consumer instead? > > > > > > > > > > > > Yes, exactly, to avoid the need for allocating another array that > > > > > > would just contain pointers to uprobe_consumer. Consumers would > > > > > > never > > > > > > just have an array of `struct uprobe_consumer *`, because > > > > > > uprobe_consumer struct is embedded in some other struct, so the > > > > > > array > > > > > > interface isn't the most convenient. > > > > > > > > > > OK, I understand it. > > > > > > > > > > > > > > > > > If you feel strongly, I can do an array, but this necessitates > > > > > > allocating an extra array *and keeping it* for the entire duration > > > > > > of > > > > > > BPF multi-uprobe link (attachment) existence, so it feels like a > > > > > > waste. This is because we don't want to do anything that can fail in > > > > > > the detachment logic (so no temporary array allocation there). > > > > > > > > > > No need to change it, that sounds reasonable. > > > > > > > > > > > > > Great, thanks. > > > > > > > > > > > > > > > > Anyways, let me know how you feel about keeping this callback. > > > > > > > > > > IMHO, maybe the interface function is better to change to > > > > > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > > > > > side uses a linked list of structure, index access will need to > > > > > follow the list every time. > > > > > > > > This would be problematic. Note how we call get_uprobe_consumer(i, > > > > ctx) with i going from 0 to N in multiple independent loops. So if we > > > > are only allowed to ask for the next consumer, then > > > > uprobe_register_batch and uprobe_unregister_batch would need to build > > > > its own internal index and remember ith instance. Which again means > > > > more allocations and possibly failing uprobe_unregister_batch(), which > > > > isn't great. > > > > > > No, I think we can use a cursor variable as; > > > > > > int uprobe_register_batch(struct inode *inode, > > > uprobe_consumer_fn get_uprobe_consumer, void *ctx) > > > { > > > void *cur = ctx; > > > > > > while ((uc = get_uprobe_consumer()) != NULL) { > > > ... > > > } > > > > > > cur = ctx; > > > while ((uc = get_uprobe_consumer()) != NULL) { > > > ... > > > } > > > } > > > > > > This can also remove the cnt. > > > > Ok, if you prefer this I'll switch. It's a bit more cumbersome to use > > for callers, but we have one right now, and might have another one, so > > not a big deal. > > > > Actually, now that I started implementing this, I really-really don't > like it. In the example above you assume by storing and reusing > original ctx value you will reset iteration to the very beginning. > This is not how it works in practice though. Ctx is most probably a > pointer to some struct somewhere with iteration state (e.g., array of > all uprobes + current index), and so get_uprobe_consumer() doesn't > update `void *ctx` itself, it updates the state of that struct. Yeah, that should be noted so that if the get_uprobe_consumer() is called with original `ctx` value, it should return the same. Ah, and I found we need to pass both ctx and pos... while ((uc = get_uprobe_consumer(, ctx)) != NULL) { ... } Usually it is enough to pass the cursor as similar to the other for_each_* macros. For example, struct foo has .list and .uc, then struct uprobe_consumer *get_uprobe_consumer_foo(void **pos, void *head) { struct foo *foo = *pos; if (!foo) return NULL; *pos = list_next_entry(foo, list); if (list_is_head(pos, (head))) *pos = NULL; return foo->uc; } This works something like this. #define for_each_uprobe_consumer_from_foo(uc, pos, head)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, Jul 1, 2024 at 10:55 AM Andrii Nakryiko wrote: > > On Sat, Jun 29, 2024 at 4:30 PM Masami Hiramatsu wrote: > > > > On Fri, 28 Jun 2024 09:34:26 -0700 > > Andrii Nakryiko wrote: > > > > > On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu > > > wrote: > > > > > > > > On Thu, 27 Jun 2024 09:47:10 -0700 > > > > Andrii Nakryiko wrote: > > > > > > > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > > > > wrote: > > > > > > > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > > > > > > - loff_t ref_ctr_offset, struct > > > > > > > uprobe_consumer *uc) > > > > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > > > > + uprobe_consumer_fn get_uprobe_consumer, > > > > > > > void *ctx) > > > > > > > > > > > > Is this interface just for avoiding memory allocation? Can't we just > > > > > > allocate a temporary array of *uprobe_consumer instead? > > > > > > > > > > Yes, exactly, to avoid the need for allocating another array that > > > > > would just contain pointers to uprobe_consumer. Consumers would never > > > > > just have an array of `struct uprobe_consumer *`, because > > > > > uprobe_consumer struct is embedded in some other struct, so the array > > > > > interface isn't the most convenient. > > > > > > > > OK, I understand it. > > > > > > > > > > > > > > If you feel strongly, I can do an array, but this necessitates > > > > > allocating an extra array *and keeping it* for the entire duration of > > > > > BPF multi-uprobe link (attachment) existence, so it feels like a > > > > > waste. This is because we don't want to do anything that can fail in > > > > > the detachment logic (so no temporary array allocation there). > > > > > > > > No need to change it, that sounds reasonable. > > > > > > > > > > Great, thanks. > > > > > > > > > > > > > Anyways, let me know how you feel about keeping this callback. > > > > > > > > IMHO, maybe the interface function is better to change to > > > > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > > > > side uses a linked list of structure, index access will need to > > > > follow the list every time. > > > > > > This would be problematic. Note how we call get_uprobe_consumer(i, > > > ctx) with i going from 0 to N in multiple independent loops. So if we > > > are only allowed to ask for the next consumer, then > > > uprobe_register_batch and uprobe_unregister_batch would need to build > > > its own internal index and remember ith instance. Which again means > > > more allocations and possibly failing uprobe_unregister_batch(), which > > > isn't great. > > > > No, I think we can use a cursor variable as; > > > > int uprobe_register_batch(struct inode *inode, > > uprobe_consumer_fn get_uprobe_consumer, void *ctx) > > { > > void *cur = ctx; > > > > while ((uc = get_uprobe_consumer()) != NULL) { > > ... > > } > > > > cur = ctx; > > while ((uc = get_uprobe_consumer()) != NULL) { > > ... > > } > > } > > > > This can also remove the cnt. > > Ok, if you prefer this I'll switch. It's a bit more cumbersome to use > for callers, but we have one right now, and might have another one, so > not a big deal. > Actually, now that I started implementing this, I really-really don't like it. In the example above you assume by storing and reusing original ctx value you will reset iteration to the very beginning. This is not how it works in practice though. Ctx is most probably a pointer to some struct somewhere with iteration state (e.g., array of all uprobes + current index), and so get_uprobe_consumer() doesn't update `void *ctx` itself, it updates the state of that struct. And so there is no easy and clean way to reset this iterator without adding another callback or something. At which point it becomes quite cumbersome and convoluted. How about this? I'll keep the existing get_uprobe_consumer(idx, ctx) contract, which works for the only user right now, BPF multi-uprobes. When it's time to add another consumer that works with a linked list, we can add another more complicated contract that would do iterator-style callbacks. This would be used by linked list users, and we can transparently implement existing uprobe_register_batch() contract on top of if by implementing a trivial iterator wrapper on top of get_uprobe_consumer(idx, ctx) approach. Let's not add unnecessary complications right now given we have a clear path forward to add it later, if necessary, without breaking anything. I'll send v2 without changes to get_uprobe_consumer() for now, hopefully my above plan makes sense to you. Thanks! > > > > Thank you, > > > > > > > > For now this API works well, I propose to keep it as is. For linked > > > list case consumers would need to allocate one extra array or
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Sat, Jun 29, 2024 at 4:30 PM Masami Hiramatsu wrote: > > On Fri, 28 Jun 2024 09:34:26 -0700 > Andrii Nakryiko wrote: > > > On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu > > wrote: > > > > > > On Thu, 27 Jun 2024 09:47:10 -0700 > > > Andrii Nakryiko wrote: > > > > > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > > > wrote: > > > > > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > > > Andrii Nakryiko wrote: > > > > > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > > > > > - loff_t ref_ctr_offset, struct > > > > > > uprobe_consumer *uc) > > > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > > > + uprobe_consumer_fn get_uprobe_consumer, > > > > > > void *ctx) > > > > > > > > > > Is this interface just for avoiding memory allocation? Can't we just > > > > > allocate a temporary array of *uprobe_consumer instead? > > > > > > > > Yes, exactly, to avoid the need for allocating another array that > > > > would just contain pointers to uprobe_consumer. Consumers would never > > > > just have an array of `struct uprobe_consumer *`, because > > > > uprobe_consumer struct is embedded in some other struct, so the array > > > > interface isn't the most convenient. > > > > > > OK, I understand it. > > > > > > > > > > > If you feel strongly, I can do an array, but this necessitates > > > > allocating an extra array *and keeping it* for the entire duration of > > > > BPF multi-uprobe link (attachment) existence, so it feels like a > > > > waste. This is because we don't want to do anything that can fail in > > > > the detachment logic (so no temporary array allocation there). > > > > > > No need to change it, that sounds reasonable. > > > > > > > Great, thanks. > > > > > > > > > > Anyways, let me know how you feel about keeping this callback. > > > > > > IMHO, maybe the interface function is better to change to > > > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > > > side uses a linked list of structure, index access will need to > > > follow the list every time. > > > > This would be problematic. Note how we call get_uprobe_consumer(i, > > ctx) with i going from 0 to N in multiple independent loops. So if we > > are only allowed to ask for the next consumer, then > > uprobe_register_batch and uprobe_unregister_batch would need to build > > its own internal index and remember ith instance. Which again means > > more allocations and possibly failing uprobe_unregister_batch(), which > > isn't great. > > No, I think we can use a cursor variable as; > > int uprobe_register_batch(struct inode *inode, > uprobe_consumer_fn get_uprobe_consumer, void *ctx) > { > void *cur = ctx; > > while ((uc = get_uprobe_consumer()) != NULL) { > ... > } > > cur = ctx; > while ((uc = get_uprobe_consumer()) != NULL) { > ... > } > } > > This can also remove the cnt. Ok, if you prefer this I'll switch. It's a bit more cumbersome to use for callers, but we have one right now, and might have another one, so not a big deal. > > Thank you, > > > > > For now this API works well, I propose to keep it as is. For linked > > list case consumers would need to allocate one extra array or pay the > > price of O(N) search (which might be ok, depending on how many uprobes > > are being attached). But we don't have such consumers right now, > > thankfully. > > > > > > > > Thank you, > > > > > > > > > > > > > > > > > > > > Thank you, > > > > > > > > > > -- > > > > > Masami Hiramatsu (Google) > > > > > > > > > -- > > > Masami Hiramatsu (Google) > > > -- > Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Fri, 28 Jun 2024 09:34:26 -0700 Andrii Nakryiko wrote: > On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu wrote: > > > > On Thu, 27 Jun 2024 09:47:10 -0700 > > Andrii Nakryiko wrote: > > > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > > wrote: > > > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > > Andrii Nakryiko wrote: > > > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > > > > - loff_t ref_ctr_offset, struct > > > > > uprobe_consumer *uc) > > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > > + uprobe_consumer_fn get_uprobe_consumer, void > > > > > *ctx) > > > > > > > > Is this interface just for avoiding memory allocation? Can't we just > > > > allocate a temporary array of *uprobe_consumer instead? > > > > > > Yes, exactly, to avoid the need for allocating another array that > > > would just contain pointers to uprobe_consumer. Consumers would never > > > just have an array of `struct uprobe_consumer *`, because > > > uprobe_consumer struct is embedded in some other struct, so the array > > > interface isn't the most convenient. > > > > OK, I understand it. > > > > > > > > If you feel strongly, I can do an array, but this necessitates > > > allocating an extra array *and keeping it* for the entire duration of > > > BPF multi-uprobe link (attachment) existence, so it feels like a > > > waste. This is because we don't want to do anything that can fail in > > > the detachment logic (so no temporary array allocation there). > > > > No need to change it, that sounds reasonable. > > > > Great, thanks. > > > > > > > Anyways, let me know how you feel about keeping this callback. > > > > IMHO, maybe the interface function is better to change to > > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > > side uses a linked list of structure, index access will need to > > follow the list every time. > > This would be problematic. Note how we call get_uprobe_consumer(i, > ctx) with i going from 0 to N in multiple independent loops. So if we > are only allowed to ask for the next consumer, then > uprobe_register_batch and uprobe_unregister_batch would need to build > its own internal index and remember ith instance. Which again means > more allocations and possibly failing uprobe_unregister_batch(), which > isn't great. No, I think we can use a cursor variable as; int uprobe_register_batch(struct inode *inode, uprobe_consumer_fn get_uprobe_consumer, void *ctx) { void *cur = ctx; while ((uc = get_uprobe_consumer()) != NULL) { ... } cur = ctx; while ((uc = get_uprobe_consumer()) != NULL) { ... } } This can also remove the cnt. Thank you, > > For now this API works well, I propose to keep it as is. For linked > list case consumers would need to allocate one extra array or pay the > price of O(N) search (which might be ok, depending on how many uprobes > are being attached). But we don't have such consumers right now, > thankfully. > > > > > Thank you, > > > > > > > > > > > > > > > Thank you, > > > > > > > > -- > > > > Masami Hiramatsu (Google) > > > > > > -- > > Masami Hiramatsu (Google) -- Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Thu, Jun 27, 2024 at 11:28 PM Masami Hiramatsu wrote: > > On Thu, 27 Jun 2024 09:47:10 -0700 > Andrii Nakryiko wrote: > > > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu > > wrote: > > > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > > Andrii Nakryiko wrote: > > > > > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > > > - loff_t ref_ctr_offset, struct > > > > uprobe_consumer *uc) > > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > > + uprobe_consumer_fn get_uprobe_consumer, void > > > > *ctx) > > > > > > Is this interface just for avoiding memory allocation? Can't we just > > > allocate a temporary array of *uprobe_consumer instead? > > > > Yes, exactly, to avoid the need for allocating another array that > > would just contain pointers to uprobe_consumer. Consumers would never > > just have an array of `struct uprobe_consumer *`, because > > uprobe_consumer struct is embedded in some other struct, so the array > > interface isn't the most convenient. > > OK, I understand it. > > > > > If you feel strongly, I can do an array, but this necessitates > > allocating an extra array *and keeping it* for the entire duration of > > BPF multi-uprobe link (attachment) existence, so it feels like a > > waste. This is because we don't want to do anything that can fail in > > the detachment logic (so no temporary array allocation there). > > No need to change it, that sounds reasonable. > Great, thanks. > > > > Anyways, let me know how you feel about keeping this callback. > > IMHO, maybe the interface function is better to change to > `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller > side uses a linked list of structure, index access will need to > follow the list every time. This would be problematic. Note how we call get_uprobe_consumer(i, ctx) with i going from 0 to N in multiple independent loops. So if we are only allowed to ask for the next consumer, then uprobe_register_batch and uprobe_unregister_batch would need to build its own internal index and remember ith instance. Which again means more allocations and possibly failing uprobe_unregister_batch(), which isn't great. For now this API works well, I propose to keep it as is. For linked list case consumers would need to allocate one extra array or pay the price of O(N) search (which might be ok, depending on how many uprobes are being attached). But we don't have such consumers right now, thankfully. > > Thank you, > > > > > > > > > > Thank you, > > > > > > -- > > > Masami Hiramatsu (Google) > > > -- > Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Thu, 27 Jun 2024 09:47:10 -0700 Andrii Nakryiko wrote: > On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu wrote: > > > > On Mon, 24 Jun 2024 17:21:38 -0700 > > Andrii Nakryiko wrote: > > > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > > - loff_t ref_ctr_offset, struct uprobe_consumer > > > *uc) > > > +int uprobe_register_batch(struct inode *inode, int cnt, > > > + uprobe_consumer_fn get_uprobe_consumer, void *ctx) > > > > Is this interface just for avoiding memory allocation? Can't we just > > allocate a temporary array of *uprobe_consumer instead? > > Yes, exactly, to avoid the need for allocating another array that > would just contain pointers to uprobe_consumer. Consumers would never > just have an array of `struct uprobe_consumer *`, because > uprobe_consumer struct is embedded in some other struct, so the array > interface isn't the most convenient. OK, I understand it. > > If you feel strongly, I can do an array, but this necessitates > allocating an extra array *and keeping it* for the entire duration of > BPF multi-uprobe link (attachment) existence, so it feels like a > waste. This is because we don't want to do anything that can fail in > the detachment logic (so no temporary array allocation there). No need to change it, that sounds reasonable. > > Anyways, let me know how you feel about keeping this callback. IMHO, maybe the interface function is better to change to `uprobe_consumer *next_uprobe_consumer(void **data)`. If caller side uses a linked list of structure, index access will need to follow the list every time. Thank you, > > > > > Thank you, > > > > -- > > Masami Hiramatsu (Google) -- Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Thu, Jun 27, 2024 at 6:04 AM Masami Hiramatsu wrote: > > On Mon, 24 Jun 2024 17:21:38 -0700 > Andrii Nakryiko wrote: > > > -static int __uprobe_register(struct inode *inode, loff_t offset, > > - loff_t ref_ctr_offset, struct uprobe_consumer > > *uc) > > +int uprobe_register_batch(struct inode *inode, int cnt, > > + uprobe_consumer_fn get_uprobe_consumer, void *ctx) > > Is this interface just for avoiding memory allocation? Can't we just > allocate a temporary array of *uprobe_consumer instead? Yes, exactly, to avoid the need for allocating another array that would just contain pointers to uprobe_consumer. Consumers would never just have an array of `struct uprobe_consumer *`, because uprobe_consumer struct is embedded in some other struct, so the array interface isn't the most convenient. If you feel strongly, I can do an array, but this necessitates allocating an extra array *and keeping it* for the entire duration of BPF multi-uprobe link (attachment) existence, so it feels like a waste. This is because we don't want to do anything that can fail in the detachment logic (so no temporary array allocation there). Anyways, let me know how you feel about keeping this callback. > > Thank you, > > -- > Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, 24 Jun 2024 17:21:38 -0700 Andrii Nakryiko wrote: > -static int __uprobe_register(struct inode *inode, loff_t offset, > - loff_t ref_ctr_offset, struct uprobe_consumer *uc) > +int uprobe_register_batch(struct inode *inode, int cnt, > + uprobe_consumer_fn get_uprobe_consumer, void *ctx) Is this interface just for avoiding memory allocation? Can't we just allocate a temporary array of *uprobe_consumer instead? Thank you, -- Masami Hiramatsu (Google)
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Wed, Jun 26, 2024 at 4:27 AM Jiri Olsa wrote: > > On Mon, Jun 24, 2024 at 05:21:38PM -0700, Andrii Nakryiko wrote: > > SNIP > > > + for (i = 0; i < cnt; i++) { > > + uc = get_uprobe_consumer(i, ctx); > > + > > + /* Each consumer must have at least one set consumer */ > > + if (!uc || (!uc->handler && !uc->ret_handler)) > > + return -EINVAL; > > + /* Racy, just to catch the obvious mistakes */ > > + if (uc->offset > i_size_read(inode)) > > + return -EINVAL; > > + if (uc->uprobe) > > + return -EINVAL; > > + /* > > + * This ensures that copy_from_page(), copy_to_page() and > > + * __update_ref_ctr() can't cross page boundary. > > + */ > > + if (!IS_ALIGNED(uc->offset, UPROBE_SWBP_INSN_SIZE)) > > + return -EINVAL; > > + if (!IS_ALIGNED(uc->ref_ctr_offset, sizeof(short))) > > + return -EINVAL; > > + } > > > > - down_write(>register_rwsem); > > - consumer_add(uprobe, uc); > > - ret = register_for_each_vma(uprobe, uc); > > - if (ret) > > - __uprobe_unregister(uprobe, uc); > > - up_write(>register_rwsem); > > + for (i = 0; i < cnt; i++) { > > + uc = get_uprobe_consumer(i, ctx); > > > > - if (ret) > > - put_uprobe(uprobe); > > + uprobe = alloc_uprobe(inode, uc->offset, uc->ref_ctr_offset); > > + if (IS_ERR(uprobe)) { > > + ret = PTR_ERR(uprobe); > > + goto cleanup_uprobes; > > + } > > + > > + uc->uprobe = uprobe; > > + } > > + > > + for (i = 0; i < cnt; i++) { > > + uc = get_uprobe_consumer(i, ctx); > > + uprobe = uc->uprobe; > > + > > + down_write(>register_rwsem); > > + consumer_add(uprobe, uc); > > + ret = register_for_each_vma(uprobe, uc); > > + if (ret) > > + __uprobe_unregister(uprobe, uc); > > + up_write(>register_rwsem); > > + > > + if (ret) { > > + put_uprobe(uprobe); > > + goto cleanup_unreg; > > + } > > + } > > + > > + return 0; > > > > +cleanup_unreg: > > + /* unregister all uprobes we managed to register until failure */ > > + for (i--; i >= 0; i--) { > > + uc = get_uprobe_consumer(i, ctx); > > + > > + down_write(>register_rwsem); > > + __uprobe_unregister(uc->uprobe, uc); > > + up_write(>register_rwsem); > > + } > > +cleanup_uprobes: > > when we jump here from 'goto cleanup_uprobes' not all of the > consumers might have uc->uprobe set up > > perhaps we can set cnt = i - 1 before the goto, or just check uc->uprobe below yep, you are right, I missed this part during multiple rounds of refactorings. I think the `if (uc->uprobe)` check is the cleanest approach here. > > > > + /* put all the successfully allocated/reused uprobes */ > > + for (i = cnt - 1; i >= 0; i--) { > > curious, any reason why we go from the top here? No particular reason. This started as (i = i - 1; i >= 0; i--), but then as I kept splitting steps I needed to do this over all uprobes. Anyways, I can do a clean `i = 0; i < cnt; i++` with `if (uc->uprobe)` check. > > thanks, > jirka > > > + uc = get_uprobe_consumer(i, ctx); > > + > > + put_uprobe(uc->uprobe); > > + uc->uprobe = NULL; > > + } > > return ret; > > } > > > > int uprobe_register(struct inode *inode, struct uprobe_consumer *uc) > > { > > - return __uprobe_register(inode, uc->offset, uc->ref_ctr_offset, uc); > > + return uprobe_register_batch(inode, 1, uprobe_consumer_identity, uc); > > } > > EXPORT_SYMBOL_GPL(uprobe_register); > > > > -- > > 2.43.0 > >
Re: [PATCH 06/12] uprobes: add batch uprobe register/unregister APIs
On Mon, Jun 24, 2024 at 05:21:38PM -0700, Andrii Nakryiko wrote: SNIP > + for (i = 0; i < cnt; i++) { > + uc = get_uprobe_consumer(i, ctx); > + > + /* Each consumer must have at least one set consumer */ > + if (!uc || (!uc->handler && !uc->ret_handler)) > + return -EINVAL; > + /* Racy, just to catch the obvious mistakes */ > + if (uc->offset > i_size_read(inode)) > + return -EINVAL; > + if (uc->uprobe) > + return -EINVAL; > + /* > + * This ensures that copy_from_page(), copy_to_page() and > + * __update_ref_ctr() can't cross page boundary. > + */ > + if (!IS_ALIGNED(uc->offset, UPROBE_SWBP_INSN_SIZE)) > + return -EINVAL; > + if (!IS_ALIGNED(uc->ref_ctr_offset, sizeof(short))) > + return -EINVAL; > + } > > - down_write(>register_rwsem); > - consumer_add(uprobe, uc); > - ret = register_for_each_vma(uprobe, uc); > - if (ret) > - __uprobe_unregister(uprobe, uc); > - up_write(>register_rwsem); > + for (i = 0; i < cnt; i++) { > + uc = get_uprobe_consumer(i, ctx); > > - if (ret) > - put_uprobe(uprobe); > + uprobe = alloc_uprobe(inode, uc->offset, uc->ref_ctr_offset); > + if (IS_ERR(uprobe)) { > + ret = PTR_ERR(uprobe); > + goto cleanup_uprobes; > + } > + > + uc->uprobe = uprobe; > + } > + > + for (i = 0; i < cnt; i++) { > + uc = get_uprobe_consumer(i, ctx); > + uprobe = uc->uprobe; > + > + down_write(>register_rwsem); > + consumer_add(uprobe, uc); > + ret = register_for_each_vma(uprobe, uc); > + if (ret) > + __uprobe_unregister(uprobe, uc); > + up_write(>register_rwsem); > + > + if (ret) { > + put_uprobe(uprobe); > + goto cleanup_unreg; > + } > + } > + > + return 0; > > +cleanup_unreg: > + /* unregister all uprobes we managed to register until failure */ > + for (i--; i >= 0; i--) { > + uc = get_uprobe_consumer(i, ctx); > + > + down_write(>register_rwsem); > + __uprobe_unregister(uc->uprobe, uc); > + up_write(>register_rwsem); > + } > +cleanup_uprobes: when we jump here from 'goto cleanup_uprobes' not all of the consumers might have uc->uprobe set up perhaps we can set cnt = i - 1 before the goto, or just check uc->uprobe below > + /* put all the successfully allocated/reused uprobes */ > + for (i = cnt - 1; i >= 0; i--) { curious, any reason why we go from the top here? thanks, jirka > + uc = get_uprobe_consumer(i, ctx); > + > + put_uprobe(uc->uprobe); > + uc->uprobe = NULL; > + } > return ret; > } > > int uprobe_register(struct inode *inode, struct uprobe_consumer *uc) > { > - return __uprobe_register(inode, uc->offset, uc->ref_ctr_offset, uc); > + return uprobe_register_batch(inode, 1, uprobe_consumer_identity, uc); > } > EXPORT_SYMBOL_GPL(uprobe_register); > > -- > 2.43.0 >