To elaborate on some comments you got on the SO version of this question, the main thing you're missing is stack alignment, a requirement of the SysV ABI calling conventions that's often overlooked by beginners.
The requirement is (ABI 3.2.2):
The end of the input argument area shall be aligned on a 16 (32 or 64, if
__m512 is passed on stack) byte boundary.
So that means that, at the instant before you execute a
call instruction, the stack pointer
%rsp needs to be a multiple of 16. In your case you have a
push of 8 bytes without a
pop in between your two calls to
multiply, so they can't both have correct alignment.
Some wrinkles are introduced here by the fact that your parent function is
_start instead of
main or another function called by C code:
The conditions on entry to
_start are described in 3.4 of the ABI. In particular, the stack is aligned to 16 bytes at the instant
_start gets control. Also, since you cannot return from
_start (there is no return address on the stack), you have to exit with a system call as you do, and so there is no need to save any registers for the caller.
main or any other function, the stack would have been aligned to 16 bytes before your function was called, so the extra 8 bytes for the return address mean that on entry to your function, the stack is now "misaligned", i.e. the value of
rsp is 8 more or less than a multiple of 16. (Since one would normally only manipulate the stack in 8-byte increments, it's only really ever in two possible states, which I'll call "aligned" and "misaligned".) Also, in such functions, you would need to preserve the contents of the callee-saved registers
%rbx, %rbp, %r12-r15.
So as it stands, your first call to
multiply has correct stack alignment, but your second does not. Of course, it's only of academic interest in this case, because
multiply doesn't do anything that needs stack alignment (it doesn't even use the stack at all), but it's good practice to do it right.
One way to fix it would be to subtract another 8 bytes from the stack pointer before the second call, either with
sub $8, %rsp or (more efficiently) by simply
pushing any random 64-bit register. But why should we bother to use the stack at all to save this value? We could simply put it in one of the callee-saved registers, say
%rbx, which we know
multiply must preserve. Normally this would require us to save and restore the contents of this register, but since we are in the special case of being
_start, we don't have to.
A separate comment is that you have a lot of instructions like
mov $7, %rdi where you operate on 64-bit registers. This would be better to write as
mov $7, %edi. Recall that every write to a 32-bit register will zero the upper half of the corresponding 64-bit register, so the effect is the same as long as your constant is unsigned 32 bits, and the encoding of
mov $7, %edi is one byte shorter as it doesn't need a REX prefix.
So I'd revise your code as
# Calculate 2*3 + 7*9 = 6 + 63 = 69
# The multiplication will be done with a separate function call
# Parameters passed in System V ABI
# The first 6 integer/pointer arguments are passed in:
# %rdi, %rsi, %rdx, %rcx, %r8, and %r9
# The return value is passed in %rax
# multiply(2, 3)
# Part 1 --> Load the parameters
mov $2, %edi
mov $3, %esi
# Part 2 --> Call the function (`push` return address onto stack and `jmp` to function label)
# Part 3 --> Save the return value
mov %rax, %rbx # could also do mov %ebx, %eax if you know the result fits in 32 bits
# multiply(7, 9)
mov $7, %edi
mov $9, %esi
# Add the two together
add %rbx, %rax
mov %rax, %rdi
# for the 64-bit calling convention, do syscall instead of int 0x80
# use %rdi instead of %rbx for the exit arg
# use $60 instead of 1 for the exit code
mov $60, %eax # use the `_exit` [fast] syscall
# rdi contains out exit code
syscall # make syscall
mov %rdi, %rax
imul %rsi, %rax
If you want to rely on the result of
multiply fitting in 32 bits, you could replace
mov %rax, %rbx with
mov %eax, %ebx to save one byte. And likewise, the "Add the two together" could use 32-bit instructions instead to save two more bytes.
Finally, there's a stylistic point on whether to use the AT&T-syntax operand size suffixes, like
add. They are optional when one operand is a register, since the operand size can be deduced from the size of that register (e.g. 32 bits for
%eax, 64 bits for
%rax, etc). My personal preference is to always use them, as a little extra verification that you're really writing what you mean, but omitting them as you (mostly) did is also common and fine; just be consistent. You did have one instance of
movq $60, %rax where it wasn't needed, so for consistency I omitted the suffix there. (I also changed it to
%eax for the reasons noted above.)