Debugging variable arguments functions (stdarg)

G tjmadwja@gmail.com
Mon Jul 19 14:19:00 GMT 2010


On Sun, Jul 18, 2010 at 9:27 PM, Petr Hluzín <petr.hluzin@gmail.com> wrote:
> On 16 July 2010 21:12, Jan Kratochvil <jan.kratochvil@redhat.com> wrote:
>> On Fri, 16 Jul 2010 20:57:57 +0200, Petr Hluzín wrote:
>>> Workaround B:
>>> Get value of stack pointer (RSP?) of frame MysqlWrapper() and dump raw
>>> memory around the address. You should see these values somewhere
>>> around:
>>> 0x00000000004041e2 (return address in MysqlWrapper)
>>> 0x406bf0 (the third argument to WriteLog)
>>> Between these two values should be the values of 3rd and 4th argument.
>>
>> This is not so simple on x86_64, it passes even (first few) stdarg parameters
>> in registers.
>>        http://www.x86-64.org/documentation/abi.pdf
>
> Nice reading.
> I suppose it is documented section "3.5.7 Variable Argument Lists".
> Unfortunately I was not able to find any specific wording.
> But it makes sense. You are probably right.
>
> It should be possible by reading the document to determine if the two
> arguments go to stack or remained in registers. I failed to figure
> that out.
>
> Ok, this means that G has to examine stack memory of all frames - infeasible.

First, thanks for the replies. I've actually found a solution which
lets me look at the arguments passed in the "..." list without too
much effort, and thought I'd share it. A Google search for "gdb
variadic" instead of "gdb variable arguments" turned up a blog post at
http://www.moythreads.com/wordpress/2008/05/ which contained an almost
complete solution.

In case that blog post disappears, and for archiving reasons, I'm
including a very short writeup/example here. First some simple code:


#include <stdarg.h>
#include <stdio.h>

void myfunc(const char *fmt, ...)
{
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
        return;
}

int main(int argc, char *argv[])
{
        myfunc("test 1: %s %s\n", "one", "two");
        myfunc("test 2: %s %d %c\n", "apple", 222, 'y');
        return 0;
}


A short debugging session with some comments so that some gdb novice
like myself might get an idea of what is happening (I hope my comments
are correct...):


$ gdb testprog
GNU gdb (GDB) 7.1-debian
[snip]
Reading symbols from /home/user/testprog...done.
(gdb) break myfunc
Breakpoint 1 at 0x400552: file testprog.c, line 7.
(gdb) run
Starting program: /home/user/testprog

Breakpoint 1, myfunc (fmt=0x4006f4 "test 1: %s %s\n") at testprog.c:7
7               va_start(args, fmt);
(gdb) # initialize args to hold correct values:
(gdb) step
8               vprintf(fmt, args);
(gdb) # print first argument in "..." list which we know is a char*:
(gdb) p *(char **)(((char *)args[0].reg_save_area)+args[0].gp_offset)
$1 = 0x4006f0 "one"
(gdb) # print first byte in argument in "..." list which we know is a char*:
(gdb) p **(char **)(((char *)args[0].reg_save_area)+args[0].gp_offset)
$2 = 111 'o'
(gdb) # print first byte in second argument in "..." list which is also a char*
(gdb) # (we use +8 since this is a 64-bit machine):
(gdb) p **(char **)(((char *)args[0].reg_save_area)+args[0].gp_offset+8)
$3 = 116 't'
(gdb) # print second argument in "..." list:
(gdb) p *(char **)(((char *)args[0].reg_save_area)+args[0].gp_offset+8)
$4 = 0x4006ec "two"
(gdb) # look at what lies beyond:
(gdb) p *(long *)(((char *)args[0].reg_save_area)+args[0].gp_offset+16)
$5 = 0
(gdb) # step program to execute the vprintf() call:
(gdb) next
test 1: one two
11      }
(gdb) # return to main():
(gdb) next
main (argc=1, argv=0x7fffffffeb58) at testprog.c:16
16              myfunc("test 2: %s %d %c\n", "apple", 222, 'y');
(gdb) # execute call to myfunc() a second time with different arguments:
(gdb) next

Breakpoint 1, myfunc (fmt=0x400715 "test 2: %s %s %s\n") at testprog.c:7
7               va_start(args, fmt);
(gdb) # initialize args to hold correct values:
(gdb) step
8               vprintf(fmt, args);
(gdb) # print first argument in "..." list which again we know is a char*:
(gdb) p *(char **)(((char *)args[0].reg_save_area)+args[0].gp_offset)
$6 = 0x40070f "apple"
(gdb) # print second argument in "..." list which is an int:
(gdb) p *(int *)(((char *)args[0].reg_save_area)+args[0].gp_offset+8)
$7 = 222
(gdb) # print third argument in "..." list which is a char:
(gdb) p *(char *)(((char *)args[0].reg_save_area)+args[0].gp_offset+16)
$8 = 121 'y'
(gdb) # look at what lies beyond:
(gdb) p *(long *)(((char *)args[0].reg_save_area)+args[0].gp_offset+24)
$9 = 1
(gdb) # OK, so the 0 at the end of the last "..." list seems like just a
(gdb) # coincidence, not an intentional NULL value.
(gdb) # let program finish:
(gdb) cont
Continuing.
test 2: apple 222 y

Program exited normally.
(gdb)



More information about the Gdb mailing list