[关闭]
@dodola 2017-01-22T10:55:17.000000Z 字数 7795 阅读 2335

Linux Assembly

Linux


寄存器

用于解决处理器与内存之前的数据存储效率问题存在的。
IA-32平台寄存器核心组有下面几种.

  1. 通用寄存器 8个32位寄存器 用于存储正在访问的数据
  2. 段寄存器 6个16位寄存器 用于处理内存访问
  3. 指令指针 单一的32位寄存器 指向要执行的下一条指令码
  4. 浮点数据 8个80位寄存器 用于浮点数学数据
  5. 控制 5个32位寄存器 用于确定处理器的操作模式
  6. 调试 8个32位寄存器 用于在调试处理器时包含信息

通用寄存器

用于临时的存储数据。

32位eax、ebx、ecx、edx 可以通过16位和8位名称引用

image_1b721dk581qlkv66d6et814l39.png-25.8kB

段寄存器

用于引用内存位置。ia-32处理器允许3种不同的访问系统内存方法:

可用的段寄存器
* cs 代码段
包含指向内存中代码段的指针 代码段是内存中存储指令码的位置 程序不能显示的加载改变cs寄存器
* ss 堆栈段 包含传递给程序中的函数和过程的数据值
* ds 数据段
* es 附加段指针
* fs 附加段指针
* gs 附加段指针

每个段寄存器都是16位,包含指向内存特定段的起始位置的指针。

指令指针寄存器

EIP寄存器有时称为程序计数器,用于跟踪要执行的下一条指令码。应用程序不能直接修改指令指针。

控制寄存器

标志

处理器标志用于确定操作是否成功。
标志被分为3组:
* 状态标志
* 控制标志
* 系统标志

状态标志

  1. cf 进位标志
  2. pf 奇偶校验标志
    用于表明数学操作中结果寄存器是否包含错误数据,如果结果中为1的位的总数是偶数,奇偶校验标志设置为1,否则为0
  3. zf 零标志 如果操作结果为零,零标志就被设置为1,经常用作确定数学操作结果是否为零
  4. af 辅助进位标志 如果寄存器的第三位发生进位或者借位操作,该标志设置为1
  5. sf 符号标志 表明结果是正值还是负值
  6. of 溢出标志

控制标志

Heading

当前只有一个控制标志DF标志,用于控制处理器处理字符串方式。
当DF设置为1,字符串指令自动递减内存地址到下一个字节,0为递增

系统标志

汇编程序

程序的组成

汇编语言程序由定义好的段构成,三个最常用的段:
* 数据段 声明带有初始值的数据元素,程序中的变量
* bss段 声明使用零值初始化的数据元素 常用做程序缓冲区
* 文本段 每个程序必须有文本段。用于声明指令码

image_1b721ekr01jek1lrj113n1tml1e34m.png-20.4kB

定义段

.section命令语句声明段,有一个参数用于声明段的类型。
bss段总是在文本段之后

定义起始点

_start标签用于表明程序的起始点。
.globl命令声明程序入口点。
程序模板:

  1. .section .data
  2. #<带有初始值的数据>
  3. .section .bss
  4. #<使用0初始化的数据>
  5. .section .text
  6. .globl _start
  7. _start:
  8. #<Code>

例子,用于读取cpu信息

  1. # as hello_asm.s -o hello_asm.o
  2. # ld hello_asm.o -e _start -o hello_asm
  3. # ./hello_asm
  4. .section __DATA,__data #for linux .section .data
  5. output:
  6. .ascii "The processor Vender ID is 'xxxxxxxxxxxx'\n"
  7. .section __TEXT,__text #for linux .section .text
  8. .globl _start
  9. _start:
  10. movl $0 ,%eax
  11. cpuid
  12. movl $output, %edi
  13. movl %ebx,28(%edi)
  14. movl %edx,32(%edi)
  15. movl %ecx,36(%edi)
  16. movl $4,%eax
  17. movl $1,%ebx
  18. movl $output,%ecx
  19. movl $42 ,%edx
  20. int $0x80
  21. movl $1,%eax
  22. movl $0,%ebx
  23. int $0x80

汇编中是用C库函数

以printf函数为例

  1. .section .data
  2. output:
  3. .asciz "The processor vender id is '%s'\n"
  4. .section .bss
  5. .lcomm buffer ,12
  6. .section .text
  7. .globl _start
  8. _start:
  9. movl $0,%eax
  10. movl %ebx, (%edi)
  11. movl %edx, 4(%edi)
  12. movl %ecx, 8(%edi)
  13. pushl $buffer
  14. pushl $output
  15. call printf
  16. addl $8 ,%esp
  17. pushl $0
  18. call exit

需注意的是,在64位机上编译上面程序需要强制32位编译,用gcc -m32 、as --32选项

使用as编译

  1. as --32 -o printcpu.o printcpu.s
  2. ld -dynamic-linker /lib/ld-linux.so.2 -o cpu2 -lc printcpu.o
  3. ./cpu2

使用gcc编译
使用gcc汇编程序时需要注意gcc连接器查找的是main标签作为入口,所以需要将程序的.globl命令修改成下面这样

  1. .section .text
  2. .globl main
  3. main:
  1. gcc -m32 -o cpu printcpu.s
  2. ./cpu

第五章 传送数据

5.1 定义数据元素

5.1.1 数据段

程序的数据段是最常见的定义数据元素的位置。
使用.data指令来声明数据段。在这个段中声明的任何数据元素都保留在内存中并可以被汇编语言程序中的指令读取和写入。

有另一种类型的数据段.rodata,该数据段中定义的数据元素只能只读

数据段中定义数据需要两个语句:一个标签、一个命令。

标签相当于c语言的变量名。

命令相当于c语言的类型声明关键字

命令 数据类型
.ascii 文本字符串
.asciz 以空字符结尾的文本字符串
.byte 字节
.double 双精度浮点数
.float 单精度浮点数
.int 32位整数
.long 32位整数(和int相同)
.octa 16字节整数
.quad 8字节整数
.short 16位整数
.single 单精度浮点数(和.float相同)

举个栗子:

  1. output:
  2. .ascii "the string example\n"
  3. pi:
  4. .float 3.1415926
  5. ;可以在一行里定义多个值
  6. sizes:
  7. .long 100,150,200,250,300
  8. ;把长整型(4字节)值100放在sizes引用开始的内存位置中,类似数组,每个长整型占用4个字节 32位,可以通过访问内存位置sizes+8访问200这个值
  1. .section .data
  2. msg:
  3. .ascii "This is a test message"
  4. factors:
  5. .double 37.5,45.33,12.30
  6. height:
  7. .int 54
  8. length:
  9. .int 62,35,47

下面的图描述了数据在内存中存放的情况

73页图

5.1.2 定义静态符号(常量值)

.equ命令用于定义常量值

  1. .equ factor ,3
  2. .equ LINUX_SYS_CALL, 0x80
  3. ;引用静态数据元素
  4. movl $LINUX_SYS_CALL, %eax

5.1.3 bss段

bss段数据元素和数据段中的定义有些不同,无需声明特定的数据类型。

命令 描述
.comm 声明未初始化的数据的通用内存区域
.lcomm 声明未初始化的数据的本地通用内存区域

本地通用内存区域是为不会从本地汇编代码之外进行访问的数据保留的。
命令格式: .comm symbol, length
例子:待定,未明白

  1. .section .bss

5.2 传递数据元素

注:在csapp里对此指令的讲述更好
MOV指令用作通用数据传送指令。用于在内存和寄存器之间传送数据。

5.2.1 mov指令

MOV 基本格式:
movx source, dest

根据数据元素的长度不同movx有以下变种

把32位eax寄存器值传送给32位ebx寄存器:movl %eax, %ebx
对于16位寄存器:movw %ax,%bx
对于8位寄存器:movb %al,%bl

mov的操作指令可用于以下类型的传送:

5.2.2 立即数传送给寄存器和内存

立即数的每个值前面需要加上$符号
例子:

  1. movl $0, %eax ;move the value 0 to he eax register
  2. movl $0x80, %ebx ;move the hexadecimal value 80 to the ebx register
  3. movl $100, height ;move the value 100 to the height memory location

5.2.3 在寄存器之间传递数据

8个通用寄存器eax,ebx,ecx,edx,edi,esi,ebp,esp是用于保存数据的最常用寄存器,这些寄存器内容可以传送给可用的任何其他类型寄存器,和通用寄存器不同,专用寄存器只能传送给通用寄存器,或者接收通用寄存器传送过来的内容。

  1. movl %eax, %ecx # move 32-bits
  2. movw %ax, %cx #move 16-bits

5.2.4 在内存和寄存器之间传送数据

1 把数值从内存传送到寄存器

movl value, %eax

指令把value标签指定的内存位置的数值传送给eax,传送value标签引用的内存位置开始的4字节数据,movb用于1字节,movw用于2字节

  1. .section .data
  2. value:
  3. .int 1
  4. .section .text
  5. .globl _start
  6. _start:
  7. nop
  8. movl value, %ecx
  9. movl $1, %eax
  10. movl $0, %ebx
  11. int $0x80

2 把数值从寄存器传送给内存

movl %ecx, value

指令把ecx寄存器里的4字节数据传送给value标签指定内存位置

3 使用变址的内存位置

  1. values:
  2. .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:

  1. movl $2, %edi
  2. movl values(, %edi,4), %eax

例子,输出values数组中的数值

  1. .section .data
  2. output:
  3. .asciz "The value is %d\n"
  4. values:
  5. .int 10,15,20,25,30,35,40,45,50,55,60
  6. .section .text
  7. .globl _start
  8. _start:
  9. nop
  10. movl $0,%edi
  11. loop:
  12. movl values(,%edi,4),%eax
  13. pushl %eax
  14. pushl $output
  15. call printf
  16. addl $8,%esp
  17. inc %edi
  18. cmpl $11,%edi
  19. jne loop
  20. movl $0, %ebx
  21. movl $1, %eax
  22. int $0x80
  1. #mac下
  2. .section __DATA,__data
  3. output:
  4. .asciz "The value is %d\n"
  5. values:
  6. .int 10,15,20,25,30,35,40,45,50,55,60
  7. .section __TEXT,__text
  8. .globl _main
  9. _main:
  10. nop
  11. movl $0,%edi
  12. loop:
  13. movl values(,%edi,4),%eax
  14. pushl %eax
  15. pushl $output
  16. call _printf
  17. addl $8,%esp
  18. inc %edi
  19. cmpl $11,%edi
  20. jne loop
  21. movl $0, %ebx
  22. movl $1, %eax
  23. int $0x80

image_1b721g1k943c1aoa119toth19p313.png-8.4kB

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)

演示间接寻址模式:

  1. ;➜ as -gstabs -o movtest.o mvotest4.s
  2. ;➜ ld movtest.o -e _start -o movtest
  3. .section .data
  4. values:
  5. .int 10,15,20,25,30,35,40,45,50,55,60
  6. .section .text
  7. .globl _start
  8. _start:
  9. nop
  10. movl values,%eax
  11. movl $values, %edi
  12. movl $100, 4(%edi)
  13. movl $1, %edi
  14. movl values(,%edi,4), %ebx
  15. movl $1,%eax
  16. int $0x80

可以通过echo命令查看程序退出码

  1. asm ./movtest
  2. asm echo $?
  3. 100

5.3 条件传送指令

条件传送指令功能是mov指令发生在特定的条件下。

旧式的汇编语言中会看到下面代码:

  1. dec %ecx ;ecx递增1 如果ecx寄存器没有溢出 进位标志没有设置位1
  2. jz continue ;jnc指令跳转continue
  3. movl $0, %ecx ;如果溢出了 ecx设置为0
  4. continue:

条件传送指令可以避免处理jmp指令,提高速度

5.3.1 cmov 指令

指令格式如下: cmovx source, destination

x是一个或者两个字母的代码,表示将触发传送操作的条件,条件取决于eflags寄存器当前值:

EFLAGS 名称 描述
CF 进位(carry)标志 进位或者借位
OF 溢出overflow标志 整数值过大或者过小
PF 奇偶校验parity标志 寄存器包含数学操作造成的错误数据
SF 符号标志 指出结果为正还是负
ZF 零zero标志

image_1b721h0u31br01n17so11r7u1bpk1g.png-72kB

image_1b721hd6lqjh1jcr7if7nhef41t.png-49.9kB

5.3.2 使用comv指令

下面是选择整数中最大一个的程序

  1. .section .data
  2. output:
  3. .asciz "The largest value is %d\n"
  4. values:
  5. .int 105,222,23,56,52,445,25,53,117,5
  6. .section .text
  7. .globl main
  8. main:
  9. nop
  10. movl values, %ebx
  11. movl $1, %edi
  12. loop:
  13. movl values(,%edi,4), %eax
  14. cmp %ebx,%eax
  15. cmova %eax,%ebx
  16. inc %edi
  17. cmp $10, %edi
  18. jne loop
  19. pushl %ebx
  20. pushl $output
  21. call printf
  22. addl $8, %esp
  23. pushl $0
  24. call exit

5.4交换数据

image_1b721i15r1t201hbn1r611vde72k2a.png-98.3kB

如果需要交换两个寄存器的数据,指令码就像下面这样:

  1. movl %eax, %ecx
  2. movl %ebx, %eax
  3. movl %ecx, %ebx

需要增加一个临时保存数据的寄存器来交换数据,数据交换指令就解决了这个问题

5.4.1

指令 描述
XCHG 在两个寄存器之间或者寄存器和内存位置之间交换
BSWAP 反转一个32位寄存器中的字节顺序
XADD 交换两个值并且把总和存储在目标操作数中
CMPXCHG 把一个值和一个外部值进行比较,并且交换它和另一个值
CMPXCHG8B 比较两个64位值并且交换他们

1. XCHG

在两个通用寄存器之间或者寄存器和内存位置之间交换数据值。
格式如下:
xchg operand1, operand2
operand1或者operand2可以是通用寄存器,也可以是内存位置(但是二者不能都是内存位置),可以对任何通用的8,16,32位寄存器使用这个命令,但是两个操作数的长度必须相同
当一个操作数是内存位置时,处理器LOCK信号被自动标明,防止在交换过程中任何其他处理器访问这个内存位置。
XCHG指令可能对程序性能有不良影响

2. BSWAP

image_1b721ii931fvm1iga1s91qjjtva2n.png-29.6kB

第0-7位和第24-31位进行交换,第8-15位和第6-23位交换

例子:

  1. .section .text
  2. .globl _start
  3. _start:
  4. nop
  5. movl $0x12345678, %ebx
  6. bswap %ebx
  7. movl $1, %eax
  8. int $0x80

运行结果:

  1. asm gcc -o swaptest -gstabs swaptest.s
  2. asm ./swaptest
  3. asm gdb -q swaptest
  4. Reading symbols from swaptest...done.
  5. (gdb) break *main+1
  6. Breakpoint 1 at 0x80483bc: file swaptest.s, line 5.
  7. (gdb) run
  8. Starting program: /home/dodola/asm/swaptest
  9. Breakpoint 1, main () at swaptest.s:5
  10. 5 movl $0x12345678, %ebx
  11. (gdb) step
  12. 6 bswap %ebx
  13. (gdb) print/x $ebx
  14. $1 = 0x12345678
  15. (gdb) step
  16. 7 movl $1, %eax
  17. (gdb) print/x $ebx
  18. $2 = 0x78563412
  19. (gdb)

3. XADD

XADD指令用于交换两个寄存器或者内存位置和寄存器的值,把两个值相加,然后把结果存储在目标位置(寄存器或者内存位置),指令格式:

xadd source, destination

其中source*必须*是寄存器,destination可以是寄存器也可以是内存位置,并且destination包含相加的结果。

4. CMPXCHG

//待

5. CMPXCHG8B

//待

5.4.2 使用数据交换指令

冒泡排序

  1. .section .data
  2. values:
  3. .int 105,235,61,315,134,221,53,145,117,5
  4. .section .text
  5. .globl main
  6. main:
  7. movl $values,%esi
  8. movl $9,%ecx
  9. movl $9,%ebx
  10. loop:
  11. movl (%esi), %eax
  12. cmp %eax, 4(%esi)
  13. jge skip
  14. xchg %eax, 4(%esi)
  15. movl %eax,(%esi)
  16. skip:
  17. add $4,%esi
  18. dec %ebx
  19. jnz loop
  20. dec %ecx
  21. jz end
  22. movl $values, %esi
  23. movl %ecx, %ebx
  24. jmp loop
  25. end:
  26. movl $1, %eax
  27. movl $0,%ebx
  28. int $0x80

#

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注