1 寄存器的分类
1.1 CPU寄存器
CPU寄存器(通用寄存器):
- 通用寄存器(ARM中有37个)是CPU的组成部分,CPU的很多活动都需要通用寄存器的支持和参与。
- 专用指令执行、数据运算、变量处理、参数传递
1.2 外设寄存器
外设寄存器(SFR):
- SFR(special function register,特殊功能寄存器)不在CPU中,而存在于CPU的外设中,我们通过访问外设的SFR来编程操控这个外设,这就是硬件编程控制的方法。
- 用于控制外设的行为和工作方式。
- 寄存器值的配置需要根据芯片手册完成。
对于外设寄存器来说:
寄存器属于CPU外设的硬件组成部分。 CPU可以像访问内存一样访问寄存器。 寄存器是CPU的硬件设计者制定的,目的是留作外设被编程控制的“活动开关”。 正如汇编指令集是CPU的编程接口API一样,寄存器是外设硬件的软件编程接口API。使用软件编程控制某一硬件,其实就是编程读写该硬件的寄存器。 编程操作寄存器类似于访问内存。 寄存器中每个bit位都有特定含义,因此编程操作时需要位操作。 单个寄存器的位宽一般和CPU的位宽一样,以实现最佳访问效率。
编程访问寄存器的方法:
汇编方式:
ldr r1, =0xE0200280
str r0, [r1]
mov r0, #0
C语言方式:
int p = (int )0x30008000;
*p = 16;
2 处理器中的关键寄存器
2.1 PC和SP寄存器
PC - 程序计数器(指令指针 IP):
- 每执行一条指令,PC中的值就会发生变化
- PC时钟保存下一条CPU要执行的指令地址
SP - 栈指针(Stack Pointer):
- 始终指向栈空间的顶端,实现LIFO特性
- 保存中断断点、保存函数调用返回点、保存CPU现场数据等
2.2 PC 和 SP的使用案例 - 函数调用
函数调用相关的寄存器:
函数调用栈信息:
如上为linux系统的栈帧,window系统下的栈帧与linux系统下的栈帧略有差异:被调用保存的寄存器信息linux系统下位于old ebp下面,windows系统下则位于局部变量下。如下举例为windows系统。
函数调用相关汇编代码(intel格式):
// C代码
#include <stdio.h>
void first(int a);
void second(int a);
int main(void)
{
first(10);
return 0;
}
void first(int a)
{
printf("a = %d\n", a);
a = 100;
second(a);
}
void second(int a)
{
printf("a = %d\n", a);
}
// C代码对应的汇编代码
void first(int a)
{
00A613D0 push ebp
00A613D1 mov ebp,esp
00A613D3 sub esp,0C0h
00A613D9 push ebx
00A613DA push esi
00A613DB push edi
00A613DC lea edi,[ebp-0C0h]
00A613E2 mov ecx,30h
00A613E7 mov eax,0CCCCCCCCh
00A613EC rep stos dword ptr es:[edi]
printf("a = %d\n", a);
00A613EE mov esi,esp
00A613F0 mov eax,dword ptr [a]
00A613F3 push eax
00A613F4 push 0A65858h
00A613F9 call dword ptr ds:[0A69114h]
00A613FF add esp,8
00A61402 cmp esi,esp
00A61404 call __RTC_CheckEsp (0A61140h)
a = 100;
00A61409 mov dword ptr [a],64h
second(a);
00A61410 mov eax,dword ptr [a]
00A61413 push eax
00A61414 call _second (0A61127h)
00A61419 add esp,4
}
00A6141C pop edi
00A6141D pop esi
00A6141E pop ebx
00A6141F add esp,0C0h
00A61425 cmp ebp,esp
00A61427 call __RTC_CheckEsp (0A61140h)
00A6142C mov esp,ebp
00A6142E pop ebp
00A6142F ret
#include <stdio.h>
void first(int a);
void second(int a);
int main(void)
{
00A61450 push ebp
00A61451 mov ebp,esp
00A61453 sub esp,0C0h
00A61459 push ebx
00A6145A push esi
00A6145B push edi
00A6145C lea edi,[ebp-0C0h]
00A61462 mov ecx,30h
00A61467 mov eax,0CCCCCCCCh
00A6146C rep stos dword ptr es:[edi]
first(10);
00A6146E push 0Ah
00A61470 call _first (0A610A0h)
00A61475 add esp,4
return 0;
00A61478 xor eax,eax
}
00A6147A pop edi
00A6147B pop esi
00A6147C pop ebx
00A6147D add esp,0C0h
00A61483 cmp ebp,esp
00A61485 call __RTC_CheckEsp (0A61140h)
00A6148A mov esp,ebp
00A6148C pop ebp
00A6148D ret
void second(int a)
{
00A614A0 push ebp
00A614A1 mov ebp,esp
00A614A3 sub esp,0C0h
00A614A9 push ebx
00A614AA push esi
00A614AB push edi
00A614AC lea edi,[ebp-0C0h]
00A614B2 mov ecx,30h
00A614B7 mov eax,0CCCCCCCCh
void second(int a)
{
00A614BC rep stos dword ptr es:[edi]
printf("a = %d\n", a);
00A614BE mov esi,esp
00A614C0 mov eax,dword ptr [a]
00A614C3 push eax
00A614C4 push 0A65858h
00A614C9 call dword ptr ds:[0A69114h]
00A614CF add esp,8
00A614D2 cmp esi,esp
00A614D4 call __RTC_CheckEsp (0A61140h)
}
00A614D9 pop edi
00A614DA pop esi
00A614DB pop ebx
00A614DC add esp,0C0h
00A614E2 cmp ebp,esp
00A614E4 call __RTC_CheckEsp (0A61140h)
00A614E9 mov esp,ebp
00A614EB pop ebp
00A614EC ret
// 下面对整个程序的汇编代码进行一一分析:
main:
int main(void)
{
push ebp
mov ebp,esp
sub esp,0C0h
// 保存了EBP旧值,EBP指向了ESP所在位置,之后将ESP向下移动
push ebx
push esi
push edi
// 保存相关寄存器信息
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
// 将增长栈帧中部分内容初始化为0CCCCCCCCh(主要用来存放局部变量)
first(10);
push 0Ah
// 参数入栈
call _first (0A610A0h)
// 注意call会做两件事情:返回地址入栈、执行被调函数
first:
void first(int a)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
// 如上完成保存EBP旧值、栈帧增长、保存寄存器信息、初始化部分栈内存值
printf("a = %d\n", a);
mov esi,esp
// 留着后面做ESP校验的时候用
mov eax,dword ptr [a]
push eax
push 0A65858h
// 参数入栈,共两个参数,分别为第一个字符串参数和第二个整形参数
call dword ptr ds:[0A69114h]
add esp,8
// 平衡栈:函数调用前后栈指针应该相同
cmp esi,esp
call __RTC_CheckEsp (0A61140h)
// ESP校验
a = 100;
mov dword ptr [a],64h
second(a);
mov eax,dword ptr [a]
push eax
call _second (0A61127h)
second:
void second(int a)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
void second(int a)
{
rep stos dword ptr es:[edi]
printf("a = %d\n", a);
mov esi,esp
mov eax,dword ptr [a]
push eax
push 0A65858h
call dword ptr ds:[0A69114h]
add esp,8
cmp esi,esp
call __RTC_CheckEsp (0A61140h)
}
pop edi
pop esi
pop ebx
// 弹出之前保存的相关寄存器信息
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0A61140h)
mov esp,ebp
// ESP移动到EBP所指的位置
pop ebp
ret
// second函数返回
add esp,4
// 平衡栈,first函数中调用second函数前后的栈指针相同
}
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0A61140h)
mov esp,ebp
pop ebp
ret
// first函数返回
add esp,4
// 平衡栈,first函数中调用second函数前后的栈指针相同
return 0;
xor eax,eax
}
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0A61140h)
mov esp,ebp
pop ebp
ret
// main函数返回
函数调用总结:
- 参数入栈
- 返回地址入栈
- 转去执行被调函数
- 保存EBP旧值
- 栈帧增长(ESP发生移动)
- 保存相关寄存器信息(被调用者保存寄存器)
- 初始化局部变量内存空间
- 执行相关代码
- 之前保存的相关寄存器信息出栈
- ESP移动到EBP所指向位置
- EBP旧值出栈
- ret
- 平衡栈(此为外平栈,即由调用者平衡栈;另外还有内平栈)
参考资料: