I am trying to understand how to properly work with stack in x86-64 assembly in Linux. I am pushing some numbers to the stack then I want to take the top number in the stack and print the appropriate ASCII value. But it prints different stuff every time. Please help me understand why the code below behaves that way.
.section .bss
.comm x, 4
.section .text
.globl _start
_start:
push $52
push $65
mov %rsp, (x)
mov $1, %rax
mov $1, %rdi
mov $x, %rsi
mov $1, %rdx
syscall
mov $60, %rax
movb $0, %dil
syscall
Also, I would appreciate it if you gave me some sources where I can learn to work with the stack in assembly.
CodePudding user response:
The instruction mov %rsp, (x)
writes the value of RSP (current stack pointer) to the location x
in memory, which is in your .bss
section. Then, you are doing mov $x, %rsi
: this moves the address of x
into RSI, so RSI will point to your .bss
variable, which holds the value of the stack pointer. When you try and issue a write
syscall reading from the memory pointed by RSI, your output will be the least significant byte of RSP that you saved there. Since the position of the stack can vary every single execution of your program, this is why you are getting a different output every time.
Also, you are reserving space in .bss
with .comm x, 4
, but you are moving RSP into it with mov %rsp, (x)
, which is an 8-byte move.
What you really want to do to print values from the stack to standard output is simply mov %rsp, %rsi
. You don't need x
at all:
.section .text
.globl _start
_start:
push $65
mov $1, %rax
mov $1, %rdi
mov %rsp, %rsi
mov $1, %rdx
syscall
mov $60, %rax
mov $0, %rdi
syscall
The above code should output A
and exit.
If you want to use an intermediate variable in the .bss
, you will have to perform two moves to copy a value from the stack into it using an intermediate scratch register (since mov
can only take one memory operand at a time):
.section .bss
.comm x, 4
.section .text
.globl _start
_start:
push $65
movb (%rsp), %al
movb %al, (x)
mov $1, %rax
mov $1, %rdi
mov $x, %rsi
mov $1, %rdx
syscall
mov $60, %rax
mov $0, %rdi
syscall
Also, note that on Linux exit
takes an int
as parameter which is 4 bytes on x86, so movb $0, %dil
won't suffice in general. In your case it's ok since you previously set RDI to 1
and RDI is preserved on syscall
.
In general, you can use xor