Ananth, Srikar, and I have been kicking this around for a few days. With Roland rumored to be back and Chris talking about adding breakpoint support to froggy, we figure that now's a good time to post this for your consideration. Comments welcome.
By the way, there are many references to single-stepping out of line (SSOL), which is used by kprobes and uprobes. Section 6.1 of ols.108.redhat.com/2007/Reprints/keniston-Reprint.pdf provides an explanation of SSOL. Jim Keniston User-space Breakpoint Support (ubp) =================================== Here is a plan for factoring out a portion of uprobes so that it can be used by multiple clients -- e.g., froggy, ptrace++, or some new layer of utrace. This subsystem encapsulates the architecture-specific components of uprobes (excluding uretprobes): - instruction validation and analysis - breakpoint insertion/removal - post-single-step cleanup Requirements: ------------- - Support probing of multithreaded apps using single-stepping out of line (SSOL). - Support single-stepping inline as a fallback (for architectures with no SSOL support yet; for instructions where SSOL is tough; for clients that can't provide an unending supply of SSOL slots). - Be configurable as a kernel subsystem or kernel module. Non-requirements: ----------------- - Client, not ubp, creates and tracks breakpoint/probepoint objects. In particular, ubp doesn't remember anything about ubp objects -- including their addresses -- between calls into the ubp API. - Client, not ubp, creates and tracks per-task objects. - Client, not ubp, creates and manages SSOL slots. - utrace tie-ins. The client is responsible for creating utrace engines, quiescing threads, and handling SIGTRAP events, as needed. - If multiple ubp clients operate simultaneously, it's up to them to coordinate their efforts. E.g., if Client A has set a breakpoint at address X in process Y, ubp will reject Client B's subsequent request to do the same (since there's already a breakpoint there). [But see enhancement request #1.] Stretch (?) Requirement: ------------------------ - Support independent operation of different clients probing different processes. Detect and prevent collisions. Enhancement Requests: --------------------- Review has yielded the following enhancement requests: 1. Provide an API that enables different clients to place probes at the same virtual address in the same process. (But what other shared-among-clients resources would need to be managed? SSOL slots, at least.) Issues: ------- - SSOL vma wants to be allocated by probed process. This could complicate a ubp-based enhancement to ptrace. ========================================================================== Client API: ----------- int ubp_init(u16 *strategies); int ubp_insert_bkpt(struct task_struct *tsk, struct ubp_bkpt *ubp); int ubp_pre_sstep(struct task_struct *tsk, struct ubp_bkpt *ubp, struct ubp_task_arch_info *tskinfo, struct pt_regs *regs); int ubp_post_sstep(struct task_struct *tsk, struct ubp_bkpt *ubp, struct ubp_task_arch_info *tskinfo, struct pt_regs *regs); int ubp_cancel_ssol(struct task_struct *tsk, struct ubp_bkpt *ubp); int ubp_remove_bkpt(struct task_struct *tsk, struct ubp_bkpt *ubp); Typical usage by client: ------------------------ Call ubp_init(). For each probepoint: - Call ubp_insert_bkpt(). - At some point before calling ubp_pre_sstep() for that probepoint, allocate an SSOL slot and set ubp->ssol_vaddr. If no SSOL slot is available, call ubp_cancel_ssol(). - Each time the probepoint is hit: - Run whatever instrumentation is associated with that probepoint -- ubp plays no part here. - Call ubp_pre_sstep(). - Single-step the CPU. - Call ubp_post_sstep(). - When you're done probing, call ubp_remove_bkpt(). /** * ubp_init - initialize the ubp data structures * @strategies indicates which breakpoint-related strategies are * supported by the client: * %UBP_HNT_INLINE: Client supports only single-stepping inline. * Otherwise client must provide an instruction slot * (UBP_SSOL_SLOT_BYTES bytes) in the probed process's address * space for each instruction to be single-stepped out of line. * %UBP_HNT_TSKINFO: Client can provide and maintain one * @ubp_task_arch_info object for each probed task. (Failure to * support this will prevent SSOL of rip-relative instructions on * x86_64, at least.) * Upon return, @strategies is updated to reflect those strategies * required by this particular architecture's implementation of ubp: * %UBP_HNT_INLINE: Architecture or client supports only * single-stepping inline. * %UBP_HNT_TSKINFO: Architecture uses @ubp_task_arch_info, and will * expect it to be passed to @ubp_pre_sstep() and @ubp_post_sstep() * as needed (see @ubp_insert_bkpt()). * Possible errors: * -%ENOSYS: ubp not supported for this architecture. * -%EINVAL: unrecognized flags in @strategies */ /** * ubp_insert_bkpt - insert breakpoint * Insert a breakpoint into the process that includes @tsk, at the * virtual address @ubp->vaddr. * * @ubp->strategy affects how this breakpoint will be handled: * %UBP_HNT_INLINE: Probed instruction will be single-stepped inline. * %UBP_HNT_TSKINFO: As above. * %UBP_HNT_PERMSL: An SSOL instruction slot in the probed process's * address space has been allocated to this probepoint, and will * remain so allocated as long as it's needed. @ubp->ssol_vaddr is * its address. (This slot can be reallocated if * @ubp_insert_bkpt() fails.) The client is NOT required to * allocate an instruction slot before calling @ubp_insert_bkpt(). * @ubp_insert_bkpt() updates @ubp->strategy as needed: * %UBP_HNT_INLINE: Architecture or client cannot do SSOL for this * probepoint. * %UBP_HNT_TSKINFO: @ubp_task_arch_info will be used for this * probepoint. * * All threads of the probed process must be stopped while * @ubp_insert_bkpt() runs. * * Possible errors: * -%ENOSYS: ubp not supported for this architecture * -%EINVAL: unrecognized/invalid strategy flags * -%EINVAL: invalid instruction address * -%ESRCH: no such process * -%EEXIST: breakpoint instruction already exists at that address * -%EPERM: cannot probe this instruction * -%EFAULT: failed to insert breakpoint instruction * [TBD: Validate ssol_vaddr?] */ /** * ubp_pre_sstep - prepare to single-step the probed instruction * @tsk: the probed task * @ubp: the probepoint information, as returned by @ubp_insert_bkpt(). * Unless the %UBP_HNT_INLINE flag is set in @ubp->strategy, * @ubp->ssol_vaddr must be the address of an SSOL instruction slot * that is allocated to this probepoint at least until after the * completion of @ubp_post_sstep(), and populated with the contents * of @ubp->insn. [Need to be more precise here to account for * untimely exit or UBP_HNT_BOOSTED.] * @tskinfo: points to a @ubp_task_arch_info object for @tsk, if * the %UBP_HNT_TSKINFO flag is set in @ubp->strategy. * @regs: reflects the saved user state of @tsk. @ubp_pre_sstep() * adjusts this. In particular, the instruction pointer is set * to the instruction to be single-stepped. * Possible errors: * -%EFAULT: Failed to read or write @tsk's address space as needed. * * The client must ensure that the contents of @ubp are not * changed during the single-step operation -- i.e., between when * @ubp_pre_sstep() is called and when @ubp_post_sstep() returns. * Additionally, if single-stepping inline is used for this probepoint, * the client must serialize the single-step operation (so multiple * threads don't step on each other while the opcode replacement is * taking place). */ /** * ubp_post_sstep - prepare to resume execution after single-step * @tsk: the probed task * @ubp: the probepoint information, as with @ubp_pre_sstep() * @tskinfo: the @ubp_task_arch_info object, if any, passed to * @ubp_pre_sstep() * @regs: reflects the saved state of @tsk after the single-step * operation. @ubp_post_sstep() adjusts @tsk's state as needed, * including pointing the instruction pointer at the instruction * following the probed instruction. * Possible errors: * -%EFAULT: Failed to read or write @tsk's address space as needed. */ /** * ubp_cancel_ssol - cancel SSOL for this probepoint * @tsk: a task in the probed process * @ubp: the probepoint information * Switch @ubp's single-stepping strategy from out-of-line to inline. * If the client employs lazy SSOL-slot allocation, it can call * this function if it determines that it can't provide an SSOL * slot for @ubp. @ubp_cancel_ssol() adjusts @ubp appropriately. * * @ubp_cancel_ssol()'s behavior is undefined if @ubp_pre_sstep() has * already been called for @ubp. * * Possible errors: * Can't think of any yet. */ /** * ubp_remove_bkpt - remove breakpoint * For the process that includes @tsk, remove the breakpoint specified * by @ubp. * * Possible errors: * -%EINVAL: @ubp->vaddr is not a valid instruction address. * -%ENOENT: There is no breakpoint instruction at @ubp->vaddr. * -%EFAULT: Failed to read/write @tsk's address space as needed. */ ========================================================================== ubp Data Structures and Macros: ------------------------------- /** * Strategy hints: * * %UBP_HNT_INLINE: Specifies that the instruction must * be single-stepped inline. Can be set by the caller of * @arch->analyze_insn() -- e.g., if caller is out of SSOL slots -- * or by @arch->analyze_insn() if there's no viable SSOL strategy * for that instruction. Ignored in arch->strategies. * * %UBP_HNT_SSOL: Set in @arch->strategies if the architecture * supports SSOL. Ignored otherwise. * * %UBP_HNT_PERMSL: Specifies that the instruction slot whose * address is @ubp->ssol_vaddr is assigned to @ubp for the life of * the process. Can be used by @arch->analyze_insn() to simplify * SSOL in some cases. Ignored in @arch->strategies. * * %UBP_HNT_TSKINFO: Set in @arch->strategies if the architecture's * SSOL handling requires the preservation of special * task-specific info between the calls to @arch->pre_ssol() * and @arch->post_ssol(). (E.g., SSOL of x86_64 rip-relative * instructions uses a scratch register, whose value is saved * by pre_ssol() and restored by post_ssol().) The caller * of @arch->analyze_insn() should set %UBP_HNT_TSKINFO in * @ubp->strategy if it's set in @arch->strategies and the caller * can maintain a @ubp_task_arch_info object for each probed task. * @arch->analyze_insn() should leave this flag set in @ubp->strategy * if it needs to use the per-task @ubp_task_arch_info object. */ #define UBP_HNT_INLINE 0x1 /* Single-step this insn inline. */ #define UBP_HNT_SSOL 0x2 /* SSOL is supported. */ #define UBP_HNT_PERMSL 0x4 /* SSOL slot assignment is permanent */ #define UBP_HNT_TSKINFO 0x8 /* SSOL requires ubp_task_arch_info */ /* For future consideration... */ #define UBP_HNT_SHAREANY 0x10 /* Consider all insns for sharing of SSOL slots */ #define UPB_HNT_SHARELST 0x20 /* Consider insns from arch-specific list */ #define UBP_HNT_BOOSTBL 0x40 /* Insn can be boosted. */ #define UBP_HNT_BOOSTED 0x80 /* Insn has been boosted: no single-step needed */ /** * struct ubp_bkpt - user-space breakpoint/probepoint * * @vaddr: virtual address of probepoint * @ssol_vaddr: virtual address of SSOL slot assigned to this probepoint * @opcode: copy of opcode at @vaddr * @insn: typically a copy of the instruction at @vaddr. More * precisely, this is the instruction (stream) that will be * executed in place of the original instruction. * @strategy: hints about how this instruction will be single-stepped * @fixups: set of fixups to be executed by @arch->post_ssol() * @arch_info: architecture-specific info about this probepoint */ struct ubp_bkpt { unsigned long vaddr; unsigned long ssol_vaddr; ubp_opcode_t opcode; u8 insn[UBP_SSOL_SLOT_BYTES]; u16 strategy; u16 fixups; struct ubp_bkpt_arch_info arch_info; }; /* Post-single-step fixups. Some architectures may define others. */ #define UPB_FIX_NONE 0x0 /* No fixup needed */ #define UBP_FIX_IP 0x1 /* Adjust IP back to vicinity of actual insn */ #define UBP_FIX_CALL 0x2 /* Adjust the return address of a call insn */ #ifndef UPB_FIX_DEFAULT #define UPB_FIX_DEFAULT UBP_FIX_IP #endif ========================================================================== Architecture-specific Underpinnings: ------------------------------------ A port of ubp consists of the following: - Populating struct ubp_arch_info (see below) with the appropriate parameters and functions. - Defining the ubp_opcode_t typedef, an intergral type of appropriate size to hold the architecture's breakpoint instruction. - Defining the UBP_SSOL_SLOT_BYTES macro, which is the number of bytes the client needs to allocate for an SSOL slot. Typically, this is the same as arch->max_insn_bytes. (See "uprobe booster for x86_64" in PR 5509 for the only situation I can think of where it wouldn't be.) - Defining the (typically empty) structs ubp_task_arch_info and ubp_bkpt_arch_info. Note: arch->foo is shorthand for ubp_arch_info.foo. /** * struct ubp_arch_info - architecture-specific parameters and functions * * Most architectures can use the default versions of @read_opcode(), * @set_bkpt(), @set_orig_insn(), and @is_bkpt_insn(); ia64 is an * exception. All functions (including @validate_address()) can assume * that the caller has verified that the probepoint's virtual address * resides in an executable VM area. * * @bkpt_insn: * The architecture's breakpoint instruction. This is used by * the default versions of @set_bkpt(), @set_orig_insn(), and * @is_bkpt_insn(). * @max_insn_bytes: * The maximum length, in bytes, of an instruction in this * architecture. This must be <= UBP_SSOL_SLOT_BYTES; * @strategies: * Bit-map of %UBP_HNT_* values recognized by this architecture. * Include %UBP_HNT_SSOL if this architecture supports * single-stepping out of line. Include %UBP_HNT_TSKINFO if * SSOL of at least some instructions requires communication of * per-task state between @pre_ssol() and @post_ssol(). * @set_ip: * Set the instruction pointer in @regs to @vaddr. * @validate_address: * Return 0 if @vaddr is a valid instruction address, or a negative * errno (typically -%EINVAL) otherwise. If you don't provide * @validate_address(), any address will be accepted. * @read_opcode: * For task @tsk, read the opcode at @vaddr and store it in * @opcode. Return 0 (success) or a negative errno. Defaults to * @ubp_read_opcode(). * @set_bkpt: * For task @tsk, store @bkpt_insn at @ubp->vaddr. Return 0 * (success) or a negative errno. Defaults to @ubp_set_bkpt(). * @set_orig_insn: * For task @tsk, restore the original opcode (@ubp->opcode) at * @ubp->vaddr. If @check is true, first verify that there's * actually a breakpoint instruction there. Return 0 (success) or * a negative errno. Defaults to @ubp_set_orig_insn(). * @is_bkpt_insn: * Return %true if @ubp->opcode is @bkpt_insn. Defaults to * @ubp_is_bkpt_insn(), which just tests (ubp->opcode == * arch->bkpt_insn). * @analyze_insn: * Analyze @ubp->insn. Return 0 if @ubp->insn is an instruction * you can probe, or a negative errno (typically -%EPERM) * otherwise. The caller sets @ubp->strategy to %UBP_HNT_INLINE * to suppress SSOL for this instruction (e.g., because we're * out of SSOL slots). If the instruction can be probed but * can't be single-stepped out of line, set @ubp->strategy to * %UBP_HNT_INLINE. Otherwise, determine what sort of SSOL-related * fixups @post_ssol() (and possibly @pre_ssol()) will need * to do for this instruction, and annotate @ubp accordingly. * You may modify @ubp->insn (e.g., the x86_64 port does this * for rip-relative instructions), but if you do so, you should * retain a copy in @ubp->arch_info in case you have to revert * to single-stepping inline (see @cancel_ssol()). * @pre_ssol: * Called just before single-stepping the instruction associated * with @ubp out of line. @ubp->ssol_vaddr is the address in * @tsk's virtual address space where @ubp->insn has been copied. * @pre_ssol() should at least set the instruction pointer in * @regs to @ubp->ssol_vaddr -- which is what the default, * @ubp_pre_ssol(), does. If @ubp->strategy includes the * %UBP_HNT_TSKINFO flag, then @tskinfo points to a per-task * copy of struct ubp_task_arch_info. * @post_ssol: * Called after single-stepping the instruction associated with * @ubp out of line. @post_ssol() should perform the fixups * specified in @ubp->fixups, which includes ensuring that the * instruction pointer in @regs points at the next instruction in * the probed instruction stream. @tskinfo is as for @pre_ssol(). * You must provide this function. * @cancel_ssol: * The instruction associated with @ubp cannot be single-stepped * out of line after all. (This can happen when SSOL slots * are lazily assigned, and we run out of slots before we * hit this breakpoint. This function should never be called * if @analyze_insn() was previously called for @ubp with a * non-zero value of @ubp->ssol_vaddr and with %UBP_HNT_PERMSL * set in @ubp->strategy.) Adjust @ubp as needed so it can be * single-stepped inline. Omit this function if you don't need it. */ struct ubp_arch_info { ubp_opcode_t bkpt_insn; u8 max_insn_bytes; u16 strategies; void (*set_ip)(struct pt_regs *regs, unsigned long vaddr); int (*validate_address)(unsigned long vaddr); int (*read_opcode)(struct task_struct *tsk, unsigned long vaddr, ubp_opcode_t *opcode); int (*set_bkpt)(struct task_struct *tsk, struct ubp_bkpt *ubp); int (*set_orig_insn)(struct task_struct *tsk, struct ubp_bkpt *ubp, bool check); bool (*is_bkpt_insn)(struct ubp_bkpt *ubp); int (*analyze_insn)(struct task_struct *tsk, struct ubp_bkpt *ubp); int (*pre_ssol)(struct task_struct *tsk, struct ubp_bkpt *ubp, struct ubp_task_arch_info *tskinfo, struct pt_regs *regs); int (*post_ssol)(struct task_struct *tsk, struct ubp_bkpt *ubp, struct ubp_task_arch_info *tskinfo, struct pt_regs *regs); void (*cancel_ssol)(struct task_struct *tsk, struct ubp_bkpt *ubp); }; /* * NOTE: ubp_arch_info contains no "pre" or "post" callbacks for * single-stepping inline because I think ubp can handle that with * architecture-independent code. */