我正在使用以下 hello world 程序学习汇编
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;our string
len equ $ - msg ;length of our string
Run Code Online (Sandbox Code Playgroud)
我最初的问题是字符串的长度是什么意思。它是指字符数还是内存中的长度(字节数)?为了检查这一点,我想打印变量 len。我怎样才能做到这一点?我天真地试图定义新变量
len2 equ $ - len
Run Code Online (Sandbox Code Playgroud)
然后运行
mov edx,len2 ;message length
mov ecx,len ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
Run Code Online (Sandbox Code Playgroud)
尝试打印 len,但这没有打印任何内容。如何打印由 len 表示的数字?
...
mov edx,len ;message length
Run Code Online (Sandbox Code Playgroud)
这会加载edx某种数值,例如本例中的 14。len是“equ”常量符号,类似于#defineC 中的内容。
mov ecx,msg ;message to write
Run Code Online (Sandbox Code Playgroud)
这加载ecx了第一个字符的地址(msg是标签,指向内存)。
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
...
msg db 'Hello, world!', 0xa ;our string
Run Code Online (Sandbox Code Playgroud)
这定义了 14 个字节的内存,值为 72 ('H'), 101 ('e'), ... 。第一个字节由msg标签(它的内存地址)指向。
len equ $ - msg ;length of our string
Run Code Online (Sandbox Code Playgroud)
这定义了len编译时可见的常量。它没有定义任何内存内容,因此您无法在可执行文件中或在运行时找到它(除非使用它mov edx,len,否则它当然会被编译为该特定指令)。
定义是$ - msg,$在这个上下文中作为“当前地址”,下一个定义的机器码字节将被编译,所以在这个地方它等于msg + 14(我希望我没有正确计算字符数:))。和((msg+14) - msg) = 14=定义在内存中定义len和标签之间的字节数msg。
请注意我如何避免将单词作为变量或字符,ASM 的级别更低,因此内存和字节中的标签是更准确的措辞,我希望它能帮助您识别细微的差异。
您的len2 equ $ - lenafterlen确实因此将 value 定义len2为(msg+14)(仍然在内存中,没有根据len定义添加新字节)减去lenwhich is 14,因此您有效地定义了len2等于msg。
然后:
mov edx,len2 ;message length
mov ecx,len ;message to write
...
Run Code Online (Sandbox Code Playgroud)
调用sys_write时是否使用指向字符串的指针等于14(无效的内存引用,该内存区域对普通用户代码是禁止的),并且长度等于 address msg,这在 32b linux 上很可能是某个值,例如0x80004000,超过 2G 的字符到输出。
该sys_write自然不一样的是,失败,并返回错误代码eax。
要将任何内容输出到控制台,sys_write您必须首先将其作为 ASCII(我认为 UTF8 在 Ubuntu shell 中默认支持,但懒得验证)编码字符串sys_write写入内存,并给出该内存的地址和字节长度(对于 UTF8 字符串,字节和字符之间的区别很重要,sys_write不知道字符,它适用于二进制文件和字节,因此长度是字节数)。
我不打算编写代码来输出数字,因为它有几行长(简化的printf实现)并且 SO 对此有几个 Q+A,但我希望我的解释能帮助您理解发生了什么以及它是如何工作的。
如果您只是在学习 ASM,请考虑链接以clib使其printf可用,或者甚至更好,使用调试器,并直接在调试器中的寄存器中验证值,不要打扰字符串输出,这是一个比初始主题更高级的主题算术,以及基本的流量控制和操作堆栈。在您对基本指令的工作方式以及如何调试代码更加熟悉之后,尝试输出数字将更加容易。