Need help understanding stack frame layout

280 views Asked by At

While implementing a stack walker for a debugger I am working on I reached the point to extract the arguments to a function call and display them. To make it simple I started with the cdecl convention in pure 32-bit (both debugger and debuggee), and a function that takes 3 parameters. However, I cannot understand why the arguments in the stack trace are out of order compared to what cdecl defines (right-to-left, nothing in registers), despite trying to figure it out for a few days now.

Here is a representation of the function call I am trying to stack trace:

void Function(unsigned long long a, const void * b, unsigned int c) {
    printf("a=0x%llX, b=%p, c=0x%X\n", a, b, c);
    _asm { int 3 }; /* Because I don't have stepping or dynamic breakpoints implemented yet */
 }
 int main(int argc, char* argv[]) {
     Function(2, (void*)0x7A3FE8, 0x2004);
     return 0;
 }

This is what the function (unsurprisingly) printed to the console:

a=0x2, c=0x7a3fe8, c=0x2004

This is the stack trace generated at the breakpoint (the debugger catches the breakpoint and there I try to walk the stack):

0x3EF5E0: 0x10004286 /* previous pc */
0x3EF5DC: 0x3EF60C   /* previous fp */
0x3EF5D8: 0x7A3FE8   /* arg b --> Wait... why is b _above_ c here? */
0x3EF5D4: 0x2004     /* arg c */
0x3EF5D0: 0x0        /* arg a, upper 32 bit */
0x3EF5CC: 0x2        /* arg a, lower 32 bit */

The code that's responsible for dumping the stack frames (implemented using the DIA SDK, though, I don't think that is relevant to my problem) looks like this:

ULONGLONG stackframe_top = 0;
m_frame->get_base(&stackframe_top); /* IDiaStackFrame */

/* dump 30 * 4 bytes */
for (DWORD i = 0; i < 30; i++)
{
    ULONGLONG address = stackframe_top - (i * 4);
    DWORD value;
    SIZE_T read_bytes;
    if (ReadProcessMemory(m_process, reinterpret_cast<LPVOID>(address), &value, sizeof(value), &read_bytes) == TRUE)
    {
        debugprintf(L"0x%llX: 0x%X\n", address, value); /* wrapper around OutputDebugString */
    }
}

I am compiling the test program without any optimization in vs2015 update 3.

I have validated that I am indeed compiling it as cdecl by looking in the pdb with the dia2dump sample application. I do not understand what is causing the stack to look like this, it doesn't match anything I learned, nor does it match the documentation provided by Microsoft.
I also checked google a whole lot (including osdev wiki pages, msdn blog posts, and so on), and checked my (by now probably outdated) books on 32-bit x86 assembly programming (that were released before 64-bit CPUs existed).

Thank you very much in advance for any explanations or links!

1

There are 1 answers

1
Warepire On

I had somehow misunderstood where the arguments to a function call end up in memory compared to the base of the stack frame, as pointed out by Raymond. This is the fixed code snippet:

ULONGLONG stackframe_top = 0;
m_frame->get_base(&stackframe_top); /* IDiaStackFrame */

/* dump 30 * 4 bytes */
for (DWORD i = 0; i < 30; i++)
{
    ULONGLONG address = stackframe_top + (i * 4); /* <-- Read before the stack frame */
    DWORD value;
    SIZE_T read_bytes;
    if (ReadProcessMemory(m_process, reinterpret_cast<LPVOID>(address), &value, sizeof(value), &read_bytes) == TRUE)
    {
        debugprintf(L"0x%llX: 0x%X\n", address, value); /* wrapper around OutputDebugString */
    }

}