传送数据
定义数据
-
数据段 使用.data 命令声明数据段, .rodata 则用来声明只读数据段. 在数据段中定义数据元素需要: 一个标签和一个命令. 标签类似于C中的变量名, 方便我们访问数据.
命令 数据类型 .ascii 文本字符串 .asciz 以空字符结尾的文本字符串 .byte 字节值 .short 16位整数 .int 32位整数 .long 32位整数(和int相同) .octa 16字节整数 .quad 8字节整数 .double 双精度浮点数 .float 单精度浮点数 .single 单精度浮点数(和.float相同) 声明命令之后必须定义一个(或者多个)默认值, 比如:
.section .data msg: .ascii "This is a test message" factors: .double 37.45, 45.33, 12.30 height: .int 54
保留的内存数量取决于定义数据的类型以及声明这个类型的项目的数量.
-
静态符号
虽然数据段主要用于定义变量数据, 但是也可以在这里声明静态数据符号..equ
命令用于把常量值设置为可以在文本段中使用的符号..equ factor, 3 .equ LINUX_SYS_CALL, 0x80
为了引用静态数据元素, 必须在标签名称前面使用
$
符号:movl $LINUX_SYS_CALL, %eax
-
bss 段
bss 段中定义数据无须声明特定的数据类型, 常用的数据声明命令如下:命令 数据类型 .comm 声明未初始化的数据的通用内存区域 .lcomm 声明未初始化的数据的本地通用内存区域 .lcomm 使数据不能被本地汇编代码之外访问到, 即不能在 .globl命令中使用它们.
命令格式为
.comm symblol, length
, symblol 是代表内存区域的标签, length为内存区域包含的字节数量. 例如:.section .bss .lcomm buffer, 10000
在 bss 段中声明数据的一个好处是数据不包含在可执行程序中, 而在数据段中定义数据时, 它必须包含在可执行程序中, 因为必须使用特定的值初始化它.
数据传送
-
MOV 指令
MOV 是通用的数据传送指令, 基本格式为mov source, destination
, source 和 destination 的值可以是内存地址
、存储在内存中的数据值
、立即数
、寄存器
.
GNU 为 MOV 指令添加了另一维度, 在其中必须声明要传送的数据元素的长度:
movx
其中 x 可以是下面的字符:- q 用于 64 位
- l 用于 32 位的
- w 用于 16 位
- b 用于 8 位
例如:
movl %eax, %ebx // 32 位寄存器操作 movl %ax, %bx // 16 位寄存器操作 movl %al, %bl // 8 位寄存器操作
MOV 的源和目的操作数的位置是有限制的, 其组合如下:
- 立即数 --> 通用寄存器
- 立即数 --> 内存位置
- 通用寄存器 --> 另一个通用寄存器
- 通用寄存器 --> 段寄存器
- 段寄存器 --> 通用寄存器
- 通用寄存器 --> 控制寄存器
- 控制寄存器 --> 通用寄存器
- 通用寄存器 --> 调试寄存器
- 调试寄存器 --> 通用寄存器
- 内存位置 --> 通用寄存器
- 通用寄存器 --> 内存位置
- 内存位置 --> 段寄存器
- 段寄存器 --> 内存位置
-
立即数 --> 寄存器或内存
立即数是在指令语句中直接指定的, 并且在运行时不能改动movl $0, %eax movl $0x80, %ebx movl $100, height // 把立即数 100 传送到 height 变量所在的内存地址
-
寄存器之间传送数据
8 个通用寄存器(EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP) 是用于保存数据的最常用寄存器, 这些寄存器的内容可以传送给任何其他类型的寄存器, 而专用寄存器(控制、调试、段寄存器)的内容只能传送给通用寄存器或者从通用寄存器接收内容.movl %eax, %ecx movw %ax, %cx
在长度不同的寄存器之前传送数据比如:
movb %al, %bx
编译器会报告错误, 这条指令试图把 AL 寄存器中的8位传送给BX寄存器中的低8位, 替换的做法是使用 movm 把整个 %ax 寄存器内容传送给 %bx 寄存器
-
内存和寄存器之间传送数据
-
内存 --> 寄存器
movl value, %eax
由于 movl 传送32位的信息, 因此他传送value 标签引用的内存位置开始的4字节数据, 如果数据长度小于4字节, 就必须使用其他MOV指令之一, 比如 movb 用于 1 字节, movw 用于 2 字节.
-
寄存器 --> 内存
movl %ecx, value
这条指令把 ECX 寄存器中存储的 4 字节数据传送给 value 标签指定的内存位置.
-
使用变址的内存地址
引用数组中的数据时, 必须使用变址系统确定你要访问的是哪个值. 表达格式为base_address (offset_address, index, size)
, 获取的数据位于base_address + offset_address + index*size
.
如果其中的任何值为0, 则可以忽略它们(但仍需逗号作为占位符). offset_address 和 index 的值必须是寄存器, 但是 size 的值可以是数字值. 例如values: .int 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 movl $2, %edi movl values(, %edi, 4), %eax // 将获取 values 数组的第三个值 20
实例, 遍历整个数组
.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 main main: movl $0, %edi loop: pushl values(, %edi, 4) pushl $output call printf inc %edi cmpl $11, %edi jne loop push $0 call exit
-
寄存器间接寻址
寄存器除了保存数据之外, 还可以保存内存地址, 当保存内存地址时, 它被称为指针, 使用指针访问存储在内存位置中的数据称为间接寻址. 首先通过$
符号获取标签所引用的内存地址值:movl $values, %edi
把 values 标签引用的内存地址传送给 EDI 寄存器
然后使用间接寻址:
movl %ebx, (%edi)
EDI 寄存器外面加了括号, 表示把 EBX 寄存器中的值传给EDI寄存器中包含的内存位置.
GNU 不允许把值和寄存器相加, 因此若要表示间接寻址的偏移地址, 必须把 值放在括号外面:
movl %edx, 4(%edi) // 把 EDX 寄存器中的值放在 EDI 寄存器指向的位置之后4字节内存位置中 movl %edx, -4(%edi) // EDI指向的地址位置之前4字节的内存位置
-