已关闭。此问题需要更多focused。当前不接受答案。
**想要改进此问题吗?**更新问题,使其仅关注editing this post的一个问题。
三年前就关门了。
Improve this question
Linux如何通过系统调用确定另一个进程的地址?就像这个例子一样?
mov rax, 59
mov rdi, progName
syscall
看起来我的问题有点混乱,澄清一下,我想问的是系统调用是如何工作的,独立于寄存器或传递的参数。当调用另一个进程时,它如何知道跳转到哪里,返回到哪里等等。
1条答案
按热度按时间ubof19bj1#
系统调用
syscall
指令实际上只是一条INTEL/AMD CPU指令。最重要的部分是保存和管理RIP寄存器的两条指令:
换句话说,
IA32_LSTAR
(一个寄存器)中保存的地址必须有代码,RCX
是返回地址。CS
和SS
段也进行了调整,以便内核代码能够进一步在CPU级别0(特权级别)上运行。如果您没有执行
syscall
的权限或指令不存在,则可能会发生#UD
。如何解释
RAX
?这只是一个内核函数指针表的索引。首先,内核执行边界检查(如果
RAX > __NR_syscall_max
,则返回-ENOSYS),然后调度到(C语法)sys_call_table[rax](rdi, rsi, rdx, r10, r8, r9);
现代的Linux在实践中更加复杂,因为有一些x86漏洞(如Meltdown和L1 TF)的变通办法,通过更改页表,在用户空间运行时,大部分内核内存不会被Map。(来自AT&T语法)在Linux 4.12 arch/x86/entry/entry_64.S(在添加Spectre/Meltdown缓解措施之前)中从
ENTRY(entry_SYSCALL_64)
转换为call *sys_call_table(, %rax, 8)
。What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?提供了关于系统调用调度的内核端的更多详细信息。快?
该指令被称为 fast。这是因为在过去,必须使用
INT3
这样的指令。中断使用内核堆栈,它将许多寄存器压入堆栈,并使用相当慢的RTE
来退出异常状态并返回中断后的地址。这通常要慢得多。有了
syscall
,你也许可以避免大部分的开销。但是,在你所要求的方面,这并没有真正的帮助。swapgs
是另一条与syscall
一起使用的指令。它为内核提供了一种访问自己的数据和堆栈的方法。您应该查看Intel/AMD文档中有关这些指令的详细信息。新流程?
Linux系统有一个所谓的任务表,每个进程和进程内的每个线程实际上都被称为任务。
当你创建一个新的进程时,Linux会创建一个任务。为了使它工作,它会运行一些代码,这些代码会做如下事情:
当然,这是超级简化的。
起始地址是在ELF二进制文件中定义的。它实际上只需要确定一个地址,并将其保存在任务的当前
RIP
指针中,然后“返回”到用户空间。正常的请求分页机制将处理其余的工作:如果代码尚未加载,它将生成#PF页面错误异常,内核将在此时加载必要的代码。尽管在大多数情况下,加载程序已经加载了软件的某个部分,作为避免初始页面错误的优化。(未Map的页面上的#PF将导致内核向进程发送SIGSEGV segfault信号,但“有效”页面错误由内核以静默方式处理。)
所有的新进程通常都加载到同一个虚拟地址(忽略PIE + ASLR)。这是可能的,因为我们使用了MMU(内存管理单元)。协处理器在虚拟地址空间和物理地址空间之间转换内存地址。
(编者注:MMU实际上不是协处理器;在现代CPU中,虚拟内存逻辑沿着L1指令/数据缓存一起紧密集成到每个内核中。不过,一些古老的CPU确实使用了外部MMU芯片。)
确定地址?
所以,现在我们知道所有进程都有相同的虚拟地址(0x 400000在Linux下是
ld
的默认值)。为了确定真实的的物理地址,我们使用MMU。内核如何确定物理地址?它有一个内存分配函数。就这么简单。它会调用一个malloc()函数,该函数会搜索当前未使用的内存块,并在该位置创建(也称为加载)进程。如果当前没有可用的内存块,内核会检查是否有内存块被换出。如果换出失败,则进程的创建失败。
在创建一个进程的情况下,它会分配相当大的内存块来启动。分配1 Mb或2 Mb的缓冲区来启动一个新进程是很常见的。这会使事情进行得更快。
同样,如果进程已经在运行,并且您再次启动它,则可以重用已经运行的示例所使用的大量内存。在这种情况下,内核不会分配/加载这些部分。它将使用MMU来共享那些可以为进程的两个示例所共用的页面(即,在大多数情况下,进程的代码部分可以被共享,因为它是只读的,当数据的某个部分也被标记为只读时,它可以被共享;如果未将数据标记为只读,则在数据尚未修改的情况下仍然可以共享数据--在本例中,它被标记为 copy on write。)