c - 从静态编译函数返回时子例程线程 JIT x86 机器代码段错误

标签 c linux gcc x86 x86-64

我正在尝试编写子例程线程字节码解释器(在运行时将字节码操作动态映射到 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 %ebpret 语句被执行。

你的code += 4;调整应该是code += sizeof(offset),最简单的做法是在偏移量计算之前(同时保存之前的在 memcpy 中使用的值)。

不相关,但您可以交换offset 计算的操作数并去掉- 来否定结果。

关于c - 从静态编译函数返回时子例程线程 JIT x86 机器代码段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57136107/

相关文章:

c++ - 需要 gcc/g++ 在 SCO6 上工作

c - 静态结构初始化

java - Android JNI GetMethodID() 失败

c++ - 为什么我的程序在恰好循环 8192 个元素时很慢?

c++ - 编译报错cygwin regex.h No such file or directory

c - 程序结束后出现段错误

c - 在 OSX 上使用 GDB 检测堆栈损坏(在金丝雀值上设置观察点)

java - 在 Amazon Linux AMI 2013.03 上托管 Java Webservice

c - 将 TASK_COMM_LEN Linux 内核限制增加到 pthread_setname_np

linux - 环境变量的问题