ARM inline asm: выход системного вызова со значением, считанным из памяти

Проблема

Я хочу выполнить системный вызов exit в ARM, используя встроенную сборку на Android-устройстве Linux, и я хочу, чтобы значение выхода считывалось из местоположения в памяти.

Пример

Без предоставления этого дополнительного аргумента макрос для вызова выглядит следующим образом:

#define ASM_EXIT() __asm__("mov     %r0, #1\n\t" \
                           "mov     %r7, #1\n\t" \
                           "swi     #0")

Это хорошо работает. Чтобы принять аргумент, я настраиваю его на:

#define ASM_EXIT(var) __asm__("mov     %r0, %0\n\t" \
                              "mov     %r7, #1\n\t" \
                              "swi     #0"          \
                              :                     \
                              : "r"(var))

и я вызываю его, используя:

#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address

ASM_EXIT(GET_STATUS());

Ошибка

недействительный 'asm': номер операнда вне диапазона

Я не могу объяснить, почему я получаю эту ошибку, так как я использую одну входную переменную в приведенном выше фрагменте (% 0/var). Кроме того, я попытался с обычной переменной и по-прежнему получил ту же ошибку.

Ответ 1

Синтаксис Extended-asm требует записи %%, чтобы получить одиночный % в asm-выходе. например для x86:

asm("inc %eax")                // bad: undeclared clobber
asm("inc %%eax" ::: "eax");    // safe but still useless :P

%r7 обрабатывает r7 как число операндов. Как отмечали комментаторы, просто опустите % s, потому что они вам не нужны для ARM, даже с GNU as.


К сожалению, похоже, не способ запросить входные операнды в определенных регистрах на ARM, как вы можете это сделать для x86. (например, ограничение "a" означает eax).

Вы можете использовать register int var asm ("r7"), чтобы заставить var использовать определенный регистр, а затем использовать ограничение "r" и предположить, что он будет в этом регистре. Я не уверен, что это всегда безопасно или хорошая идея, но, похоже, работает даже после inlining. @Jeremy комментирует, что этот метод был рекомендован командой GCC.

Я получил некоторый эффективный код, который позволяет избежать потери команды reg-reg move:

Посмотрите на сборщик компиляторов Godbolt:

__attribute__((noreturn)) static inline void ASM_EXIT(int status)
{
  register int status_r0 asm ("r0") = status;
  register int callno_r7 asm ("r7") = 1;
  asm volatile("swi  #0\n"
      :
      : "r" (status_r0), "r" (callno_r7)
  );
}

#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address

void foo(void) { ASM_EXIT(12); }
    push    {r7}    @            # gcc is still saving r7 before use, even though it sees the "noreturn" and doesn't generate a return
    movs    r0, #12 @ stat_r0,
    movs    r7, #1  @ callno,
    swi  #0
     # yes, it literally ends here, after the inlined noreturn

void bar(int status) { ASM_EXIT(status); }
    push    {r7}    @
    movs    r7, #1  @ callno,
    swi  #0                  # doesn't touch r0: already there as bar() first arg.

Поскольку вы всегда хотите, чтобы значение читалось из памяти, вы можете использовать ограничение "m" и включить ldr в свой встроенный asm. Тогда вам не понадобится трюк register int var asm("r0"), чтобы избежать потраченного впустую mov для этого операнда.

Не всегда может понадобиться mov r7, #1, поэтому я использовал синтаксис register asm() для него. Если gcc хочет константу 1 в регистре где-то еще в функции, она может сделать это в r7, так что она уже существует для ASM_EXIT.


В любой момент, когда первая или последняя инструкция оператора GNU C inline asm являются инструкциями mov, вероятно, есть способ удалить их с лучшими ограничениями.