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

проблема

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

пример

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

#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") чтобы заставить переменную использовать определенный регистр, а затем использовать "r" ограничение и предположим, что он будет в этом регистре. Я не уверен, что это всегда безопасно или хорошая идея, но, похоже, это работает даже после встраивания. @ Джереми комментирует, что этот метод был рекомендован командой GCC.

Я получил сгенерированный эффективный код, который позволяет не тратить впустую инструкцию на перемещение reg-reg:

Посмотрите это в проводнике компилятора 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()'s first arg.

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

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


Каждый раз, когда первая или последняя инструкция встроенного asm-оператора GNU C mov инструкции, вероятно, есть способ удалить их с лучшими ограничениями.

Другие вопросы по тегам