Прямой вызов функции C с использованием встроенной сборки GCC

Если вы хотите вызвать функцию C/С++ из встроенной сборки, вы можете сделать что-то вроде этого:

void callee() {}
void caller()
{
    asm("call *%0" : : "r"(callee));
}

Затем GCC испускает код, который выглядит следующим образом:

movl $callee, %eax
call *%eax

Это может быть проблематично, поскольку косвенный вызов уничтожит конвейер на более старых процессорах.

Так как адрес callee в конечном итоге является константой, можно предположить, что можно было бы использовать ограничение i. Цитата из GCC онлайн docs:

`я '

Допускается немедленный целочисленный операнд (один с постоянным значением). Эта включает символические константы, значения будут известны только при сборке время или позже.

Если я попытаюсь использовать его так:

asm("call %0" : : "i"(callee));

Я получаю следующую ошибку от ассемблера:

Ошибка: суффикс или операнды недействительны для `call '

Это связано с тем, что GCC испускает код

call $callee

Вместо

call callee

Итак, мой вопрос заключается в том, можно ли сделать вывод GCC правильным call.

Ответ 1

Я получил ответ из списка рассылки GCC:

asm("call %P0" : : "i"(callee));

Теперь мне просто нужно выяснить, что означает %P0, потому что это кажется недокументированной функцией...

Изменить. Посмотрев исходный код GCC, он не совсем ясно, что означает код P перед ограничением. Но, помимо всего прочего, он не позволяет GCC помещать $ перед постоянными значениями. Это именно то, что мне нужно в этом случае.

Ответ 2

Возможно, я что-то упустил, но

extern "C" void callee(void) 
{

}

void caller(void)
{
  asm("call callee\n");
}

должен работать нормально. Вам нужно extern "C", чтобы имя не было оформлено на основе правил переключения имен С++.

Ответ 3

Как насчет.

asm volatile ("call $callee");

так как вы знаете имя символа.

Изменить: То, что я хотел сделать, это то, что вам не нужно проходить gcc-соглашения для аргументов asm здесь, но вам просто нужно выполнить обработку текста, чтобы выполнить задание.

Ответ 4

Трюк - это строковое литеральное конкатенация. Прежде чем GCC начнет пытаться получить какой-либо реальный смысл из вашего кода, он будет конкатенировать смежные строковые литералы, поэтому, хотя строковые строки не совпадают с другими строками, которые вы используете в своей программе, они должны быть объединены, если вы это сделаете:

#define ASM_CALL(X) asm("\t call  " X "\n")


int main(void) {
    ASM_CALL( "my_function" );
    return 0;
}

Поскольку вы используете GCC, вы также можете сделать

#define ASM_CALL(X) asm("\t call  " #X "\n")

int main(void) {
   ASM_CALL(my_function);
   return 0;
}

Если вы еще не знаете, вы должны знать, что вызов вещей из встроенной сборки очень сложный. Когда компилятор генерирует свои собственные вызовы другим функциям, он включает в себя код для настройки и восстановления вещей до и после вызова. Однако он не знает, что это должно быть сделано для вашего вызова. Вам придется либо включить это сами (очень сложно получить право, и может порвать с обновлением компилятора или флагами компиляции) или убедиться, что ваша функция написана таким образом, что она, похоже, не изменила никаких регистров или условий стек (или переменная на нем).

edit this будет работать только для имен функций C, а не С++, поскольку они искажены.

Ответ 5

Если вы генерируете 32-битный код (например, -m32 gcc), следующий asm-поток выдает прямой вызов:

asm ("call %0" :: "m" (callee));