【问题标题】:mmap system call returning -14(-EFAULT??)mmap 系统调用返回 -14(-EFAULT??)
【发布时间】:2016-05-23 06:29:30
【问题描述】:

我正在使用系统调用实现 mmap 功能。(由于某些原因,我正在手动实现 mmap。)

但是我得到了返回值 -14(-EFAULT,我用 GDB 进行了检查)这个消息:

WARN  Nar::Mmap: Memory allocation failed.

这里是函数:

void *Mmap(void *Address, size_t Length, int Prot, int Flags, int Fd, off_t Offset) {
    MmapArgument ma;
    ma.Address = (unsigned long)Address;
    ma.Length = (unsigned long)Length;
    ma.Prot = (unsigned long)Prot;
    ma.Flags = (unsigned long)Flags;
    ma.Fd = (unsigned long)Fd;
    ma.Offset = (unsigned long)Offset;
    void *ptr = (void *)CallSystem(SysMmap, (uint64_t)&ma, Unused, Unused, Unused, Unused);
    int errCode = (int)ptr;
    if(errCode < 0) {
        Print("WARN  Nar::Mmap: Memory allocation failed.\n");
        return NULL;
    }
    return ptr;
}

我写了一个宏(使用类似 malloc() 函数):

#define Malloc(x) Mmap(0, x, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)

我是这样使用的:

Malloc(45);

我查看了手册页。我在 mmap 手册页上找不到有关 EFAULT 的信息,但我在 mmap2 手册页上找到了有关 EFAULT 的信息。

EFAULT 从用户空间获取数据时出现问题。

我认为这意味着将结构传递给系统调用有问题。 但我相信我的结构没有任何问题:

struct MmapArgument {
    unsigned long Address;
    unsigned long Length;
    unsigned long Prot;
    unsigned long Flags;
    unsigned long Fd;
    unsigned long Offset;
};

处理结果值可能有问题? 使用 CallSystem 打开一个文件(不存在)给了我 -2(-ENOENT),这是正确的。

编辑:CallSystem 的完整来源。打开、写入、关闭工作,但 mmap(或 old_mmap)不起作用。 所有的参数都通过了。

section     .text

global CallSystem
CallSystem:
    mov rax, rdi        ;RAX
    mov rbx, rsi        ;RBX

    mov r10, rdx
    mov r11, rcx
    mov rcx, r10        ;RCX
    mov rdx, r11        ;RDX

    mov rsi, r8     ;RSI
    mov rdi, r9     ;RDI

    int 0x80
    mov rdx, 0  ;Upper 64bit
    ret                 ;Return

【问题讨论】:

  • 发布CallSystem的源代码。
  • 请提供最少但完整的示例代码。此外,删除所有宏(它们是邪恶的,考虑内联函数或常量)和所有不必要的强制转换。然后,如果您使用 mmap() 使用 strace 运行最小示例和等效示例,会发生什么?哦,请选择 C ​​和 C++ 之一。
  • 没有代码可以将MmapArgument 的格式转换为系统调用所期望的格式。这段代码只能靠魔法工作。
  • 我看到的另一个重大错误是您使用的是int 0x80。这不处理 64 位地址,并且由于您的指针参数之一似乎是堆栈上的对象 - 地址将不正确(64 位代码使用无法以 32 位表示的堆栈地址) .在开发 64 位代码时,您确实需要使用 syscall 而不是 int 0x80(这也将涉及将参数重新排列为系统调用)。这可能是 int 0x80 失败的原因 - 它使用了错误的地址 ma
  • int 0x80 仅使用 64 位寄存器的低 32 位,因此它们被有效截断。 sycall 使用整个 64 位寄存器。

标签: c++ linux assembly x86-64 system-calls


【解决方案1】:

不清楚你为什么通过CallSystem 函数调用mmap,我认为这是你的任务的要求。

您的代码的主要问题是您使用的是int 0x80。这只有在传递给int 0x80 的所有地址都可以用 32 位整数表示时才有效。在您的代码中并非如此。这一行:

MmapArgument ma;

将你的结构放在堆栈上。在 64 位代码中,堆栈位于可寻址地址空间的顶端,远远超出 32 位地址所能表示的范围。通常堆栈的底部在 0x00007FFFFFFFFFFF 区域的某个地方。 int 0x80 仅适用于 64 位寄存器的下半部分,因此有效地基于堆栈的地址会被截断,从而导致地址不正确。要进行正确的 64 位系统调用,最好使用 syscall 指令

64-bit System V ABI 在第 A.2.1 节 AMD64 Linux 内核约定中有一节介绍了 syscall 接口的一般机制。它说:

  1. 用户级应用程序用作整数寄存器,用于传递序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9。内核接口使用 %rdi, %rsi、%rdx、%r10、%r8 和 %r9。
  2. 系统调用是通过系统调用指令完成的。内核破坏 注册 %rcx 和 %r11。

我们可以通过将systemcallnum 作为最后一个参数来创建您的SystemCall 代码的简化版本。作为第 7 个参数,它将是堆栈上传递的第一个也是唯一的值。我们可以将该值从堆栈中移动到 RAX 以用作系统调用号。前 6 个值在寄存器中传递,除了 RCX 我们可以简单地保持所有寄存器不变。 RCX 必须移到 R10,因为第 4 个参数在普通函数调用和 Linux 内核 SYSCALL 约定之间有所不同。

一些用于演示的简化代码如下所示:

global CallSystem

section .text
CallSystem:

    mov rax, [rsp+8]    ; CallSystem 7th arg is 1st val passed on stack
    mov r10, rcx        ; 4th argument passed to syscall in r10
                        ; RDI, RSI, RDX, R8, R9 are passed straight through
                        ; to the sycall because they match the inputs to CallSystem
    syscall
    ret

C++ 可能看起来像:

#include <stdlib.h>
#include <sys/mman.h>
#include <stdint.h>
#include <iostream>

using namespace std;

extern "C" uint64_t CallSystem (uint64_t arg1, uint64_t arg2,
                                uint64_t arg3, uint64_t arg4,
                                uint64_t arg5, uint64_t arg6,
                                uint64_t syscallnum);

int main()
{
        uint64_t addr;
        addr = CallSystem(static_cast<uint64_t>(NULL), 45,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0, 0x9);
        cout << reinterpret_cast<void *>(addr) << endl;
}

对于mmap,系统调用是0x09。这可以在文件asm/unistd_64.h中找到:

#define __NR_mmap 9

其余参数是mmap 较新形式的典型参数。从手册页:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

如果你在你的可执行文件(即strace ./a.out)上运行strace,你应该会找到如下所示的行:

mmap(NULL, 45, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fed8e7cc000

返回值会有所不同,但应该与演示程序显示的一致。

您应该能够根据您的工作调整此代码。这至少应该是一个合理的起点。


如果要将syscallnum 作为第一个参数传递给CallSystem,则必须修改汇编代码以移动所有寄存器,以便它们在函数调用约定和syscall 约定之间正确对齐。我把它作为一个简单的练习留给读者。这样做会产生效率低得多的代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-22
    • 2012-03-25
    • 1970-01-01
    • 1970-01-01
    • 2014-12-10
    • 2012-02-11
    • 2013-03-25
    • 2012-10-07
    相关资源
    最近更新 更多