X86汇编入门-寄存器32位 - 弦外之音

/ 0评 / 2

如果程序运行在 32位 模式下,常用的寄存器(register)有以下8个。

  1. EAX,EAX 是 累加器 (accumulator), 它是很多加法乘法指令的缺省寄存器。
  2. EBX,EBX 是 基地址(base)寄存器, 在内存寻址时存放基地址。
  3. ECX,ECX 是 计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
  4. EDX,EDX 总是被用来放整数除法产生的余数。
  5. EBP,EBP是 基址指针(BASE POINTER), 软件破解领域 经常用到这个 基址
  6. ESP ,ESP 是 堆栈指针(stack point)
  7. ESI/EDI,分别叫做 源/目标索引寄存器(source/destination index),因为在很多字符串操作指令中,DS:ESI 指向源串,而 ES:EDI 指向目标串。这句话我也不明白

一共有 8 个 通用 寄存器,什么是通用? 就是 EAX 寄存器可以干 EBX 寄存器的活。他们其实都是同一种寄存器,CPU 提供的寄存器,只是从使用习惯上 EAX 叫 EAX。乘法指令 能不能用 EBX 寄存器?也是可以的,只是使用习惯上没这么搞。



不熟悉 AT&T 汇编语法的可以先快速看一遍 官方文档 《Using as-The GNU Assembler》

相应的 X86 指令可以查 《英特尔® 64 位和 IA-32 架构开发人员手册》。

  1. 《英特尔® 64 位和 IA-32 架构开发人员手册:卷 1》
  2. 《英特尔® 64 位和 IA-32 架构开发人员手册:卷 2A》

先简单讲解 一下 EAX 寄存器的使用。C代码如下:

int main() {
    int a = 1;
    a = a + 8;
    return a;
}

用汇编代码实现 跟 上面 C代码一样的功能,汇编代码如下:

        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $1, %eax
        addl    $8, %eax
        popl    %ebp
        ret

上面的汇编代码 流程如下:

1,pushl 压栈

2,把 1 这个值 存储在 eax 寄存器,然后用 addl 指令把 eax 寄存器 + 8

3,pop 弹栈,返回。

一个程序返回的时候,eax 里面的值就是这个程序的返回值,linux 下 用 echo $? 可以看到上一条命令的返回值。

现在编译一下上面的 汇编代码,在 64位 的 ubuntu下要 加上 -m32 才能编译成可执行文件。

gcc -m32 -o main32 main-32.s

可以看到 返回值是 9,以上的汇编程序没有问题。


接下来用 gdb 简单调试一下上面这段汇编代码,不熟悉 gdb 的可以先看以下教程。

  1. 《GDB官网手册》

执行以下命令进入 gdb 调试

# 运行 gdb
gdb ./main32
# 显示寄存器窗口
layout regs
# 自动反汇编后面要执行的代码
set disassemble-next-line on
# 设置 main 第一条汇编断点
b *main
# 查看汇编代码
disassemble
# 查看寄存器的值
i registers

GDB 调试技巧:

  1. GDB 调试 C语言 用的是 s(step) 跟 n(next),单步调试汇编是 sini

下面开始一行一行代码讲解。

    .text
    .globl  main
    .type   main, @function

1,.text,Tells as to assemble the following statements onto the end of the text subsection numbered subsection, which is an absolute expression. If subsection is omitted, subsection number zero is used.(官网注释,我也不懂)

2,.globl,应该是告诉 链接器 ld 一些信息。

3,.type.type main, @function 这个是定义一个 main 函数,汇编里面也有函数,推荐阅读 《AT&T 函数定义》

上面的 汇编代码,.file.text.globl 等这些,其实是伪指令(Assembler Directives)。

伪指令 是汇编器 搞出来的东西,不是CPU指令集提供的,你可以理解为 伪指令 是基于 CPU指令集 封装的。

不伪的指令就是 CPU 指令集,pushl %rbp 这种就是真正的指令。

实际上,伪指令 跟 真正的指令,你用汇编的时候,是没有感知的。只是内部实现有区别,所以把这两个东西看成一个东西就行。


main:
.LFB0:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $1, %eax
        addl    $8, %eax
        popl    %ebp
        ret

1,main: 之前用 type 定义了一个 main 函数

2,.LFB0: 是一个 label。把它理解成 C语言的 goto 的标记就行。

3,pushl %ebpebp 寄存器的值 压进去 esp 寄存器,不用写上 esppush 指令默认就是存到 esp 的。压进去之后,esp 就会减 4个字节。

pushl %ebp 没执行之前,ebp 的值是 0x0esp0xffffd34c。执行之后 esp0xffffd348。内存里面的 0xffffd348 ~ 0xffffd34c 4个字节的数据就是 ebp 的值,也就是 0x0。可以用命令 x /20xh 0xffffd348 打印出来,如下图:

注意,esp 只是一个指向内存的指针,esp 不存储实际的数据,实际的数据在它指向的内存地址。

4,movl %esp, %ebp ,把 esp 的值 复制到 ebp。

5,movl $1, %eax,把变量 1 存进去 eax 寄存器

6,addl $8, %eax,把 eax 寄存器的值加上 8

7,popl %ebp,把 esp 指向的4字节内存拷贝到 ebp 寄存器,4字节内容 是之前的 0x0,然后 esp + 4字节。

第 7 条指令 执行完之后,esp ebp 就回到还没执行第一条汇编的时候。

既然都回到了 第一条汇编之前的状态,那 最后的 ret 指令是干什么的呢?

因为我们的汇编程序,是运行在 /lib32/libc.so C语言的运行时库里面的,也就是 libc.so 调用了 CALL 执行我们的汇编程序 main-32,我们的 main-32 相当于一个子函数。

libc.so 在调用 main-32 之前,已经把它当时的指令位置压进去 esp 里面了。也就是 0xffffd34c ~ 0xffffd350 存的是 libc.so 的指令位置,main-32 执行完之后,要跳转到这个位置。这个跳转 就是用 ret 指令执行。

ret 干的活 如下:

1,把 0xffffd34c ~ 0xffffd350的数据拿出来,存到 eip 寄存器, eip 寄存器永远指向下一条要执行的指令。

2,把 esp 加 4个字节,esp 就会变成 0xffffd350

这样做完之后,程序就会跳回去 libc.so 那里了。如下图:

重点:

1,AT&T 的汇编语法,数字前面要加 $,寄存器前面要加 %


再讲一个扩展知识点,如何 查看 汇编代码 最后编译成的 机器指令是什么?

解答:直接 看 eip 寄存器指向的地址,如图:

上图中,下一条要执行的 汇编指令是 movl $1, %eax,此时 eip 指向的地址是 0x565561a0,再下一个指令 addl $8, %eax 的地址是 0x565561a5

所以推算得知 ,movl $1, %eax 翻译成的机器指令 占5个字节。现在只需要打印内存地址 0x565561a0 ~ 0x565561a5 就能看到真正的机器指令。

执行以下 命令:

x /5xb 0x565561a0 

如上图所示,movl $1, %eax 对应的机器指令是 b8 01 00 00 00,b8 是指令,后面的 4字节是值,值是1。


现在应用层的汇编优化,通常都是在 64 位下面做的。 32 位本来就没有64位 快,用汇编也只是处理一些 gcc 编译优化没有顾及到的地方,想让程序更快。

而且现在大部分机器都是 64 位,做汇编优化是需要研发成本的,通常不会 为了一个 32 位的程序去使用汇编。

所以本书,只是提及一下 32 位的汇编一点点知识,后续的汇编代码都是基于 64位的。


相关阅读:

  1. EAX、ECX、EDX、EBX寄存器的作用
  2. 《Using as-The GNU Assembler》
  3. 《英特尔® 64 位和 IA-32 架构开发人员手册:卷 1》
  4. 《英特尔® 64 位和 IA-32 架构开发人员手册:卷 2A》

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。QQ:2338195090。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注