Windows 为什么OSX在amd64间接跳转上出现总线错误?

Windows 为什么OSX在amd64间接跳转上出现总线错误?,windows,macos,assembly,x86-64,Windows,Macos,Assembly,X86 64,我正在尝试为x86和amd64编写蹦床,以便将给定的函数调用立即矢量化到存储在已知内存位置的地址(目的是确保第一个目标地址位于给定的DLL(windows)中) 以下代码试图使用\u fn作为内存位置(或其中一组)来启动实际目标地址: (*_fn[IDX])(); // rough equivalent in C .globl _asmfn _asmfn: jmp *_fn+8*IDX(%rip) IDX旨在使用一些CPP宏来构造,以提供一系列嵌入式DLL向量,每个向量都唯一地映射到函数

我正在尝试为x86和amd64编写蹦床,以便将给定的函数调用立即矢量化到存储在已知内存位置的地址(目的是确保第一个目标地址位于给定的DLL(windows)中)

以下代码试图使用
\u fn
作为内存位置(或其中一组)来启动实际目标地址:

(*_fn[IDX])(); // rough equivalent in C

.globl _asmfn
_asmfn:
  jmp *_fn+8*IDX(%rip)
IDX
旨在使用一些CPP宏来构造,以提供一系列嵌入式DLL向量,每个向量都唯一地映射到函数指针数组中的插槽。 这在一个简单的测试程序中起作用,但当我实际将其放入一个共享库(目前在OSX上进行测试)时,我在尝试向_asmfn代码向量时遇到了一个总线错误:

Invalid memory access of location 0x10aa1f320 rip=0x10aa1f320
这段代码的最终目标是Windows,尽管我还没有在那里尝试过(我想我至少可以先在OSX/intel上的测试用例中证明这个程序集)。amd64跳转至少在名义上是正确的,还是我错过了什么

一个很好的参考

编辑


跳转在Windows7上运行正常(终于有机会进行测试)。然而,我仍然很想知道为什么它在OSX上失败了。总线错误是由KERN_PROTECTION_故障引起的,这似乎表明操作系统保护正在阻止该代码的执行。目标地址是分配的内存(它是libffi生成的蹦床),但我相信它被正确地标记为可执行内存。如果是可执行内存问题,这可以解释为什么我的独立测试代码可以工作(回调蹦床是编译的,而不是分配的)。

使用PC相对寻址时,请记住偏移量必须在+-2GB范围内。这意味着你的跳台和蹦床之间不能太远。就蹦床而言,在Windows x64上可以做什么来传输而不需要删除任何寄存器:

  • 序列:
    推送

    MOV-DWORD PTR[RSP-4],

    RET

    这适用于Win64和UN*X x86_64。虽然在UN*X上,如果函数使用了redzone,那么您正在使用

  • 序列:
    JMP[RIP]

    .L:

    同样,适用于Win64和UN*X x86_64

  • 序列:
    MOV-DWORD-PTR[RSP+c],

    MOV DWORD PTR[RSP+8],

    JMP[RSP+8]

    这是特定于Win64的,因为它(ab)使用Win64 ABI保留的部分32字节“参数空间”(就在堆栈上的返回地址上方);UN*X x86_64相当于(ab)使用保留的128字节“红色区域”(就在堆栈上的返回地址下方)的一部分:
    MOV-DWORD-PTR[RSP-c],

    MOV-DWORD PTR[RSP-8],

    JMP[RSP-8]

    只有在调用蹦床时可以重击(覆盖)其中的内容时,这两个选项才可用

  • 如果有可能在内存中直接构建这样一个位置独立的寄存器中性蹦床-就像这样(对于方法1):

    #包括
    #包括
    char*mystr=“你好,世界!\n”;
    int main(int argc,字符**argv)
    {
    结构属性((压缩)){
    炭推;
    uint32持续推送;
    uint32移动到4个PLC;
    uint32施工至移动;
    charret;
    }mycode={
    0x68,((uint32_t)printf),
    0x042444c7,(uint32_t)((uintpttr_t)printf>>32),
    0xc3
    };
    void*buf=/*以特定于操作系统的方式填充以获取可执行缓冲区*/;
    memcpy(buf和mycode,sizeof(mycode));
    __asm\uuuuuuuuuuuuuuuuuu挥发性__(
    “push$0f\n\t”//这是为了使“jmp”返回
    “jmp*%0\n\t”
    “0:\n\t”:“r”(buf),“D”(mystr),“a”(0));
    返回0;
    }
    

    注意,这并没有考虑函数“调用”是否正在对任何非易失性寄存器进行阻塞;我还遗漏了如何使trampoline缓冲区可执行(堆栈通常不在Win64/x86_64上)。

    使用PC相对寻址时,请记住偏移量必须在+-2GB范围内。这意味着你的跳台和蹦床之间不能太远。就蹦床而言,在Windows x64上可以做什么来传输而不需要删除任何寄存器:

  • 序列:
    推送

    MOV-DWORD PTR[RSP-4],

    RET

    这适用于Win64和UN*X x86_64。虽然在UN*X上,如果函数使用了redzone,那么您正在使用

  • 序列:
    JMP[RIP]

    .L:

    同样,适用于Win64和UN*X x86_64

  • 序列:
    MOV-DWORD-PTR[RSP+c],

    MOV DWORD PTR[RSP+8],

    JMP[RSP+8]

    这是特定于Win64的,因为它(ab)使用Win64 ABI保留的部分32字节“参数空间”(就在堆栈上的返回地址上方);UN*X x86_64相当于(ab)使用保留的128字节“红色区域”(就在堆栈上的返回地址下方)的一部分:
    MOV-DWORD-PTR[RSP-c],

    MOV-DWORD PTR[RSP-8],

    JMP[RSP-8]

    只有在调用蹦床时可以重击(覆盖)其中的内容时,这两个选项才可用

  • 如果有可能在内存中直接构建这样一个位置独立的寄存器中性蹦床-就像这样(对于方法1):

    #包括
    #包括
    char*mystr=“你好,世界!\n”;
    int main(int argc,字符**argv)
    {
    结构属性((压缩)){
    炭推;
    uint32持续推送;
    uint32移动到4个PLC;
    uint32施工图
    
    #include <stdint.h>
    #include <stdio.h>
    
    char *mystr = "Hello, World!\n";
    
    int main(int argc, char **argv)
    {
        struct __attribute__((packed)) {
                    char PUSH;
                    uint32_t CONST_TO_PUSH;
                    uint32_t MOV_TO_4PLUS_RSP;
                    uint32_t CONST_TO_MOV;
                    char RET;
        } mycode = {
                    0x68, ((uint32_t)printf),
                    0x042444c7, (uint32_t)((uintptr_t)printf >> 32),
                    0xc3
        };
        void *buf = /* fill in an OS-specific way to get an executable buffer */;
        memcpy(buf, &mycode, sizeof(mycode));
    
        __asm__ __volatile__(
            "push $0f\n\t"         // this is to make the "jmp" return
            "jmp *%0\n\t"
            "0:\n\t" : : "r"(buf), "D"(mystr), "a"(0));
    
        return 0;
    }