ATT汇编(gas:gnu assembly)

ATT汇编(gas:gnu assembly)

前话

汇编不用多数,就是符号化了机器指令,是一种很低级(靠近硬件)的编程。很多人接触比较多的是 windows 下的 Intel 汇编,然而 unix/linux 下 or 使用 gcc 的话,用的是 ATT 汇编。(unix 最初是 AT&T 实验室中的 Ken Thompson 发明的。)

Intel 汇编和 ATT 汇编使用的指令基本一样,就是写法上有些差异,鉴于前者有很多人介绍,而后者相对比较少,故有了此篇文章。 (me 也曾经想在 linux 看下汇编的写法,然而搜到的基本都是 Intel 汇编,赶脚很失落,O__O"…)

ATT 汇编格式

ATT 汇编大体格式是: 指令 源操作数 目的操作数 ,比如将 10 移动到 eax 寄存器的写法: movl $10, %eax

  1. ATT 汇编的源操作数和目的操作数和 Intel 正好相反,也就是数据流向是从左到右;
  2. ATT 中立即数前需要加 $ 符号, 寄存器前加 % 符号;
  3. ATT 的指令加后缀 b 、 w、 l 、q 表明处理的数据长度,分别是字节、字(2B)、双字(4B)、四字(8B);
  4. ATT 以寄存器中的值为地址的内存单元的访问(间接寻址)是加上括号比如 (%eax),而非 Intel 的 [EAX] 。

ATT 常用指令

(1) 移动指令 movN src dst : movl $a, %ebx

(2) 运算指令 加 addN 、 减 subN 、按位与 andN 、按位异或 xorN src dst : xor %eax %eax (将 eax 值置为 0)

(3) 比较指令 cmpN src dst : cmp (%eax) 10

(4) 跳转指令 jmp、大于跳转 jg、小于跳转 jl 、大于等于跳转 jge、 jle、 je、 jne

(5) 函数调用 call 和返回 ret

ATT 汇编代码示例

(1) hello,world

上来先见识一下 hello,world 的写法:

  1.     .data
  2. msg :
  3.     .ascii "hello,world\0"
  4.    
  5.     .text
  6.     .globl  _main
  7. _main :
  8.     # 函数调用的“开场白”
  9.     pushl   % ebp
  10.     movl     % esp , % ebp
  11.     andl     $ - 16 , % esp
  12.        
  13.     # 调用 puts 函数
  14.     pushl   $msg
  15.     call    _puts
  16.    
  17.     # 设置返回值
  18.     movl     $ 0 , % eax
  19.    
  20.     # 函数调用的“结束语”
  21.     leave
  22.     ret
  23.  

上面 "hello,world" 字符串的输出是通过调用 c 的库函数 puts 实现的。 汇编上面的代码可以:$ gcc -o hello hello.s 。

(1) 在 linux 下汇编此程序如果说找不到 _start 那么将程序中的 _main 改成 _start 就好。

(2) 除了使用 gcc 命令之外,也可以先汇编 $ as -o hello.o hello.s 然后再链接 $ ld hello.o -o hello 生成可执行文件。(如果 windows 下说找不到 _puts 标识符,可以 $ld hello.o -lmsvcrt )。

程序解释:

(1) 实现 "hello,world" 打印输出的其实只有下面两行:

  1.     pushl   $msg
  2.     call    _puts

_main 是一个函数。汇编函数的写法有一些约定俗成的习惯,比如进入函数的“开场白” (第三行可以省略):

  1.     pushl   % ebp
  2.     movl     % esp , % ebp
  3.     andl     $ - 16 , % esp

和退出函数的“结束语”:

  1.     leave
  2.     ret

(3) 数据段 .data 是定义函数外的数据的, 文本段 .text 就是通常所的代码段。

Tags: 

Article type: 

Comments

ATT汇编代码实例

前面是汇编的 hello,world 程序。为什么借助于 c 的 puts 函数呢?那是因为只有借助于 c 标准库的可移植性达到汇编代码的——可移植性,因为 linux 和 windows 的更低一级的系统调用是不一样的。

程序实例 2 : 调用 c 的 printf 函数

  1.     .data
  2. _format :
  3.     .ascii     "the answer to life the universe and everything = %d\n\0"  
  4. a : .long 42
  5.  
  6.     .text
  7.     . global _main
  8. _main :
  9.     pushl       % ebp
  10.     movl        % esp , % ebp
  11.  
  12. # 调用 printf 函数
  13.     pushl     $ 42    # 右边数第一个参数
  14.     pushl     $_format        # 右边第二个参数
  15.     call    _printf
  16.  
  17.     pushl    a    # 右边数第一个参数
  18.     pushl     $_format
  19.     call    _printf
  20.    
  21.     movl        $ 0 , % eax
  22.     leave    # 函数返回前的准备: ( 1 ) movl % ebp , % esp ( 2 ) popl % ebp
  23.     ret

程序解释:

(1) 函数的开场白(主要是两行,保存原来的 %ebp 和重新设定 %ebp) 就不多说了,结束语有两行:leave 和 ret 。 leave 实际上是开场白的“反操作”,具体的可以参看代码中的注释部分。

(2) c 的 printf 函数,在汇编中调用是 _printf 函数,如同例子 1 中的 _puts 而不是 puts 函数。

(3) 汇编中调用函数之前,需要将参数压入栈中,参数压入的顺序是从右到左。比如 printf("\d", 42); 调用先压入 42 再压入 "%d" 。

(4) 汇编中变量实际上都是标号 LABEL,$LABEL 取的是变量对应的地址,而 LABEL 则代表变量的值;$42 是常量 42 的写法;上面的汇编代码的两次 printf 调用输出结果是一样的。

c 中函数调用的一些细节

(1) 虽然标准 c 中没有说明函数调用时候参数求值的顺序(从左到右?),然而至少从 gas 中可以看到,参数是从右到左传递过去的;

(2) c 的被调用函数 callee 不负责存储参数,参数是调用者 caller 压入栈中的,参数的清理也是调用者 caller 的责任;

(3) 在汇编函数中, 8(%ebp) 是函数的从左到右数的第一个参数,12(%ebp) 是第二个参数。试问 4(%ebp) 是谁呢? 是 caller 的 %ebp 的值!也就是 %ebp 的旧值,caller 的 %ebp 的值!

精诚所至,金石为开


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值