寄存器

本文深入探讨了寄存器的分类及其在CPU中的作用,包括通用寄存器和外设寄存器的功能。同时,详细分析了处理器中的关键寄存器,如PC和SP寄存器的工作原理及在函数调用过程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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函数返回

函数调用总结:

  1. 参数入栈
  2. 返回地址入栈
  3. 转去执行被调函数
    1. 保存EBP旧值
    2. 栈帧增长(ESP发生移动)
    3. 保存相关寄存器信息(被调用者保存寄存器)
    4. 初始化局部变量内存空间
    5. 执行相关代码
    6. 之前保存的相关寄存器信息出栈
    7. ESP移动到EBP所指向位置
    8. EBP旧值出栈
    9. ret
  4. 平衡栈(此为外平栈,即由调用者平衡栈;另外还有内平栈)

参考资料:

  1. 嵌入式操作系统原理课
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值