windows - 为什么 OSX 在 amd64 间接跳转时出现总线错误?

标签 windows macos assembly x86-64

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

以下代码试图使用 _fn 作为内存位置(或它们的一组)来启动实际目标地址:

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

.globl _asmfn
_asmfn:
  jmp *_fn+8*IDX(%rip)

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

Invalid memory access of location 0x10aa1f320 rip=0x10aa1f320

此代码的最终目标是 Windows,尽管我还没有在那里尝试过(我认为我至少可以先在 OSX/intel 上的测试用例中证明该程序集)。 amd64 跳转至少在名义上是正确的,还是我错过了什么?

关于 trampolines on amd64 的一个很好的引用.

编辑

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

最佳答案

使用 PC 相对寻址时,请记住偏移量必须在 +- 2GB 范围内。这意味着您的跳台和蹦床不能相距太远。关于蹦床,在 Windows x64 上无需破坏任何寄存器就可以完成的传输是:

  1. 一个序列:
    PUSH <high32>
    MOV DWORD PTR [ RSP - 4 ], <low32>
    RET
    这适用于 Win64 和 UN*X x86_64。尽管在 UN*X 上,如果该函数使用 redzone,那么您就是在破坏...

  2. 一个序列:
    JMP [ RIP ]
    .L: <tgtaddr64>
    同样,适用于 Win64 和 UN*X x86_64。

  3. 一个序列:
    MOV DWORD PTR [ RSP + c ], <low32>
    MOV DWORD PTR [ RSP + 8 ], <high32>
    JMP [ RSP + 8 ]
    这是特定于 Win64 的,因为它 (ab) 使用了 Win64 ABI 保留的 32 字节“参数空间”的一部分(恰好 高于 堆栈上的返回地址);与此等价的 UN*X x86_64 将(ab)使用保留的 128 字节“红色区域”的一部分(就在堆栈上的返回地址下方):
    MOV DWORD PTR [ RSP - c ], <low32>
    MOV DWORD PTR [ RSP - 8 ], <high32>
    JMP [ RSP - 8 ]
    只有在调用 trampoline 时可以接受破坏(覆盖)其中的内容时,两者才可用。

如果可以在内存中直接构造这样一个与位置无关的寄存器中性蹦床 - 就像这样(对于方法 1.):

#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;
}

请注意,这没有考虑是否有任何非 volatile 寄存器被“调用”函数破坏;我还遗漏了如何使蹦床缓冲区可执行(堆栈通常不在 Win64/x86_64 上)。

关于windows - 为什么 OSX 在 amd64 间接跳转时出现总线错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11932609/

相关文章:

swift - 如果我在 NSDocument 中取消保存操作,则无法退出应用程序

arrays - 如何在 MIPS 中初始化数组?

assembly - 为什么x86 movsd导致三重故障异常?

Windows:如何将文件规范化到特殊文件夹?

macos - 获取文件夹(或子文件/文件夹)的最后修改日期和时间

java - 使用 Eclipse 开发 Web 应用程序

linux - 在 Debian 上链接依赖于位置的程序集

windows - 在powershell中调用远程脚本时如何调用invoke-expression传递参数?

python - 如何从 Windows API 文档中使用的接口(interface)名称获取 COM 类的 GUID?

c++ - x86下划线前缀问题 : Calling NASM function from C++ function works in x64 but fails in x86