0%

汇编语言基础

阅读更多

1 Intel and AT&T Syntax

汇编语言有2种不同的语法,分别是Intel Syntax以及AT&T Syntax。它们在形式上大致相同,但是在细节上存在很大差异。极易混淆

Intel AT&T
注释 ; #
指令 无后缀,例如add 有后缀,会带上操作数的类型大小,例如addq
寄存器 eaxebx等等 %eax%ebx等等
立即数 0x100 $0x100
直接寻址 [eax] (%eax)
间接寻址 [base + reg + reg * scale + displacement] displacement(reg, reg, scale)

1.1 内存引用

Intel Syntax的间接内存引用的格式为:section:[base + index*scale + displacement]
AT&T Syntax的间接内存引用的格式为:section:displacement(base, index, scale)

  • 其中,baseindex是任意的32-bitbaseindex寄存器
  • scale可以取值1248。如果不指定scale值,则默认值为1
  • section可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情况下不一样

一些例子:

  1. -4(%ebp)
    • base%ebp
    • displacement-4
    • section:未指定
    • index:未指定,默认为0
    • scale:未指定,默认为1

2 指令

Intel Syntax语法的特点如下:

1
助记符 目的操作数 源操作数

AT&T Syntax语法的特点如下:

1
助记符 源操作数 目的操作数

下面以AT&T Syntax的形式列出常用的指令

数据传送指令:

指令格式 描述
movl src dst 传双字
movw src dst 传单字
movb src dst 传字节
movsbl src dst src(字节)进行符号位填充,变为dst(双字)
movzbl src dst src(字节)进行零填充,变为dst(双字)
pushl src 压栈
R[%esp] -= 4
M[R[%esp]] = src
popl dst 出栈
dst = M[R[%esp]]
R[%esp] += 4
xchg mem/reg mem/reg 交换两个寄存器或者交换寄存器和内存之间的内容(至少有一个是寄存器)
两个操作数的数据类型要相同,比如一个是字节另一个也得是字节

算数和逻辑操作指令:

指令格式 描述
leal src dst dst = &srcdst只能是寄存器
incl dst dst += 1
decl dst dst -= 1
negl dst dst = -dst
notl dst dst = ~dst
addl src dst dst += src
subl src dst dst -= src
imull src dst dst *= src
xorl src dst dst ^= src
orl src dst dst |= src
andl src dst dst &= src
sall k dst dst << k
shll k dst dst << k(同sall
sarl k dst dst >> k
shrl k dst dst >> k(同sarl

比较指令:

指令格式 描述
cmpb s2 s1 s2 - s1,比较字节,差关系
testb s2 s1 s2 & s1,比较字节,与关系
cmpw s2 s1 s2 - s1,比较字,差关系
testw s2 s1 s2 & s1,比较字,与关系
cmpl s2 s1 s2 - s1,比较双字,差关系
testl s2 s1 s2 & s1,比较双字,与关系

跳转指令:

指令格式 描述
jmp label 直接跳转
jmp *operand 间接跳转
je label 相等跳转
jne label 不相等跳转
jz label 零跳转
jnz label 非零跳转
js label 负数跳转
jns label 非负跳转
jg label 大于跳转
jnle label 大于跳转
jge label 大于等于跳转
jnl label 大于等于跳转
jl label 小于跳转
jnge label 小于跳转
jle label 小于等于跳转
jng label 小于等于跳转

其他:

指令 描述
ret 函数调用后返回
cli 关中断,ring0
sti 开中断,ring0
lgdt src 加载全局描述符
lidt src 加载中断描述符

2.1 如何查指令

3 寄存器

64-bit register Lower 32 bits Lower 16 bits Lower 8 bits
rax eax ax al
rbx ebx bx bl
rcx ecx cx cl
rdx edx dx dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
r8 r8d r8w r8b
r9 r9d r9w r9b
r10 r10d r10w r10b
r11 r11d r11w r11b
r12 r12d r12w r12b
r13 r13d r13w r13b
r14 r14d r14w r14b
r15 r15d r15w r15b

向量化相关寄存器

寄存器名 位数
xmm 128
ymm 256
zmm 512

4 汇编语法

参考:

4.1 Intel Syntax

4.1.1 注释

1
; this is comment

4.2 AT&T Syntax

4.2.1 汇编器命令

汇编器命令(Assembler Directives)由英文句号(’.’)开头,命令名的其余是字母,通常使用小写,下面仅列出一些常见的命令

命令 描述
.abort 本命令立即终止汇编过程。这是为了兼容其它的汇编器。早期的想法是汇编语言的源码会被输送进汇编器。如果发送源码的程序要退出,它可以使用本命令通知as退出。将来可能不再支持使用.abort
.align abs-expr, abs-expr, abs-expr 增加位置计数器(在当前的子段)使它指向规定的存储边界。第一个表达式参数(必须)表示边界基准;第二个表达式参数表示填充字节的值,用这个值填充位置计数器越过的地方;第三个表达式参数(可选)表示本对齐命令允许越过字节数的最大值
.ascii "str"... .ascii可不带参数或者带多个由逗点分开的字符串。它把汇编好的每个字符串(在字符串末不自动追加零字节)存入连续的地址
.asciz "str"... .asciz类似与.ascii,但在每个字符串末自动追加一个零字节
.byte .byte可不带参数或者带多个表达式参数,表达式之间由逗点分隔。每个表达式参数都被汇编成下一个字节
.data subsection .data通知as汇编后续语句,将它们追加在编号为subsectionsubsection必须是纯粹的表达式)数据段末。如果参数subsection省略,则默认是0
.def name 开始定义符号name的调试信息,定义区延伸至遇到.endef命令
.end .end标记着汇编文件的结束。as不处理.end命令后的任何语句
.err 如果as汇编一条.err命令,将打印一条错误信息
.float flonums 汇编0个或多个浮点数,浮点数之间由逗号分隔
.global symbol .global使符号symbol对连接器ld可见
.int intnums 汇编0个或多个整数,整数数之间由逗号分隔
.long .int
.macro .macro.endm用于定义宏,宏可以用来生成汇编输出
.quad bignums 汇编0个或多个长整数,长整数之间由逗号分隔
.section name 使用.section命令将后续的代码汇编进一个定名为name的段
.short shortnums 汇编0个或多个短整数,短整数之间由逗号分隔
.single flonums .float
.size 本命令一般由编译器生成,以在符号表中加入辅助调试信息
.string "str" 将参数str中的字符复制到目标文件中去。您可以指定多个字符串进行复制,之间使用逗号分隔
.text subsection 知as把后续语句汇编到编号为subsection的正文子段的末尾,subsection是一个纯粹的表达式。如果省略了参数subsection,则使用编号为0的子段
.title "heading" 当生成汇编清单时,把heading作为标题使用
.word .short

4.2.2 Symbol

Symbol由字母下划线构成,最后跟一个冒号:

1
<symbol_name>:

4.2.3 注释

1
/* this is comment */

5 实战

本小节转载摘录自不吃油条针对汇编语言的系列文章

5.1 准备环境

汇编工具:

  1. nasm:全称为Netwide Assembler,是通用的汇编器,采用Intel Syntax
  2. masm:全称为Microsoft Macro Assembler,是微软专门为windows下汇编而写的
  3. gas:全称为GNU Assembler,采用AT&T Syntax
1
2
wget http://mirror.centos.org/centos/7/os/x86_64/Packages/nasm-2.10.07-7.el7.x86_64.rpm
yum localinstall -y nasm-2.10.07-7.el7.x86_64.rpm

5.2 第一个程序

本小节的任务:编写等效于下面cpp程序的汇编代码

1
2
3
int main() {
return 0;
}

5.2.1 Intel版本

first.asm如下:

1
2
3
4
5
global main

main:
mov eax, 0
ret

编译执行:

1
2
3
4
nasm -o first.o -f elf64 first.asm
gcc -o first -m64 first.o

./first; echo $?

5.2.2 AT&T版本

first.asm如下:

1
2
3
4
5
    .text
.globl main
main:
mov $0, %eax
ret

编译执行:

1
2
3
4
as -o first.o first.asm
gcc -o first -m64 first.o

./first; echo $?

5.3 使用内存

本小节的任务:利用内存计算1+2的值

5.3.1 Intel版本

use_memory.asm如下:

1
2
3
4
5
6
7
8
9
10
11
global main

main:
mov ebx, 1
mov ecx, 2
add ebx, ecx

mov [0x233], ebx
mov eax, [0x233]

ret

尝试编译执行:

1
2
3
4
nasm -o use_memory.o -f elf64 use_memory.asm
gcc -o use_memory -m64 use_memory.o

./use_memory; echo $?

结果发现core dump了,这是因为在Linux操作系统上,内存是受操作系统管控的,不能随便读写,可以改成如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
global main

main:
mov ebx, 1 ;将ebx赋值为1
mov ecx, 2 ;将ecx赋值为2
add ebx, ecx ;ebx = ebx + ecx

mov [sui_bian_xie], ebx ; 将ebx的值保存起来
mov eax, [sui_bian_xie] ; 将刚才保存的值重新读取出来,放到eax中

ret ; 返回,整个程序最后的返回值,就是eax中的值

section .data
sui_bian_xie dw 0

再次尝试编译执行:

1
2
3
4
nasm -o use_memory.o -f elf64 use_memory.asm
gcc -o use_memory -m64 use_memory.o

./use_memory; echo $?

这次的代码除了用[sui_bian_xie]代替内存地址外,还多出了如下两行

  • 第一行先不管是表示接下来的内容经过编译后,会放到可执行文件的数据区域,同时也会随着程序启动的时候,分配对应的内存
  • 第二行就是描述真实的数据的关键所在里,这一行的意思是开辟一块4字节的空间,并且里面用0填充。这里的dw(double word)就表示4个字节,前面那个sui_bian_xie的意思就是这里可以随便写,也就是起个名字而已,方便自己写代码的时候区分,这个sui_bian_xie会在编译时被编译器处理成一个具体的地址,我们无需理会地址具体时多少,反正知道前后的sui_bian_xie指代的是同一个东西就行了
1
2
section .data
sui_bian_xie dw 0

再来个例子,use_memory2.asm如下:

1
2
3
4
5
6
7
8
9
10
11
12
global main

main:
mov eax, [number_1]
mov ebx, [number_2]
add eax, ebx

ret

section .data
number_1 dw 10
number_2 dw 20

编译执行:

1
2
3
4
nasm -o use_memory2.o -f elf64 use_memory2.asm
gcc -o use_memory2 -m64 use_memory2.o

./use_memory2; echo $?

5.3.2 AT&T版本

use_memory.asm如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
    .text
.data
sui_bian_xie:
.int 0
.text
.globl main
main:
mov $1, %ebx
mov $2, %ecx
add %ecx, %ebx
mov %ebx, (sui_bian_xie)
mov (sui_bian_xie), %eax
ret

编译执行:

1
2
3
4
as -o use_memory.o use_memory.asm
gcc -o use_memory -m64 use_memory.o

./use_memory; echo $?

use_memory2.asm如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
    .text
.data
number_1:
.int 10
number_2:
.int 20
.text
.globl main
main:
mov (number_1), %eax
mov (number_2), %ebx
add %ebx, %eax
ret

编译执行:

1
2
3
4
as -o use_memory2.o use_memory2.asm
gcc -o use_memory2 -m64 use_memory2.o

./use_memory2; echo $?

6 Tips

6.1 如何生成易读的汇编

方式1:(并不容易看懂)

1
2
3
4
5
# 生成汇编
gcc main.cpp -S -g -fverbose-asm

# 查看汇编
cat main.s

方式2:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 生成目标文件
gcc main.cpp -c -g

# 反汇编(AT&T Syntax)
# -d: 仅显式可执行部分的汇编
# -r: 在重定位时显示符号名称
# -w: 以多于 80 列的宽度对输出进行格式化
# -C: 对修饰过的 (mangled) 符号名进行解码
# -S: 将源代码与反汇编混合在一起,便于阅读
objdump -drwCS main.o

# 反汇编(Intel Syntax)
objdump -drwCS -M intel main.o

7 参考