我正在尝试编写子例程线程字节码解释器(在运行时将字节码操作动态映射到 native 调用指令)。当我使用 asm("ret")
从被调用函数(用 C 编写)手动返回时,我的翻译过程测试程序正确执行,但编译器生成返回时出现段错误。
我之前使用过汇编,但主要使用 32 位 MIPS,所以不可否认,我仍在学习 x86。
这是我的编译器信息:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/9.1.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto
Thread model: posix
gcc version 9.1.0 (GCC)
我正在编译:
gcc subroutine_threading.c -std=gnu99 -no-pie -g -Wall -Wextra -O0 -o subroutine_threading
我已经使用 cgdb 遍历了代码,在进入我动态生成的子例程后发生了段错误,调试器无法单步执行。
此外,我查看了生成的可执行文件的反汇编,主要区别在于两个版本之间在 greet
中是否存在我手动插入的 ret
.我将在下面包含函数的两个反汇编版本。
生成代码中出现段错误的潜在原因可能是我的call rel32
指令中的一个问题,但是,如下所示,第一次调用成功并且错误仅在在 greet
的末尾调用 puts
。此外,在使用 -no-pie
编译和在 mmap
中使用 MAP_32BIT
之间,我的跳转应该始终在 2GB 以内。所以我认为这不太可能。
我怀疑问题是当它尝试在 greet
中的 pop %rbp
之后返回时,返回地址无效,但我不完全确定。我已经阅读了一些有关为我的设置调用约定的内容,但我不确定如果这是问题所在我做错了什么,因为我动态生成的子例程不接受任何参数并且不返回任何值。
这是源代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/resource.h>
#ifdef C_RETURN
#define RETURN return
#else
#define RETURN asm("ret")
#endif
void greet(void) {
puts("Hello, World!");
RETURN;
}
void dismiss(void) {
puts("Goodbye, World!");
RETURN;
}
void (*jump_table[])(void) = {
greet, dismiss
};
const size_t SIZE = 1024;
void make_subroutine(unsigned char* code, int* bytecode, size_t length, void** jump_table) {
int32_t offset;
unsigned char* original = code;
// push %rbp
*code++ = 0x55; //?
for (size_t i = 0; i < length; i++) {
// call
*code++ = 0xe8;
// relative addr of function
offset = -((int32_t) (code - (unsigned char*) jump_table[bytecode[i]]));
memcpy(code, &offset, sizeof offset);
code += 4;
}
// pop %rbp
*code++ = 0x5d; //?
// ret
*code++ = 0xc3;
// dump generated machine code to file for inspection
FILE* dump = fopen("dump.out", "wb");
fwrite(original, sizeof (unsigned char), code - original, dump);
fclose(dump);
}
void run_subroutine(void) {
void* m = mmap(0, SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
assert(m);
int bytecode[] = {0, 0, 0, 1};
make_subroutine(m, bytecode, sizeof bytecode/sizeof (int), (void**) jump_table);
void (*fn)(void) = m;
fn();
munmap(m, SIZE);
return;
}
int main(int argc, char** argv) {
run_subroutine();
return 0;
}
这是给我带来问题的函数的反汇编
使用-DC_RETURN;
0000000000401196 <greet>:
void greet(void) {
401196: 55 push %rbp
401197: 48 89 e5 mov %rsp,%rbp
puts("Hello, World!");
40119a: 48 8d 3d 67 0e 00 00 lea 0xe67(%rip),%rdi # 402008 <_IO_stdin_used+0x8>
4011a1: e8 8a fe ff ff callq 401030 <puts@plt>
RETURN;
4011a6: 90 nop
}
4011a7: 5d pop %rbp ; probably fails here
4011a8: c3 retq ; or here
没有:
0000000000401196 <greet>:
void greet(void) {
401196: 55 push %rbp
401197: 48 89 e5 mov %rsp,%rbp
puts("Hello, World!");
40119a: 48 8d 3d 67 0e 00 00 lea 0xe67(%rip),%rdi # 402008 <_IO_stdin_used+0x8>
4011a1: e8 8a fe ff ff callq 401030 <puts@plt>
RETURN;
4011a6: c3 retq
}
4011a7: 90 nop
4011a8: 5d pop %rbp
4011a9: c3 retq
还有我动态生成的代码的格式化 hexdump:
55 | push %rbp
e8 943145bf | call greet
e8 8f3145bf | call greet
e8 8a3145bf | call greet
e8 983145bf | call dismiss
5d | pop %rbp
c3 | ret
程序的输出应该是
Hello, World!
Hello, World!
Hello, World!
Goodbye, World!
但是,当我使用 -DC_RETURN 编译时,我得到了
Hello, World!
fish: “./subroutine_threading” terminated by signal SIGSEGV (Address boundary error)
最佳答案
调用的相对地址取自指令的结尾,因此您的起始偏移量不应是code
,而是code + sizeof(offset)
。这将导致您的 greet
函数开始执行 after 序言,从而导致在您 pop %ebp
和 ret
语句被执行。
你的code += 4;
调整应该是code += sizeof(offset)
,最简单的做法是在偏移量计算之前(同时保存之前的在 memcpy
中使用的值)。
不相关,但您可以交换offset
计算的操作数并去掉-
来否定结果。
关于c - 从静态编译函数返回时子例程线程 JIT x86 机器代码段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57136107/