AT&T x86_32 汇编_004_数据传递
Posted neooelric
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AT&T x86_32 汇编_004_数据传递相关的知识,希望对你有一定的参考价值。
这一讲主要讲
MOV
指令的各种用法. 如何把数据在寄存器, 内存中互相传递.
1. MOV指令格式
MOV
指令的基本格式为: MOV source, destination
总的来说, MOV
的作用, 其实就是把"数据", 从一个地方, 挪到另外一个地方, 这里, 数据分为三类:
- 常量数据, 即是汇编界术语, 所谓的"立即数". 将这种数据移至某个寄存器, 或某块内存区域中去.
- 寄存器中的数据
- 内存中的某块区域中的数据
所以, MOV
指令涵盖了如下诸多场景:
- 立即数 -> 寄存器 : 给寄存器赋值
- 立即数 -> 内存地址 : 给指定内存区域赋值
- 寄存器 -> 寄存器 : 将寄存器A中的值, 复制粘贴至寄存器B中
- 寄存器 -> 内存地址 : 将寄存器中的值, 复制粘贴至指定内存区域中去
- 内存地址 -> 寄存器 : 将指定内存区域中的值, 复制粘贴至寄存器中去
并且, 由于寄存器是有固定规格的. 一般来讲, MOV
并不能一次性移动大量数据(比如500字节), 因为这是汇编, 指令是cpu和寄存器玩耍的手段, mov
指令操作的都是8/16/32/64
位数据. 且由于我们目前接触的是x86_32
汇编, 所以寄存器的最大宽度只有32
位.
寄存器普遍为32位, 但为了向前兼容, cpu是支持把一个寄存器拆成两半用的, 甚至拆成四半用. 所以, MOV
命令会有以下三个变种:
movl
, 该指令一次性操作32位数据movw
, 该指令一次性操作16位数据movb
, 该指令一次性操作8位数据
示例如下:
movl %eax, %ebx # 将32位寄存器eax中的值, 复制粘贴至32位寄存器ebx中去
movw %ax, %bx # 将16位寄存器ax中的值, 复制粘贴至16位寄存器bx中去
movb %al, %bl # 将8位寄存器al中的值, 复制粘贴至8位突破口bl中去
2. MOV指令的限制
使用MOV系列指令是有一定的特殊规则的, 并不是任意寄存器与内存地址, 只要位宽匹配就能调用mov指令互相传递数据. 符合规则的mov指令仅支持以下的源与目的地:
ps : 由于目前还未介绍寄存器的分类, 所以这一段可以先不看, 因为你目前并不了解什么是段寄存器, 而什么又是通用寄存器, 什么是调试寄存器等
源 | 目的地 |
---|---|
立即数 | 通用寄存器, 内存 |
通用寄存器 | 通用寄存器, 段寄存器, 控制寄存器, 调试寄存器, 内存 |
段寄存器 | 通用寄存器, 内存 |
控制寄存器 | 通用寄存器 |
调试寄存器 | 通用寄存器 |
内存 | 通用寄存器, 段寄存器 |
下面我们会分情况, 逐个讨论上面13个场景(是的, 很繁杂)
下面的表格, 我们也列出了两种简单场景下, mov指令的写法, 注意这需要熟练掌握:
应用场景 | 示例 |
---|---|
立即数 -> 通用寄存器, 内存 | movl $0, %eax # 立即数要以 $ 标示开头 movl $0x80, %ebx # 寄存器要以 % 标示开头 movl $100, height # height应当是一个符号(变量/标签), 代表的是一个内存地址 |
寄存器 -> 寄存器 | movl %eax, %ecx # 寄存器要以 % 开头 movw %ax, %cx # 要注意两个寄存器的宽度要匹配, 否则编译将不能通过 |
而关于如何在寄存器及内存之间传递数据, 这有一点复杂, 我们慢慢来看
3. 在内存与寄存器之间传递数据
这里令问题复杂化的主要是: 如何表达内存.
3.1 把数据从内存, 复制粘贴至寄存器中
movl value, %eax
上面的语句中, 如果value是一个定义在数据段或bss段中的标签, 那么就是一个把数据从内存中复制粘贴至寄存器的语句.
需要格外注意的是, movl
复制的是32位数据, 因此, 它必须从value标签(符号/变量)引用的内存位置处, 开始复制4字节数据. 如果value
是一个byte
数据, 程序的编译或许不会出错, 但程序运行的过程中, 实际上是从value
指代的那一字节内存的起始处, 共计复制了4字节数据, 粘贴至eax
寄存器中! 一定要避免这种悲剧的发生.
3.2 把数据从寄存器, 传递给指定内存位置
与上相同:
movl %ecx, value
还是需要额外注意数据长度问题
3.3 使用变址的内存位置
前面在介绍数据段时, 我们介绍过, 可以通过一个标签(符号), 来作为一个数组, 如下:
values:
.int 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
这时, 如果我们要向数组中的某个元素位置处粘贴数据, 或把某具元素的数据复制粘贴至寄存器中. 表示内存位置的表达式由四部分数据组成:
- 基地址 : 一个基本的地址, 一般情况下为标签表示的内存地址(即是变量名)
- 基地址偏移量 : 在基地址上的一个偏移量
- 索引值 : 类似于数组索引值, 具体的偏移量还取决于数据长度, 即是数组中单个元素的长度
- 数据长度 : 当使用索引值时, 必须要有的数据. 它决定了数组中单个元素的长度是多少
即一个完整的内存地址, 最多可以由四部分, 按这样的语法写成: base_address (offset, index, size)
而我们上面两个简单的例子, 是忽略了offset
, index
, size
三部分后, 仅使用base_address
的场景. 所以显得很easy
比如下面这个例子:
movl $2, %edi
movl values(, %edi, 4), %eax
其中
- 基地址 : 是为标签
values
所指代的内存区域的起始地址. 即是上面包含11个int数值的数组的起始地址 - 基地址偏移量 : 为0, 所以忽略, 但是即便忽略了, 后面的逗号也不能省略
- 索引值 : 是
%edi
, 即是以寄存器edi中的值作为索引值. 该值是为2 - 数据长度 : 4, 指代表数组中单个元素的长度为4字节
所以, 最终, 指代的内存地址 == values内存块首地址 + 0(基地址偏移量) + (4 * edi寄存器中的值) == base + 4*2 = 数组中值20的起始地址. 所以, 上面的意思是把数组中的第三个元素(索引为2), 即20, 复制粘贴至寄存器eax中
还有一种写法, 我们在之前的示例小程序的代码中已经见识过了, 如下所示:
movl %edx, 32(%edi)
在这里, 基地址是常量值32
, 偏移量为寄存器edi中的值, 而索引值与数据长度均被忽略. 并且, 更过分的是, 基地址还可以是负数, 如下:
movl %edx, -4(%edi)
这种写法中, 虽然名义上, 符合语法逻辑, 但实际上, 所谓的其地址, 32也好, -4了好, 更像是偏移量, 而语法上的偏移量, 倒更像是所谓的基地址
需要特别注意的是:
offset
与index
的值只能由寄存器给出, 不能以立即数给出!size
的值可以是立即数, 且通常情况下, 都是立即数base_address
并未做限制, 可以使用立即数, 也可以使用寄存器给出值
3.4 汇编中的指针: 使用寄存器保存内存地址, 而不是数据
寄存器除了保存内存地址之外, 还可以来保存"内存地址". 刺激不刺激, 是不是很像C/C++中的指针? 当寄存器中保存的是内存地址时, 在汇编中, 也会称该寄存器是一个指针. 而借助这样的寄存器, 来更加灵活的定位内存的手段, 称为间接寻址.
下面, 是一个把内存地址保存在寄存器中的语句:
movl $values, %edi
可以看到, 与把数据从内存->寄存器中相比, 唯一的区域, 就是values
标签之前多了一个$
. 为了帮助你理解与记忆, 请谨记, 并理解以下:
- 汇编中的标签, 出现在数据段, 就是变量名, 出现在代码段, 就是函数名
- 所有的标签, 本质上都是一个数值. 对于变量, 其值就是变量在内存中的起始地址. 对于函数, 其值就是在进程内存空间中的函数指针值.
- 在汇编语言中, 直接使用变量标签, 不附加任何添加剂, 实际上等同于将变量的值. 即内存中的数据.
- 在汇编语言中, 在变量标签之前加
$
, 其实取的是标签的值, 符号的值. 也就是那块内存的起始地址, 而不再表示内存中的数据. - 在汇编语言中, 标签, 其实就是个立即数
4. 两个示例程序
这里列出两个示例程序, 主要是介绍这一讲提到的"内存地址表示法", 以及"指针". 示例程序中涉及到了当前还未接触到的"指令跳转", 但基本也能猜出来代码逻辑, 两个示例程序分别如下:
4.1 利用灵活的内存地址表示, 遍历数组中的数据
.section .data
values:
.int 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
output:
.asciz "The value is %d
"
.section .text
.globl _start
_start:
nop
movl $0, %edi
loop:
movl values(, %edi, 4), %eax # %eax = values[i]
pushl %eax # printf第二个参数入参
pushl $output # printf第一个参数入参
call printf # 调用printf
addl $8, %esp # 退栈
inc %edi # %edi++
cmpl $11, %edi # 比较
jne loop # 条件跳转
movl $0, %ebx # sys_exit系统调用, ebx中放错误码, eax值为1
movl $1, %eax
int $0x80
4.2 利用指针, 通过间接寻址的方式, 遍历输出数组中的数据
.section .data
values:
.int 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
output:
.asciz "values[%d] == %d
"
.section .text
.globl _start
_start:
nop
movl $values, %edi # edi寄存器现在变成了一个指向数组的指针
movl $0, %ebx # ebx用来做遍历数组的索引
loop:
movl (%edi, %ebx, 4), %eax # %eax = values[index]
pushl %eax # 调用printf
pushl %ebx
pushl $output
call printf
addl $12, %esp
inc %ebx # %ebx++
cmpl $11, %ebx
jne loop
movl $0, %ebx
movl $1, %eax
int $0x80
以上是关于AT&T x86_32 汇编_004_数据传递的主要内容,如果未能解决你的问题,请参考以下文章