[RFC] Multiple kprobes at an address redux (take3)
Hien Nguyen
hien@us.ibm.com
Mon Apr 11 19:55:00 GMT 2005
Hi Ananth,
The multiprobe is include in the 2.6.12-rc2-mm3 kernel (I guess it is
Prasanna's patch). I wonder if you notice that.
Hien.
> Hi,
>
> This patch builds on Suparna's idea of a layered approach to providing
> multiple kprobe support at an address.
>
> Salient features:
>
> - The interfaces to register/unregister kprobes remains the same.
> - The kprobes infrastructure takes care if a kprobe already exists at
> the requested address.
> - No new structures are added.
> - Current kprobes code not modified (except to renaming of routines).
> - Architecture agnostic approach.
> - We now track the currently executing kprobe (in case of multiple
> kprobes at the address). In the case of a fault during handler
> execution, only the current kprobe's fault handler is invoked.
> This insulates other kprobes at the location from the fault.
>
> Details of the design are in the accompanying document.
>
> Patch is against 2.6.12-rc2-mm2.
>
> Thanks,
> Ananth
>
>------------------------------------------------------------------------
>
>Draft of 11 April 2005.
>
>1 Multiple kprobes at an address
>
>1.1 Overview
>
>One of the requirements of SystemTAP is the capability of defining
>multiple kprobes per probe address. Current design of kprobes does
>not allow the registration of more than one kprobe at an address.
>
>This is an attempt to come up with a design that will enable a user
>to register multiple kprobes at a given address.
>
>
>2 Design
>
>2.1 struct kprobe
>
>Struct kprobe will now have an additional "list" field to
>facilitate list.h usage.
>
>struct kprobe {
> struct hlist_head hlist;
> struct list_head list;
> kprobe_opcode_t *addr;
> kprobe_pre_handler_t pre_handler;
> kprobe_post_handler_t post_handler;
> kprobe_fault_handler_t fault_handler;
> kprobe_break_handler_t break_handler;
> kprobe_opcode_t opcode;
> struct arch_specific_insn ainsn;
>};
>
>NOTE: It is always a good idea to set the handler fields not being
>used explicitly to NULL. The design determines if a probe is a
>jprobe by virtue of the fact that only jprobes define break_handlers.
>
>2.2 Base kprobes infrastructure
>
>With this design, the "current" register/unregister_kprobe()
>routines have been renamed register/unregister_kprobe_single()
>and a wrapper takes care of invoking the _single() versions when
>necessary. The _single() versions are NOT exported.
>
>With this change:
>
>- The current interfaces (register/unregister_kprobes()) continue
> to be the only ones available
>- The interfaces can be used without bothering if a kprobe already
> existed at the required address. The wrappers take care of that
>- The current base kprobes code does not change, except of course,
> the renaming.
>
>2.2.1 Registering a kprobe
>
>1. Kprobes are registered with a call to register_kprobe().
>2. The kprobe infrastructure determines if a probe already exists at
> the requested location.
>3. If this is a new kprobe, registration proceeds as it done
> currently.
>4. If a kprobe already exists at the requested address:
> a. If the call is to register a jprobe, the new handler
> registration fails since we can't have a jprobe and a kprobe
> at the same address. Whether a registration is for a jprobe
> or not is determined by the presence of a break_handler.
> b. If the call is to register a kprobe and if the pre_handler
> for the existing kprobe is not aggr_pre_handler, we then:
> 1. Allocate a new struct kprobe (manager kprobe).
> 2. Populate the manager kprobe with appropriate values for
> the addr, opcode, ainsn fields
> 3. Set the manager kprobe's handlers to custom handlers
> with list walk logic
> 4. Add the existing kprobe and the new kprobe to be
> manager kprobe's "list"
> c. If the call is to register a kprobe and if the pre_handler
> for the existing kprobe is aggr_pre_handler, it means that
> we already have an manager kprobe defined at this address.
> Unless the new probe to register is not a jprobe, it is
> initialized and added to the manager kprobe's list.
> The registration fails if the new probe is a jprobe.
>
>2.2.2 Unregistering a kprobe
>
>1. Kprobes are unregistered by making a call to unregister_kprobe()
>2. If a kprobe exists and its pre_handler is aggr_pre_handler(),
> then, this is one of the possibly many kprobes at the address:
> a. Delete the kprobe from the manager kprobe's list.
> b. If the manager kprobe's "list" is empty, then free the
> manager kprobe.
>3. If the kprobe to be unregistered is the only one at the address,
> remove it as is done currently.
>
>2.3 Handler invocation
>
>Multiple kprobes at an address are managed by a "manager kprobe"
>allocated and freed by the kernel. All kprobes at the address are
>added to the "list" of the manager kprobe. The handlers of the
>manager kprobe are aliased to aggregate handlers that have in
>them the required list walk logic to invoke the registered
>kprobes' handlers.
>
>This is accomplished by aliasing the aggr_probe's kprobe handlers
>to be:
>
> ap->kp.pre_handler = aggr_pre_handler;
> ap->kp.post_handler = aggr_post_handler;
> ap->kp.fault_handler = aggr_fault_handler;
>
>Please refer to the accompanying patch for details about these
>handlers.
>
>
>2.4 More points on handler invocation
>
>2.4.1 Pre handlers
>
>We care about the return code for a pre_handler only in case
>of a jprobe. Return code from all other (read kprobe) handlers
>are ignored. But, it is advised that all pre_handlers return 0
>for compatibility sake with future enhancements.
>
>For a multiple kprobes at an address, all pre handlers will be
>called.
>
>2.4.2 Post handlers
>
>All post handlers will be called.
>
>2.4.3 Fault handlers
>
>We now track, using a "curr_kprobe", which kprobe handler
>(pre/post) is currently executing. When a fault occurs and the
>curr_kprobe is not NULL, it means that the fault occured while
>executing the user defined handler. In that case, the
>aggr_fault_handler invokes just the fault_handler that is
>defined for the curr_kprobe
>
>No other fault handlers are called.
>
>NOTE:
>
>a. Fault handlers are meant not just to handle faults during
> the execution of the handlers, but also in cases when we fault
> while single-stepping out of line. This is the most common case
> with user-space probes, but, the design has to keep this in
> mind so as to accomodate addition of user-space probes at a
> later date.
>
>2.5 Arch specific implementations
>
>This design does not require any changes to the arch specific
>kprobe implementations.
>
>
>3 Caveats
>
>- It is assumed that the handlers written behave well. :-)
>- Multiple kprobes cannot be registered at a location that already
> has a jprobe registered. In other words, to register a jprobe,
> no other kprobes must be registered at that address and
> vice-versa.
>- The current design builds on the existing kprobe locking
> infrastructure. It is hoped that this design (or a variant)
> can be the base for the scalability changes envisaged.
> Suggestions are welcome.
>- There is a small race window while registering a kprobe wherein,
> the registration may fail with an -EEXIST. This limitation can
> be resolved if/when the x86_64 "wart" is removed. It can also
> be tackled when we work on the kprobes scalability issues.
>
>
>4. TODO
>
>a. Indicate that a kprobe is disabled when disarm_kprobe() is
> called.
>b. jprobe/kprobe coexistance.
>
>
>5 Questions
>
>- Is the design implementable on all architectures kprobes is
> currently available?
> A: Already done! :-)
> With the new design, no changes are required for the arch
> specific kprobe implementations.
>
>
>
>------------------------------------------------------------------------
>
>diff -Naurp temp/linux-2.6.12-rc2/include/linux/kprobes.h linux-2.6.12-rc2/include/linux/kprobes.h
>--- temp/linux-2.6.12-rc2/include/linux/kprobes.h 2005-04-04 12:38:05.000000000 -0400
>+++ linux-2.6.12-rc2/include/linux/kprobes.h 2005-04-11 13:37:10.000000000 -0400
>@@ -43,6 +43,9 @@ typedef int (*kprobe_fault_handler_t) (s
> struct kprobe {
> struct hlist_node hlist;
>
>+ /* list of kprobes for multi-handler support */
>+ struct list_head list;
>+
> /* location of the probe point */
> kprobe_opcode_t *addr;
>
>diff -Naurp temp/linux-2.6.12-rc2/kernel/kprobes.c linux-2.6.12-rc2/kernel/kprobes.c
>--- temp/linux-2.6.12-rc2/kernel/kprobes.c 2005-04-04 12:40:16.000000000 -0400
>+++ linux-2.6.12-rc2/kernel/kprobes.c 2005-04-11 14:05:02.000000000 -0400
>@@ -44,6 +44,7 @@ static struct hlist_head kprobe_table[KP
>
> unsigned int kprobe_cpu = NR_CPUS;
> static DEFINE_SPINLOCK(kprobe_lock);
>+static struct kprobe *curr_kprobe;
>
> /* Locks kprobe: irqs must be disabled */
> void lock_kprobes(void)
>@@ -73,7 +74,50 @@ struct kprobe *get_kprobe(void *addr)
> return NULL;
> }
>
>-int register_kprobe(struct kprobe *p)
>+int aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
>+{
>+ struct kprobe *kp;
>+
>+ list_for_each_entry(kp, &p->list, list) {
>+ if (kp->pre_handler) {
>+ curr_kprobe = kp;
>+ kp->pre_handler(kp, regs);
>+ curr_kprobe = NULL;
>+ }
>+ }
>+ return 0;
>+}
>+
>+void aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
>+ unsigned long flags)
>+{
>+ struct kprobe *kp;
>+
>+ list_for_each_entry(kp, &p->list, list) {
>+ if (kp->post_handler) {
>+ curr_kprobe = kp;
>+ kp->post_handler(kp, regs, flags);
>+ curr_kprobe = NULL;
>+ }
>+ }
>+ return;
>+}
>+
>+int aggr_fault_handler(struct kprobe *p, struct pt_regs *regs, int trapnr)
>+{
>+ /*
>+ * if we faulted "during" the execution of a user specified
>+ * probe handler, invoke just that probe's fault handler
>+ * to see if it can handle the fault.
>+ */
>+ if (curr_kprobe && curr_kprobe->fault_handler) {
>+ if (curr_kprobe->fault_handler(curr_kprobe, regs, trapnr))
>+ return 1;
>+ }
>+ return 0;
>+}
>+
>+static int register_kprobe_single(struct kprobe *p)
> {
> int ret = 0;
> unsigned long flags = 0;
>@@ -104,7 +148,7 @@ rm_kprobe:
> return ret;
> }
>
>-void unregister_kprobe(struct kprobe *p)
>+static void unregister_kprobe_single(struct kprobe *p)
> {
> unsigned long flags;
> arch_remove_kprobe(p);
>@@ -116,6 +160,120 @@ void unregister_kprobe(struct kprobe *p)
> spin_unlock_irqrestore(&kprobe_lock, flags);
> }
>
>+static inline void add_aggr_kprobe(struct kprobe *ap, struct kprobe *p)
>+{
>+ ap->addr = p->addr;
>+ ap->opcode = p->opcode;
>+ memcpy(&ap->ainsn, &p->ainsn,
>+ sizeof(struct arch_specific_insn));
>+
>+ ap->pre_handler = aggr_pre_handler;
>+ ap->post_handler = aggr_post_handler;
>+ ap->fault_handler = aggr_fault_handler;
>+
>+ INIT_LIST_HEAD(&ap->list);
>+ list_add(&p->list, &ap->list);
>+
>+ INIT_HLIST_NODE(&ap->hlist);
>+ hlist_del(&p->hlist);
>+ hlist_add_head(&ap->hlist,
>+ &kprobe_table[hash_ptr(ap->addr,
>+ KPROBE_HASH_BITS)]);
>+}
>+
>+static int register_aggr_kprobe(struct kprobe *p)
>+{
>+ int ret = 0;
>+ unsigned long flags = 0;
>+ struct kprobe *old_p, *ap;
>+
>+ ap = kcalloc(1, sizeof(struct kprobe), GFP_KERNEL);
>+ if (!ap)
>+ return -ENOMEM;
>+
>+ spin_lock_irqsave(&kprobe_lock, flags);
>+ old_p = get_kprobe(p->addr);
>+ if (old_p) {
>+ if (old_p->break_handler || p->break_handler) {
>+ ret = -EEXIST;
>+ goto free_ap;
>+ } else if (old_p->pre_handler == aggr_pre_handler) {
>+ list_add(&p->list, &old_p->list);
>+ goto free_ap;
>+ } else {
>+ add_aggr_kprobe(ap, old_p);
>+ list_add(&p->list, &ap->list);
>+ goto out;
>+ }
>+ } else {
>+ /*
>+ * kprobe at this addr was deleted between the
>+ * check in register_kprobe() and the get_kprobe()
>+ * call above
>+ */
>+ spin_unlock_irqrestore(&kprobe_lock, flags);
>+ ret = register_kprobe_single(p);
>+ if (ret == -EEXIST) {
>+ spin_lock_irqsave(&kprobe_lock, flags);
>+ old_p = get_kprobe(p->addr);
>+ if (old_p) {
>+ add_aggr_kprobe(ap, old_p);
>+ list_add(&p->list, &ap->list);
>+ }
>+ goto out;
>+ } else
>+ kfree(ap);
>+ return ret;
>+ }
>+free_ap:
>+ kfree(ap);
>+out:
>+ spin_unlock_irqrestore(&kprobe_lock, flags);
>+ return ret;
>+}
>+
>+static void unregister_aggr_kprobe(struct kprobe *old_p,
>+ struct kprobe *p, unsigned long flags)
>+{
>+ /* we have taken the spinlock in unregister_kprobe() */
>+ list_del(&p->list);
>+
>+ if (list_empty(&old_p->list)) {
>+ spin_unlock_irqrestore(&kprobe_lock, flags);
>+ unregister_kprobe_single(old_p);
>+ kfree(old_p);
>+ return;
>+ }
>+ spin_unlock_irqrestore(&kprobe_lock, flags);
>+}
>+
>+int register_kprobe(struct kprobe *p)
>+{
>+ int ret = 0;
>+
>+ ret = register_kprobe_single(p);
>+ if (ret == -EEXIST)
>+ ret = register_aggr_kprobe(p);
>+
>+ return ret;
>+}
>+
>+void unregister_kprobe(struct kprobe *p)
>+{
>+ unsigned long flags;
>+ struct kprobe *old_p;
>+
>+ spin_lock_irqsave(&kprobe_lock, flags);
>+ old_p = get_kprobe(p->addr);
>+
>+ if (old_p && (old_p->pre_handler == aggr_pre_handler))
>+ unregister_aggr_kprobe(old_p, p, flags);
>+ else if (old_p == p) {
>+ spin_unlock_irqrestore(&kprobe_lock, flags);
>+ unregister_kprobe_single(p);
>+ }
>+}
>+
> static struct notifier_block kprobe_exceptions_nb = {
> .notifier_call = kprobe_exceptions_notify,
> .priority = 0x7fffffff /* we need to notified first */
>
>
More information about the Systemtap
mailing list