0%

汇编程序寄存器的理解

引子

汇编不属性,现在拿来复习下

寄存器的分类

通用寄存器

寄存器 名称 作用
AX 通常用来保存函数的返回值
CX 计数器 用作计数器
DX
BX
SP 栈顶指针寄存器 保存栈顶地址
BP 栈底指针寄存器 保存栈底地址
SI 源变址寄存器 MOVS 或 STOS等指令
DI 目的变址寄存器

16位寄存器:(14个)

  • 4个数据寄存器(AX,BX,CX,DX)
  • 2个变址和指针寄存器(SI,DI)
  • 2个指针寄存器(SP,BP)
  • 4个段寄存器(ES,CS,DS,SS)
  • 1个指令指针寄存器(IP)
  • 1个标志寄存器(Flags)

32位寄存器:(16个)

  • 4个数据寄存器(EAX,EBX,ECX,EDX)
  • 2个变址和指针寄存器(ESI,EDI)
  • 2个指针寄存器(ESP,EBP)
  • 6个段寄存器(ES、CS、SS、DS、FS、GS)
  • 1个指令指针寄存器(EIP)
  • 1个标志寄存器(EFlags)

64位寄存器

  • 64位有16个寄存器,32位只有8个。但是32位前8个都有不同的命名,分别是e ,而64位前8个使用了r代替e,也就是r 。e开头的寄存器命名依然可以直接运用于相应寄存器的低32位。而剩下的寄存器名则是从r8 - r15,其低位分别用d,w,b指定长度。
  • 32位使用栈帧来作为传递的参数的保存位置,而64位使用寄存器,分别用rdi,rsi,rdx,rcx,r8,r9作为第1-6个参数。rax作为返回值
  • 64位没有栈帧的指针,32位用ebp作为栈帧指针,64位取消了这个设定,rbp作为通用寄存器使用
  • 64位支持一些形式的以PC相关的寻址,而32位只有在jmp的时候才会用到这种寻址方式。

ESP EBP 寄存器

使用IDA 反汇编的时候经常能看到ESP,EBP这种字眼;
但是不明所以

新建一个程序cpp 程序片段

1
2
3
4
5
6
7
8
9
10
11
12
#include"stdio.h"
int add(int x,int y)
{
return x+y;
}

int main()
{
int p = add(6,5);
printf("%d\n",p);
return 0;
}

这个程序很简单,就是求两个数的值,然后输出即可。所以首先把它用gcc编译链接成a.out,进入gdb进行调试

1
2
3
4
5
6
7
8
[[email protected] cpp]# gcc add.cpp -o add.o
[[email protected] cpp]# ll -al
total 20
drwxr-xr-x. 2 root root 34 Oct 15 02:14 .
drwxr-xr-x. 4 root root 39 Oct 15 00:41 ..
-rw-r--r--. 1 root root 121 Oct 15 00:43 add.cpp
-rwxr-xr-x. 1 root root 12776 Oct 15 02:14 add.o
[[email protected] cpp]#

反汇编 disassemble main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[[email protected] cpp]# gdb -q add.o
Reading symbols from add.o...(no debugging symbols found)...done.
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004005aa <+0>: push %rbp
0x00000000004005ab <+1>: mov %rsp,%rbp
0x00000000004005ae <+4>: sub $0x10,%rsp
0x00000000004005b2 <+8>: mov $0x5,%esi
0x00000000004005b7 <+13>: mov $0x6,%edi
0x00000000004005bc <+18>: callq 0x400596 <_Z3addii>
0x00000000004005c1 <+23>: mov %eax,-0x4(%rbp)
0x00000000004005c4 <+26>: mov -0x4(%rbp),%eax
0x00000000004005c7 <+29>: mov %eax,%esi
0x00000000004005c9 <+31>: mov $0x400678,%edi
0x00000000004005ce <+36>: mov $0x0,%eax
0x00000000004005d3 <+41>: callq 0x4004a0 <[email protected]>
0x00000000004005d8 <+46>: mov $0x0,%eax
0x00000000004005dd <+51>: leaveq
0x00000000004005de <+52>: retq
End of assembler dump.
(gdb)

反汇编 disassemble add

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) disassemble add
Dump of assembler code for function _Z3addii:
0x0000000000400596 <+0>: push %rbp
0x0000000000400597 <+1>: mov %rsp,%rbp
0x000000000040059a <+4>: mov %edi,-0x4(%rbp)
0x000000000040059d <+7>: mov %esi,-0x8(%rbp)
0x00000000004005a0 <+10>: mov -0x4(%rbp),%edx
0x00000000004005a3 <+13>: mov -0x8(%rbp),%eax
0x00000000004005a6 <+16>: add %edx,%eax
0x00000000004005a8 <+18>: pop %rbp
0x00000000004005a9 <+19>: retq
End of assembler dump.
(gdb)

这里可以看到Add函数和Main函数反汇编出来的代码

现在来看下rbp 和rsp 内容的变化

1
2
3
4
5
6
7
8
9
10
(gdb) b main
Breakpoint 1 at 0x4005ae
(gdb) b add
Breakpoint 2 at 0x40059a
(gdb) r
Starting program: /root/projects/cpp/add.o
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-101.el8.x86_64

Breakpoint 1, 0x00000000004005ae in main ()
(gdb)

BP 为基址寄存器,一般在函数中用来保存进入函数时SP的栈顶基址
SP 时栈顶指针,每次指向栈顶

在函数进入时:
push bp //保存bp指针
mov sp,bp //将sp指针传给bp,此时bp指向sp的基地址。这个时候,如果该函数有参数,则[bp-4]则是该子函数的第一个参数,[bp-8]则是该子函数的第二个参数,以此类推,有多少个参数则[bp-4^n]。
…..
…..
函数结束时:
pop bp //恢复原bp的值。
ret //退出子函数

看这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Reading symbols from add.o...done.
(gdb) b main
Breakpoint 1 at 0x4005b2: file add.cpp, line 9.
(gdb) l
1 #include"stdio.h"
2 int add(int x,int y)
3 {
4 return x+y;
5 }
6
7 int main()
8 {
9 int p = 4;
10 int i;
(gdb)
11 for(i = 1;i<3;i++)
12 {
13 p = add(i,p);
14 }
15 printf("%d\n",p);
16 return 0;
17 }
(gdb) p p
No symbol "p" in current context.
(gdb) r
Starting program: /root/projects/cpp/add.o
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-101.el8.x86_64

Breakpoint 1, main () at add.cpp:9
9 int p = 4;
(gdb) p p
$1 = 0
(gdb) si
11 for(i = 1;i<3;i++)
(gdb) disass main
Dump of assembler code for function main():
0x00000000004005aa <+0>: push %rbp
0x00000000004005ab <+1>: mov %rsp,%rbp
0x00000000004005ae <+4>: sub $0x10,%rsp
0x00000000004005b2 <+8>: movl $0x4,-0x4(%rbp)
=> 0x00000000004005b9 <+15>: movl $0x1,-0x8(%rbp)
0x00000000004005c0 <+22>: cmpl $0x2,-0x8(%rbp)
0x00000000004005c4 <+26>: jg 0x4005de <main()+52>
0x00000000004005c6 <+28>: mov -0x4(%rbp),%edx
0x00000000004005c9 <+31>: mov -0x8(%rbp),%eax
0x00000000004005cc <+34>: mov %edx,%esi
0x00000000004005ce <+36>: mov %eax,%edi
0x00000000004005d0 <+38>: callq 0x400596 <add(int, int)>
0x00000000004005d5 <+43>: mov %eax,-0x4(%rbp)
0x00000000004005d8 <+46>: addl $0x1,-0x8(%rbp)
0x00000000004005dc <+50>: jmp 0x4005c0 <main()+22>
0x00000000004005de <+52>: mov -0x4(%rbp),%eax
0x00000000004005e1 <+55>: mov %eax,%esi
0x00000000004005e3 <+57>: mov $0x400698,%edi
0x00000000004005e8 <+62>: mov $0x0,%eax
0x00000000004005ed <+67>: callq 0x4004a0 <[email protected]>
0x00000000004005f2 <+72>: mov $0x0,%eax
0x00000000004005f7 <+77>: leaveq
0x00000000004005f8 <+78>: retq
End of assembler dump.
(gdb) p $p
$2 = void
(gdb) p p
$3 = 4
(gdb) p &p
$4 = (int *) 0x7fffffffe41c
(gdb) p $rbp
$5 = (void *) 0x7fffffffe420
(gdb) p &i
$6 = (int *) 0x7fffffffe418
(gdb)

可以看出 在 Main 函数执行的时候

先将 $rbp 寄存器的值放入栈中保存起来,防止后续被破坏,因为后面会使用 bp 寄存器

欢迎关注我的其它发布渠道