AT&T 汇编-2: 使用C标准库IO函数 (1)

AT&T 汇编中没有现成的输入输出数值的方法,想查看运算结果比较麻烦。不过这个功能可以通过调用 c 标准库里的 printf() 函数来实现,虽然下层调用上层的函数看起来有些奇怪,但是这的确很方便……

# cio-1.s
# using c function printf() to display a string

.section .rodata
msg:
   .asciz   "Hello, world.\n" # null-terminated string

.section .text
.globl main
main:
   pushl $msg
   call  printf
   popl  %eax

   pushl $0
   call  exit

编译方法:

gcc cio-1.s -o cio-1

第 6 行的 .asciz 声明了以 ‘\0’ 结尾的字符串,因为 printf() 打印的字符串需要以 ‘\0’ 结尾。在上一篇提到,如果不需要末尾的 ‘\0’ 可以使用 .ascii。

.globl main
main:

这里的标识符从“_start”变成了“main”。因为在这个程序中使用了 c 标准库,为了方便,我们使用 gcc 来编译这个汇编程序,如果用“_start”的话 gcc 会报错说找不到标识符“main”。如果想使用 as 和 ld 编译可以参考 Richard Blum 写的《Professional Assembly Language》(中文名《汇编语言程序设计》,马朝晖等译)。

以后如果使用 main 作为入口点的话默认使用 gcc 编译,使用 _start 的话默认使用 as 和 ld。

pushl $msg

这一句把 msg 的地址压入堆栈。

堆栈是程序保存数据的一种方式,也是参数传递的重要方式。这里的堆栈操作与数据结构中的堆栈操作相似,都遵循“后进先出”的原则。在本人的 intel T2050 cpu 上,堆栈的增长方式是从高地址向低地址增长。

push 指令与 mov 系列指令相似,也有相应的后缀,不同的后缀代表不同的数据长度,例如 pushl 的操作数是 32 位,而 pushw 的操作数则是 16 位的数据。push 系列指令的格式是:

push source

汇编里使用寄存器 esp 保存当前的堆栈地址。执行命令“pushl $msg”(假设 msg 的地址是 0x12345678)后,esp 的变化情况如图所示:

             +----+                      +----+
             | .. |                      | .. |
             +----+                      +----+
             | 10 | <-- original esp     | 10 |
             +----+                      +----+
                                         | 12 |
                                         +----+
                                         | 34 |
                                         +----+
    ^                                    | 56 |
    |                                    +----+
  memory                                 | 78 | <-- esp after 'pushl $msg'
 addresses                               +----+

把参数压入堆栈后就可以调用函数了:

call  printf

当 printf() 执行完以后程序继续从下一条指令往下执行:

popl  %eax

有入栈操作就有出栈操作。与 push 系列指令相对的是出栈指令 pop 系列。类似的,pop 系列指令也是通过不同的后缀来区分要出栈的数据长度。pop 系列指令的格式是:

pop destination

pop 指令把栈顶指定长度的数据存放到 destination 中,并且设置相应的 esp 的值使它始终指向栈顶位置。

当执行完 printf() 后再执行“popl %eax”,堆栈的情况如下所示:

              +----+                              +----+
              | .. |                              | .. |
              +----+                              +----+
              | 10 |                              | 10 | <-- esp after 'popl %eax'
              +----+                              +----+
              | 12 |                              | 12 |
              +----+                              +----+
              | 34 |                              | 34 |
              +----+                              +----+
    ^         | 56 |                              | 56 |
    |         +----+                              +----+
  memory      | 78 | <-- esp after 'pushl $msg'   | 78 |
 addresses    +----+                              +----+

popl 会从栈顶取出 4 个字节(0x12345678)放到 eax 中,并把 esp 的值加 4,但是并不会把栈原来的内容清除。

程序中的 pop 语句如果省略了也不影响函数的最终执行,但是为了防止栈的不断增大以致超出了范围,执行完函数后及时恢复栈在调用前的状态(也就是 esp 的值)是个好习惯。

最后两句:

pushl $0
call  exit

把参数 0 压入栈中,并且调用 exit(),整个程序结束。

发表于 2011年4月19日
  1. 2011年4月19日 14:22 | #1

    还在看这个书啊。。。都开始写汇编教程了……

    • ou
      2011年4月19日 14:32 | #2

      不是还在看,是才开始看。。。

  2. 2012年2月3日 14:49 | #3

    汇编器提示:
    Error: invalid instruction suffix for `push’
    是怎么回事阿..

    • ou
      2012年2月3日 15:19 | #4

      用64位的系统?试试在文件开头加上一行

      .code32
      

      指定在32位模式下编译就没问题了,不过运行的时候会出错,用gdb查看下应该是链接了64位的库造成的:

      Program received signal SIGSEGV, Segmentation fault.
      0x00007ffff7aa5df5 in printf_size () from /lib/x86_64-linux-gnu/libc.so.6
      (gdb) bt
      #0  0x00007ffff7aa5df5 in printf_size () from /lib/x86_64-linux-gnu/libc.so.6
      #1  0x00007ffff7dd8320 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
      #2  0x00007ffff7deb060 in ?? () from /lib64/ld-linux-x86-64.so.2
      #3  0x00007ffff7ffe1c8 in _r_debug ()
      #4  0x0000000000000000 in ?? ()
      

      建议在32位linux上运行程序或者安装32位的.so试试。

      • 2012年2月3日 20:36 | #5

        .code32就没事了..

      • 2012年2月3日 20:37 | #6

        段错误..

        • ou
          2012年2月3日 21:36 | #7

          上面的程序都是在32位系统下写的,没在64位上测试过。google了一下发现64位和32位的汇编还不太一样,如pushl -> pushq, eax -> rax等,也不清楚函数调用和参数传递之间是不是也有区别,尝试把pushl -> pushq,popl -> popq,eax -> rax替换之后还是出错。《professional assembly language》只讲了32位的情况,我也不想翻官方手册,so,目前除了拿到32位上跑我也不清楚怎么解决这些问题…

          • 2012年2月4日 01:12 | #8

            好吧我先把32位搞明白再研究64..

  3. yorktsai
    2012年3月23日 18:58 | #9

    popl %eax
    这一句,修改了eax寄存器的内容,不会产生不利的影响吗?

    • ou
      2012年3月23日 23:21 | #10

      这个语句唯一的影响就是覆盖了printf()的返回值……其实在这个程序里这句是多余的,因为执行完printf()之后整个将要退出,栈的清理与否已经无所谓了。这句只是表示一个好习惯,就是在进入一个函数的时候压入栈的内容离开的时候要清理干净。

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>