Я пытаюсь динамически генерировать и выполнять ассемблерный код ARM64, используя GCC и библиотеку AsmJit в системе Linux aarch64. Я постоянно сталкиваюсь с ошибкой «недопустимой инструкции» даже в самых простых программах. Вот пример кода, вызывающего проблему:
#include <stdio.h>
#include "asmjit.h"
#include "a64.h"
using namespace asmjit;
typedef int (*SumFunc)(int, int);
int main() {
// Signature of the generated function.
for (int nop_num = 0; nop_num < 10; nop_num += 1) {
JitRuntime rt; // Create a runtime specialized for JIT.
CodeHolder code; // Create a CodeHolder.
code.init(
rt.environment()); // Initialize code to match the JIT environment.
a64::Assembler a(&code); // Create and attach a64::Assembler to code.
// a64::Gp s1 = a64::w0;
// a64::Gp s2 = a64::w1;
// a64::Gp result = a64::w11;
a.add(a64::w0, a64::w0, a64::w1);
a.ret(a64::w0);
SumFunc fn;
Error err = rt.add(&fn, &code); // Add the generated code to the runtime.
if (err) return 1; // Handle a possible error returned by AsmJit.
int final_result = fn(10, 20); // Execute our constructed function fn
printf("%d", final_result);
rt.release(fn); // Explicitly remove the function from the runtime
code.reset();
}
return 0;
}
Почему даже этот простой ассемблерный код ARM64 вызывает ошибку «недопустимой инструкции» в моей системе Linux aarch64? Нужно ли мне настраивать или изменять какие-либо настройки в AsmJit, чтобы адаптировать его к архитектуре aarch64?
Шаги, которые я пробовал Я подтвердил, что моя система имеет архитектуру aarch64 и что настройки компилятора GCC верны. Я попробовал другие фрагменты кода из примеров AsmJit и столкнулся с той же проблемой. Я просмотрел документацию AsmJit на предмет соответствующих параметров конфигурации или известных проблем совместимости, но не нашел никаких явных указаний. Я надеюсь найти способ правильно использовать AsmJit в Linux aarch64 или понять потенциальные причины этой проблемы. Спасибо за любую помощь, которую вы можете оказать!
Проблема в вашей ret
инструкции:
a.ret(a64::w0);
Это недействительно. Во-первых, потому что вы используете 32-битный регистр для указания адреса возврата (он должен быть 64-битным), а во-вторых, инструкция ret
ожидает регистр, содержащий адрес, куда нужно вернуться, а не возвращаемое значение.
Вам необходимо сначала изучить соглашения о вызовах, если вы хотите сгенерировать код для AArch64. И одно замечание из моего опыта: Apple внесла некоторые изменения в соглашение о вызовах AArch64 по умолчанию, так что вам тоже придется это учитывать.
Таким образом, правильная инструкция возврата будет такой:
a.ret(a64::x30)
Возможно, вас смутил инструмент компилятора AsmJit, который обычно использует ret()
в качестве встроенной функции, но здесь вы используете напрямую Ассемблер, который не предоставляет никаких встроенных функций (что вы генерируете, то и получаете).
Кстати: в документации AsmJit есть раздел рекомендаций (https://asmjit.com/doc/index.html), в котором предлагается использовать Logger и ErrorHandler. Когда вы выдаете недопустимую инструкцию, обработчик ошибок будет ее правильно отформатировать, так что вы точно будете знать, что это такое, а если вы поставите точку останова в свой обработчик ошибок, вы будете точно знать, какая часть вашего кода вызвала ошибку. В AsmJit имеется множество инструментов, которые могут значительно облегчить вашу жизнь при их использовании.
ret w0
даже не кодируется (ret
может принимать только 64-битный регистровый операнд), поэтому мне интересно, что же было сгенерировано. Если бы это было ret x0
, это не должно было привести к сбою при использовании SIGILL, поскольку это действительная инструкция; он просто разветвился бы на абсолютный адрес 30, который, как я предполагаю, был бы просто SIGSEGV, поскольку это должна быть несопоставленная страница.
Ох, испустило udf
; или же он ничего не излучал и выполнял нули, оставшиеся в неинициализированном пространстве. Было бы лучше, если бы ассемблер сообщал об ошибке при указании недопустимого операнда вместо того, чтобы собирать мусор или ничего.
Ассемблер выдает ошибку kErrorInvalidInstruction и отказывается кодировать инструкцию. Вот почему я упомянул об использовании регистратора и обработчика ошибок — я не думаю, что люди захотят проверять возвращаемое значение каждой «испускающей» функции, поэтому существует обработчик ошибок, который может быть запущен, когда происходит что-то подобное.
После использования оператора a.ret(a64::x30); я получил правильный результат. Следует отметить, что возвращаемое значение должно быть заранее записано в регистр x0. Спасибо за ответ на этот вопрос для начинающих.
@ChalesGuo Я не пытаюсь документировать соглашения о вызове функций в AsmJit, поскольку это указано в другом месте. AsmJit также предоставляет API-интерфейс функций, который используется его компилятором и может также использоваться пользователями Assembler/Builder - он понимает соглашения о вызовах и может генерировать для вас пролог/эпилог - однако возвращаемое значение всегда должно обрабатываться пользователем. в любом случае.
Инструкция
ret
выглядит неправильно. Его операндом должен быть регистр, содержащий обратный адрес (обычно этоx30
, регистр связи), но вы передаете емуw0
. Вы уверены, что это правильно? Также проверьте, нужен ли какой-то барьер между генерацией кода и его выполнением.