mmap系统调用返回-14(-EFAULT ??)

时间:2016-02-12 07:25:12

标签: c++ linux assembly 64-bit x86-64

我正在使用系统调用实现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传递给系统调用会出错。 但我相信我的结构没有问题:

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

1 个答案:

答案 0 :(得分:2)

目前尚不清楚为什么要通过mmap函数致电CallSystem,我认为这是您作业的要求。

您的代码的主要问题是您使用的是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.   
  3. 系统调用通过syscall指令完成。内核破坏了   注册%rcx和%r11。
  4.   

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

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

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(即strace ./a.out),您应该找到一条如下所示的行:

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

返回值会有所不同,但它应与演示程序显示的内容相匹配。

您应该能够将此代码调整为您正在执行的操作。这至少应该是一个合理的起点。

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