【问题标题】:mmap Mac: Segmentation faultmmap Mac:分段错误
【发布时间】:2018-06-06 05:01:51
【问题描述】:

我的 Mac 上的以下操作成功:

int main() {
    int* addr = (int*) mmap(0, 100, 1 | 2, 2 | 4096, -1, 0);

    *addr = 25;

    return 0;
}

但是下面的代码是相同的,但是当我尝试用分段错误写入*addr 时失败:

int main() {
    int* addr = (int*) syscall(SYS_mmap, 0, 100, 1 | 2, 2 | 4096, -1, 0);

    *addr = 25;

    return 0;
}

syscall 成功返回给我一个内存地址,但是当我尝试写入它时它失败了。

我是这样编译的:

g++ ./c++/mmap.cc -o ./mmap && ./mmap

如果我使用dtruss 运行这两个版本:

g++ ./c++/mmap.cc -o ./mmap && sudo dtruss ./mmap

然后两个版本都成功了,我看到两个相同的 mmap 调用:

mmap(0x0, 0x64, 0x3, 0x1002, 0xFFFFFFFF, 0x0)            = 0xXXXXXXX 0

为什么syscall 版本给我分段错误,我错过了什么?

附:如果我在 Linux 上做类似的事情,它就可以正常工作。

所以,据我了解,Mac 上的 mmap 函数不会执行 syscall(SYS_mmap, ...。那它有什么作用呢?谁能给我一些我可以看到实现的链接。

编辑:

Mac 上的 syscall 似乎只返回前 4 个字节。有 64 位的syscall 版本吗?

拆开:

mmap版本:

_main:
0000000100000cf0        pushq   %rbp
0000000100000cf1        movq    %rsp, %rbp
0000000100000cf4        subq    $0x30, %rsp
0000000100000cf8        xorl    %eax, %eax
0000000100000cfa        movl    %eax, %ecx
0000000100000cfc        movl    $0x64, %eax
0000000100000d01        movl    %eax, %esi
0000000100000d03        movl    $0x3, %edx
0000000100000d08        movl    $0x1002, %eax
0000000100000d0d        movl    $0xffffffff, %r8d
0000000100000d13        movl    $0x0, -0x14(%rbp)
0000000100000d1a        movq    %rcx, %rdi
0000000100000d1d        movq    %rcx, -0x28(%rbp)
0000000100000d21        movl    %eax, %ecx
0000000100000d23        movq    -0x28(%rbp), %r9
0000000100000d27        callq   0x100000ed6 ## symbol stub for: _mmap
0000000100000d2c        movq    0x2cd(%rip), %rdi ## literal pool symbol address: __ZNSt3__14coutE
0000000100000d33        movq    %rax, -0x20(%rbp)
0000000100000d37        movq    -0x20(%rbp), %rax
0000000100000d3b        movq    %rax, %rsi

syscall版本:

_main:
0000000100000cf0        pushq   %rbp
0000000100000cf1        movq    %rsp, %rbp
0000000100000cf4        subq    $0x30, %rsp
0000000100000cf8        movl    $0xc5, %edi
0000000100000cfd        xorl    %esi, %esi
0000000100000cff        movl    $0x64, %edx
0000000100000d04        movl    $0x3, %ecx
0000000100000d09        movl    $0x1002, %r8d
0000000100000d0f        movl    $0xffffffff, %r9d
0000000100000d15        movl    $0x0, -0x14(%rbp)
0000000100000d1c        movl    $0x0, (%rsp)
0000000100000d23        movb    $0x0, %al
0000000100000d25        callq   0x100000ed6 ## symbol stub for: _syscall
0000000100000d2a        movq    0x2cf(%rip), %rdi ## literal pool symbol address: __ZNSt3__14coutE
0000000100000d31        movslq  %eax, %r10
0000000100000d34        movq    %r10, -0x20(%rbp)
0000000100000d38        movq    -0x20(%rbp), %r10
0000000100000d3c        movq    %r10, %rsi

【问题讨论】:

  • @t0mm13b 这是一个纯编程问题,以一个非常典型/正确的 SO 问题 Why does the syscall version give me segmentation fault, what am I missing? 结尾。只有第二个问题是要求链接,这只能证明 Vad 愿意自己排序;很好!
  • @t0mm13b Mac 上的 SYS_mmap 评估为 197,如果您看到 197 系统调用也在您的链接中,它确实是 mmap
  • 也许在mac上阅读man syscall
  • @usr2564301 - syscall 的返回类型是 int
  • 在 Linux 上 syscall 是 64 位的。 FreeBSD 人如何执行 64 位系统调用?

标签: c++ c system-calls


【解决方案1】:

显然Mac没有64位的syscall功能,这里简单实现一下:

#include <sys/types.h>

#define CARRY_FLAG_BIT 1

inline int64_t syscall6(int64_t num, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5, int64_t arg6) {
    int64_t result;
    int64_t flags;

    __asm__ __volatile__ (
        "movq %6, %%r10;\n"
        "movq %7, %%r8;\n"
        "movq %8, %%r9;\n"
        "syscall;\n"
        "movq %%r11, %1;\n"
        : "=a" (result), "=r" (flags)
        : "a" (num), "D" (arg1), "S" (arg2), "d" (arg3), "r" (arg4), "r" (arg5), "r" (arg6)
        : "%r10", "%r8", "%r9", "%rcx", "%r11"
    );

    return (flags & CARRY_FLAG_BIT) ? -result : result;
}

你可以通过将系统调用号码移动0x2000000来在mac上使用它:

int* addr = (int*) syscall6(0x2000000 + SYS_mmap, 0, 100, 1 | 2, 2 | 4096, -1, 0);

您可以找到更多here

【讨论】:

  • 干得好!关于术语的小注解:不是syscall 编号被移动,而是它的(必需的)class。见dustin.schultz.io/mac-os-x-64-bit-assembly-system-calls.html。由于 OS X 需要它,您可以将其放入您的调用例程中。
  • 我不会太相信 "a"(num) 被放在 *ax 上,因为这仅适用于 x86 IIRC。你最好有一个明确的movq %2, %%rax,恕我直言。
  • @edmz:像"a" 这样的特定寄存器约束在 x86-64 上的工作方式与在 i386 上的工作方式完全相同。 gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html。 (除了 "=A" 不幸地会选择 RDX or RAX 而不是 EDX:EAX 用于 64 位操作数。这意味着 RDX:RAX 用于 128 位操作数)。 r8..r15 没有特定的寄存器限制,但您可以使用 register int foo asm("r10"); Constraining r10 register in gcc inline x86_64 assembly
  • 这个 asm 语句仍然只需要 "syscall" 指令 + 适当的 asm 约束。但是它缺少"memory" clobber:在以这种方式进行write() 系统调用之前存储到缓冲区中可以优化为死存储或重新排序,因为你还没有告诉任何东西都可以读取该存储的编译器。
  • 您还可以使用 GCC6 标志输出语法让 gcc 生成直接在 CF 上分支的代码(而不是 R11 中 RFLAGS 的副本)。但不幸的是,clang 仍然不支持这种语法,所以可能不是一个好主意。使用 R11 是一个好主意,只要 OS X 在返回用户空间期间总是 使用当前标志更新 R11,即使它选择使用 iret 而不是 sysret。 (就像 Linux 一样,如果使用了 ptrace,解决 Intel CPU 的错误,该错误会让调试器设置的非规范返回 RIP 导致环 0 中的 #GP 异常,RSP 指向用户堆栈!)跨度>
猜你喜欢
  • 1970-01-01
  • 2020-06-29
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 2011-11-29
  • 2021-12-22
  • 2018-10-26
  • 2017-07-30
相关资源
最近更新 更多