6

So I've got this example C program.

int worship(long john)
{
    return 0 * john;
}

int main()
{
    return worship(666);
}

The assembly looks (essentially) like this:

worship(long):
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rdi, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $666, %edi
    call    worship(long)
    popq    %rbp
    ret

I ran into this while reading about stack smashing. In the assembly worship(long): section where it says movq %rdi, -8(%rbp) I would expect it to be using pushq based on everything I've read so far. Is this the new way that GCC is pushing arguments onto the stack and if so is there a compiler flag I could be using to toggle this?

  • oh, maybe that's an optimization. as far as i know movq doesn't change the value of stack pointer – Hayri Uğur Koltuk Mar 08 '14 at 10:04
  • What? it is a very common and basic function frame... – 0x90 Mar 08 '14 at 10:10
  • 1
    in x86_64 the number of registers has been double, so the [calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions) uses registers for the first parameters, not stack – phuclv Mar 08 '14 at 10:11
  • I think it gives more space for optimizations. If you have a sequence of two function calls differing only by the second argument, you can simply change this argument while keeping the first on the top of the stack. And of course you do not need to adjust SP when returning from functions. – Marian Mar 08 '14 at 10:15
  • 1
    Poss duplicate http://stackoverflow.com/questions/4534791/why-does-it-use-the-movl-instead-of-push – harmic Mar 08 '14 at 10:46
  • 4
    It is a basic code optimization. The PUSH instruction is awkward because it makes *two* modifications, it writes to [ESP] *and* modifies the ESP register. This prevents out-of-order execution, a problem that MOV doesn't have. – Hans Passant Mar 08 '14 at 11:08
  • @harmic, yes it would appear so. It even looks like he's reading the same article on buffer overflows as I am. –  Mar 08 '14 at 21:31
  • @LưuVĩnhPhúc, I thought it might be skipping the stack so I tried a case with a bunch of arguments to worship() and it behaved the same but I didn't realize there were twice as many registers in x86_64. –  Mar 08 '14 at 21:35
  • why do you think that `movq %rdi, -8(%rbp)` is a parameter pushing? Any parameters must be inputted from the calling function, which is `main` here. The callee must read out the transferred parameter – phuclv Mar 09 '14 at 05:35
  • 2
    if you're using Linux then the first 6 integer parameters are passed in RDI, RSI, RDX, RCX, R8, and R9. So that's why there's RDI in the function. Pass 6 parameters and you'll see that it uses R8 and R9, which is one of the new registers. Try passing more than 7 parameters and then the stack will be used – phuclv Mar 09 '14 at 05:39
  • In my defence of this repost thing, the original answer is very vaguely titled –  Mar 10 '14 at 21:00

2 Answers2

7

GCC manual says,

-mpush-args

Push instructions will be used to pass outgoing arguments when functions are called. Enabled by default.

-mno-push-args   

Use PUSH operations to store outgoing parameters. This method is shorter and usually equally fast as method using SUB/MOV operations and is enabled by default. In some cases disabling it may improve performance because of improved scheduling and reduced dependencies.

-maccumulate-outgoing-args

If enabled, the maximum amount of space required for outgoing arguments will be computed in the function prologue. This is faster on most modern CPUs because of reduced dependencies, improved scheduling and reduced stack usage when preferred stack boundary is not equal to 2. The drawback is a notable increase in code size. This switch implies -mno-push-args.

Even -mpush-args enabled by default it is override by -maccumulate-outgoing-args which is enabled by default. Compiling passing option -mno-accumulate-outgoing-args explicitly could change the instructions to push.

Sunil Bojanapally
  • 11,964
  • 4
  • 34
  • 43
1

Compilers like GCC are written by people who very carefully consider how to make often used code snippets (like function call/return) as efficient as possible. Sure, their solutions target the general case, in special cases there might be better options.

vonbrand
  • 10,868
  • 8
  • 30
  • 50