1

I want to write simple and short assembly code in Arm Linux that wait forever.

label:
b label

That works, but takes lots of CPU time.

I thought about using nanosleep and jump to label each X seconds, but how do I use it in assembly?

pause syscall (suggested in comments) is not good because the process has a signal handler.

Is there simple way to do that?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
yfr24493AzzrggAcom
  • 159
  • 1
  • 2
  • 13
  • Yes, you need a system call, and yes reading from a TTY can work if no characters will ever show up on it. If ARM Linux has 64-bit `time_t`, then yeah nanosleep could wait for 2^64-1 seconds, which should be long enough. Otherwise you'd want something that truly blocks. As for how to invoke system calls, search stack overflow (e.g. google with `site:stackoverflow.com`) for ARM system calls, or just google in general. Given the man page and the general method for mapping C args to registers (the calling convention), you can call anything. – Peter Cordes Dec 21 '20 at 02:13
  • 2
    There is a [pause](https://man7.org/linux/man-pages/man2/pause.2.html) system call as well. – Jester Dec 21 '20 at 02:20
  • 1
    @Jester: Thanks, I thought I remembered Linux having a system call like that, but couldn't remember the name right away. Was searching the section 2 man pages when I saw your comment :P – Peter Cordes Dec 21 '20 at 02:40
  • Fun fact, `strace sleep infinity` shows it also uses `pause`, I had just found and tried that, too. – Peter Cordes Dec 21 '20 at 03:51
  • `strace sleep infinity` used `pause` ? That strange, because `sleep` do not need to return us signal has been arrived while `pause ` is return while signal is arrived – yfr24493AzzrggAcom Dec 21 '20 at 06:12
  • 1
    The sleep(3) and nanosleep man pages document otherwise: *sleep() causes the calling thread to sleep either until the number of real-time seconds specified in seconds have elapsed or until a signal arrives which is not ignored.*. The libc wrapper might retry / restart it automatically on EINTR? – Peter Cordes Dec 21 '20 at 06:17
  • 1
    @yfr24493AzzrggAcom Usually you solve this by calling `pause` in an infinite loop. This way, the process only runs briefly when a signal arrives, just to fall asleep immediately after. You can alternatively use signal masks or `sigsuspend` to pause and avoid the delivery of signals. – fuz Dec 21 '20 at 12:35

1 Answers1

6

Yes, you need a system call, and yes both your ideas are viable. (Although nanosleep is limited to the max number of seconds a time_t can represent. This may "only" be 2^31-1 on ARM Linux, which is famously about 68 years, the interval from 1970 until 32-bit time overflows in 2038)

However, Linux has a system call specifically for this, pause(2):

pause() causes the calling process (or thread) to sleep until a signal is delivered that either terminates the process or causes the invocation of a signal-catching function

If you have no signal handlers installed, pause can't return, you could only exit (still by hitting control-C, which delivers SIGINT), or by killing it (SIGTERM) or other normal ways.

If you do have a signal handler installed and want to keep pausing, then obviously you can just call it in a loop. (Probably best to do that instead of calling pause inside the signal handler instead of returning from it). You'll only use CPU time during that brief wakeup to run the signal handler and restart execution of the main thread, which will immediately call into the kernel for another pause.

sleep and nanosleep also return on handled signals, returning -EINTR according to the man pages. (The libc wrapper functions might retry / restart the system call for you, but if you want to use raw kernel calls you'll have to do that yourself.)


As for how to invoke system calls, search stack overflow (e.g. google with site:stackoverflow.com) for ARM system calls, or just google in general. Given the man page and the general method for mapping C args to registers (the calling convention), you can call anything. What is the interface for ARM system calls and where is it defined in the Linux kernel?

Pause takes no args, so you'll just need the call number (__NR_pause) in the right register:

@ arm_pause.S   - build with gcc -nostdlib arm_pause.S
#include <asm/unistd.h>   // contains only C preprocessor macros,  no C stuff.

.globl _start
_start:
  mov  r7, #__NR_pause
  swi  0x0
  b   _start        @ rerun if interrupted by a signal

(untested; I only have arm-none-eabi-gcc installed, not arm-linux-eabi-gcc. But confirmed that ARM Linux headers do have __NR_pause, and using inline asm to check that the syntax assembles: https://godbolt.org/z/PerGTx).

Of course there's no need to use asm for this, you could just as well have used while(1){ pause(); } in C, although that would call the libc wrapper for the system call.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847