tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

ptrace(2) interface for hardware watchpoints (breakpoints)



I've prepared interface for hardware watchpoints:

    http://netbsd.org/~kamil/patch-00023-ptrace-watchpoints.txt

For the purpose of this task I propose to call monitoring operations of
data as "watchpoints" and monitoring of instruction's executaion as
"breakpoints". However this interface is not limited to neither, as a
port might expose any other type of events to be monitored (like branch
instructions). Sometimes I'm referring to "hardware" watchpoints it
means just any type of trap realized with hardware association without
need for software trap.



My goals of this project:
 - restrict code in the kernel-side to functional minimum,
 - restrict performance impact to minimum,
 - security - don't expose weaker points,
 - make common MI parts where applicable,
 - if something is doable with a userlevel debugger code, don't put
extra functions to the kernel.

Benefits of this project:
 - hardware breakpoints without violating mproctect restrictions,
 - make possible observability of data changes,
 - basic and common interface to set breakpoints within few lines of
code [1].

[1] Software breakpoints are definitely more complex, as they overwrite
target's .text section inserting there instructions to generate traps..
and then they need to move PC backwards and insert original instruction
for target...


The design is as follows:

1. Accessors through:
 - PT_WRITE_WATCHPOINT - write new watchpoint's state (set, unset, ...),
 - PT_READ_WATCHPOINT - read watchpoints's state,
 - PT_COUNT_WATCHPOINT - receive the number of available watchpoints.

2. Hardware watchpoint API is designed to be MI with MD specialization.
MI parts:
 - ptrace(2) calls as mentioned in 1.

 - struct ptrace_watchpoint of the following shape:

/*
 * Hardware Watchpoints
 *
 * MD code handles switch informing whether a particular watchpoint is
enabled
 */
typedef struct ptrace_watchpoint {
	int		pw_index;	/* HW Watchpoint ID (count from 0) */
	lwpid_t		pw_lwpid;	/* LWP described */
	struct mdpw	pw_md;		/* MD fields */
} ptrace_watchpoint_t;

 - example specialization for amd64:

/*
 * This MD structure translates into x86_hw_watchpoint
 *
 * pw_address - 0 represents disabled hardware watchpoint
 *
 * conditions:
 *     0b00 - execution
 *     0b01 - data write
 *     0b10 - io read/write (not implemented)
 *     0b11 - data read/write
 *
 * length:
 *     0b00 - 1 byte
 *     0b01 - 2 bytes
 *     0b10 - undefined (8 bytes in modern CPUs - not implemented)
 *     0b11 - 4 bytes
 *
 * Helper symbols for conditions and length are available in <x86/dbregs.h>
 *
 */
struct mdpw {
	void	*md_address;
	int	 md_condition;
	int	 md_length;
};

 - I put md_address and others field to MD part as it's purely MD
specific. I wanted to leave room for possible watchpoints of types
without specified address.


3. Do not expose CPU Debug Registers to userland. I finally decided to
restrict the underlying hardware implementation (based on CPU Debug
Registers) to kernel only. In FreeBSD these registers are a part of
machine context (mcontext), I think it's a misdesign as it should be
limited to the tracer only and it has no use-case in the tracee. CPU
Debug Registers can expose privileged gates and in theory can try to
alter watchpoints set by a debugger on the fly.. AMD64 Debug Registers
are rather part of the tracer context observing a tracee.

4. Do not set watchpoints globally per process, limit them to threads
(LWP). In general kernel hardware watchpoints must be set for all CPUs
and userland watchpoints must be limited to CPU running a thread. Adding
process-wide management in the ptrace(2) interface calls adds extra
complexity that should be pushed away to user-land code in debuggers.

5. Do not allow to mix PT_STEP and hardware watchpoint, in case of
single-stepping the code, disable (it means: don't set) hardware
watchpoints for threads. Some platforms might implement single-step with
hardware watchpoints and managing both at the same time is generating
extra pointless complexity.

6. I have no strong opinions on si_code, on amd64 we set TRAP_TRACE for
hardware watchpoints. POSIX specifies two types: TRAP_BRKPT and
TRAP_TRACE. I don't want to introduce new third type as: it might be
unportable across ports, its usability is questionable (I would limit it
myself to curiosity without significant real-life impact).

7. Linux has interfaces to allocate (reserve) watchpoints for in-kernel
usage and user-land one... I think it's unnecessary complexity for
little gain. In the case that someone would use in-kernel hardware
watchpoints I just recommend to stop setting them for userland (it's
doable with a single if() condition...).

8. The design for the amd64 port is as follows:
 - track watchpoints in a private LWP structure, within a table,
 - all threads before entering userland call userret() - at the end of
this function check if a thread has active watchpoints and isn't going
to single-step -- if so set hardware watchpoint
 - never reset hardware watchpoints after entering the kernel, traps
must be specified for memory in the user-level range and mustn't trigger
trap within kernel code

9. I was trying to leave room for in-kernel x86 watchpoints in future.



Currently only the amd64 part is finished and tested. The i386 and XEN
configurations aren't tested, while both might work out of the box.
compat32 isn't fully implemented.

Tomorrow, I plan to add dedicated ATF tests for this interface,
currently a draft that I used is available at:

    http://netbsd.org/~kamil/t_ptrace_wait-hw-watchpoint-draft.c

The 32-bit work is planned to be started once LLDB will be fully
functional on amd64 first, currently it's out of the scope.

Attachment: signature.asc
Description: OpenPGP digital signature



Home | Main Index | Thread Index | Old Index