procfs watchpoints

Procfs watchpoints are a little used but very powerful debugging tool. They are documented in man -s4 proc . They allow a program to be interrupted when it performs a memory read/write/execute within a specified range of addresses. A simple bit of code that detects writes to an integer could be based on this ..

--------------------------------------------
#include <sys/types.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/errno.h>
#include <ucontext.h>
#include <procfs.h>
#include <sys/uio.h>

struct thing {
char buffer[64];
int x;
char buffer1[512];
} thingy;

void
sub( int \* x)
{
\*x=getpid();
}

int
main(int argc, char \* argv[])
{

iovec_t iov[2];
struct {
long cmd;
union {
long flags;
prwatch_t prwatch;
} arg;
} ctl;

int pfd;


pfd = open("/proc/self/ctl",O_WRONLY);
if (pfd == -1)
abort()
ctl.cmd = PCWATCH;
ctl.arg.prwatch.pr_wflags = WA_READ|WA_WRITE|WA_EXEC;
ctl.arg.prwatch.pr_vaddr = (caddr_t)&(thingy.x);
ctl.arg.prwatch.pr_size = sizeof(int);
printf("setting watchpoint at address 0x%p size %d\\n",
ctl.arg.prwatch.pr_vaddr, ctl.arg.prwatch.pr_size);
(void)write(pfd, (char \*)&ctl, sizeof (long)+sizeof (prwatch_t));

sub(&(thingy.x));
}
-------------------------------------------
so compiling that into bar and running that under mdb produces..

hoofhearted ksh: mdb bar
> :r
setting watchpoint at address 0x8060de0 size 4
mdb: target stopped at:
sub+0x10:       movl   %edx,(%eax)
>
> $r
%cs = 0x003b            %eax = 0x08060de0      bar`thingy+0x40
%ds = 0x0043            %ebx = 0xbfffb824
%ss = 0x0043            %ecx = 0xbff9da37        libc.so.1`getpid+7
%es = 0x0043            %edx = 0x00000603
%fs = 0x0000            %esi = 0x08047264
%gs = 0x01c3            %edi = 0x080473a0
%eip = 0x08050ab0 sub+0x10
%ebp = 0x08047208
%kesp = 0x00000000
%eflags = 0x00010202
  id=0 vip=0 vif=0 ac=0 vm=0 rf=1 nt=0 iopl=0x0
  status=<of,df,IF,tf,sf,zf,af,pf,cf>
%esp = 0x08047200
%trapno = 0xe
%err = 0x6
>

So we took a SIGTRAP as soon as we tried to modify thingy.x. If the program had set the breakpoint on its own data then it would probably capture the signal, process it and return. If the breakpoint was set by a debugging process then the debugger would have registered an interest in that signal and could regain control at that point.

So I am using this in my debugging malloc library to watch the areas of the mmap that fall outside of the exact area requested in the call to malloc(). So far I have discovered that strlen has intimate knowledge of malloc and if the base address is aligned it uses a very clever algorithm to find the null terminating byte, but that causes it to use 32 bit load instructions to scan the string, so I have had to change the watchpoint at the end of the returned area to be a write watchpoint until we become 4 byte aligned and then to continue to the end of the page with a read/write watchpoint.

To avoid being accused of endianness I have finally got i86 and amd64 versions working -found a interesting little bug in the kernel whilst I was at it.

The procfs watchpoints work by clearing all  permissions of any page that has any part of a watchpoint in it.  Then when any access to the page happens we examine the avl tree of per process watch points to see if the accesed address is inside a watched area, if it is we send the SIGTRAP and setup to single step the process on return from the signal and set the page permissions  back to normal. After the single instruction has accessed the memory we clear the page permissions and allow the process to run normally again. You can see why it is quite slow!


In the case of sparc the instructions size is 4 bytes and the data access size can be easily calculated from the instruction. On a x86 machine we have to decode the instruction thoroughly to find the instruction and data access size. The small bug I found caused that code to calculate the data access size for a pushl instruction in a 32 bit application running on an amd64 kernel as 8 bytes instead of the correct 4 bytes. So if the data being pushed onto the stack was the last 4 bytes before a watchpoint the kernel thought that you were accessing 8 bytes so triggered the watchpoint, took me all day to find it, I was sure it was my code.


Comments:

Hi Tim, I found this particular blog entry (and the blog as a whole) invaluable! One question though - how do I clear watchpoints programatically? I couldn't find much information, except that PCWATCH is used to set/unset a watchpoint (in procfs.h), will that (clearing the watchpoint) work? Cheers, Stoyan

Posted by Stoyan Damov on June 02, 2006 at 10:12 AM GMT+00:00 #

Aaah, found it - a zero size clears the watchpoint. Cheers, Stoyan

Posted by Stoyan Damov on June 04, 2006 at 11:53 PM GMT+00:00 #

Post a Comment:
Comments are closed for this entry.
About

timatworkhomeandinbetween

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today
News

No bookmarks in folder

Blogroll

No bookmarks in folder