我正在尝试使用sys_brk
系统调用在Linux中分配一些内存。这是我尝试过的:
BYTES_TO_ALLOCATE equ 0x08
section .text
global _start
_start:
mov rax, 12
mov rdi, BYTES_TO_ALLOCATE
syscall
mov rax, 60
syscall
事情是根据linux调用约定我期望返回值在rax
寄存器(指向分配的内存的指针)。我在gdb中运行它,在制作sys_brk
系统调用后,我注意到以下寄存器内容
在系统调用之前
rax 0xc 12
rbx 0x0 0
rcx 0x0 0
rdx 0x0 0
rsi 0x0 0
rdi 0x8 8
在系统调用之后
rax 0x401000 4198400
rbx 0x0 0
rcx 0x40008c 4194444 ; <---- What does this value mean?
rdx 0x0 0
rsi 0x0 0
rdi 0x8 8
在这种情况下,我不太了解rcx
寄存器的价值。哪一个用作指向我用sys_brk
分配的8个字节开头的指针?
系统调用返回值一如既往地在rax
中。见What are the calling conventions for UNIX & Linux system calls on i386 and x86-64。
请注意,sys_brk
的界面与brk
/ sbrk
POSIX函数略有不同;看到C library/kernel differences section of the Linux brk(2)
man page。具体来说,Linux sys_brk
设置程序中断; arg和返回值都是指针。见Assembly x86 brk() call use。这个答案需要upvotes,因为它是这个问题上唯一的好人。
你问题的另一个有趣的部分是:
在这种情况下,我不太了解rcx寄存器中的值
您正在看到syscall
/ sysret
指令如何设计为允许内核恢复用户空间执行但仍然快速的机制。
syscall
不做任何加载或存储,它只修改寄存器。它不使用特殊寄存器来保存返回地址,而只使用常规整数寄存器。
在内核返回到用户空间代码之后,RCX=RIP
和R11=RFLAGS
并不是巧合。这种情况的唯一方法是,如果ptrace
系统调用在内核中修改了进程保存的rcx
或r11
值。 (ptrace
是系统调用gdb使用的)。在这种情况下,Linux将使用iret
而不是sysret
返回用户空间,因为较慢的通用情况iret
可以做到这一点。 (请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?,了解Linux的系统调用入口点。主要是32位进程的入口点,而不是64位进程中的syscall
。)
int 0x80
:而不是将返回地址推送到内核堆栈(如syscall
)
syscall
之前,内核甚至无法查看这些regs的原始值)。RFLAGS
MSR)中的预配置掩码屏蔽IA32_FMASK
。这让内核禁用中断(IF),直到它完成swapgs
并将rsp
设置为指向内核堆栈。即使将cli
作为入口处的第一条指令,也会有一个漏洞的窗口。你也可以通过屏蔽cld
免费获得DF
所以rep movs
/ stos
向上,即使用户空间使用std
。
有趣的事实:AMD首次提出的syscall
/ swapgs
设计并没有掩盖RFLAGS,而是they changed it after feedback from kernel developers on the amd64 mailing list(在〜2000年,第一个芯片之前的几年)。syscall
入口点(设置CS:RIP = IA32_LSTAR
)。我认为旧的CS
值不能保存在任何地方。swapgs
来访问保存内核堆栈指针的信息块,因为rsp
仍然有来自用户空间的值。所以syscall
的设计需要一个系统调用ABI,clobbers注册,这就是为什么这些值是它们的原因。