[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

user-space breakpoint support

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

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

- 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.)

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


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

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.

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]