2

I have simple C program that produces this x86-64 assembly for function func

#include <stdio.h>
#include <string.h>

void func(char *name)
{
    char buf[90];
    strcpy(buf, name);
    printf("Welcome %s\n", buf);
}

int main(int argc, char *argv[])
{
   func(argv[1]);
   return 0;
}

So I think this

   0x000000000000118d <+4>: push   %rbp

pushes the base pointer like placed argument which is char *name

then 0x000000000000118e <+5>: mov %rsp,%rbp set stack pointer to what at base pointer I belive that above and this makes stack point points to char *name at this point

then

   0x0000000000001191 <+8>: add    $0xffffffffffffff80,%rsp

I am little unsure about this. Why is 0xffffffffffffff80 added to rsp? What is the point of this instruction. Can any one please tell.

then in next instruction 0x0000000000001195 <+12>: mov %rdi,-0x78(%rbp) its just setting -128 decimal to rdi. But still no buffer char buf[90] can be seen, where is my buffer? in following assmebly, can anyone please tell?

also what this line 0x00000000000011a2 <+25>: mov %rax,-0x8(%rbp)

Dump of assembler code for function func:
   0x0000000000001189 <+0>: endbr64 
   0x000000000000118d <+4>: push   %rbp
   0x000000000000118e <+5>: mov    %rsp,%rbp
   0x0000000000001191 <+8>: add    $0xffffffffffffff80,%rsp
   0x0000000000001195 <+12>:    mov    %rdi,-0x78(%rbp)
   0x0000000000001199 <+16>:    mov    %fs:0x28,%rax
   0x00000000000011a2 <+25>:    mov    %rax,-0x8(%rbp)
   0x00000000000011a6 <+29>:    xor    %eax,%eax
   0x00000000000011a8 <+31>:    mov    -0x78(%rbp),%rdx
   0x00000000000011ac <+35>:    lea    -0x70(%rbp),%rax
   0x00000000000011b0 <+39>:    mov    %rdx,%rsi
   0x00000000000011b3 <+42>:    mov    %rax,%rdi
   0x00000000000011b6 <+45>:    call   0x1070 <strcpy@plt>
   0x00000000000011bb <+50>:    lea    -0x70(%rbp),%rax
   0x00000000000011bf <+54>:    mov    %rax,%rsi
   0x00000000000011c2 <+57>:    lea    0xe3b(%rip),%rax        # 0x2004
   0x00000000000011c9 <+64>:    mov    %rax,%rdi
   0x00000000000011cc <+67>:    mov    $0x0,%eax
   0x00000000000011d1 <+72>:    call   0x1090 <printf@plt>
   0x00000000000011d6 <+77>:    nop
   0x00000000000011d7 <+78>:    mov    -0x8(%rbp),%rax
   0x00000000000011db <+82>:    sub    %fs:0x28,%rax
   0x00000000000011e4 <+91>:    je     0x11eb <func+98>
   0x00000000000011e6 <+93>:    call   0x1080 <__stack_chk_fail@plt>
   0x00000000000011eb <+98>:    leave  
   0x00000000000011ec <+99>:    ret    
End of assembler dump.

also what in above assembly the use of fs register what this instruction actually doing 0x0000000000001199 <+16>: mov %fs:0x28,%rax

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
user786
  • 3,902
  • 4
  • 40
  • 72
  • Look closely: `rsp` is not set so `0xffffffffffffff80`. – Jabberwocky Feb 10 '22 at 07:36
  • @Jabberwocky so why 0xffffffffffffff80 added? What's the point of this instruction. I am plusing what ever in rsp which I assume should be 0 so adding it to 0xffffffffffffff80 will assign 0xffffffffffffff80 to rsp. Why its needed may be unrelated to assembly but love to know this? – user786 Feb 10 '22 at 07:59
  • It's unlikely that rsp is ever zero. – Jabberwocky Feb 10 '22 at 08:02
  • 1
    `push %rbp` and `mov %rsp,%rbp` is the *function prologue* is sets up the stack frame for the function. See [Function Prologue and Epilogue in C](https://stackoverflow.com/q/14765406/3422102) – David C. Rankin Feb 10 '22 at 08:04
  • @DavidC.Rankin on the link u posted. It says return of function gets stored before other local variables. And then when the function almost finishes its execution code then it pops all the variables out of stack to find the return of a function then it gets executed. Is this correct understanding can u please tell https://stackoverflow.com/a/61307067/4808760 – user786 Feb 10 '22 at 09:42
  • @Jabberwocky `It's unlikely that rsp is ever zero.` do u mean its not zero. And the result of `add 0xffffffffffffff80,%rsp` is not zero assigned to rsp. Rsp is 8 bytes. And we are assigning 1s to all bytes except last hex 4 bits. I didn't understand what u mean. Still looking for why this instruction needed – user786 Feb 10 '22 at 09:53
  • @user786 `rsp` is the stack pointer, so it can virtually never be 0. And `add 0xffffffffffffff80,%rsp`, of course _adds_ 0xffffffffffffff80 to `rsp`. But it doesn't _set_ `rsp` to 0xffffffffffffff80. – Jabberwocky Feb 10 '22 at 09:55
  • @Jabberwocky. OK sounds good. But why adding this. What is the achievement of this statement. If I add two things to assign to some variable and i.e. compare with something else and excute based on conditional outcome it makes sense. But in my assembly its just adding for no objective. Why? – user786 Feb 10 '22 at 10:14
  • @Jabberwocky or is it just gcc B.S. adding for no apparent reason – user786 Feb 10 '22 at 10:15
  • @user786 adding 0xffffffffffffff80 is actually the same as substracting 0x80. And if gcc generates some code, there _is_ a reason. – Jabberwocky Feb 10 '22 at 10:20
  • @Jabberwocky are u sure rsp is 8 bytes. Adding 0xffffffffffffff80 to 0x0000000000000000 the result is ffffffffffffff80 that's big number – user786 Feb 10 '22 at 10:38
  • 1
    @user786 rsp is definitely a 64 bit register, so yes, it takes 8 bytes. _Adding 0xffffffffffffff80 to 0x0000000000000000 the result is ffffffffffffff80_: true, but `rsp` is never 0. Adding 0xffffffffffffff80 to an 64 bit integer is the same as substracting 0x80, that's basic 2th complement arithmetic. – Jabberwocky Feb 10 '22 at 10:38
  • @Jabberwocky or gcc does some magic work with last byte out of 0xffffffffffffff80 – user786 Feb 10 '22 at 10:41
  • 1
    @user786 there is no magic invlolved whatsoever. Study your 2th complement arithmetic. Remember google is your friend – Jabberwocky Feb 10 '22 at 10:42
  • @user786 BTW consider https://www.godbolt.org/ for experimenting with compilers and their asembly output – Jabberwocky Feb 10 '22 at 10:43
  • @Jabberwocky `Adding 0xffffffffffffff80 to an 64 bit integer is the same as substracting 0x80` where adding 0xffffffffffffff80 to `64 bit integer` where is it happening in my assembly. I am just adding it to zero which is rsp default value do u mean 2s complement applies in adding of 0xffffffffffffff80+0x0000000000000000=subtracting 80 from 0x0000000000000000 – user786 Feb 10 '22 at 10:49
  • @Jabberwocky OK I understood 2s complement story. So simple question is does 2s complement applies to add instruction in general. For example I do `add 0xf3,0x02` . So 2s commplent of 0xf3 would be on one byte architecture should be three then what next happens in add instruction does the negative value 3 added to 0x02 as in `add 0xf3,0x02 – user786 Feb 10 '22 at 11:02
  • @user786 yes, it does – Jabberwocky Feb 10 '22 at 11:05
  • @Jabberwocky so it just does add instruction. Do u know the reason for this why exactly its done? – user786 Feb 10 '22 at 11:22
  • Where is my 90 byte buffer placement on stack. Shouldn't it be in assembly somewhere – user786 Feb 10 '22 at 11:50
  • As to why the stack adjustment is done as `add $0xffffffffffffff80,%rsp` instead of the seemingly more natural `sub $0x80, %rsp`, see https://stackoverflow.com/questions/70453036/why-gcc-generates-strange-way-to-move-stack-pointer/70453146#70453146. Because of the way the encoding works, the `add` instruction is three bytes shorter. – Nate Eldredge Feb 10 '22 at 17:23
  • @user786: The `add $0xffffffffffffff80,%rsp` effectively allocates 128 bytes on the stack. 128 is greater than 90, no? Your buffer is within that space. – Nate Eldredge Feb 10 '22 at 17:24

2 Answers2

3

As already mentioned in comments, your buffer is on the stack. In the beginning of the function the rsp is decreased to allow more space (stack grows towards lower addresses, thus rsp is decreased as stack grows). This space is generally used for local variables, arguments passed to the function, and also for other purposes (will get back to it below). In your case, you may trace back where your buffer buf is by looking at what arguments are passed to the strcpy - the first argument is passed in rdi register, the second - in rsi.

   0x00000000000011b0 <+39>:    mov    %rdx,%rsi
   0x00000000000011b3 <+42>:    mov    %rax,%rdi
   0x00000000000011b6 <+45>:    call   0x1070 <strcpy@plt>

In the snippet above you can see that the pointer to buf (first argument to strcpy) was in rax prior to being put to rdi. And rax got its value from this instruction:

   0x00000000000011ac <+35>:    lea    -0x70(%rbp),%rax

which means "load effective address (i.e. a pointer) that resides at offset -0x70 from the address rbp is pointing to". rbp points to where the stack pointer was in the beginning of the function (function frame pointer).

So it answers where the compiler has put your buffer.

Now for other questions:

then in next instruction 0x0000000000001195 <+12>: mov %rdi,-0x78(%rbp) its just setting -128 decimal to rdi.

As we said, rdi holds the first argument to a function. Here it holds a first argument to func(), which is a pointer to name. This instruction puts this argument onto a stack at an offset of -0x78 from rbp - 8 bytes right before the space reserved for your buffer buf.

And the last two questions are related:

also what this line 0x00000000000011a2 <+25>: mov %rax,-0x8(%rbp)

and

also what in above assembly the use of fs register what this instruction actually doing 0x0000000000001199 <+16>: mov %fs:0x28,%rax

   0x0000000000001199 <+16>:    mov    %fs:0x28,%rax
   0x00000000000011a2 <+25>:    mov    %rax,-0x8(%rbp)
   ...
   ...
   0x00000000000011d7 <+78>:    mov    -0x8(%rbp),%rax
   0x00000000000011db <+82>:    sub    %fs:0x28,%rax
   0x00000000000011e4 <+91>:    je     0x11eb <func+98>
   0x00000000000011e6 <+93>:    call   0x1080 <__stack_chk_fail@plt>
   0x00000000000011eb <+98>:    leave  

There is some value at %fs:0x28 (which denotes an offset of 0x28 in an fs segment). And this value is being placed (via rax) to the stack. To the very first 8 bytes in the space allocated for your function. And there it stays, hopefully untouched, until the function is about to return. There, it checks whether the value on the stack was changed. If it remained unchanged, the jump (je) will take you to the leave and the function will return. If, by any chance, the value on the stack got changed - your code has caused a stack overflow (aha!) and a call to __stack_chk_fail will be triggered, which perhaps will warn you about the overflow, and perhaps dump some debug information. So the value at %fs:0x28 is a kind of a unique magic/canary value.

And one last thing - about why add $0xffffffffffffff80,%rsp was used to allocate space on the stack, and not sub - other compilers do use sub as did GCC (version 8.5.0 20210514):

    sub    $0x70,%rsp

It allocated less, and one of the reasons is that the compiler did not reserve space for the stack overflow check.

waterloooo
  • 31
  • 4
  • 1
    You can compile with `-fno-stack-protector` to simplify the asm by leaving out the stuff with `%fs:0x28`. (See also [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116)) – Peter Cordes Feb 10 '22 at 20:40
  • Can u please tell `In the beginning of the function the rsp is decreased to allow more space` is rsp decreased with `add` instruction in my beginning of assembly code? – user786 Feb 11 '22 at 07:37
  • 1
    @user786, yes, exactly, in your assembler it is decreased by adding a negative number to it (and in the assembler I got with GCC it was decreased with subtracting a positive number). – waterloooo Feb 11 '22 at 21:21
2

As to "why use an add %rsp rather than a sub %rsp instruction":

On x86_64 there are actually two versions of these add/sub immediate with rsp instructions

  • a 4 byte version with a 1 byte immediate
  • a 7 byte version with a 4 byte immediate

For both versions, the immediate will be sign-extended to 64 bits and then added to (or subtracted from) %rsp. Now because of that sign extension, a 1-byte immediate can be any value from -128 (-0x80) up to 127 (0x7f). So the instruction

add $-0x80, %rsp

can use the 4-byte encoding, while the instruction

sub $0x80, %rsp

would require the 7 byte encoding. All else being equal (as it never is), the shorter encoding is better as it occupies less memory/cache.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226