【问题标题】:How to print the length of a string in assembly如何在汇编中打印字符串的长度
【发布时间】:2017-07-28 07:47:52
【问题描述】:

我正在使用以下 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 表示的数字?

【问题讨论】:

  • 您可能需要检查How to print a number in assembly NASM? 简短的回答是您将字符 写入stdout。数字是十进制值,不是可以立即写入的字符串,必须将整数或无符号值转换为其字符表示,然后写入stdout
  • 出于好奇,您正在为什么操作系统编程?视窗? Unix?苹果系统?我之所以这么问,是因为除了这样的问题之外,经常有人在 SO 上提问的人正在使用错误平台的教程。
  • 谢谢。我正在使用 ubuntu。出于好奇,当我尝试直接使用 stdout 打印数字时,我什么也没得到。为什么它什么都不打印,而不是打印一些废话?
  • 因为 SYS_write 系统调用打印字符串而不是数字。您必须将数字转换为字符串并打印该字符串。您还可以选择使用 C 库并使用 printf 之类的东西来打印数字。 printf 会为你完成数字到字符串的转换,否则你必须自己编写代码。
  • @JonathanLindgren:您正在传递一个数字,SYS_write 试图将其用作指针,因此它返回 -EFAULT。 (对坏指针的系统调用会返回错误而不是引发 SIGSEGV。)使用调试器和/或strace。见底部the x86 tag wiki

标签: assembly x86 nasm


【解决方案1】:
   ...
   mov  edx,len     ;message length

这会为edx 加载某种数值,例如本例中的 14。 len 是“equ”常量符号,类似于 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 定义没有添加新字节)减去len14,所以你有效地定义了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 可用,或者更好的是,使用调试器,并在调试器中直接验证寄存器中的值,不要打扰字符串输出,这是一个比初始算术、基本流控制和操作堆栈更高级的主题。在您对基本指令的工作原理以及如何调试代码更加熟悉之后,尝试输出数字会更加容易。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-12
    • 1970-01-01
    • 2017-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多