@dodola
2017-01-22T10:55:17.000000Z
字数 7795
阅读 2335
Linux
用于解决处理器与内存之前的数据存储效率问题存在的。
IA-32平台寄存器核心组有下面几种.
用于临时的存储数据。
eax
用于操作数和结果数据的累加器ebx
指向数据内存段中数据的指针ecx
字符串和循环操作的计数器edx
I/O指针edi
用于字符串的目标的数据指针esi
用于字符串操作的源的数据指针esp
堆栈指针ebp
堆栈数据指针32位eax
、ebx、ecx、edx 可以通过16位和8位名称引用
用于引用内存位置。ia-32处理器允许3种不同的访问系统内存方法:
可用的段寄存器
* cs 代码段
包含指向内存中代码段的指针 代码段是内存中存储指令码的位置 程序不能显示的加载改变cs寄存器
* ss 堆栈段 包含传递给程序中的函数和过程的数据值
* ds 数据段
* es 附加段指针
* fs 附加段指针
* gs 附加段指针
每个段寄存器都是16位,包含指向内存特定段的起始位置的指针。
EIP寄存器有时称为程序计数器,用于跟踪要执行的下一条指令码。应用程序不能直接修改指令指针。
CR0
控制操作模式和处理器状态的系统标志CR1
没有被使用CR2
内存页面错误信息CR3
内存页面目录信息CR4
支持处理器特性和说明处理器特性能力的标志处理器标志用于确定操作是否成功。
标志被分为3组:
* 状态标志
* 控制标志
* 系统标志
cf
进位标志 pf
奇偶校验标志 zf
零标志 如果操作结果为零,零标志就被设置为1,经常用作确定数学操作结果是否为零af
辅助进位标志 如果寄存器的第三位发生进位或者借位操作,该标志设置为1sf
符号标志 表明结果是正值还是负值of
溢出标志 Heading
当前只有一个控制标志DF标志,用于控制处理器处理字符串方式。
当DF设置为1,字符串指令自动递减内存地址到下一个字节,0为递增
略
汇编语言程序由定义好的段构成,三个最常用的段:
* 数据段 声明带有初始值的数据元素,程序中的变量
* bss段 声明使用零值初始化的数据元素 常用做程序缓冲区
* 文本段 每个程序必须有文本段。用于声明指令码
.section
命令语句声明段,有一个参数用于声明段的类型。
bss段总是在文本段之后
_start
标签用于表明程序的起始点。
.globl
命令声明程序入口点。
程序模板:
.section .data
#<带有初始值的数据>
.section .bss
#<使用0初始化的数据>
.section .text
.globl _start
_start:
#<Code>
例子,用于读取cpu信息
# as hello_asm.s -o hello_asm.o
# ld hello_asm.o -e _start -o hello_asm
# ./hello_asm
.section __DATA,__data #for linux .section .data
output:
.ascii "The processor Vender ID is 'xxxxxxxxxxxx'\n"
.section __TEXT,__text #for linux .section .text
.globl _start
_start:
movl $0 ,%eax
cpuid
movl $output, %edi
movl %ebx,28(%edi)
movl %edx,32(%edi)
movl %ecx,36(%edi)
movl $4,%eax
movl $1,%ebx
movl $output,%ecx
movl $42 ,%edx
int $0x80
movl $1,%eax
movl $0,%ebx
int $0x80
.section .data
output:
.asciz "The processor vender id is '%s'\n"
.section .bss
.lcomm buffer ,12
.section .text
.globl _start
_start:
movl $0,%eax
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $buffer
pushl $output
call printf
addl $8 ,%esp
pushl $0
call exit
需注意的是,在64位机上编译上面程序需要强制32位编译,用gcc -m32 、as --32选项
使用as编译
as --32 -o printcpu.o printcpu.s
ld -dynamic-linker /lib/ld-linux.so.2 -o cpu2 -lc printcpu.o
./cpu2
使用gcc编译
使用gcc汇编程序时需要注意gcc连接器查找的是main
标签作为入口,所以需要将程序的.globl
命令修改成下面这样
.section .text
.globl main
main:
gcc -m32 -o cpu printcpu.s
./cpu
程序的数据段是最常见的定义数据元素的位置。
使用.data
指令来声明数据段。在这个段中声明的任何数据元素都保留在内存中并可以被汇编语言程序中的指令读取和写入。
有另一种类型的数据段.rodata
,该数据段中定义的数据元素只能只读
数据段中定义数据需要两个语句:一个标签、一个命令。
标签相当于c语言的变量名。
命令相当于c语言的类型声明关键字
命令 | 数据类型 |
---|---|
.ascii | 文本字符串 |
.asciz | 以空字符结尾的文本字符串 |
.byte | 字节 |
.double | 双精度浮点数 |
.float | 单精度浮点数 |
.int | 32位整数 |
.long | 32位整数(和int相同) |
.octa | 16字节整数 |
.quad | 8字节整数 |
.short | 16位整数 |
.single | 单精度浮点数(和.float相同) |
举个栗子:
output:
.ascii "the string example\n"
pi:
.float 3.1415926
;可以在一行里定义多个值
sizes:
.long 100,150,200,250,300
;把长整型(4字节)值100放在sizes引用开始的内存位置中,类似数组,每个长整型占用4个字节 32位,可以通过访问内存位置sizes+8访问200这个值
.section .data
msg:
.ascii "This is a test message"
factors:
.double 37.5,45.33,12.30
height:
.int 54
length:
.int 62,35,47
下面的图描述了数据在内存中存放的情况
73页图
.equ
命令用于定义常量值
.equ factor ,3
.equ LINUX_SYS_CALL, 0x80
;引用静态数据元素
movl $LINUX_SYS_CALL, %eax
bss段数据元素和数据段中的定义有些不同,无需声明特定的数据类型。
命令 | 描述 |
---|---|
.comm | 声明未初始化的数据的通用内存区域 |
.lcomm | 声明未初始化的数据的本地通用内存区域 |
本地通用内存区域是为不会从本地汇编代码之外进行访问的数据保留的。
命令格式: .comm symbol, length
例子:待定,未明白
.section .bss
注:在csapp里对此指令的讲述更好
MOV
指令用作通用数据传送指令。用于在内存和寄存器之间传送数据。
MOV
基本格式:
movx source, dest
根据数据元素的长度不同movx有以下变种
把32位eax寄存器值传送给32位ebx寄存器:movl %eax, %ebx
对于16位寄存器:movw %ax,%bx
对于8位寄存器:movb %al,%bl
mov的操作指令可用于以下类型的传送:
立即数的每个值前面需要加上$
符号
例子:
movl $0, %eax ;move the value 0 to he eax register
movl $0x80, %ebx ;move the hexadecimal value 80 to the ebx register
movl $100, height ;move the value 100 to the height memory location
8个通用寄存器eax,ebx,ecx,edx,edi,esi,ebp,esp是用于保存数据的最常用寄存器,这些寄存器内容可以传送给可用的任何其他类型寄存器,和通用寄存器不同,专用寄存器只能传送给通用寄存器,或者接收通用寄存器传送过来的内容。
movl %eax, %ecx # move 32-bits
movw %ax, %cx #move 16-bits
1 把数值从内存传送到寄存器
movl value, %eax
指令把value标签指定的内存位置的数值传送给eax,传送value标签引用的内存位置开始的4字节数据,movb
用于1字节,movw
用于2字节
.section .data
value:
.int 1
.section .text
.globl _start
_start:
nop
movl value, %ecx
movl $1, %eax
movl $0, %ebx
int $0x80
2 把数值从寄存器传送给内存
movl %ecx, value
指令把ecx寄存器里的4字节数据传送给value标签指定内存位置
3 使用变址的内存位置
values:
.int 10,15,20,25,30,35,40,45,50,55,60
变址内存模式,内存位置由以下因素确定
表达式的格式:base_address (offset_address,index,size)
获取的数值位于:base_address+offset_address_index * size
例子,引用values数组中20:
movl $2, %edi
movl values(, %edi,4), %eax
例子,输出values数组中的数值
.section .data
output:
.asciz "The value is %d\n"
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.section .text
.globl _start
_start:
nop
movl $0,%edi
loop:
movl values(,%edi,4),%eax
pushl %eax
pushl $output
call printf
addl $8,%esp
inc %edi
cmpl $11,%edi
jne loop
movl $0, %ebx
movl $1, %eax
int $0x80
#mac下
.section __DATA,__data
output:
.asciz "The value is %d\n"
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.section __TEXT,__text
.globl _main
_main:
nop
movl $0,%edi
loop:
movl values(,%edi,4),%eax
pushl %eax
pushl $output
call _printf
addl $8,%esp
inc %edi
cmpl $11,%edi
jne loop
movl $0, %ebx
movl $1, %eax
int $0x80
4 使用寄存器间接寻址
使用指针访问存储在内存位置中的数据称为间接寻址(indirect addressing)
当使用标签引用内存位置中包含的数值时,可以通过在指令中的标签加上$
符号获取数值的内存位置的地址
movl $values, %edi
用于把values标签引用的内存地址传送给edi寄存器
在平坦内存模型中,所有内存地址都是使用32位数字表示的
movl %ebx, (%edi)
如果edi寄存器外边没有括号,指令只是把ebx寄存器内容加载到edi寄存器中,如果edi寄存器外边有括号,那么指令就把ebx寄存器的内容传送给edi寄存器中包含的内存位置
movl %edx, 4(%edi)
这条指令把edx值放在edi寄存器指向位置之后的4字节内存位置,也可以放到相反方向:
movl %edx, -4(%edi)
此处有疑问书中写的是-4(&edi)
演示间接寻址模式:
;➜ as -gstabs -o movtest.o mvotest4.s
;➜ ld movtest.o -e _start -o movtest
.section .data
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.section .text
.globl _start
_start:
nop
movl values,%eax
movl $values, %edi
movl $100, 4(%edi)
movl $1, %edi
movl values(,%edi,4), %ebx
movl $1,%eax
int $0x80
可以通过echo命令查看程序退出码
➜ asm ./movtest
➜ asm echo $?
100
条件传送指令功能是mov指令发生在特定的条件下。
旧式的汇编语言中会看到下面代码:
dec %ecx ;ecx递增1 如果ecx寄存器没有溢出 进位标志没有设置位1
jz continue ;jnc指令跳转continue
movl $0, %ecx ;如果溢出了 ecx设置为0
continue:
条件传送指令可以避免处理jmp指令,提高速度
指令格式如下: cmovx source, destination
x是一个或者两个字母的代码,表示将触发传送操作的条件,条件取决于eflags寄存器当前值:
EFLAGS | 名称 | 描述 |
---|---|---|
CF | 进位(carry)标志 | 进位或者借位 |
OF | 溢出overflow标志 | 整数值过大或者过小 |
PF | 奇偶校验parity标志 | 寄存器包含数学操作造成的错误数据 |
SF | 符号标志 | 指出结果为正还是负 |
ZF | 零zero标志 |
下面是选择整数中最大一个的程序
.section .data
output:
.asciz "The largest value is %d\n"
values:
.int 105,222,23,56,52,445,25,53,117,5
.section .text
.globl main
main:
nop
movl values, %ebx
movl $1, %edi
loop:
movl values(,%edi,4), %eax
cmp %ebx,%eax
cmova %eax,%ebx
inc %edi
cmp $10, %edi
jne loop
pushl %ebx
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
如果需要交换两个寄存器的数据,指令码就像下面这样:
movl %eax, %ecx
movl %ebx, %eax
movl %ecx, %ebx
需要增加一个临时保存数据的寄存器来交换数据,数据交换指令就解决了这个问题
指令 | 描述 |
---|---|
XCHG | 在两个寄存器之间或者寄存器和内存位置之间交换 |
BSWAP | 反转一个32位寄存器中的字节顺序 |
XADD | 交换两个值并且把总和存储在目标操作数中 |
CMPXCHG | 把一个值和一个外部值进行比较,并且交换它和另一个值 |
CMPXCHG8B | 比较两个64位值并且交换他们 |
在两个通用寄存器之间或者寄存器和内存位置之间交换数据值。
格式如下:
xchg operand1, operand2
operand1或者operand2可以是通用寄存器,也可以是内存位置(但是二者不能都是内存位置),可以对任何通用的8,16,32位寄存器使用这个命令,但是两个操作数的长度必须相同
当一个操作数是内存位置时,处理器LOCK信号被自动标明,防止在交换过程中任何其他处理器访问这个内存位置。
XCHG指令可能对程序性能有不良影响
第0-7位和第24-31位进行交换,第8-15位和第6-23位交换
例子:
.section .text
.globl _start
_start:
nop
movl $0x12345678, %ebx
bswap %ebx
movl $1, %eax
int $0x80
运行结果:
➜ asm gcc -o swaptest -gstabs swaptest.s
➜ asm ./swaptest
➜ asm gdb -q swaptest
Reading symbols from swaptest...done.
(gdb) break *main+1
Breakpoint 1 at 0x80483bc: file swaptest.s, line 5.
(gdb) run
Starting program: /home/dodola/asm/swaptest
Breakpoint 1, main () at swaptest.s:5
5 movl $0x12345678, %ebx
(gdb) step
6 bswap %ebx
(gdb) print/x $ebx
$1 = 0x12345678
(gdb) step
7 movl $1, %eax
(gdb) print/x $ebx
$2 = 0x78563412
(gdb)
XADD指令用于交换两个寄存器或者内存位置和寄存器的值,把两个值相加,然后把结果存储在目标位置(寄存器或者内存位置),指令格式:
xadd source, destination
其中source*必须*是寄存器,destination可以是寄存器也可以是内存位置,并且destination包含相加的结果。
//待
//待
冒泡排序
.section .data
values:
.int 105,235,61,315,134,221,53,145,117,5
.section .text
.globl main
main:
movl $values,%esi
movl $9,%ecx
movl $9,%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:
movl $1, %eax
movl $0,%ebx
int $0x80