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.
 */


Reply via email to