YASM 程序集在 jitted 函数中调用 stdout.write
YASM assembly calling stdout.write in jitted function
我正在尝试编写一个即时编译器,但我有一段代码不想工作。我的平台是 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;
};
写系统调用需要一个指向要写的东西的指针,而不是立即数。此外,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
。来自系统调用的 return 值在 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
必须根据调用约定保留。
我正在尝试编写一个即时编译器,但我有一段代码不想工作。我的平台是 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;
};
写系统调用需要一个指向要写的东西的指针,而不是立即数。此外,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
。来自系统调用的 return 值在 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
必须根据调用约定保留。