Я пытаюсь написать компилятор точно в срок, и у меня есть кусок кода, который просто не хочет работать. Моя платформа — x86-64 Ubuntu.
У меня есть следующий код, написанный на yasm:
bits 64
mov rdx, 1
mov rcx, 'A'
mov rbx, 1
mov rax, 4
int 0x80
ret
Так что если я правильно понял это надо написать A
на стандартный вывод Теперь я компилирую этот код с
yasm -f bin test.yasm
Это привело к следующему машинному коду:
0x48 0xc7 0xc2 0x01 0x00 0x00 0x00 0x48 0xc7 0xc1 0x41 0x00
0x00 0x00 0x48 0xc7 0xc3 0x01 0x00 0x00 0x00 0x48 0xc7 0xc0
0x04 0x00 0x00 0x00 0xcd 0x80 0xc3
а затем я читаю полученный код на C ++ и вызываю его:
void *memory = allocate_executable_memory(sizeof(code));
emit_code_into_memory(sizeof(code), code, memory);
JittedFunc func = reinterpret_cast<JittedFunc>(memory);
func();
Я думаю, что часть C ++ хороша, так как я уже попробовал это с простыми арифметическими операциями, и это работало хорошо.
Так что в любом случае нет ошибки сегментации, кажется, что код выполняется, но ничего не происходит, в stdout ничего нет.
Любой совет?
//РЕДАКТИРОВАТЬ: полный код C ++:
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <sys/mman.h>
void* allocate_executable_memory(size_t size) {
void *ptr = mmap(
0,
size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (ptr == (void*)(-1)) {
perror("mmap");
return nullptr;
}
return ptr;
};
void emit_code_into_memory(size_t code_length, uint8_t *code, void *memory) {
memcpy(reinterpret_cast<uint8_t*>(memory), code, code_length);
};
typedef void (*JittedFunc)();
int main(int argc, char* argv[]) {
/* Use like this:
bin/jit 0xb8 0x11 0x00 0x00 0x00 0xc3
*/
if (argc <= 1) {
return 1;
}
uint8_t code[argc-1];
for (int i = 1; i < argc; i++) {
code[i-1] = std::stoul(argv[i], nullptr, 16);
}
void *memory = allocate_executable_memory(sizeof(code));
emit_code_into_memory(sizeof(code), code, memory);
JittedFunc func = reinterpret_cast<JittedFunc>(memory);
func();
return 0;
};
Системный вызов write ожидает указатель на записываемый объект, а не немедленный. Кроме того, 64-битное использование syscall
инструкция с другим соглашением о вызовах. Это важно для указателей, которые в противном случае были бы усечены до 32 бит. Кроме того, номера функций также отличаются, поэтому ваш код фактически вызывает stat
системный вызов, как можно увидеть с помощью strace
:
stat(NULL, NULL) = -1 EFAULT (Bad address)
Вместо этого вы должны попробовать следующий код:
push 'A'
mov rdi, 1 ; stdout
mov rsi, rsp ; buf
mov rdx, 1 ; count
mov rax, 1 ; sys_write
syscall
pop rdi ; cleanup
ret
Это использует стек для хранения письма для печати. Очистка может использовать любой сохраненный вызывающим регистр нулевой регистр или может быть переписан как add rsp, 8
, Возвращаемое значение из системного вызова находится в eax
,
32-битная версия может выглядеть так:
push ebx ; callee-saved
push 'A'
mov ebx, 1 ; stdout
mov ecx, esp ; buf
mov edx, 1 ; count
mov eax, 4 ; sys_write
int 0x80
pop edi ; cleanup buf
pop ebx ; restore ebx
ret
Заметить, что ebx
должен быть сохранен в соответствии с соглашением о вызовах.
Других решений пока нет …