我正在使用系统调用来实现mmap函数(由于某些原因,我正在手动实现mmap)。
但我得到的返回值-14(-EFAULT,我检查了GDB)whith这个消息:
WARN Nar::Mmap: Memory allocation failed.
这里是function:
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; }
我写了一个macros(使用像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的完整来源。 打开,写入,closures作品,但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
,我会认为这是你的任务的要求。
你的代码的主要问题是你使用int 0x80
。 如果所有传递给int 0x80
的地址都可以用一个32位整数表示,那么这个函数只能工作。 在你的代码中情况并非如此。 这一行:
MmapArgument ma;
把你的结构放在堆栈上。 在64位代码中,堆栈位于可寻址地址空间的顶端,远远超出了可以用32位地址表示的地址。 通常堆栈的底部是0x00007FFFFFFFFFFF区域的某处。 int 0x80
仅适用于64位寄存器的下半部分,所以有效的基于堆栈的地址会被截断,导致地址不正确。 要进行正确的64位系统调用,最好使用syscall
指令
64位系统V ABI在A.2.1 AMD64 Linux coreel约定中有关于syscall
接口一般机制的一节。 它说:
- 用户级应用程序使用整数寄存器来传递序列%rdi,%rsi,%rdx,%rcx,%r8和%r9。 内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9。
- 系统调用是通过系统调用指令完成的。 内核破坏寄存器%rcx和%r11。
我们可以通过将systemcallnum
作为最后一个参数来创建SystemCall
代码的简化版本。 作为第七个参数,它将是堆栈中传递的第一个也是唯一的值。 我们可以把这个值从栈中移到RAX中作为系统调用号。 前6个值被传递到寄存器中,除了RCX,我们可以简单地保留所有的寄存器。 必须将RCX移到R10,因为正常的函数调用和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
),你应该找到一个如下的代码:
mmap(NULL, 45, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fed8e7cc000
返回值会有所不同,但它应该与演示程序显示的内容相匹配。
你应该能够使这个代码适应你正在做的事情。 这至少应该是一个合理的出发点。
如果要将syscallnum
作为第一个参数传递给CallSystem
,则必须修改汇编代码以移动所有寄存器,以便在函数调用约定和syscall
约定之间正确对齐。 我把这个作为一个简单的练习留给读者。 这样做会产生效率较低的代码。