>我正在使用以下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
我最初的问题是字符串的长度是什么意思。它是否意味着字符数或内存中的长度(字节数(? 为了检查这一点,我想打印可变镜头。我该怎么做?我天真地试图定义新变量
len2 equ $ - len
然后改为运行
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
尝试打印 len,但这什么也没打印。如何打印由 len 表示的数字?
...
mov edx,len ;message length
这会edx
加载某种数值,例如本例中的 14。len
是"等值"常数符号,类似于 C 中的#define
。
mov ecx,msg ;message to write
这将加载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
这定义了 14 个字节的内存,值为 72 ('H'(、101 ('e'(、... .第一个字节由标签(它的内存地址(指向msg
。
len equ $ - msg ;length of our string
这定义了在编译时可见的常量len
。它没有定义任何内存内容,因此您无法在可执行文件或运行时中找到它(除非使用,例如该mov edx,len
,然后当然将其编译为该特定指令(。
定义是$ - msg
,此上下文中的$
用作"当前地址",其中将编译下一个定义的机器代码字节,因此在这个地方它等于msg + 14
(我希望我确实正确计算了字符数:)(。而((msg+14) - msg) = 14
=len
定义和标签msg
之间的内存中定义的字节数。
请注意我如何避免将单词作为变量或字符,ASM 是更底层的,因此将标签放入内存和字节是更准确的措辞,我希望它能帮助您识别细微的差异。
因此,您在len
之后的len2 equ $ - len
确实将值len2
定义为(msg+14)
(仍然存在于内存中,len
定义没有添加新字节(减去14
len
,因此您有效地定义了len2
等于msg
。
然后:
mov edx,len2 ;message length
mov ecx,len ;message to write
...
调用sys_write
时,指向字符串的指针等于14
(无效的内存引用,该内存区域禁止普通用户代码使用(,长度等于地址msg
,这将在 32b Linux 上很可能是一些值,例如0x80004000
,即输出超过 2G 的字符。
sys_write
自然不喜欢这样,失败,并在eax
中返回错误代码。
要使用sys_write
输出任何内容到控制台,您必须首先将其作为 ASCII(我认为 Ubuntu shell 默认支持 UTF8,但懒得验证(编码字符串写入内存,并给出该内存的sys_write
地址和以字节为单位的长度(对于 UTF8 字符串,字节和字符之间的差异很重要,sys_write
不知道字符,它适用于二进制文件和字节,因此长度是字节数(。
我不打算编写代码来输出数字,因为这有几行长(简化printf
实现(,并且SO对此有几个Q + A,但我希望我的解释能帮助您理解发生了什么以及它是如何工作的。
如果您只是在学习 ASM,请考虑针对clib
进行链接以提供printf
,或者更好的是,使用调试器,并直接在调试器中的寄存器中验证值,不要为字符串输出而烦恼,这是一个比初始算术、基本流控制和操作堆栈更高级的主题。在你对基本指令的工作原理以及如何调试代码更加熟悉之后,尝试输出数字会更容易。