-2

I wrote a simple C program to look at the stack frame of printf()

#include <stdio.h>
int main(void){
    printf("%s");
}

I thought the way the stack would work is main() would first push "%s" onto the stack, so printf will either seg fault or print out garbage. However, nowhere in my disassembly does it push "%s" onto the stack. I printed out all the values between %fp and %sp, but none of them contains "%s".

The assembly dump for main:

0x00400950 <+0>:    lui gp,0x2
0x00400954 <+4>:    addiu   gp,gp,-32224
0x00400958 <+8>:    addu    gp,gp,t9
0x0040095c <+12>:   addiu   sp,sp,-32
0x00400960 <+16>:   sw  ra,28(sp)
0x00400964 <+20>:   sw  s8,24(sp)
0x00400968 <+24>:   move    s8,sp
0x0040096c <+28>:   sw  gp,16(sp)
0x00400970 <+32>:   lw  v0,-32744(gp)
0x00400974 <+36>:   nop
0x00400978 <+40>:   addiu   v0,v0,2864
0x0040097c <+44>:   move    a0,v0
0x00400980 <+48>:   lw  v0,-32688(gp)
0x00400984 <+52>:   nop
0x00400988 <+56>:   move    t9,v0
0x0040098c <+60>:   jalr    t9
0x00400990 <+64>:   nop
0x00400994 <+68>:   lw  gp,16(s8)
0x00400998 <+72>:   move    sp,s8
0x0040099c <+76>:   lw  ra,28(sp)
0x004009a0 <+80>:   lw  s8,24(sp)
0x004009a4 <+84>:   addiu   sp,sp,32
0x004009a8 <+88>:   jr  ra
0x004009ac <+92>:   nop

If "%s" is not stored on the stack, where is it stored? Also, where does it get the corresponding string to print out?

  • 3
    Start reading the C standard. It does not even enforce using a specific memory model for automatic variables, etc. Then read the ABI of your platform to see how parameters are passed to/from functions. And your code invokes undefined behaviour. The compiler is free to generate code which eats all your food. – too honest for this site Apr 08 '16 at 01:41
  • Possible duplicate of [C String literals: Where do they go?](http://stackoverflow.com/questions/2589949/c-string-literals-where-do-they-go) – 001 Apr 08 '16 at 01:44
  • Please indicate which CPU you are targeting and what compiler you are using – M.M Apr 08 '16 at 02:05

3 Answers3

1

As far as I remember, mips arch uses a0 ~ a3 as the first four arguments to function calls.

Pan Ruochen
  • 1,990
  • 6
  • 23
  • 34
0

Typically, what will be going on at the implementation level is that the "%s" string literal is in some kind of static storage. When printf is called, a pointer to this string is passed as a parameter. That doesn't necessarily mean that this pointer is pushed onto the stack. How the parameter is passed depends on the parameter passing conventions. It could be loaded into a register.

In your particular case, here is where "%s" is being prepared for passage:

0x00400970 <+32>:   lw  v0,-32744(gp)
0x00400974 <+36>:   nop
0x00400978 <+40>:   addiu   v0,v0,2864
0x0040097c <+44>:   move    a0,v0

First a base address is loaded from a data area relative to the global pointer register. Then this base address is offset by 2864 to get the address of the "%s". The address is then moved to a0, and the register v0 is re-used for calculating the address of printf (which is complicated by the fact that it's in a shared library).

Now since "%s" has no corresponding char * argument, of course the behavior is formally undefined. But what is the actual behavior?

The actual behavior is probably that printf will try to extract a char * pointer somehow, perhaps from the stack. (The trailing arguments of a variadic function are often just put onto the stack.)

Now since the caller didn't put an argument there, printf extracts some "garbage" word and treats it as a char *, printing the memory which that word points to as a null-terminated string. That is, if that word points to valid memory.

If your goal is to dump some bytes of stack memory, this is simply not reliable at all. You have no idea what kind of value gets interpreter as the char * pointer, or what it points to, or whether it points to anything at all, let alone particularly the stack.

That bogus char * itself may be pulled from the stack, but you're not actually printing that pointer itself.

The following might get you a few bytes of stack:

printf("%p\n");

Also, similarly, as might any numeric conversion without an argument. Reason being that %p, unlike %s, actually prints the pointer itself. If the argument value for %p is pulled from the stack, then the printed representation of that value leaks some information about a small piece of the stack.

Kaz
  • 55,781
  • 9
  • 100
  • 149
-1
        .file   "1.c"
        .section        .rodata
.LC0:
        .string "%s"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010"
        .section        .note.GNU-stack,"",@progbits

I have used gcc to generate assembly. The string is not stored the way you think. The string is stored statically.

Shiv
  • 1,912
  • 1
  • 15
  • 21