注:以下内容为学习笔记,多数是从书本、资料中得来,只为加深印象,及日后参考。然而本人表达能力较差,写的不好。因非翻译、非转载,只好选原创,但多数乃摘抄,实为惭愧。但若能帮助一二访客,幸甚!
一.定义数据
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 AT&T\n"
这个代码片段会留出11字节的内存,把定义的字符串顺序放到内存字节中,且把标签output赋值为第一个字节地址。
可以在一行中定义多个值:
sizes:
.long 100, 101, 102, 103
上面的代码把4个长整数(4字节)按顺序放到内存中,可按相对地址访问每个值,如访问内存sizes+8访问102.
2.定义静态符号
.equ命令用于把常量值设置为可以在为本段中使用的符号:
.equ LINUX_SYS_CALL_WRITE 4
引用静态数据元素:
movl $LINUX_SYS_CALL_WRITE, %eax
3.BSS段
在bss段中定义数据无需声明特定的数据类型,只要声明为所需目的保留的原始部分即可。
上句感觉汉语版翻译的不太明白,英文原句:
Instead of declaring specific data types, you just declare raw segment of memory that are reserved fo whatever purpose you need them for.
GNU 汇编器使用两个命令声明缓冲区:
--------------------------------------------------------------------
命令 描述
-------------------------------------------------------------
.comm 声明未初始化的数据的通用内存区域
.lcomm 声明未初始化的数据的本地通用内存区域
--------------------------------------------------------------------
本地通用内存区域是为不会从本地汇编代码之外进行访问的数据保留的。
命令格式:
.comm symbol, length
例如:
.section .bss
.lcomm buffer, 100
二.数据传送
1.MOV 指令
格式:
mov* src, dst
GNU汇编器要求MOV指定传送的数据的长度mov*中的*:
---------------------------------------------------
l 32位
w 16位
b 8位
---------------------------------------------------
注:未包含字符串传递指令MOVS等
src、dst的值可以是内存地址、存储在内存中的数据值、指令语句中定义的数据值、寄存器。
2.把立即数传送到寄存器、内存
立即数前面要加$符号,寄存器前面要加%符号
1)立即数传送到寄存器:
movl $0x80, %eax
2) 立即数传送到指定内存地址
若数据段中有:
.section .data
height:
.int 10
则可用下面的方法将立即数传递到height表示的内存地址:
mvol $0x100, height
注:标签表示的是内存地址
3.在寄存器之间传送数据
8个通用寄存器:EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP,这些寄存器中的值可以传送给可用的任何其他类型的寄存器。
专用寄存器(控制、调试、段寄存器)的内容只能传送给通用寄存器,或从通用寄存器接收内容。
movl %eax, %ebx
movw %ax, %bx
4.在内存和寄存器之间传送数据
1)内存 -> 寄存器
movl value, %eax
该指令将value指定的内存开始的32位数据传送给eax。若数据长度小于4自己,则需使用movw或movb。
测试代码:
# 测试从内存传送数据到寄存器
.section .data
value:
.byte 0x01, 0x02, 0x03, 0x04
.section .text
.global _start
_start:
nop
xorl %ecx, %ecx
movl value, %ecx
xorl %edx, %edx
movw value, %dx
xorl %ebx, %ebx
movb value, %bl
movl $1, %eax
movl $0, %ebx
int $0x80
makefile:all: mem_to_reg
mem_to_reg: mem_to_reg.o
ld -o $@ $<
mem_to_reg.o: mem_to_reg.s
as -gstabs -o $@ $<
调试及结果:liury@liury-laptop:~/program/asm/move_data$ make
as -gstabs -o mem_to_reg.o mem_to_reg.s
ld -o mem_to_reg mem_to_reg.o
liury@liury-laptop:~/program/asm/move_data$ ls
makefile mem_to_reg mem_to_reg.o mem_to_reg.s
liury@liury-laptop:~/program/asm/move_data$ gdb ./mem_to_reg
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/liury/program/asm/move_data/mem_to_reg...done.
(gdb) l
1 # 测试从内存传送数据到寄存器
2
3 .section .data
4 value:
5 .byte 0x01, 0x02, 0x03, 0x04
6
7 .section .text
8 .global _start
9
10 _start:
(gdb) b *_start+1
Breakpoint 1 at 0x8048075: file mem_to_reg.s, line 12.
(gdb) r
Starting program: /home/liury/program/asm/move_data/mem_to_reg
Breakpoint 1, _start () at mem_to_reg.s:12
12 xorl %ecx, %ecx
(gdb) n
13 movl value, %ecx
(gdb) print /x $ecx
$1 = 0x0
(gdb) n
15 xorl %edx, %edx
(gdb) print /x $ecx
$2 = 0x4030201
(gdb) n
16 movw value, %dx
(gdb) print /x $edx
$3 = 0x0
(gdb) n
18 xorl %ebx, %ebx
(gdb) print /x $edx
$4 = 0x201
(gdb) n
19 movb value, %bl
(gdb) print /x $ebx
$5 = 0x0
(gdb) n
21 movl $1, %eax
(gdb) print /x $ebx
$6 = 0x1
(gdb) c
Continuing.
Program exited normally.
(gdb) q
2)寄存器 -> 内存
movl %ecx, value
movw %cx, value
movb %cl, value
5.使用变址的内存位置
values:
.int 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
引用数组中的数据时,必须使用变址系统确定要访问的元素。称为变址内存模式(indexed memory mode)。
表达式格式:
base_address(offset_address, index, size)
获取数据的地址:
base_address + offset_address + index * size
若其中任何值为0,可省略,但需要','作为占位符。其中offset_address 和index 值必须是寄存器,size可以是数字。
例如访问第三个元素(12):
movl $2, %edi
movl values(, %edi, 4)
代码示例及makefile:
# using indexed memory locations .section .data output: .asciz "values[%d] is %d\n" values: .int 10, 11, 12, 13, 14, 15 len: .int 6 .section .text .global _start _start: nop movl $0, %edi loop: movl values(, %edi, 4), %eax pushl %eax pushl %edi pushl $output call printf addl $12, %esp inc %edi cmpl len, %edi jne loop exit: movl $0, %ebx movl $1, %eax int $0x80
all: indexed_mem_mode indexed_mem_mode: indexed_mem_mode.o ld -dynamic-linker /lib/ld-linux.so.2 -lc -o $@ $< indexed_mem_mode.o: indexed_mem_mode.s as -o $@ $< clean: del indexed_mem_mode.o, indexed_mem_mode运行:
result:
liury@liury-laptop:~/program/asm/move_data/indexed_mem_mode$ make
as -o indexed_mem_mode.o indexed_mem_mode.s
ld -dynamic-linker /lib/ld-linux.so.2 -lc -o indexed_mem_mode indexed_mem_mode.o
liury@liury-laptop:~/program/asm/move_data/indexed_mem_mode$ ls
indexed_mem_mode indexed_mem_mode.o indexed_mem_mode.s makefile
liury@liury-laptop:~/program/asm/move_data/indexed_mem_mode$ ./indexed_mem_mode
values[0] is 10
values[1] is 11
values[2] is 12
values[3] is 13
values[4] is 14
values[5] is 15
6.使用寄存器间接寻址
寄存器也可以保存内存地址,此时它被称为指针(pointer)。使用指针访问存储在内存位置中的数据称为间接寻址(indirect addressing)
当使用标签引用内存位置中包含的数据值时,可以通过在指令中的标签前面加上'$'符号获得数据值的内存位置的地址。
下面语句将values引用的内存地址传送给edi:
movl $values, %edi
movl %ebx, (%edi)
而第二句中若edi不加()则将ebx的值传送给edi,加()则将ebx值传送给edi中存放的内存地址的位置。
GNU 汇编器不允许把值与寄存器相加,必须把值放在括号之外:
movl %edx, 4(%edi)
movl %edx, -1(%edi)
三.条件传送指令
旧式的汇编程序中,有类似与下面的代码
dec %ecx
jz continue
movl $0, %ecx
continue:
Intel已经改进来IA-32平台以便提供附加的功能,条件传送指令就是改进之一,这些指令从奔腾处理器P6系列(奔腾Pro及更新幸好)开始使用。
它们格式如下
cmovx src, dst
x是一个或两个字母的代码,表示触发传送操作的条件,条件取决于EFLAGS寄存器的当前值。EFLAGS:
CF 进位标志,OF 溢出标志,PF 奇偶校验标志,SF 符号标志,ZF 零标志
无符号条件传送指令:
CMOVA/CMOVBE 大于/小于等于
CMOVE/CMOVZ 等于/零
等等
带符号条件传送指令:
CMOVGE/CMOVNL 大于等于/不小于
CMOVO 溢出
等等
例如:查找数组中的最大值
# An example of the cmov instructions .section .data output: .asciz "The largest valueis %d\n" values: .int 102, 232, 56, 198, 333, 4, 134, 222 len: .int 8 .section .text .global _start _start: nop movl values, %ebx movl $1, %edi loop: movl values(, %edi, 4), %eax cmp %ebx, %eax cmova %eax, %ebx inc %edi cmp len, %edi jne loop pushl %ebx pushl $output call printf addl $8, %esp pushl $0 call exit
all: cmovx cmovx: cmovx.o ld -dynamic-linker /lib/ld-linux.so.2 -lc -o $@ $< cmovx.o: cmovx.s as -o $@ $< clean: del cmovx, cmovx.o运行结果:
liury@liury-laptop:~/program/asm/move_data/cmovx$ make
as -o cmovx.o cmovx.s
ld -dynamic-linker /lib/ld-linux.so.2 -lc -o cmovx cmovx.o
liury@liury-laptop:~/program/asm/move_data/cmovx$ ls
cmovx cmovx.o cmovx.s makefile
liury@liury-laptop:~/program/asm/move_data/cmovx$ ./cmovx
The largest valueis 333
四.交换数据
交换eax,ebx值:
movl %eax, %ecx
movl %ebx, %eax
movl %ecx, %ebx
数据交换指令:
--------------------------------------------------------------------
指令 描述
----------------------------------------------------------------
XCHG 两寄存器或寄存器与内存间交换值
BSWAP 反转一个32位寄存器中的字节顺序
XADD 交换两个值并且把总和存储在目标操作数中
CMPXCHG 把一个值和一个外部值比较,且交换它和另一个值
CMPXCHG8B 比较两个64位值并交换他们
--------------------------------------------------------------------
1)XCHG
xchg operand1, operand2
当一个操作数是内存位置时处理器的LOCK信号被自动标明,防止在交换过程中任何其他处理器访问这个内存位置(LOCK处理非常耗时,且可能对程序性能有不良影响)
2)BSWAP
_start:
movl $0x12345678, %ebx
bswap %ebx
最后ebx中值为0x78563412
3)XADD
xadd src, dst
交换两个寄存器或者内存位置的值,把两个值相加,放到目标位置。
src必须是寄存器,dst可以是寄存器或内存地址,且dst包含相加后的结果。
4)CMPXCHG
比较目标操作数和EAX,AX,AL的值。若相等,把源操作数值加载到目标操作数中。不等,把目标操作数加载到EAX,AX或AL中。
示例:冒泡排序
C伪码:
for (out = len-1; out > 0; out--) { for (in = 0; in < out; in++) { if (array[in] > array[in+1]) swap(array[in], array[in+1]); } }汇编代码:
# An example of the xchg instruction .section .data values: .int 105, 222, 54, 315, 145, 221, 32, 167, 111, 4 len: .int 10 output: .asciz "%d\n" output_before: .asciz "before sort:\n" output_after: .asciz "after sorted:\n" .section .text .global _start _start: nop movl $0, %edi pushl $output_before call printf addl $4, %esp print_datas: movl values(, %edi, 4), %eax pushl %eax pushl $output call printf addl $8, %esp inc %edi cmpl len, %edi jne print_datas bubble_sort: movl $values, %esi movl len, %ecx dec %ecx movl len, %ebx dec %ebx loop: movl (%esi), %eax cmp %eax, 4(%esi) jge skip xchg %eax, 4(%esi) movl %eax, (%esi) skip: add $4, %esi dec %ebx jnz loop dec %ecx jz end movl $values, %esi movl %ecx, %ebx jmp loop end: pushl $output_after call printf addl $4, %esp movl $0, %edi print_datas_after: movl values(, %edi, 4), %eax pushl %eax pushl $output call printf addl $8, %esp inc %edi cmpl len, %edi jne print_datas_after movl $1, %eax movl $0, %ebx int $0x80运行结果:
before sort:
105
222
54
315
145
221
32
167
111
4
after sorted:
4
32
54
105
111
145
167
221
222
315
五.堆栈
五.堆栈
pushx src
src可以是16/32位寄存器值,16/32位内存值,16位段寄存器值,8/16/32位立即数
popx dst
dst可以是16/32位寄存器值,16位段寄存器值,16/32位内存值
压入和弹出所有寄存器
----------------------------------------------------
指令 描述
-------------------------------------------------
PUSHA/POPA 压入/弹出所有16位通用寄存器
PUSHAH/POPAD 压入/弹出所有32位通用寄存器
PUSHF/POPF 压入/弹出EFLAGS寄存器的低16位
PUSHFD/POPFD 压入/弹出EFLAGS寄存器的全部32位
----------------------------------------------------