Well, I've been digging around process command line arguments as well as environment variables a bit these last days, especially looking up the way main function arguments were pushed onto the stack.

So far I've got it that a certain _libc_start_main() function is responsible of setting up everything the main() function needs in terms of parameters before actually calling it.
Without getting into much details, I've noticed that when debugging a simple main program, the main stack frame is different whether we look it up in radare2 or gdb.
For instance, let's take this minimalist C program :

int main (int argc, char *argv[], char *envp[])

And simply debug it without any additional parameters :

With GDB

After setting a breakpoint on the first assembly instruction of main (I couldn't dump the stack frame without running the program), what I got in gdb is something very sensible, as one can see :

(gdb) x/3xw $esp
0xffffcfbc:     0xf7db7b41          0x00000001      0xffffd054      
#                  ^                    ^               ^     
#              PC (somewhere          argc            argv             
#          in __libc_start_main())

Now by actually inspecting the pointed out memory regions for argv :

(gdb) x/2xw 0xffffd054   # argv
0xffffd054:     0xffffd1ef      0x00000000
#                  ^                ^ 
#                argv[0]          argv[argc]
#           (another pointer)              

(gdb) x/s 0xffffd1ef  # argv[0]
0xffffd1ef:     "<path>/argvonstack32"
#                      ^ 
#                  Exepected program name

So what was basically pushed onto the main stack frame, for both argv and envp (even if I didn't show the dump for envp for simplicity's sake) is exactly what we were entitled to expect from a debugger, that is a pointer to pointer to char (as stated in the main function signature).

With Radare2

Without setting any breakpoint, and directly inspecting the stack frame without running the program, radare2 shows a different stack frame :

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF  comment
0xffa63d10  0100 0000 1953 a6ff 0000 0000 2953 a6ff  .....S......)S..  ; esp
                ^         ^         ^       
              argc     argv[0]    argv[1]

When inspecting argv[0] :

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF  comment
0xffa65319  2e2f 6172 6776 6f6e 7374 6163 6b33 3200  ./argvonstack32.
0xffa65329  5348 454c 4c3d 2f62 696e 2f62 6173 6800  SHELL=/bin/bash.

That shows that radare2 skipped the first pointer indirection of argv and pushed the list argv[0]...argv[argc] directly onto the main stack frame.

What explains such a difference ?

PS: As you can see, the only difference there is between my usage of radare2 and gdb was that I runned the program in gdb while didn't need to actually run it on radare2 to dump the main stack frame memory.


You are sort of comparing oranges and apples.

In the first example, you are looking at the arguments passed to the main function by the C library, and they match the C standard requirements (array of char pointers).

In the second example, you are looking at the low level entry point to the binary and the parameters from the kernel, before they have been processed by the C library. The kernel does no fancy processing: it just puts all strings in one block delimited by zero bytes (and terminated with two zeroes) and leaves the splitting to the program itself. Usually this is done by __libc_start_main or similar function before calling main().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.