Bry*_*con 7 linux assembly x86-64 nasm system-calls
Linux 如何通过系统调用确定要执行的另一个进程的地址?就像在这个例子中一样?
mov rax, 59
mov rdi, progName
syscall
Run Code Online (Sandbox Code Playgroud)
我的问题似乎有点混乱,澄清一下,我问的是系统调用是如何工作的,与传递的寄存器或参数无关。当另一个进程被调用时,它如何知道跳转、返回等位置。
Ale*_*lke 10
该syscall指令实际上只是一条 INTEL/AMD CPU 指令。以下是概要:
IF (CS.L =\xcc\xb8 1 ) or (IA32_EFER.LMA =\xcc\xb8 1) or (IA32_EFER.SCE =\xcc\xb8 1)\n(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)\n THEN #UD;\nFI;\nRCX \xe2\x86\x90 RIP; (* Will contain address of next instruction *)\nRIP \xe2\x86\x90 IA32_LSTAR;\nR11 \xe2\x86\x90 RFLAGS;\nRFLAGS \xe2\x86\x90 RFLAGS AND NOT(IA32_FMASK);\nCS.Selector \xe2\x86\x90 IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)\n(* Set rest of CS to a fixed value *)\nCS.Base \xe2\x86\x90 0;\n (* Flat segment *)\nCS.Limit \xe2\x86\x90 FFFFFH;\n (* With 4-KByte granularity, implies a 4-GByte limit *)\nCS.Type \xe2\x86\x90 11;\n (* Execute/read code, accessed *)\nCS.S \xe2\x86\x90 1;\nCS.DPL \xe2\x86\x90 0;\nCS.P \xe2\x86\x90 1;\nCS.L \xe2\x86\x90 1;\n (* Entry is to 64-bit mode *)\nCS.D \xe2\x86\x90 0;\n (* Required if CS.L = 1 *)\nCS.G \xe2\x86\x90 1;\n (* 4-KByte granularity *)\nCPL \xe2\x86\x90 0;\nSS.Selector \xe2\x86\x90 IA32_STAR[47:32] + 8;\n (* SS just above CS *)\n(* Set rest of SS to a fixed value *)\nSS.Base \xe2\x86\x90 0;\n (* Flat segment *)\nSS.Limit \xe2\x86\x90 FFFFFH;\n (* With 4-KByte granularity, implies a 4-GByte limit *)\nSS.Type \xe2\x86\x90 3;\n (* Read/write data, accessed *)\nSS.S \xe2\x86\x90 1;\nSS.DPL \xe2\x86\x90 0;\nSS.P \xe2\x86\x90 1;\nSS.B \xe2\x86\x90 1;\n (* 32-bit stack segment *)\nSS.G \xe2\x86\x90 1;\n (* 4-KByte granularity *)\nRun Code Online (Sandbox Code Playgroud)\n最重要的部分是保存和管理RIP寄存器的两条指令:
\nRCX \xe2\x86\x90 RIP\nRIP \xe2\x86\x90 IA32_LSTAR\nRun Code Online (Sandbox Code Playgroud)\n换句话说,IA32_LSTAR(寄存器)中保存的地址处一定有代码,并且RCX是返回地址。
和段也进行了调整,以便您的内核代码将能够进一步在 CPU 级别 0(特权级别)上运行CS。SS
#UD如果您无权执行syscall或指令不存在,则可能会发生这种情况。
RAX解读的呢?这只是内核函数指针表的索引。首先内核进行边界检查(如果 则返回 -ENOSYS RAX > __NR_syscall_max),然后分派到(C 语法)sys_call_table[rax](rdi, rsi, rdx, r10, r8, r9);
; Intel-syntax translation of Linux 4.12 syscall entry point\n ... ; save user-space registers etc.\n call [sys_call_table + rax * 8] ; dispatch to sys_execve() or whatever kernel C function\n\n;;; execve probably won\'t return via this path, but most other calls will\n ... ; restore registers except RAX return value, and return to user-space\nRun Code Online (Sandbox Code Playgroud)\n现代 Linux 在实践中更加复杂,因为 x86 漏洞(如 Meltdown 和 L1TF)的变通方法是通过更改页表,以便在用户空间运行时不会映射大部分内核内存。上面的代码是Linux 4.12 arch/x86/entry/entry_64.Scall *sys_call_table(, %rax, 8)中ENTRY(entry_SYSCALL_64)的字面翻译(来自 AT&T 语法)(在添加 Spectre/Meltdown 缓解措施之前)。另相关:如果在 64 位代码中使用 32 位 int 0x80 Linux ABI,会发生什么?有一些关于系统调用调度的内核端的更多细节。
据说指令速度很快。这是因为在过去,人们必须使用诸如 之类的指令INT3。中断利用了内核堆栈,它将许多寄存器压入堆栈并使用相当慢的速度IRET退出异常状态并返回到中断后的地址。这通常要慢得多。
有了它,syscall您也许能够避免大部分开销。然而,对于你所问的问题,这并没有真正的帮助。
一起使用的另一个指令syscall是swapgs。这为内核提供了一种访问其自己的数据和堆栈的方法。您应该查看有关这些说明的 Intel/AMD 文档以了解更多详细信息。
Linux 系统有所谓的任务表。每个进程和进程内的每个线程实际上称为一个任务。
\n当您创建一个新进程时,Linux 会创建一个任务。为了实现这一点,它运行的代码执行以下操作:
\n当然,这是超级简化的。
\n起始地址在 ELF 二进制文件中定义。它实际上只需要确定一个地址并将其保存在任务当前RIP指针中并“返回”到用户空间。正常的请求分页机制将处理其余的事情:如果代码尚未加载,它将生成 #PF 页面错误异常,并且内核将在此时加载必要的代码。尽管在大多数情况下,加载程序已经加载了软件的某些部分作为优化以避免初始页面错误。
(未映射的页面上的 #PF 将导致内核向您的进程传递 SIGSEGV 段错误信号,但内核会默默地处理“有效”页面错误。)
\n所有新进程通常都会加载到相同的虚拟地址(忽略 PIE + ASLR)。这是可能的,因为我们使用 MMU(内存管理单元)。该协处理器在虚拟地址空间和物理地址空间之间转换内存地址。
\n(编者注:MMU 并不是真正的协处理器;在现代 CPU 中,虚拟内存逻辑与 L1 指令/数据缓存一起紧密集成到每个内核中。不过,一些古老的 CPU 确实使用了外部 MMU 芯片.)
\n所以,现在我们知道所有进程都有相同的虚拟地址(Linux下的0x400000是默认选择的ld)。为了确定真实的物理地址,我们使用 MMU。内核如何决定该物理地址?嗯,它有一个内存分配功能。就这么简单。
它调用“malloc()”类型的函数,该函数搜索当前未使用的内存块并在该位置创建(也称为加载)进程。如果当前没有可用的内存块,内核会检查是否将某些内容交换出内存。如果失败,进程的创建就会失败。
\n在创建进程的情况下,它将首先分配相当大的内存块。分配 1Mb 或 2Mb 缓冲区来启动新进程并不罕见。这使得事情进展得更快。
\n此外,如果进程已经在运行并且您再次启动它,则可以重用已经运行的实例使用的大量内存。在这种情况下,内核不会分配/加载这些部分。它将使用 MMU 来共享那些可以为进程的两个实例所共用的页面(即,在大多数情况下,进程的代码部分可以共享,因为它是只读的,数据的某些部分可以在以下情况下共享)它也被标记为只读;如果没有标记为只读,则数据在尚未修改的情况下仍然可以共享——在这种情况下,它被标记为写入时复制。)
\n| 归档时间: |
|
| 查看次数: |
891 次 |
| 最近记录: |