码迷,mamicode.com
首页 > 其他好文 > 详细

AT&T x86_32 汇编_004_数据传递

时间:2018-09-23 22:21:33      阅读:164      评论:0      收藏:0      [点我收藏+]

标签:bubuko   来讲   突破口   负数   作用   利用   条件跳转   gif   地方   

这一讲主要讲MOV指令的各种用法. 如何把数据在寄存器, 内存中互相传递.

1. MOV指令格式

MOV指令的基本格式为: MOV source, destination

总的来说, MOV的作用, 其实就是把"数据", 从一个地方, 挪到另外一个地方, 这里, 数据分为三类:

  1. 常量数据, 即是汇编界术语, 所谓的"立即数". 将这种数据移至某个寄存器, 或某块内存区域中去.
  2. 寄存器中的数据
  3. 内存中的某块区域中的数据

所以, MOV指令涵盖了如下诸多场景:

  1. 立即数 -> 寄存器 : 给寄存器赋值
  2. 立即数 -> 内存地址 : 给指定内存区域赋值
  3. 寄存器 -> 寄存器 : 将寄存器A中的值, 复制粘贴至寄存器B中
  4. 寄存器 -> 内存地址 : 将寄存器中的值, 复制粘贴至指定内存区域中去
  5. 内存地址 -> 寄存器 : 将指定内存区域中的值, 复制粘贴至寄存器中去

并且, 由于寄存器是有固定规格的. 一般来讲, MOV并不能一次性移动大量数据(比如500字节), 因为这是汇编, 指令是cpu和寄存器玩耍的手段, mov指令操作的都是8/16/32/64位数据. 且由于我们目前接触的是x86_32汇编, 所以寄存器的最大宽度只有32位.

寄存器普遍为32位, 但为了向前兼容, cpu是支持把一个寄存器拆成两半用的, 甚至拆成四半用. 所以, MOV命令会有以下三个变种:

  1. movl, 该指令一次性操作32位数据
  2. movw, 该指令一次性操作16位数据
  3. 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

这时, 如果我们要向数组中的某个元素位置处粘贴数据, 或把某具元素的数据复制粘贴至寄存器中. 表示内存位置的表达式由四部分数据组成:

  1. 基地址 : 一个基本的地址, 一般情况下为标签表示的内存地址(即是变量名)
  2. 基地址偏移量 : 在基地址上的一个偏移量
  3. 索引值 : 类似于数组索引值, 具体的偏移量还取决于数据长度, 即是数组中单个元素的长度
  4. 数据长度 : 当使用索引值时, 必须要有的数据. 它决定了数组中单个元素的长度是多少

即一个完整的内存地址, 最多可以由四部分, 按这样的语法写成: base_address (offset, index, size)

而我们上面两个简单的例子, 是忽略了offset, index, size三部分后, 仅使用base_address的场景. 所以显得很easy

比如下面这个例子:

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

其中

  1. 基地址 : 是为标签values所指代的内存区域的起始地址. 即是上面包含11个int数值的数组的起始地址
  2. 基地址偏移量 : 为0, 所以忽略, 但是即便忽略了, 后面的逗号也不能省略
  3. 索引值 : 是%edi, 即是以寄存器edi中的值作为索引值. 该值是为2
  4. 数据长度 : 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了好, 更像是偏移量, 而语法上的偏移量, 倒更像是所谓的基地址

需要特别注意的是:

  1. offsetindex的值只能由寄存器给出, 不能以立即数给出!
  2. size的值可以是立即数, 且通常情况下, 都是立即数
  3. base_address并未做限制, 可以使用立即数, 也可以使用寄存器给出值

3.4 汇编中的指针: 使用寄存器保存内存地址, 而不是数据

寄存器除了保存内存地址之外, 还可以来保存"内存地址". 刺激不刺激, 是不是很像C/C++中的指针? 当寄存器中保存的是内存地址时, 在汇编中, 也会称该寄存器是一个指针. 而借助这样的寄存器, 来更加灵活的定位内存的手段, 称为间接寻址.

下面, 是一个把内存地址保存在寄存器中的语句:

movl $values, %edi

可以看到, 与把数据从内存->寄存器中相比, 唯一的区域, 就是values标签之前多了一个$. 为了帮助你理解与记忆, 请谨记, 并理解以下:

  1. 汇编中的标签, 出现在数据段, 就是变量名, 出现在代码段, 就是函数名
  2. 所有的标签, 本质上都是一个数值. 对于变量, 其值就是变量在内存中的起始地址. 对于函数, 其值就是在进程内存空间中的函数指针值.
  3. 在汇编语言中, 直接使用变量标签, 不附加任何添加剂, 实际上等同于将变量的值. 即内存中的数据.
  4. 在汇编语言中, 在变量标签之前加$, 其实取的是标签的值, 符号的值. 也就是那块内存的起始地址, 而不再表示内存中的数据.
  5. 在汇编语言中, 标签, 其实就是个立即数

4. 两个示例程序

这里列出两个示例程序, 主要是介绍这一讲提到的"内存地址表示法", 以及"指针". 示例程序中涉及到了当前还未接触到的"指令跳转", 但基本也能猜出来代码逻辑, 两个示例程序分别如下:

4.1 利用灵活的内存地址表示, 遍历数组中的数据

.section .data
values:
    .int 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
output:
    .asciz "The value is %d\n"

.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\n"

.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_数据传递

标签:bubuko   来讲   突破口   负数   作用   利用   条件跳转   gif   地方   

原文地址:https://www.cnblogs.com/neooelric/p/9693654.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!