我正在使用系统调用实现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
答案 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
接口的一般机制的部分。它说:
- 用户级应用程序使用整数寄存器来传递序列%rdi,%rsi,%rdx,%rcx,%r8和%r9。内核接口使用%rdi, %rsi,%rdx,%r10,%r8和%r9。
- 系统调用通过syscall指令完成。内核破坏了 注册%rcx和%r11。
醇>
我们可以通过将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
,则必须修改汇编代码以移动所有寄存器,以便它们在函数调用约定和{{之间正确对齐惯例。我把这作为一个简单的练习留给读者。这样做会产生效率低得多的代码。