如何在汇编中打印字符串的长度

6

我正在学习汇编语言,使用以下“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表示的数字?


7
您可能想查看如何在NASM汇编中打印数字?。简单来说,您需要向stdout写入字符。一个数字是一个十进制值,不是一串可以直接写入的字符,您必须将整数或无符号值转换为其字符表示形式,然后将其写入stdout - David C. Rankin
出于好奇,你在为哪个操作系统编程?Windows?Unix?MacOS?我问这个问题是因为除了这样的问题之外,经常有人在SO上使用错误平台的教程。 - Michael Petch
谢谢。我正在使用Ubuntu操作系统。出于好奇,当我尝试直接使用stdout打印数字时,什么都没有输出。为什么它不会打印一些无意义的内容,而是什么都不显示? - Jonathan Lindgren
因为SYS_write系统调用打印的是字符串而不是数字,所以您必须将数字转换为字符串并打印字符串。您也可以选择使用_C_库并使用诸如printf之类的东西来打印数字。printf将为您执行数字到字符串的转换,否则您必须自己编写代码。 - Michael Petch
@JonathanLindgren: 你传递了一个数字,SYS_write 尝试将其用作指针,因此返回 -EFAULT。(系统调用使用错误指针会返回错误,而不是引发 SIGSEGV。)请使用调试器和/或 strace。请参阅 x86 标签 wiki 底部。 - Peter Cordes
1个回答

11
   ...
   mov  edx,len     ;message length

这会将edx加载为某种数值,比如在这个例子中是14。len是“equ”常量符号,类似于C语言中的 #define

   mov  ecx,msg     ;message to write

这将使用第一个字符的地址(msg是指向内存的标签)加载ecx

   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更低级,所以标签和字节是更准确的措辞,我希望这将有助于您认识到微妙的差别。

你的len2 equ $-lenlen之后,因此将值len2定义为(msg+14)(仍然在内存中,由len定义没有添加新字节)减去len,其值为14,因此你实际上将len2定义为msg

然后:

   mov  edx,len2     ;message length
   mov  ecx,len     ;message to write
   ...

调用 sys_write 并传递指向字符串的指针等于 14(无效内存引用,该内存区域对普通用户代码不可访问),长度等于地址 msg,在 32 位 linux 上很可能是某个值,例如 0x80004000,即要输出超过 2G 的字符。

sys_write 自然不喜欢这样做,操作失败并在 eax 中返回错误代码。

要使用 sys_write 输出任何内容,你必须先将它作为 ASCII 编码的字符串写入内存(我认为 Ubuntu shell 默认支持 UTF8,但懒得验证),然后将该内存地址和字节数传递给 sys_write(对于 UTF8 字符串,字节数和字符数之间的差异很重要。因为 sys_write 不了解字符,它只能处理二进制文件和字节,因此长度是以字节计算的)。

我不打算编写输出数字的代码,因为那将会是几行长的代码(简化的 printf 实现),而且 SO 上有许多与此相关的问题和答案,但我希望我的解释可以帮助你理解发生了什么以及如何工作。

如果你正在学习汇编语言,请考虑链接到 clib 以使用可用的 printf 或更好地使用调试器,并在调试器中直接验证寄存器中的值,不要费心输出字符串,那是一个更高级的话题。在熟悉基本指令以及如何调试代码后,尝试输出数字会更容易一些。


1
如何在汇编级别的编程中打印一个整数,而不使用C库中的printf函数?(itoa, 整数转换为十进制ASCII字符串) 这个链接提供了一些示例,展示了如何生成一个由十进制数字组成的ASCII字符串,并将其传递给write系统调用。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接