AT&T汇编
1.Register Reference
引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。
80386有如下寄存器:
[1] 8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;
(8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,
%cx,%dx,%di,%si,%bp,%sp;
8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。它们事实上
是寄存器%ax,%bx,%cx,%dx的高8位和低8位;)
[2] 6个段寄存器:%cs(code),%ds(data),%ss(stack), %es,%fs,%gs;
[3] 3个控制寄存器:%cr0,%cr2,%cr3;
[4] 6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7;
[5] 2个测试寄存器:%tr6,%tr7;
[6] 8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。
2. Operator Sequence
操作数排列是从源(左)到目的(右),如“movl %eax(源), %ebx(目的)”
3. Immediately Operator
使用立即数,要在数前面加符号$,如“movl $0x04, %ebx”
或者:
para = 0x04
movl $para, %ebx
指令执行的结果是将立即数0x04装入寄存器ebx。
4. Symbol Constant
符号常数直接引用 如
value: .long 0x12a3f2de
movl value , %ebx
指令执行的结果是将常数0x12a3f2de装入寄存器ebx。
引用符号地址在符号前加符号$,如“movl $value, % ebx”则是将符号value的地址装入寄存器ebx。
5. Length of Operator
操作数的长度用加在指令后的符号表示 b(byte, 8-bit), w(word, 16-bits), l(long,32-bits),如“ movb %al, %bl”,“ movw
%ax, %bx”,“movl %eax, %ebx ”。
如 果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。比如指令“ mov %ax, %bx”,由于目标操作数 bx的长度为
word,那么编译器将把此指令等同于 “movw %ax,%bx”。同样道理,指令 “mov $4, %ebx”等同于指令 “movl $4,
%ebx”,“ push %al”等同于“ pushb %al”。对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令
“push $4”。
6. Sign and Zero Extension
绝大多数面向80386的AT&T汇编指令与Intel格式的汇编指令都是相同的,但符号扩展指令和零扩展指令有不同格式。符号扩展指令
和零扩展指令需要指定源操作数长度和目的操作数长度,即使在某些指令中这些操作数是隐含的。
在AT&T语法中,符号扩展和零扩展指令的格式为,基本部分 "movs"和"movz"(对应 Intel语法的 movsx和movzx),后面跟
上源操作数长度和目的操作数长度。 movsbl意味着 movs(from)byte(to)long;movbw意味着 movs(from)byte
(to)word;movswl意味着movs(from)word(to)long。对于movz指令也一样。比如指令“movsbl %al,%edx”意味着将
al寄存器的内容进行符号扩展后放置到edx寄存器中。
其它的Intel格式的符号扩展指令还有:
cbw --sign-extend byte in %al to word in %ax;
cwde --sign-extend word in %ax to long in %eax;
cwd --sign-extend word in %ax to long in %dx:%ax;
cdq --sign-extend dword in %eax to quad in %edx:%eax;
对应的AT&T语法的指令为cbtw,cwtl,cwtd,cltd。
7. Call and Jump
段内调用和跳转指令为 "call","ret"和"jmp",段间调用和跳转指令为 "lcall","lret"和"ljmp"。段间调用和跳转指令的格式为
“lcall/ljmp $SECTION, $OFFSET”,而段间返回指令则为“lret $STACK-ADJUST”。
8. Prefix
操作码前缀被用在下列的情况:
[1]字符串重复操作指令(rep,repne);
[2]指定被操作的段(cs,ds,ss,es,fs,gs);
[3]进行总线加锁(lock);
[4]指定地址和操作的大小(data16,addr16);
在AT&T汇编语法中,操作码前缀通常被单独放在一行,后面不跟任何操作数。例如,对于重复scas指令,其写法为:
repne
scas
上述操作码前缀的意义和用法如下:
[1]指定被操作的段前缀为cs,ds,ss,es,fs,和gs。在AT&T语法中,只需要按照
section:memory-operand的格式就指定了相应的段前缀。比如:
lcall %cs:realmode_swtch
[2]操作数/地址大小前缀是“data16”和"addr16",它们被用来在32-bit操作数/地址代码中指定16-bit的操作数/地址。
[3]总线加锁前缀“ lock”,它是为了在多处理器环境中,保证在当前指令执行期间禁止一切中断。这个前缀仅仅对 ADD, ADC, AND,
BTC, BTR, BTS, CMPXCHG,DEC,
INC, NEG, NOT, OR, SBB, SUB, XOR, XADD,XCHG指令有效,如果将Lock前
缀用在其它指令之前,将会引起异常。
[4]字符串重复操作前缀"rep","repe","repne"用来让字符串操作重复“%ecx”次。
9. Memory Reference
Intel语法的间接内存引用的格式为:
section:[base+index*scale+displacement]
而在AT&T语法中对应的形式为:
section:displacement(base,index,scale)
其中,base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale值,则默认值为1。
section 可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情况下不一样。如果在指令中指定了默认的段前缀,则编译器在
目标代码中不会产生此段前缀代码。
下面是一些例子:
-4(%ebp):base=%ebp,displacement=-4,section没有指定,由于 base=%ebp,所以默认的 section=%ss,index,scale
没有指定,则index为0。
foo(,%eax,4):index=%eax,scale=4,displacement=foo。其它域没有指定。这里默认的section=%ds。
foo(,1):这个表达式引用的是指针 foo指向的地址所存放的值。注意这个表达式中没有 base和index,并且只有一个逗号,这是一种
异常语法,但却合法。
%gs:foo:这个表达式引用的是放置于%gs段里变量foo的值。
如果call和jump操作在操作数前指定前缀“ *”,则表示是一个绝对地址调用 /跳转,也就是说 jmp/call指令指定的是一个绝对地址。
如果没有指定"*",则操作数是一个相对地址。
任何指令如果其操作数是一个内存操作, 则指令必须指定它的操作尺寸
(byte,word,long),也就是说必须带有指令后缀(b,w,l)。
Linux工作在保护模式下,用的是 32位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地
址计算方法:
disp + base + index * scale
下面是一些内存操作数的例子:
AT&T格式
movl -4(%ebp), %eax
movl array(, %eax, 4), %eax
movw array(%ebx, %eax, 4), %cx
movb $4, %fs:(%eax)
其中下面这些省略了浮点数及IA-32如SSE FPU等特殊的指令集部分,我觉得重要的是学习
linux汇编的语法及编译原理和程序控制流程,具体的指令细节就不那么重要了。
###########################################################################
#####################
#一, IA-32硬件特性
###########################################################################
#####################
寄存器:
1,通用寄存器,用于存放正在处理的数据
EAX用于操作数和结果数的累加器
EBX指向数据内存断中的数据的指针
ECX字符串和循环操作的计数器
EDX IO指针
EDI用于字符串操作的目标的数据指针
ESI用于字符串操作的源的数据指针
ESP堆栈指针
EBP堆栈数据指针
其中寄存器EAX, EBX, ECX, EDX又可以通过
16位和8位寄存器名称引用如EAX, AX引用EAX低16位, AL 引用EAX低8位, AH引用
AL之后的高8位
2,段寄存器:
IA-32平台允许使用3中内存模型:平坦内存模式 分段内存模式 实地址模式
平坦内存:把全部的系统内存表示为连续的地址空间, 通过线性地址的特定地址访问内存位置.
分段内存:把系统内存划分为独立的段组,通过位于寄存器中的指针进行引用.每个段用于包含特定类型的数据。 一个段用于包含指令码,另
一个段包含数据元素,第三个段包含数据堆栈。
段中的内存位置是通过逻辑地址引用的,逻辑地址是由段地址加上偏移量构成, 处理器把逻辑地址转换为相应的线性地址以便访问。
段寄存器:
CS代码段
DS数据段
SS堆栈段
ES附加段指针
FS附加段指针
GS附加段指针
每个段寄存器都是16位的, 包含指向内存特定段起始位置的指针,程序不能显示加载或改变CS寄存器, DS, ES, FS, GS都用于指向数据
段,通过
4个独立的段,程序可以分隔数据元素, 确保他们不会重叠, 程序必须加载带有段的正确指针值的数据段寄存器, 并且使用偏移
值引用各个内存的位置。
SS段寄存器用于指向堆栈段,堆栈包含传递给函数和过程的数据值。
实地址:如果实地址模式,所有段寄存器都指向线性0地址, 并且都不会被程序改动,所有的指令码 数据元素堆栈元素 都是通过他们的
线性地址直接访问的。
3, 指令指针寄存器
是EIP寄存器,它跟踪要执行程序的下一条指令代码,应用程序不能修改指令指针本身,不能指定内存地址把它拖放EIP寄存器中,相反必须
通过一般的跳转指令来改变预存取缓存的下一条指令。
在平坦内存模型中, 指令指针包含下一条指令码的线性地址,在分段模型中指令指针包含逻辑地址指针, 通过
CS寄存器的内存引用。
4,控制寄存器
CRO控制操作模式 和 处理器当前状态的系统标志
CR1当前没有使用
CR2内存页面错误信息
CR3内存页面目录信息
CR4支持处理器特性和说明处理器特性能力的标志
不能直接访问控制寄存器,但是能把控制寄存器中的值传递给通用寄存器,如果必须改动控制寄存器的标志, 可以改动通用寄存器的值, 然
后把内容传递给控制寄存器。
标志:
IA-32使用单一的寄存器来包含一组状态控制和系统标志, EFLAGS寄存器包含32位标志信息
1,状态标志
标志 位 说明
CF 0进位标志,如果无符号数的数学操作产生最高有效位的进位或者借位,此时值为1
PF 2奇偶校验标志,用于表明数学操作的结果寄存器中的是否包含错误数据
AF 4辅助进位标志,用于二进制编码的10进制(BCD)的数学操作中, 如果用于运算的
寄存器的第三位发生进位或借位, 该值为1
ZF 6 0标志,如果操作为0, 则该值为1
SF 7符号标志,设置为结果的最高有效位,这一位是符号位表明结果是正值还是负值
OF 11溢出标志
2,控制标志
当前只定义了一个控制标志
DF即方向标志,用于控制处理器处理字符串的方式如果设置为1,字符串指令自动递减内存地址以便到达字符串
中的下一字节。
反之。
3,系统标志
标志 位 说明
TF 8陷阱标志,设置为1时启用单步模式,在单步模式下处理器每次只执行一条命令。
IF 9中断使能标志,控制处理器如响应从外部源接收到的信号。
IOPL 12和13 IO特权级别标志,表明当前正在运行任务的IO特权级别,它定义IO地址空间的特权访问级别,该值必须小于或者等于访问
I/O地址空间的级别;否则任何访问
IO空间的请求都会被拒绝!
NT 14嵌套任务标志控制当前运行的任务是否连接到前一个任务,它用于连接被中断和被调用的任务.
RF 16恢复标志用于控制在调试模式中如何响应异常。
VM 17虚拟
8086模式,表明处理器在虚拟
8086模式中而不是保护模式或者实模式。
AC 18对准检查标志,用于启用内存引用的对准检查
VIF 19虚拟中断标志,当处理器在虚拟模式中操作时,该标志起IF标志的作用.
VIP 20虚拟中断挂起标志,在虚拟模式操作时用于表示一个中断正在被挂起。
ID 21表示CPU是否支持
cpuid指令,如果处理器能够设置或者清零这个标志,表示处理器支持该指令。
###########################################################################
#####################
#二,GNU汇编工具系列
###########################################################################
#####################
1,二进制工具系列
addr2line把地址转换成文件名或者行号
ar创建修改或者展开文件存档
as把汇编语言代码汇编成目标代码
常用选项:
-a ->指定输出中包含那些清单
-D ->包含它用于向下兼容 但是被忽略
--defsym ->在汇编代码之前定义符号和值
-f ->快速汇编跳过注释和空白
--gstabs ->包含每行源代码的调试信息
--gstats+ ->包含gdb专门的调试信息
-I ->指定包含文件的目录
-J ->不警告带符号溢出
-L ->在符号表中保存本地符号
-o ->给定输出目标名
-R ->把数据段合并进文本段
--statistics ->显示汇编使用的最大空间和总时间
-v ->显示as的版本号
-W ->不显示警告信息
c++filt还原
c++符号的过滤器
gprof显示程序简档信息的程序
ld把目标代码文件转换成可执行文件的转换器
常用选项:
-d ->指定目标代码输入文件的格式
-Bstatic ->只使用静态库
-Bdynamic ->只使用动态库
-Bsymbolic->把引用捆绑到共享库中的全局符号
-c ->从指定的命令文件读取命令
-cref ->创建跨引用表
-defsym ->在输出文件中创建指定的全局符号
-demangle ->在错误消息中还原符号名称
-e ->使用指定的符号作为程序的初始执行点
-E ->对于elf文件把所有的符号添加到动态符号表
-share ->创建共享库
-Ttext ->使用指定的地址作为文本段的起始点
-Tdata ->使用指定的地址作为数据段的起始点
-Tbss ->使用指定的地址作为bss段的起始点
-L ->把指定的路径添加到库搜索清单
-O ->生成优化的输出文件
-o ->指定输出名
-oformat ->指定输出文件的二进制格式
-R ->从指定的文件读取符号和地址
-rpath ->把指定的位置添加到运行时库搜索路径
-rpath-link->指定搜索运行时共享库的路径
-X ->删除本地所有临时符号
-x ->删除本地所有符号
nm列出目标文件中的符号
objcopy复制或翻译目标文件
objdump显示来自目标文件的信息
ranlib生成存档文件内容的索引
readelf按照elf格式显示目标文件信息
size列出目标文件或者存档文件的段长度
strings显示目标文件中可打印字符串
strip丢弃符号
windres编译Microsoft Windows资源文件
2, GNU编译器
gcc
常用选项:
-c编译或者汇编代码但不进行连接
-S编译后停止但不进行汇编
-E 预处理后停止但不进行编译
-o指定输出文件名
-v显示每个编译阶段使用的命令
-std指定使用的语言标准
-g生成调试信息
-pg生成
gprof制作简档要使用的额外代码
-O优化可执行代码
-W 设置编译器警告级别
-I指定包含文件清单
-L 指定库文件目录
-D预定义源代码中使用的宏
-U取消任何定义了的宏
-f指定控制编译器行为的选项
-m指定与硬件相关的选项
3, GNU调试程序
gdb
常用选项:
-d指定远程调试时串行接口的线路速度
-batch以批处理模式运行
-c指定要分析的核心转储文件
-cd指定工作目录
-d指定搜索源文件的目录
-e指定要执行的文件
-f调试时以标准格式输出文件名和行号
-q安静模式
-s指定符号的文件名
-se指定符号和要执行的文件名
-tty设置标准输出和输入设备
-x从指定的文件执行gdb命令
由于gnu调试时忽略开始处断点,需要在开始标签处执行一个空指令
如:
.globl _start
_start:
nop
此时断点可以设置成
break *_start+1
查看寄存器状态
info registers
使用print命令查看特定寄存器或者变量的值,加上修饰符可以得到不同的输出格式:
print/d显示十进制数字
print/t显示二进制数字
print/x显示16进制数字
使用x命令可以查看特定内存的值:
x/nyz
其中 n为要显示的字段数
y时输出格式,它可以是:
c用于字符,d用于十进制,x用于16进制
z是要显示的字段长度,它可以是:
b用于字节,h用于16字节,w用于32位字
如:
x/42cb用于显示前42字节
###########################################################################
#####################
#三, GNU汇编语言结构
###########################################################################
#####################
主要包括三个常用的段:
data 数据段 声明带有初始值的元素
bss数据段 声明使用0或者null初始化的元素
text正文段 包含的指令,每个汇编程序都必须包含此段
使用.section 指令定义段,如:
.section .data
.section .bss
.section .text
起始点:
gnu汇编器使用_start标签表示默认的起始点,此外如果想要汇编内部的标签能够被外部程序访问,需要使用.globl指令,
如:.globl _start
使用通用库函数时可以使用:
ld -dynamic-linker /lib/ld-linux.so.2
###########################################################################
#####################
#四,数据传递
###########################################################################
#####################
1,数据段
使用.data声明数据段,这个段中声明的任何数据元素都保留在内存中并可以被汇编程序的指令读取,此外还可以使用.rodata声明只读的数据
段,在声明一个数据元素时,需要使用标签和命令:
标签:用做引用数据元素所使用的标记, 它和c语言的变量很相似,它对于处理器是没有意义的,它只是用做汇编器试图访问内存位置时用做
引用指针的一个位置。
指令:这个名字指示汇编器为通过标签引用的数据元素保留特定数量的内存, 声明命令之后必须给出一个或多个默认值。
声明指令:
.ascii文本字符串
.asciz以空字符结尾的字符串
.byte字节值
.double双精度浮点值
.float单精度浮点值
.int 32位整数
.long 32位整数,和int相同
.octa 16字节整数
.quad 8字节整数
.short 16位整数
.single单精度浮点数(和float相同)
例子:
output:
.ascii "hello world."
pi:
.float 2.14
声明可以在一行中定义多个值,如:
ages:
.int 20, 10, 30, 40
定义静态符号:
使用.equ命令把常量值定义为可以在文本段中使用的符号,如:
.section .data
.equ LINUX_SYS_CALL, 0x80
.section .text
movl $LINUX_SYS_CALL, %eax
2, bss段
和data段不同,无需声明特定的数据类型,只需声明为所需目的保留的原始内存部分即可。
GNU汇编器使用以下两个命令声明内存区域:
.comm声明为未初始化的通用内存区域
.lcomm声明为未初始化的本地内存区域
两种声明很相似, 但.lcomm是为不会从本地汇编代码之外进行访问的数据保留的, 格式为:
.comm/.lcomm symbol, length
例子:
.section .bss
.lcomm buffer, 1000
该语句把1000字节的内存地址赋予标签
buffer,在声明本地通用内存区域的程序之外的函数是不能访问他们的.(不能在.globl命令中使用他
们)
在bss段声明的好处是,数据不包含在可执行文件中。在数据段中定义数据时,它必须被包含在可执行程序中,因为必须使用特定值初始化它。
因为不使用数据初始化
bss段中声明的数据区域,所以内存区域被保留在运行时使用,并且不必包含在最终的程序中
3, 传送数据
move指令:
格式 movex源操作数,目的操作数。 其中x为要传送数据的长度,取值有:
l用于32位的长字节
w用于16位的字
b用于8位的字节值
立即数前面要加一个$符号,寄存器前面要加%符号。
8个通用的寄存器是用于保存数据的最常用的寄存器,这些寄存器的内容可以传递给其他的任何可用的寄存器。 和通用寄存器不同,专用寄存
器(控制,调试,段)的内容只能传送给通用寄存器,或者接收从通用寄存器传过来的内容。
在对标签进行引用时:
例:
.section .data
value:
.int 100
_start:
movl value, %eax
movl $value, %eax
movl %ebx, (%edi)
movl %ebx, 4(%edi)
其中:movl value, %eax只是把标签
value当前引用的内存值传递给
eax
movl $value, %eax把标签
value当前引用的内存地址指针传递给
eax
movl %ebx, (%edi)如果edi外面没有括号那么这个指令只是把ebx中的
值加载到edi中,如果有了括号就表示把ebx中的内容
传送给
edi中包含的内存位置。
movl %ebx, 4(%edi)表示把edi中的值放在edi指向的位置之后的4字节内存位置中
movl %ebx, -4(%edi)表示把edi中的值放在edi指向的位置之前的4字节内存位置中
cmove指令(条件转移):
cmovex源操作数,目的操作数.x的取值为:
无符号数:
a/nbe大于/不小于或者等于
ae/nb大于或者等于/不小于
nc无进位
b/nae小于/不大于等于
c进位
be/na 小于或等于/不大于
e/z等于/零
ne/nz不等于/不为零
p/pe奇偶校验/偶校验
np/po非奇偶校验/奇校验
有符号数:
ge/nl大于或者等于/不小于
l/nge小于/不大于或者等于
le/ng小于或者等于/不大于
o溢出
no未溢出
s带符号(负)
ns无符号(非负)
交换数据:
xchg在两个寄存器之间或者寄存器和内存间交换值如:
xchg操作数,操作数,要求两个操作数必须长度相同且不能同时都是内存位置其中寄存器可以是32,16,8位的bswap反转一个32位寄
存器的字节顺序如: bswap %ebx
xadd交换两个值 并把两个值只和存储在目标操作数中如: xadd源操作数,目标操作数
其中源操作数必须是寄存器,目标操作数可以是内存位置也可以是寄存器其中寄存器可以是32,16,8位的
cmpxchg
cmpxchg source, destination
其中source必须是寄存器, destination可以是内存或者寄存器,用来比较两者的值,如果相等,就把源操作数的值加载到目标操作数中,如
果不等就把目标操作数加载到源操作数中,其中寄存器可以是32,16,8位的,其中源操作数是EAX,AX或者AL寄存器中的值
cmpxchg8b同cmpxchg, 但是它处理8字节值, 同时它只有一个操作数
cmpxchg8b destination其中destination引用一个内存位置,其中的8字节值会与EDX和EAX寄存器中包含的值(EDX高位寄存器,EAX
低位寄存器)进行比较,如果目标值和EDX:EAX对中的值相等,就把EDX:EAX对中的64位值传递给内存位置,如果不匹配就把内存地址中
的值加载到EDX:EAX对中
4,堆栈
ESP寄存器保存了当前堆栈的起始位置,当一个数据压入栈时, 它就会自动递减,反之其自动递增
压入堆栈操作:
pushx source, x取值为:
l 32位长字
w 16位字
弹出堆栈操作:
popx source
其中source必须是16或32位寄存器或者内存位置,当pop最后一个元素时ESP值应该和以前的相等
5,压入和弹出所有寄存器
pusha/popa 压入或者弹出所有16位通用寄存器
pushad/popad压入或者弹出所有32位通用寄存器
pushf/popf压入或者弹出
EFLAGS寄存器的低16位
pushfd/popfd压入或者弹出
EFLAGS寄存器的全部32位
6,数据地址对齐
gas汇编器支持.align 命令, 它用于在特定的内存边界对准定义的数据元素, 在数据段中.align命令紧贴在数据定义的前面
###########################################################################
#####################
#五,控制流程
###########################################################################
#####################
无条件跳转:
1,跳转
jmp location 其中location为要跳转到的内存地址,在汇编中为定义的标签
2,调用
调用指令分为两个部分:
1,调用call address跳转到指定位置
2,返回指令ret,它没有参数紧跟在call指令后面的位置
执行call指令时,它把EIP的值放到堆栈中, 然后修改
EIP以指向被调用的函数地址, 当被调用函数完成后, 它从堆栈获取过去的EIP的
值, 并把控制权返还给原始程序。
3,中断
由硬件设备生成中断。 程序生成软件中断当一个程序产生中断调用时,发出调用的程序暂停,被调用的程序接替它运行,指令指针被转移到
被调用的函数地址, 当调用完成时使用中断返回指令可以返回调原始程序。