Search code examples
x86compiler-constructionx86-64

Writing a toy compiler - call is segfaulting


This question comes out of me writing a toy compiler, but I don't think that it's necesary to show the compiler side of this question - this is strictly an issue of me not being very experienced with assembly.

I am working on implementing functions into my language. I have generated the following assembly (AT&T) which should print 6. The program segfaults at call *%rax, which I assume means that I'm not saving the address of my function correctly. What should happen is that the function f0 should read the arguments from the stack, multiply them, and leave the result in %rax. The print function is just a wrapper around the printf function from C to print %rax.

I'm running Ubuntu 20.04, and the code was compiled with gcc test.S -o test.out -no-pie, in case it matters.

.data
numfmt:
.asciz "%d\n"

.text
print:
push %rbx
mov %rax, %rsi
mov $numfmt, %rdi
xor %rax, %rax
call printf
pop %rbx
ret

.globl main
main:
push %rbp
mov %rsp, %rbp
jmp s0
f0:
push %rbp
mov %rsp, %rbp
mov 16(%rbp), %rax
push %rax
mov 8(%rbp), %rax
pop %rcx
imul %rcx, %rax
mov %rbp, %rsp
pop %rbp
ret
s0:
mov $f0, %rax
push %rax
mov $1, %rax
push %rax
mov $2, %rax
push %rax
mov -8(%rbp), %rax
call *%rax
add 16, %rsp
call print
mov $0, %rax
mov %rbp, %rsp
pop %rbp
ret

Solution

  • In AT&T syntax, mov f0, %rax is an indirect move: it loads %rax with the qword located at address f0. But you want to load the address itself. That's an immediate move, and so the operand f0 needs to be prefixed with $ to indicate that it's immediate. Thus it should say mov $f0, %rax.

    For similar reasons, add 16, %rsp is wrong as it attempts to add to %rsp the value located at address 16. That page is not mapped hence segfault. Again, you want add $16, %rsp.

    Next, in f0 your frame pointer offsets are wrong. 16(%rbp) is the second parameter pushed, not the first, and 8(%rbp) is the return address. Don't forget to account for the effect on the stack pointer of the push %rbp itself. So you end up computing 2 times the return address instead of 1 times 2. Make those 24(%rbp) and 16(%rbp).

    Finally, you need to make sure the stack is aligned to 16 bytes when you call printf. Library functions behave unpredictably when it's not. Sometimes they may happen not to do anything that requires alignment and them everything appears to work; other times they may crash.