at&t 补充知识

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,中断
由硬件设备生成中断。 程序生成软件中断当一个程序产生中断调用时,发出调用的程序暂停,被调用的程序接替它运行,指令指针被转移到
被调用的函数地址, 当调用完成时使用中断返回指令可以返回调原始程序。

 

条件跳转:
条件跳转按照EFLAGS中的值来判断是否该跳转,格式为:

jxx address,其中xx是1-3个字符的条件代码,取值如下: 
a 大于时跳转 
ae大于等于 
b小于 
be小于等于 
c进位 
cxz如果CX寄存器为0 
ecxz如果ECS寄存器为0 
e相等 
na 不大于 
nae不大于或者等于 
nb不小于 
nbe不小于或等于 
nc无进位 
ne不等于 
g大于(有符号) 
ge大于等于(有符号) 
l小于(有符号) 
le小于等于(有符号) 
ng不大于(有符号) 
nge不大于等于(有符号) 
nl不小于 
nle不小于等于 
no不溢出 
np不奇偶校验 
ns无符号 
nz非零 
o溢出 
p奇偶校验 
pe如果偶校验 
po如果奇校验 
s如果带符号 
z如果为零

条件跳转不支持分段内存模型下的远跳转, 如果在该模式下进行程序设计必须使用程序逻辑确定条件是否存在,然后实现无条件跳转,跳转
前必须设置EFLAGS寄存器

比较: 
cmp operend1, operend2


进位标志修改指令: 
CLC清空进位标志(设置为0) 
CMC对进位标志求反(把它改变为相反的值) 
STC设置进位标志(设置为1)


循环: 
loop循环直到ECX寄存器为0 
loope/loopz循环直到ecx寄存器为0或者没有设置ZF标志 
loopne/loopnz循环直到ecx为0或者设置了ZF标志

指令格式为: loopxx address注意循环指令只支持 
8位偏移地址

 

 

 


########################################################################### 
##################### 
#六,数字 
########################################################################### 
#####################


IA-32平台中存储超过一字节的数都被存储为小尾数的形式但是把数字传递给寄存器时,寄存器里面保存是按照大尾数的形式存储


把无符号数转换成位数更大的值时, 必须确保所有的高位部分都被设置为零


把有符号数转换成位数更大的数时: 
intel提供了movsx指令它允许扩展带符号数并保留符号,它与movzx相似,但是它假设要传送的字节是带符号数形式


浮点数: 
fld指令用于把浮点数字传送入和传送出 
FPU寄存器,格式: 
fld source
其中source可以为32 64或者80位整数值


IA-32使用FLD指令用于把存储在内存中的单精度和双精度浮点值FPU寄存器堆栈中, 为了区分这两种长度GNU汇编器使用 
FLDS加载单精度浮点数, FLDL加载双精度浮点数


类似 
FST用于获取FPU寄存器堆栈中顶部的值,并且把这个值放到内存位置中,对于单精度使用FSTS,对于双精度使用FSTL


########################################################################### 
##################### 
#七,基本数学运算 
########################################################################### 
##################### 
1,加法 
ADD source, destination 把两个整数相加
其中source可以是立即数内存或者寄存器, destination可以是内存或者寄存器,但是两者不能同时都是内存位置


ADC和ADD相似进行加法运算,但是它把前一个ADD指令的产生进位标志的值包含在其中,在处理位数大于32(如64)
位的整数时,该指令非常有用

2,减法 
SUB source, destination 把两个整数相减 
NEG它生成值的补码 
SBB指令,和加法操作一样,可以使用进位情况帮助执行大的无符号数值的减法运算. SBB在多字节减法操作中利用进位和溢出标志实现跨
数据边界的的借位特性

3,递增和递减 
dec destination 递减 
inc destination 递增

其中dec和inc指令都不会影响进位标志,所以递增或递减计数器的值都不会影响程序中涉及进位标志的其他任何运算

4,乘法 
mul source进行无符号数相乘
它使用隐含的目标操作数,目标位置总是使用eax的某种形式,这取决与源操作数的长度, 因此根据源操作数的长度,目标操作数必须放在 
AL, AX, EAX中。 此外由于乘法可能产生很大的值,目标位置必须是源操作数的两倍位置,源为8时,应该是16,源为16时,应该为32,但
是当源为16位时intel为了向下兼容,目标操作数不是存放在eax中,而是分别存放在DX:AX中,结果高位存储在DX中,地位存储在AX中。
对于32位的源,目标操作数存储在EDX:EAX中,其中EDX存储的是高32位, EAX存储的是低32位

 

 

 


imul source进行有符号数乘法运算,其中的目标操作数和mul的一样

imul source, destination也可以执行有符号乘法运算,但是此时可以把目标放在指定的位置,使用这种格式的缺陷
在与乘法的操作结果被限制为单一目标寄存器的长度.

imul multiplier, source, destination
其中multiplier是一个立即数,这种方式允许一个值与给定的源操作数进行快速的乘法运算,然后把结果存储在通用寄存器中

5,除法 
div divisor执行无符号数除法运算
除数的最大值取决与被除数的长度,对于16位被除数 ,除数只能为8位, 32或64位同上
被除数 被除数长度 商余数 
AX 16位 AL AH 
DX:AX 32位 AX DX 
EDX:EAX 64位 EAX EDX

idiv divisor执行有符号数的除法运算,方式和div一样

6,移位
左移位: 
sal向左移位 
sal destination 把destination向左移动 
1位 
sal %cl, destination把destination的值向左移动 
CL寄存器中指定的位数 
sal shifter, destination 把destination的值向左移动 
shifter值指定的位数
向左移位可以对带符号数和无符号数执行向左移位的操作,移位造成的空位用零填充,移位造成的超过数据长度的任何位都被存放在进位标志
中,然后在下一次移位操作中被丢弃

右移位: 
shr向右移位 
sar向右移位 
SHR指令清空移位造成的空位,所以它只能对无符号数进行移位操作 
SAR指令根据整数的符号位,要么清空,要么设置移位造成的空位,对于负数,空位被设置为1

循环移位:
和移位指令类似,只不过溢出的位被存放回值的另一端,而不是丢弃 
ROL 向左循环移位 
ROR向右循环移位 
RCL 向左循环移位,并且包含进位标志 
RCR向右循环移位,并且包含进位标志


7,逻辑运算 
AND OR XOR
这些指令使用相同的格式: 
and source, destination
其中source可以是8位 16位或者32位的立即值 寄存器或内存中的值, destination可以是8位 16位或者32位寄存器或内存中的值,
不能同时使用内存值作为源和目标。 布尔逻辑功能对源和目标执行按位操作。
也就是说使用指定的逻辑功能按照顺序对数据的元素的每个位进行单独比较。

NOT指令使用单一操作数,它即是源值也是目标结果的位置
清空寄存器的最高效方式是使用OR指令对寄存器和它本身进行异或操作.当和本身进行XOR操作时,每个设置为1的位就变为0, 每个设
置为0的位也变位0。

位测试可以使用以上的逻辑运算指令,但这些指令会修改 
destination的值,因此intel提供了test指令,它不会修改目标值而是设置相应的
标志


########################################################################### 
##################### 
#八,字符串处理

 

 

 

 

########################################################################### 
##################### 
1,传送字符串 
movs有三种格式 
movsb传送单一字节 
movsw传送一个字 
movsl传送双字

movs指令使用隐含的源和目的操作数, 隐含的源操作数是ESI, 隐含的目的操作数是EDI, 有两种方式加载内存地址到ESI和EDI,
第一种是使用标签间接寻址 movl $output, %ESI,第二种是使用lea指令, lea指令加载对象的地址到指定的目的操作数如lea output, 
%esi,每次执行movs指令后, 数据传送后ESI和EDI寄存器会自动改变,为另一次传送做准备, ESI和EDI可能随着标志 
DF的不同自动
递增或者自动递减, 如果DF标志为0则movs指令后ESI和EDI会递增,反之会递减, 为了设置DF标志,可以使用一下指令: 
CLD将DF标志清零 
STD设置DF标志


2,rep前缀 
REP指令的特殊之处在与它不执行什么操作,这条指令用于按照特定次数重复执行字符串指令,有ECX寄存器控制,但不需要额外的loop指
令,如rep movsl


rep的其他格式: 
repe等于时重复 
repne不等于时重复 
repnz不为零时重复 
repz为零时重复


3,存储和加载字符串 
LODS加载字符串, ESI为源,当一次执行完 
lods时会递增或递减 
ESI寄存器, 然后把字符串值存放到EAX中


STOS使用lods把字符串值加载到EAX后, 可以使用它把EAX中的值存储到内存中去: 
stos使用EDI作为目的操作数,执行stos指令后,会根据 
DF的值自动递增或者递减 
EDI中的值


4,比较字符串 
cmps和其他的操作字符串的指令一样,隐含的源和目标操作数都为ESI和EDI,每次执行时都会根据 
DF的值把 
ESI和EDI递增或者递减, cmps指令从目标字符串中减去源字符串,执行后会设置EFLAGS寄存器的状态.


5,扫描字符串 
scas把EDI作为目标,它把EDI中的字符串和EAX中的字符串进行比较 
,然后根据 
DF的值递增或者递减 
EDI


########################################################################### 
##################### 
#九,使用函数 
########################################################################### 
##################### 
GNU汇编语言定义函数的语法: 
.type标签(也就是函数名), @function 
ret返回到调用处


########################################################################### 
##################### 
#十,linux系统调用 
########################################################################### 
##################### 
linux系统调用的中断向量为0x80


1,系统调用标识存放在%eax中 
2,系统调用输入值: 
EBX第一个参数

 

 

 


ECX第二个参数 
EDX第三个参数 
ESI第四个参数 
EDI第五个参数

需要输入超过 
6个输入参数的系统调用, EBX指针用于保存指向输入参数内存位置的指针,输入参数按照连续的的顺序存储,系统调用的返回
值存放在EAX中

########################################################################### 
##################### 
#十一,汇编语言的高级功能 
########################################################################### 
##################### 
1,gnu内联汇编的语法: 
asm或__asm__("汇编代码");
指令必须包含在引号里
如果包含的指令超过一行 必须使用新行分隔符分隔


使用c全局变量,不能在内联汇编中使用局部变量,注意在汇编语言代码中值被用做内存位置,而不是立即数值

如果不希望优化内联汇编,则可以volatile修饰符如:__asm__ volatile("code");


2,GCC内联汇编的扩展语法 
__asm__("assembly code":output locations:input operands:changed registers);


第一部分是汇编代码
第二部分是输出位置,包含内联汇编代码的输出值的寄存器和内存位置列表
第三部分是输入操作数,包含内联汇编代码输入值的寄存器和内存位置的列表
第四部分是改动的寄存器, 内联汇编改变的任何其他寄存器的列表
这几个部分可以不全有,但是没有的还必须使用:分隔


1,指定输入值和输出值, 输入值和输出值的列表格式为: 
"constraint"(variable),其中variable是程序中声明的c变量,在扩展asm格式中, 局部和全局变量都可以使用,使用constrant(约束)
定义把变量存放到哪(输入)或从哪里传送变量(输出)
约束使用单一的字符,如下:
约束描述 
a 使用%eax, %ax, %al寄存器 
b使用%ebx, %bx, %bl寄存器 
c使用%ecx, %cx, %cl寄存器 
d使用%edx, %dx, %dl寄存器 
S使用%esi, %si寄存器 
D使用%edi, %di寄存器 
r使用任何可用的通用寄存器 
q使用%eax, %ebx, %ecx,%edx之一 
A对于64位值使用%eax, %edx寄存器 
f使用浮点寄存器 
t使用第一个(顶部)的浮点寄存器 
u 使用第二个浮点寄存器 
m使用变量的内存位置 
o使用偏移内存位置 
V只使用直接内存位置 
i使用立即整数值 
n 使用值已知的立即整数值 
g使用任何可用的寄存器和内存位置

除了这些约束之外,输出值还包含一个约束修饰符:
输出修饰符 描述 
+可以读取和写入操作数 
=只能写入操作数 
%如果有必要操作数可以和下一个操作数切换 
&在内联函数完成之前,可以删除和重新使用操作数

 

 

 


如: 
__asm__("assembly code": "=a"(result):"d"(data1),"c"(data2));
把c变量data1存放在edx寄存器中,把c变量data2存放到ecx寄存器中,内联汇编的结果将存放在eax寄存器中,然后传送给变量 
result


在扩展的asm语句块中如果要使用寄存器必须使用两个百分号符号


不一定总要在内联汇编代码中指定输出值,一些汇编指令假定输入值包含输出值,如movs指令


其他扩展内联汇编知识: 
1,使用占位符
输入值存放在内联汇编段中声明的特定寄存器中,并且在汇编指令中专门使用这些寄存器.虽然这种方式能够很好的处理只有几个输入值的情
况,但对于需要很多输入值的情况,这中方式显的有点繁琐.为了帮助解决这个问题,扩展asm格式提供了占位符,可以在内联汇编代码中使
用它引用输入和输出值.


占位符是前面加上百分号的数字,按照内联汇编中列出的每个输入和输出值在列表中的位置,每个值被赋予从0开始的地方.然后就可以在汇
编代码中引用占位符来表示值。


如果内联汇编代码中的输入和输出值共享程序中相同的c变量,则可以指定使用占位符作为约束值,如: 
__asm__("imull %1, %0" 
: "=r"(data2) 
: "r"(data1), "0"(data2));
如输入输出值中共享相同的变量data2, 而在输入变量中则可以使用标记 
0作为输入参数的约束


2,替换占位符
如果处理很多输入和输出值,数字型的占位符很快就会变的很混乱,为了使条理清晰 
,GNU汇编器(从版本3.1开始)允许声明替换的名称作为
占位符.替换的名称在声明输入值和输出值的段中定义,格式如下: 
%[name]"constraint"(variable)
定义的值name成为内联汇编代码中变量的新的占位符号标识,如下面的例子: 
__asm__("imull %[value1], %[value2]" 
: [value2] "=r"(data2) 
: [value1] "r"(data1), "0"(data2));


3,改动寄存器列表
编译器假设输入值和输出值使用的寄存器会被改动,并且相应的作出处理。程序员不需要在改动的寄存器列表中包含这些值,如果这样做了,就
会产生错误消息.注意改动的寄存器列表中的寄存器使用完整的寄存器名称,而不像输入和输出寄存器定义的那样仅仅是单一字母。 在寄存器
名称前面使用百分号符号是可选的。


改动寄存器列表的正确使用方法是,如果内联汇编代码使用了没有被初始化地声明为输入或者输出值的其他任何寄存器 ,则要通知编译器。编
译器必须知道这些寄存器,以避免使用他们。如: 
int main(void) { 
int data1 = 10; 
int result = 20;


__asm__("movl %1, %%eax\n\t" 
"addl %%eax, %0" 
: "=r"(result) 
: "r"(data1), "0"(result) 
: "%eax"); 
printf("The result is %d\n", result); 
return 0; 
}


4,使用内存位置
虽然在内联汇编代码中使用寄存器比较快,但是也可以直接使用c变量的内存位置。约束 
m用于引用输入值和输出值中的内存位置。记住,对
于要求使用寄存器的汇编指令,仍然必须使用寄存器,所以不得不定义保存数据的中间寄存器。如: 
int main(void) {

 

 

 

 

int dividentd = 20; 
int divisor = 5; 
int result;


__asm__("divb %2\n\t" 
"movl %%eax, %0" 
: "=m"(result) 
: "a"(dividend), "m"(divisor)); 
printf("The result is %d\n", result); 
return 0; 
}


5,处理跳转
内联汇编语言代码也可以包含定义其中位置的标签。 可以实现一般的汇编条件分支和无条件分支, 如:


int main(void) { 
int a = 10; 
int b = 20; 
int result;


__asm__("cmp %1, %2\n\t" 
"jge greater\n\t" 
"movl %1, %0\n\t" 
"jmp end\n" 
"greater:\n\t" 
"movl %2, %0\n" 
"end:" 
:"=r"(result) 
:"r"(a), "r"(b)); 
printf("The larger value is %d\n", result); 
return 0; 
}


在内联汇编代码中使用标签时有两个限制。第一个限制是只能跳转到相同的asm段内的标签,不能从-个asm段跳转到另一个asm段中的
标签。第二个限制更加复杂一点。 以上程序使用标签 
greater和end。 但是 ,这样有个潜在的问题,查看汇编后的代码清单, 可以发现内联
汇编标签也被编码到了最终汇编后的代码中。 这意味着如果在c代码中还有另一个asm段,就不能再次使用相同的标签,否则会因为标签重
复使用而导致错误消息。还有如果试图整合使用c关键字(比如函数名称或者全局变量)的标签也会导致错误。

########################################################################### 
##################### 
#十二,优化你的代码 
########################################################################### 
##################### 
GNU编译器提供-O选项供程序优化使用: 
-O提供基础级别的优化 
-O2提供更加高级的代码优化 
-O3提供最高级的代码优化
不同的优化级别使用的优化技术也可以单独的应用于代码。 可以使用-f命令行选项引用每个单独的优化技术。


1, 编译器优化级别 
1
在优化的第一个级别执行基础代码的优化。 这个级别试图执行9种单独的优化功能: 
-fdefer-pop:这种优化技术与汇编语言代码在函数完成时如何进行操作有关。 一般情况下,函数的输入值被保存在堆栈种并且被函数访问。
函数返回时,输入值还在堆栈种。 一般情况下,函数返回之后,输入值被立即弹出堆栈。这样做会使堆栈种的内容有些杂乱。

-fmerge-constans:使用这种优化技术,编译器试图合并相同的常量.这一特性有时候会导致很长的编译时间,因为编译器必须分析 
c或者 
c++程序中用到的每个常量,并且相互比较他们.

-fthread-jumps:使用这种优化技术与编译器如果处理汇编代码中的条件和非条件分支有关。 在某些情况下,一条跳转指令可能转移到另一
条分支语句。 通过一连串跳转,编译器确定多个跳转之间的最终目标并且把第一个跳转重新定向到最终目标。

-floop-optimize:通过优化如何生成汇编语言中的循环, 编译器可以在很大程序上提高应用程序的性能。 通常 ,程序由很多大型且复杂的循

 

 

 

 

环构成。 通过删除在循环内没有改变值的变量赋值操作,可以减少循环内执行指令的数量,在很大程度上提高性能。 此外优化那些确定何时离
开循环的条件分支, 以便减少分支的影响。

-fif-conversion: if-then语句应该是应用程序中仅次于循环的最消耗时间的部分。简单的if-then语句可能在最终的汇编语言代码中产生众多
的条件分支。 通过减少或者删除条件分支,以及使用条件传送 设置标志和使用运算技巧来替换他们,编译器可以减少 
if-then语句中花费的时
间量。

-fif-conversion2:这种技术结合更加高级的数学特性, 减少实现 
if-then语句所需的条件分支。

-fdelayed-branch:这种技术试图根据指令周期时间重新安排指令。 它还试图把尽可能多的指令移动到条件分支前,以便最充分的利用处理
器的治理缓存。

-fguess-branch-probability:就像其名称所暗示的,这种技术试图确定条件分支最可能的结果,并且相应的移动指令,这和延迟分支技术类
似。因为在编译时预测代码的安排,所以使用这一选项两次编译相同的c或者c++代码很可能会产生不同的汇编语言代码, 这取决于编译时
编译器认为会使用那些分支。因为这个原因,很多程序员不喜欢采用这个特性,并且专门地使用-fno-guess-branch-probability选项关闭这
个特性

-fcprop-registers:因为在函数中把寄存器分配给变量,所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并
且删除不必要的寄存器复制操作。

2,编译器优化级别 
2
结合了第一个级别的所有优化技术,再加上一下一些优化: 
-fforce-mem:这种优化再任何指令使用变量前,强制把存放再内存位置中的所有变量都复制到寄存器中。 对于只涉及单一指令的变量,这样
也许不会有很大的优化效果.但是对于再很多指令(必须数学操作)中都涉及到的变量来说,这会时很显著的优化,因为和访问内存中的值相比 ,
处理器访问寄存器中的值要快的多。

-foptimize-sibling-calls:这种技术处理相关的和/或者递归的函数调用。 通常,递归的函数调用可以被展开为一系列一般的指令, 而不是
使用分支。 这样处理器的指令缓存能够加载展开的指令并且处理他们,和指令保持为需要分支操作的单独函数调用相比,这样更快。

-fstrength-reduce:这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器的变量,比如使用变量,然后使用循环
计数器变量执行数学操作的for-next循环。

-fgcse: 这种技术对生成的所有汇编语言代码执行全局通用表达式消除历程。 这些优化操作试图分析生成的汇编语言代码并且结合通用片段,
消除冗余的代码段。如果代码使用计算性的goto, gcc指令推荐使用-fno-gcse选项。

-fcse-follow-jumps:这种特别的通用子表达式消除技术扫描跳转指令,查找程序中通过任何其他途径都不会到达的目标代码。这种情况最常
见的例子就式if-then-else语句的else部分。

-frerun-cse-after-loop:这种技术在对任何循环已经进行过优化之后重新运行通用子表达式消除例程。这样确保在展开循环代码之后更进一
步地优化还编代码。

-fdelete-null-pointer-checks:这种优化技术扫描生成的汇编语言代码,查找检查空指针的代码。 编译器假设间接引用空指针将停止程序。
如果在间接引用之后检查指针, 它就不可能为空。

-fextensive-optimizations:这种技术执行从编译时的角度来说代价高昂的各种优化技术,但是它可能对运行时的性能产生负面影响。

-fregmove:编译器试图重新分配 
mov指令中使用的寄存器,并且将其作为其他指令操作数,以便最大化捆绑的寄存器的数量。

-fschedule-insns:编译器将试图重新安排指令,以便消除等待数据的处理器。 对于在进行浮点运算时有延迟的处理器来说, 这使处理器在
等待浮点结果时可以加载其他指令。

-fsched-interblock:这种技术使编译器能够跨越指令块调度指令。 这可以非常灵活地移动指令以便等待期间完成的工作最大化。

-fcaller-saves:这个选项指示编译器对函数调用保存和恢复寄存器,使函数能够访问寄存器值,而且不必保存和恢复他们。 如果调用多个函
数,这样能够节省时间,因为只进行一次寄存器的保存和恢复操作,而不是在每个函数调用中都进行。

-fpeephole2:这个选项允许进行任何计算机特定的观察孔优化。

-freorder-blocks:这种优化技术允许重新安排指令块以便改进分支操作和代码局部性。

-fstrict-aliasing:这种技术强制实行高级语言的严格变量规则。 对于c和c++程序来说,它确保不在数据类型之间共享变量.例如,整数变
量不和单精度浮点变量使用相同的内存位置。

 

 

 


-funit-at-a-time:这种优化技术指示编译器在运行优化例程之前读取整个汇编语言代码。 这使编译器可以重新安排不消耗大量时间的代码以
便优化指令缓存。 但是,这会在编译时花费相当多的内存,对于小型计算机可能是一个问题。

-falign-functions:这个选项用于使函数对准内存中特定边界的开始位置。 大多数处理器按照页面读取内存,并且确保全部函数代码位于单
一内存页面内,就不需要叫化代码所需的页面。

-fcrossjumping:这是对跨越跳转的转换代码处理, 以便组合分散在程序各处的相同代码。 这样可以减少代码的长度, 但是也许不会对程
序性能有直接影响。

3,编译器优化级别 
3
它整合了第一和第二级别中的左右优化技巧,还包括一下优化: 
-finline-functions:这种优化技术不为函数创建单独的汇编语言代码, 而是把函数代码包含在调度程序的代码中。 对于多次被调用的函数
来说,为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利,但是通过最充分的利用指令缓存代码,而不是在每次函数调用时进行
分支操作,可以提高性能。

-fweb:构建用于保存变量的伪寄存器网络。伪寄存器包含数据,就像他们是寄存器一样,但是可以使用各种其他优化技术进行优化,比如cse
和loop优化技术。

-fgcse-after-reload:这中技术在完全重新加载生成的且优化后的汇编语言代码之后执行第二次gcse优化,帮助消除不同优化方式创建的
任何冗余段。

二、Hello World!

真不知道打破这个传统会带来什么样的后果,但既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串 
"Hello World!",那我们也以这种方式来开始介绍 
Linux下的汇编语言程序设计。


在 Linux操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用 Linux内核提供的
系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如 libc这样的函数库,也不
需要使用 ELF解释器,因而代码尺寸小且执行速度快。 
Linux是一个运行在保护模式下的 32位操作系统,采用 flat memory模式,目前最常用到的是 ELF格式的二进制代码。
一个 ELF格式的可执行程序通常划分为如下几个部分:.text、.data和 .bss,其中 .text是只读的代码区,.data是可读
可写的数据区,而 .bss则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要
你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF可执行程序至少应该有一个 .text部分。 下
面给出我们的第一个汇编程序,用的是 AT&T汇编语言格式:
例1. AT&T格式

#hello.s

.data # 数据段声明 
msg : .string "Hello, world!\n" # 要输出的字符串 
len = . -msg # 字串长度 
.text # 代码段声明


.global _start # 指定入口函数

_start: # 在屏幕上显示一个字符串 
movl $len, %edx # 参数三:字符串长度 
movl $msg, %ecx # 参数二:要显示的字符串 
movl $1, %ebx # 参数一:文件描述符(stdout) 
movl $4, %eax # 系统调用号(sys_write) 
int $0x80 # 调用内核功能

 

 

 

 

# 退出程序 
movl $0,%ebx # 参数一:退出代码 
movl $1,%eax # 系统调用号(sys_exit) 
int $0x80 # 调用内核功能

初次接触到 AT&T格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在 Linux平台上你同样可以使用 
Intel格式来编写汇编程序:
例2. Intel格式

; hello.asm 
section .data ;数据段声明 
msg db "Hello, world!", 0xA ;要输出的字符串 
lenequ$ -msg ;字串长度

section .text ;代码段声明 
global _start ;指定入口函数 
_start: ;在屏幕上显示一个字符串

mov edx, len ;参数三:字符串长度 
mov ecx, msg ;参数二:要显示的字符串 
movebx,1 ;参数一:文件描述符(stdout) 
moveax,4 ;系统调用号(sys_write) 
int 0x80 ;调用内核功能


;退出程序 
movebx,0 ;参数一:退出代码 
moveax,1 ;系统调用号(sys_exit) 
int 0x80 ;调用内核功能

上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用 Linux内核提供的 sys_write来显示一个字符串,然后
再调用 sys_exit退出程序。在 Linux内核源文件 
include/asm-i386/unistd.h中,可以找到所有系统调用的定义

 

lmsw   如 lmsw ax

将源操作数加载到机器状态字,即寄存器 CR0 的位 0 到 15。源操作数可以是 16 位通用寄存器或内存位置。只有源操作数的低 4 位(也就是 PE、MP、EM 及 TS 标志)会加载到 CR0。CR0 的 PG、CD、NW、AM、WP、NE 及 ET 标志不受影响。操作数大小属性不影响此指令。

如果源操作数的 PE 标志(位 0)设置为 1,则指令导致处理器切换到保护模式。处于保护模式时,不能使用 LMSW 指令清除 PE 标志,并强制切换回实地址模式。

提供的 LMSW 指令用于操作系统软件;不应该在应用程序中使用它。在保护或虚 8086 模式中,此指令只能在 CPL 0 级别执行。

提供此指令是为了同 Intel(R) 286 处理器保持兼容;对于要在奔腾(R) 4、P6 系列、奔腾、Intel486 及 Intel386 处理器上运行的程序与过程,应该使用 MOV(控制寄存器)指令加载整个 CR0 寄存器。MOV CR0 指令可用于设置或清除 CR0 中的 PE 标志,从而让过程或程序在保护与实地址模式之间转换。

此指令是序列化指令。

 

 jmpi是段间跳转指令,用于x86实模式下,

      如:BOOTSEG = 0x0c70

            jmpi    4, #BOOTSEG

      假如当前段CS==00h,那么执行此指令后将跳转到段CS==0x0c70,当然段cs的值也变为0x0c70,接下来将执行指令0x0c70:0004处的指令。

.fill 表达式

     形式:.fill repeat , size , value

posted @ 2013-10-23 11:02  三六五  阅读(1252)  评论(0编辑  收藏  举报