Naf*_*Kay 3 c assembly kernel linux-kernel
我一直在研究关于x86-64的ABI,编写汇编以及研究堆栈和堆如何工作的问题.
给出以下代码:
#include <linux/seccomp.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
// execute the seccomp syscall (could be any syscall)
seccomp(...);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在Assembly for x86-64中,这将执行以下操作:
seccomp.call seccomp.seccomp返回时,它可能是一,在C将调用exit(0)据我所知.我想谈谈上面第三步和第四步之间发生的事情.
我目前拥有当前正在运行的进程的堆栈,它在寄存器和堆栈中有自己的数据.用户空间进程如何将执行转交给内核?内核只是在调用时接收,然后从同一堆栈中推送并弹出?
我相信我听说系统调用不会立即发生,而是在某些CPU滴答或中断时发生.这是真的?例如,在Linux上,这是怎么发生的?
系统调用不会立即发生,而是在某些CPU滴答或中断时发生
完全错了.在定时器中断之前,CPU不会在那里无所事事.在大多数架构上,包括x86-64,切换到内核模式需要几十到几百个周期,但不是因为CPU在等待任何事情.这只是一个缓慢的操作.
请注意,glibc几乎在每个系统调用周围提供函数包装器,所以如果你看一下反汇编,你就会看到一个看起来很正常的函数调用.
请参阅从x86标记wiki 链接的AMD64 SysV ABI文档.它指定将args放入哪些寄存器,并使用该syscall指令进行系统调用.英特尔的insn参考手册(也从标签wiki链接)详细记录了每个syscall对CPU架构状态的变化.如果您对它的设计历史感兴趣,我会从AMD架构师和内核开发人员之间的amd64邮件列表中挖出一些有趣的邮件列表帖子.AMD在第一个AMD64硬件发布之前更新了这一行为,因此它实际上可用于Linux(和其他内核).
32位x86使用int 0x80系统调用指令,或sysenter. syscall在32位模式下不可用,并且sysenter在64位模式下不可用.您可以int 0x80使用64位代码运行,但仍然可以获得将指针视为32位的32位API.(即不要这样做).顺便说一句,也许你对系统调用不得不等待中断感到困惑int 0x80?运行该指令会在现场触发该中断,直接跳转到中断处理程序. 0x80也不是硬件可以触发的中断,因此中断处理程序只能在软件触发的系统调用之后运行.
#include <stdlib.h>
#include <unistd.h>
#include <linux/unistd.h> // for __NR_write
const char msg[]="hello world!\n";
ssize_t amd64_write(int fd, const char*msg, size_t len) {
ssize_t ret;
asm volatile("syscall" // volatile because we still need the side-effect of making the syscall even if the result is unused
: "=a"(ret) // outputs
: [callnum]"a"(__NR_write), // inputs: syscall number in rax,
"D" (fd), "S"(msg), "d"(len) // and args, in same regs as the function calling convention
: "rcx", "r11", // clobbers: syscall always destroys rcx/r11, but Linux preserves all other regs
"memory" // "memory" to make sure any stores into buffers happen in program order relative to the syscall
);
}
int main(int argc, char *argv[]) {
amd64_write(1, msg, sizeof(msg)-1);
return 0;
}
int glibcwrite(int argc, char**argv) {
write(1, msg, sizeof(msg)-1); // don't write the trailing zero byte
return 0;
}
Run Code Online (Sandbox Code Playgroud)
用godbolt Compiler Explorer编译到这个asm输出:
gcc的-masm=intel输出有点像MASM,因为它使用OFFSETkeywork来获取标签的地址.
.rodata
msg:
.string "hello world!\n"
.text
main: // using an in-line syscall
mov eax, 1 # __NR_write
mov edx, 13 # string length
mov esi, OFFSET FLAT:msg # string pointer
mov edi, eax # file descriptor = 1 happens to be the same as __NR_write
syscall
xor eax, eax # zero the return value
ret
glibcwrite: // using the normal way that you get from compiler output
sub rsp, 8 // keep the stack 16B-aligned for the function call
mov edx, 13 // put args in registers
mov esi, OFFSET FLAT:msg
mov edi, 1
call write
xor eax, eax
add rsp, 8
ret
Run Code Online (Sandbox Code Playgroud)
glibc的write包装函数只需将1放入eax并运行syscall,然后检查返回值并设置errno.还处理EINTR和东西上的重启系统调用.
// objdump -R -Mintel -d /lib/x86_64-linux-gnu/libc.so.6
...
00000000000f7480 <__write>:
f7480: 83 3d f9 27 2d 00 00 cmp DWORD PTR [rip+0x2d27f9],0x0 # 3c9c80 <argp_program_version_hook+0x1f8>
f7487: 75 10 jne f7499 <__write+0x19>
f7489: b8 01 00 00 00 mov eax,0x1
f748e: 0f 05 syscall
f7490: 48 3d 01 f0 ff ff cmp rax,0xfffffffffffff001 // I think that's -EINTR
f7496: 73 31 jae f74c9 <__write+0x49>
f7498: c3 ret
... more code to handle cases where one of those branches was taken
Run Code Online (Sandbox Code Playgroud)
系统调用不会立即发生,而是在某些CPU滴答或中断时发生
当然,你的系统调用的效果可能取决于很多东西,包括滴答声.调度程序粒度或时间分辨率可以限制为滴答周期,例如,但调用本身应该"立即"发生(内联执行).
用户空间进程如何将执行转交给内核?内核只是在调用时接收,然后从同一堆栈中推送并弹出?
它可能在架构之间略有不同,但通常syscall参数由汇编libc,然后生成处理器异常以更改上下文.
有关其他详细信息,请参阅:" 系统调用如何在x86 linux上运行 "
| 归档时间: |
|
| 查看次数: |
587 次 |
| 最近记录: |