Grab your Irn-Bru: Accessing a programs local variables using DTrace

A question that has raised its head several times on the dtrace-discuss@opensolaris.org forum has been, "how do I access the local variables in my program"? Unfortunately, the answer to this isn't particularly pretty so I thought I'd provide a simple (and contrived...) example of how to this. What we're doing here relies on getting just a teeny bit low down and dirty so you may want to grab your Irn-Bru laddy! The example used here was done on an x86 based system but the principle is exactly the same for any other architecture. Take the extremely simple piece of code below:

void
foo(void)
{
        int i;
        const char \*string = "Foo bar test string";

        for (i = 0; i < strlen(string); i++) {
                printf("%c", string[i]);
        }

        printf("%\\n");
}
We're going to use DTrace to tell us the value of the loop counter i when we encounter a space in the string that is being iterated over.

Here's where it starts to become slightly unpleasant. The storage for a programs local variables will either be in registers or somewhere in memory, depending upon where in the programs execution you want to view them. In the above code we want to see the value of the i variable when a space character has been found (i.e. when string[i] contains a space). To do this we need to first disassemble the code and we can do that using mdb(1):
> foo::dis
foo:                            pushl  %ebp
foo+1:                          movl   %esp,%ebp
foo+3:                          subl   $0x8,%esp
foo+6:                          leal   0x8050ac8,%eax
foo+0xc:                        movl   %eax,-0x8(%ebp)
foo+0xf:                        movl   $0x0,-0x4(%ebp)
foo+0x16:                       movl   -0x8(%ebp),%eax
foo+0x19:                       pushl  %eax
foo+0x1a:                       call   -0x163   < PLT:strlen >
foo+0x1f:                       addl   $0x4,%esp
foo+0x22:                       movl   -0x4(%ebp),%edx
foo+0x25:                       cmpl   %eax,%edx
foo+0x27:                       jae    +0x32     < foo+0x5b >
foo+0x29:                       movl   -0x8(%ebp),%eax
foo+0x2c:                       addl   -0x4(%ebp),%eax
foo+0x2f:                       movsbl 0x0(%eax),%eax
foo+0x33:                       pushl  %eax
foo+0x34:                       pushl  $0x8050adc
foo+0x39:                       call   -0x172   < PLT:printf >
foo+0x3e:                       addl   $0x8,%esp
foo+0x41:                       movl   -0x4(%ebp),%eax
foo+0x44:                       incl   %eax
foo+0x45:                       movl   %eax,-0x4(%ebp)
foo+0x48:                       movl   -0x8(%ebp),%eax
foo+0x4b:                       pushl  %eax
foo+0x4c:                       call   -0x195   < PLT:strlen >
foo+0x51:                       addl   $0x4,%esp
foo+0x54:                       movl   -0x4(%ebp),%edx
foo+0x57:                       cmpl   %eax,%edx
foo+0x59:                       jb     -0x32     < foo+0x29 >
foo+0x5b:                       pushl  $0x8050ae0
foo+0x60:                       call   -0x199   < PLT:printf >
foo+0x65:                       addl   $0x4,%esp
foo+0x68:                       leave  
foo+0x69:                       ret    

So, from the above instructions we need to figure out where we can insert a probe with DTrace so we can then get our hands on the relevant data! Well, it's not as bad as it looks really. Our two variables i and string have space created for them on the stack early on:

foo+6:                          leal   0x8050ac8,%eax
foo+0xc:                        movl   %eax,-0x8(%ebp)
foo+0xf:                        movl   $0x0,-0x4(%ebp)
So, i is at %ebp-0x4 and string is at %ebp-0x8. There are a couple places in the code where we could insert a probe to inspect whether or not the current index into the string is a space character. The place I'll choose here is where we load up the current character being indexed for the printf() call. You'll notice that for the call to printf() we place on the stack the character to print and the printf format modifier. Here are the instructions which do that:
foo+0x33:                       pushl  %eax
foo+0x34:                       pushl  $0x8050adc
foo+0x39:                       call   -0x172    < PLT:printf >
And we'll just verify that foo+0x34 is loading up the the printf modifier string:
> 0x8050adc/s
0x8050adc:      %c
So, we're in the right place. The character to be printed out (i.e. the current value of string[i]) will be found in the %eax register at foo+0x33 so this is where we'll place a probe with the pid provider. What we can do is to use the DTrace regs[] array to access the %eax register and predicate on whether or not we are currently looking at a space character (the space character is hex value 0x20). If the register contains the value 0x20 we can then extract the current value of the loop counter i which is found on the stack at %ebp-0x4. So our script looks like this:
#cat local1.d
#pragma D option quiet

inline int SPACE = 0x20;

pid$target:a.out:foo:33
/uregs[R_EAX] == SPACE/
{
        this->pos = \*(int \*)(copyin(uregs[R_EBP]-0x4, sizeof(int)));
        printf("Space found at char %d\\n", this->pos + 1);
}
You'll note that to access the memory location for the loop counter i (the programs stack in this case), we need to use the copyin() DTrace subroutine. Executing the above script on this code we get the following output:
# dtrace -s ./local1.d -c ./local
Foo bar test string
Space found at char 4
Space found at char 8
Space found at char 13
Voila! There you have it. It's a bit grim I know and forces you to rely on knowing a bit about the assembly language of the architecture you're executing on. However, it can prove to be invaluable technique for debugging those hard to get at problems.

Good luck!

Comments:

Think you'll be needing the full-on Irn Bru 32 for this... Wakey Wakey!

Posted by Calum on July 13, 2007 at 09:08 AM GMT+00:00 #

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

jonh

Search

Categories
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