2

My environment:

  • OS: Linux 4.15.0-202-generic Ubuntu SMP x86_64
  • GCC: gcc (Ubuntu 4.8.5-4ubuntu8) 4.8.5

I wrote a simple assembly program, named 'helloworld.s':

.section .data

msg:
    .asciz "Hello world!\n"

.section .text
.globl _start
_start:
    /* call  printf() to print "Hello world!" */
    mov $msg, %rdi
    call printf

    /* call exit() function  */
    movq $0, %rdi
    call exit

I compiled and linked it:

$ as helloworld.s
$ ld ./a.out -o helloworld -lc -dynamic-linker /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

Now the program could work well, and I got the output:

Hello world!

Then, I wanted to compile this program with GCC. I modified it, replaced the label _start with main:

.section .data

msg:
    .asciz "Hello world!\n"

.section .text
.globl main    /* replace _start with main */
main:          /* replace _start with main */
    /* call  printf() to print "Hello world!" */
    mov $msg, %rdi
    call printf

    /* call exit() function  */
    movq $0, %rdi
    call exit

After compiling it with gcc, gcc -o helloworld helloworld.s, I got the executable 'helloworld'. But when I ran it, I got a wrong prompt:

$ ./helloworld
Segmentation fault (core dumped)

I debugged it with gdb, the result was as following:

(gdb) b main
Breakpoint 1 at 0x40054d
(gdb) run
Breakpoint 1, 0x000000000040054d in main ()
(gdb) stepi
0x0000000000400554 in main ()
(gdb) stepi
0x0000000000400430 in printf@plt ()
(gdb) stepi
0x0000000000400436 in printf@plt ()
(gdb) stepi
0x000000000040043b in printf@plt ()
(gdb) stepi
0x0000000000400420 in ?? ()
(gdb) stepi
0x0000000000400426 in ?? ()
(gdb) stepi
_dl_runtime_resolve_xsave () at ../sysdeps/x86_64/dl-trampoline.h:71
71      ../sysdeps/x86_64/dl-trampoline.h: No such file or directory.
(gdb) stepi
74      in ../sysdeps/x86_64/dl-trampoline.h
(gdb) stepi
76      in ../sysdeps/x86_64/dl-trampoline.h

Then I wrote a simple 'helloworld.c':

#include <stdio.h>

int main() {
    printf("Hello world!\n");
}

I compiled it with gcc, and disassembled it with objdump. I got the assembly code:

00000000004004fd <main>:
  4004fd:       55                      push   %rbp
  4004fe:       48 89 e5                mov    %rsp,%rbp
  400501:       bf 94 05 40 00          mov    $0x400594,%edi
  400506:       e8 e5 fe ff ff          callq  4003f0 <puts@plt>
  40050b:       5d                      pop    %rbp
  40050c:       c3                      retq   
  40050d:       0f 1f 00                nopl   (%rax)

I found that there was a push %rbp before callq, and pop %rbp after it, so I added these to my program:

.section .data

msg:
    .asciz "Hello world!\n"

.section .text
.globl main    /* replace _start with main */
main:          /* replace _start with main */
    /* call  printf() to print "Hello world!" */
    push %rbp         /* add push %rbp  */
    mov $msg, %rdi
    call printf
    pop %rbp          /* add pop %rbp  */

    /* call exit() function  */
    movq $0, %rdi
    call exit

I compiled it with gcc once more, and then ran it; it worked well.

I was wondering if someone could help me understand the effect of push %rbp and pop %rbp or refer me to some link explaining. Why couldn't the program work before I added push %rbp and pop %rbp to it?

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
zhouz
  • 29
  • 1
  • You need to keep stack aligned. Also you should set `al` to the number of xmm registers used for passing arguments to variadic functions and `printf` is one. Note that process entry has `rsp` aligned differently than `main` which is invoked by the C startup code. Technically the `pop %rbp` should be after the `call exit` since that may also expect the stack to be aligned but usually it doesn't. It also doesn't return, so you can just remove the `pop %rbp` and use `jmp exit` instead of `call exit`. – Jester Feb 03 '23 at 17:21
  • `_start` isn't a function, RSP % 16 == 0 at the top of _start, vs. 8 at the top of a function. – Peter Cordes Feb 03 '23 at 17:25
  • Also, you forgot to use `xor %eax,%eax` to set AL = 0 args passed in XMM registers. – Peter Cordes Feb 03 '23 at 17:28
  • Possibly related: https://stackoverflow.com/questions/16097173/printing-floating-point-numbers-from-x86-64-seems-to-require-rbp-to-be-saved – Michael Feb 03 '23 at 17:30
  • 1
    @Jester: For correct stack alignment for `call exit`, you either need to `call` it too, or adjust the stack between `call printf` and a `jmp exit` tailcall. (By 8 up or down, doesn't matter) – Peter Cordes Feb 03 '23 at 17:30
  • @Michael: Recent builds of glibc (by recent GCC) make code that depends on 16-byte stack alignment even for the AL=0 case. Similar to the scanf code-gen, for copying some locals, not just for dumping the incoming XMM regs to the stack. That's why I chose these duplicates; `xor %eax,%eax` is a good idea, but wouldn't make it safe to call printf with a misaligned RSP. – Peter Cordes Feb 03 '23 at 17:32
  • Yeah I got that mixed up, of course you need to do the `pop %rbp` if you switch to `jmp`. You should remove it if you keep the `call`. – Jester Feb 03 '23 at 17:40
  • So why doesn't `push %rbp` need to be there in the first scenario? – puppydrum64 Feb 03 '23 at 17:49
  • Because the _C_ runtime that starts before main align the stack, then the call to `main` misaligns it by 8. The `push` realigns it back on a 16 byte boundary. When you use `_start` there is no _C_ runtime startup code. The stack started aligned and remained aligned. – Michael Petch Feb 03 '23 at 17:56

0 Answers0