没有汇编的 C/C + + 函数定义

我一直认为像 printf()这样的函数在最后一步是使用内联汇编定义的。在 stdio.h 的内部深处隐藏着一些真正告诉 CPU 该做什么的高斯代码。例如,在 dos 中,我记得它是通过首先将字符串的开头 moving 到某个内存位置或寄存器,然后调用 intterupt 来实现的。

然而,由于 x64版本的 Visual Studio 根本不支持内联汇编,这让我想知道为什么在 C/C + + 中根本就没有汇编定义的函数。像 printf()这样的库函数如何在不使用汇编代码的情况下在 C/C + + 中实现?什么实际上执行了正确的软件中断?谢谢。

11610 次浏览

编译器从 C/C + + 源代码生成程序集。

标准库函数在底层平台库(例如 UNIX API)和/或直接系统调用(仍然是 C 函数)上实现。系统调用(在我所知道的平台上)是通过内部调用一个函数来实现的,该函数使用内联提示将系统调用号和参数放入 CPU 寄存器,并触发一个中断,然后由内核进行处理。

除了系统调用之外,还有其他与硬件通信的方法,但是在现代操作系统下运行时,这些方法通常是不可用的,或者相当有限,或者至少启用它们需要一些系统调用。一个设备可能是内存映射的,所以写入特定的内存地址(通过常规指针)控制设备。I/O 端口也经常被使用,根据架构,这些端口被特殊的 CPU 操作码访问,或者它们也可能被内存映射到特定的地址。

除了分号和注释之外,所有的 C + + 语句最终都会变成告诉 CPU 该做什么的机器代码。您可以编写自己的 printf 函数,而无需使用汇编。必须在汇编中编写的唯一操作是端口的输入和输出,以及启用和禁用中断的操作。

然而,由于性能方面的原因,汇编仍然用于系统级编程。即使不支持内联程序集,也不妨碍您在程序集中编写单独的模块并将其链接到应用程序。

一般来说,库函数是预编译并分发广告对象的。内联汇编只有在特殊情况下才会出于性能考虑使用,但这只是例外,并非常规。实际上,在我看来 printf 不适合做内联组装。插入函数,比如 memcpy 或 memcmp。非常低级的函数可以由本地汇编程序编译(masm?Gnu Asm?),并作为对象在库中分发。

首先,你必须理解环的概念。
内核在环0中运行,这意味着它可以完全访问内存和操作码。
程序通常在环3中运行。它对内存的访问受到限制,不能使用所有的操作码。< br/> < br/> 因此,当一个软件需要更多的特权(用于打开文件、写入文件、分配内存等)时,它需要请求内核。
这可以通过多种方式实现,比如软件中断、 SYSENTER 等等。 < br/> < br/> < br/> 让我们以带 printf ()函数的软件中断为例:
1-您的软件调用 printf ()。
2-printf ()处理您的字符串和 args,然后需要执行一个内核函数,因为在 Ring 3中不能写入文件。
3-printf ()生成一个软件中断,将内核函数(在这种情况下是 write ()函数)的数目放入寄存器中。
软件执行被中断,指令指针移动到内核代码。所以我们现在在环0中,在一个内核函数中。
内核处理请求,写入文件(stdout 是一个文件描述符)。
完成后,内核使用 iret 指令返回到软件代码。
软件代码继续。

因此,C 标准库的函数可以在 C 中实现,它所要做的就是知道在需要更多特权时如何调用内核。

在 Linux 中,strace实用程序允许您查看程序发出的系统调用。所以,参加这样一个项目



int main(){
printf("x");
return 0;
}


比方说,你把它编译成 printx,然后 strace printx给出



execve("./printx", ["./printx"], [/* 49 vars */]) = 0
brk(0)                                  = 0xb66000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e5000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=119796, ...}) = 0
mmap(NULL, 119796, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa6dc0c7000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1811128, ...}) = 0
mmap(NULL, 3925208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa6dbb06000
mprotect(0x7fa6dbcbb000, 2093056, PROT_NONE) = 0
mmap(0x7fa6dbeba000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7fa6dbeba000
mmap(0x7fa6dbec0000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa6dbec0000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c6000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c5000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c4000
arch_prctl(ARCH_SET_FS, 0x7fa6dc0c5700) = 0
mprotect(0x7fa6dbeba000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fa6dc0e7000, 4096, PROT_READ) = 0
munmap(0x7fa6dc0c7000, 119796)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e4000
write(1, "x", 1x)                        = 1
exit_group(0)                           = ?


橡胶遇到的道路(排序,见下文)在下一个最后调用的痕迹: write(1,"x",1x)。此时,控制从用户地 printx传递到 Linux 内核,Linux 内核负责处理其余部分。write()是在 unistd.h中声明的包装函式



extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;


大多数系统调用都以这种方式包装。顾名思义,这个包装函式只不过是一个很薄的代码层,它将参数放入正确的寄存器中,然后执行软件中断0x80。内核捕获中断,剩下的就是历史了。至少以前是这样的。显然,中断捕获的开销是相当高的,并且,正如前面的文章所指出的,现代 CPU 架构引入了 sysenter汇编指令,它在速度上实现了相同的结果。本页 系统调用对系统调用的工作方式有一个很好的总结。

我觉得你可能会对这个答案有点失望,就像我一样。很明显,在某种意义上,这是一个错误的底部,因为在调用 write()和修改显卡帧缓冲区使字母“ x”出现在屏幕上之间还有很多事情要做。通过深入内核来放大接触点(保持“橡胶反对道路”的类比)肯定是有教育意义的,如果这是一个耗时的努力。我猜想您将不得不穿越几个抽象层,如缓冲输出流、字符设备等。一定要张贴结果,如果你决定跟进这一点:)