1.我知道int 0x80
在linux中会产生中断。但是,我不明白这段代码是如何工作的。它会返回什么吗?
$ - msg
代表什么?
global _start
section .data
msg db "Hello, world!", 0x0a
len equ $ - msg
section .text
_start:
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80 ;What is this?
mov eax, 1
mov ebx, 0
int 0x80 ;and what is this?
1条答案
按热度按时间o2g1uqev1#
How does $ work in NASM, exactly?解释了
$ - msg
如何让NASM将字符串长度计算为汇编时常数,而不是硬编码。通过将参数放入寄存器,然后运行
int 0x80
(32位模式)或syscall
(64位模式). What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64和The Definitive Guide to Linux System Calls,可以完成系统调用。**可以将
int 0x80
视为一种跨越用户/内核权限边界“调用”内核的方式。**内核根据int 0x80
执行时寄存器中的值执行操作,然后最终返回。返回值在EAX中。当执行到达内核的入口点时,它会查看EAX并根据EAX中的调用号分派到正确的系统调用。来自其他寄存器的值作为函数参数传递到该系统调用的内核处理程序。(例如,eax=4 /
int 0x80
将使内核调用其sys_write
内核函数,实现POSIXwrite
系统调用。)另请参见如果您在64位代码中使用32位int 0x 80 Linux ABI会发生什么?-该答案包括查看内核入口点中由
int 0x80
“调用”的asm。(也适用于32位用户空间,而不仅仅是不应该使用int 0x80
的64位)。如果您还不了解底层Unix系统编程,您可能只想在asm中编写接受args并返回值的函数(或者通过指针参数更新数组)并从C或C++程序中调用它们。然后你就可以只关心学习如何处理寄存器和内存了,而不需要学习POSIX系统调用API和使用它的ABI。这也使得比较你的代码和C实现的编译器输出非常容易。编译器通常在生成高效代码方面做得很好,但很少是完美的。
libc为系统调用提供了 Package 器函数,因此编译器生成的代码将使用
call write
,而不是直接使用int 0x80
(或者如果您关心性能,则使用sysenter
)(在x86-64代码中为usesyscall
for the 64-bit ABI)。系统调用在第2节手册页中介绍,如
write(2)
。有关libc Package 器函数和底层Linux系统调用之间的区别,请参见NOTES部分。注意,sys_exit
的 Package 器是_exit(2)
。而不是先刷新stdio缓冲区和其他清理的exit(3)
ISO C函数。实际上_exit()
使用ends all threads的exit_group
系统调用。exit(3)
也使用它,因为单线程进程没有缺点。给定系统调用调用约定和C手册页,您可以看到哪个args进入哪个寄存器;有些网页上有系统调用和注册表,但你并不需要它们。
此代码执行2个系统调用:
sys_write(1, "Hello, World!\n", sizeof(...));
sys_exit(0);
我对它进行了大量的注解(以至于它开始模糊实际代码,而没有用颜色突出显示语法)。这是试图向初学者指出一些事情,而不是通常应该如何注解代码。
注意,我们 * 不 * 将字符串长度存储在数据内存中的任何地方。它是一个汇编时常量,因此将其作为立即数操作数比load更有效。我们也可以使用三条
push imm32
指令将字符串数据压入堆栈,但代码太大不是一件好事。在Linux上,您可以将此文件另存为
Hello.asm
,并使用以下命令从该文件构建32位可执行文件:有关将汇编编译成32位或64位静态或动态链接的Linux可执行文件的更多详细信息,请参见此答案,以了解使用GNU
as
指令的NASM/YASM语法或GNU AT&T语法。在64位主机上构建32位代码时,请确保使用-m32
或等效代码,否则您将在运行时遇到令人困惑的问题。)您可以使用
strace
跟踪其执行情况,以查看其执行的系统调用:将其与动态链接进程的跟踪(如gcc从hello.c或运行
strace /bin/ls
生成的跟踪)进行比较,以了解动态链接和C库启动背后发生了多少事情。stderr上的跟踪和stdout上的常规输出都将到达这里的终端,因此它们会干扰
write
系统调用的行。如果您关心,请重定向或跟踪到一个文件。请注意,这使我们可以轻松地查看syscall返回值,而不必添加代码来打印它们。实际上比使用常规调试器更容易(如gdb)到单步执行,请查看eax
,gdb asm技巧请参见x86 tag wiki底部。(标签wiki的其余部分充满了指向好资源的链接。)这个程序的x86-64版本非常相似,将相同的参数传递给相同的系统调用,只是在不同的寄存器中,并且使用
syscall
代替int 0x80
。请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?的底部,以获得写入字符串并以64位代码退出的工作示例。相关文章:A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux。可以运行的最小二进制文件,只需执行exit()系统调用。这是关于最小化二进制文件大小,而不是源代码大小,甚至只是实际运行的指令数。