IchMagBier
IchMagBier

Reputation: 39

How do I use "nanosleep" in x86 Assembly?

I have some problems with Linux' nanosleep syscall. This code should wait 2 seconds before it exits, but it doesn't:

.text
.globl _start
_start:
pushq %rbp
movq %rsp,%rbp        

pushq $0    #0 nanoseconds
pushq $2    #2 seconds

leaq (%rbp),%rdi    #the time structure on the stack       
movq $35,%rax       #nanosleep syscall
movq $0,%rsi        #disable useless parameter           
syscall             
leave               

Upvotes: 2

Views: 2360

Answers (2)

Peter Cordes
Peter Cordes

Reputation: 365237

After pushing stuff on the stack, use mov %rsp, %rdi. RSP (the current stack pointer) is what's pointing to your newly-pushed struct, not RBP (the frame pointer). lea (%rsp), %rdi is a less-efficient way to write that, but would also work.

You're passing RBP as the pointer, but it still points to the saved RBP value from making a "stack frame". Note that is _start, not a function, so you're really just terminating the linked list of saved-RBP values. The System V ABI recommends doing this by explicitly setting RBP to zero, but Linux zeros registers (other than RSP) on process startup so this works.

Anyway, at _start, (rsp) is argc, and then you push a 0 (the saved RBP) and set RBP to point there. So the struct you're passing to sys_nanosleep is {0, argc}. Or argc nanoseconds. (Test with strace to see if I got this right; I didn't try it.)


This is what you should do:

pushq $0    #0 nanoseconds
pushq $2    #2 seconds

### RSP instead of RBP in the next instruction:
mov   %rsp, %rdi    #the time structure we just pushed
mov   $35, %eax     #SYS_nanosleep
xor   %esi, %esi    #rem=NULL, we don't care if we wake early
syscall
 # RSP is 16 bytes lower than it was before this fragment, in case that matters for later code.

I also optimized by not using 64-bit operand-size when you don't need it (because writing a 32-bit register zeros the upper 32 bits). I like letting register sizes imply operand size instead of using movq, like in Intel syntax. I also used the idiomatic way to zero a register, and improving the comments.


Your proposed answer is broken: subq $16, %rbp before leave is bad idea.

If you want to address your newly-pushed struct relative to your RBP stack frame, you could lea -16(%rbp), %rdi.

But modifying %rbp will make leave set RSP to the updated RBP and then pop the low qword of the struct into RBP, instead of the caller's saved RBP. RSP is left pointing to the high qword of your struct, rather than the function return address.

This probably only works because you just use sys_exit after leave, because you're not in a function so you couldn't ret anyway. It makes no sense to use leave in _start, because it's not a function. You have to just sys_exit or sys_exit_group.

But if you used this fragment inside an actual function, it would break the stack.

Upvotes: 7

IchMagBier
IchMagBier

Reputation: 39

I figured it out on myself. This works:

#call nanosleep    
movq $35,%rax
subq $16, %rbp
movq %rbp,%rdi  
movq $0,%rsi          
syscall             
leave  

Upvotes: 0

Related Questions