我正在使用下面的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
我最初的问题是字符串的长度是什么意思。它是指字符数还是内存中的长度(字节数)?为了检查这一点,我想打印变量len。我该怎么做?我天真地试图定义新变量
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表示的数字?
1条答案
按热度按时间nkkqxpd91#
这将为
edx
加载某种数值,例如本例中的14。len
是“equ”常量符号,类似于C中的#define
。这将用第一个字符的地址加载
ecx
(msg
是标签,指向内存)。这定义了14个字节的存储器,具有值72('H ')、101('e')、…第一字节由
msg
标签(其存储器地址)指向。这定义了在编译时可见的常量
len
。它没有定义任何内存内容,所以你不能在可执行文件或运行时找到它(除非使用,比如mov edx,len
,然后它被编译成特定的指令)。定义是
$ - msg
,在这个上下文中,$
作为“当前地址”,下一个定义的机器码字节将被编译,所以在这个地方它等于msg + 14
(我希望我正确地计算了字符数:)。((msg+14) - msg) = 14
=在len
的定义和标签msg
之间的存储器中定义的字节数。注意我是如何避免将单词作为变量或字符的,ASM是更低级别的,因此将标签放入内存和字节是更准确的措辞,我希望这将有助于您识别细微的差异。
在
len
之后的len2 equ $ - len
确实将值len2
定义为(msg+14)
(仍然在内存中,len
定义没有添加新字节)减去len
,即14
,因此实际上将len2
定义为msg
。然后:
调用
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
,或者更好的是,使用调试器,直接在调试器的寄存器中验证值,不要为字符串输出而烦恼,这是一个比初始算法、基本流控制和操作堆栈更高级的主题。当你对基本指令的工作方式以及如何调试代码更加熟悉之后,尝试输出数字将更加容易。