弄懂LR和PC寄存器

LR 寄存器在 ARM 中主要的两种用途 

在 ARM 架构中,LR(Link Register) 是一个非常重要的寄存器,具有多种用途,但其最主要的用途可以归纳为以下两种:

1. 存储函数的返回地址

在 ARM 调用约定中,LR 主要用于存储子程序调用的返回地址。

1.1 函数调用(子程序调用)中的作用

当一个函数调用(使用 BLBLX 指令)时,处理器自动将当前指令的地址(即函数返回的地址)保存在 LR 寄存器中。这个返回地址是函数执行完毕后,需要跳回的地方。

  • BL(Branch with Link)指令: 将当前指令的地址(即调用指令的下一条指令的地址)保存到 LR 中,并跳转到目标函数的起始地址。
  • BLX(Branch with Link and Exchange)指令: 类似 BL,但还会交换指令集(从 ARM 模式切换到 Thumb 模式,或反之)。
1.2 函数返回(返回地址的使用)

当函数执行完毕并准备返回时,程序通过 MOV PC, LRBX LR 指令,将 LR 寄存器的值(即返回地址)加载到 PC(Program Counter) 中,从而跳转回到调用函数的地方。

  • MOV PC, LR:将 LR 寄存器中的返回地址直接移动到程序计数器 PC。
  • BX LR:根据 LR 寄存器的低位判断是 ARM 还是 Thumb 模式,然后跳转到对应的返回地址。
1.3 示例
BL func       ; 调用 func 函数,LR 保存返回地址
...
func:
    ; 执行函数体
    MOV PC, LR   ; 使用 LR 中的返回地址返回


2. 存储异常返回地址

在 ARM 中,异常发生时,LR 也有一个非常关键的用途,那就是 存储异常的返回地址

2.1 异常模式下的作用

当发生中断、异常或系统调用时,LR 被用来存储当前程序计数器(PC)值的拷贝,以便在处理完异常后能够返回到发生异常的地方。

  • FIQ(Fast Interrupt Request)IRQ(Interrupt Request) 等模式下,LR 保存的返回地址会指向触发中断或异常的指令地址,允许在中断处理完后继续执行被中断的指令。
  • 异常模式(如 Supervisor Mode,SVC)下,LR 保存的是异常返回的地址,也就是返回到异常发生之前的程序状态。
2.2 使用方式

当发生中断或异常时,LR 会自动保存当前的 PC 值,在异常处理程序完成后,通过将 LR 的值加载到 PC 中,可以返回到中断或异常发生时的指令位置。

  • 异常返回: 通过 MOV PC, LRBX LR 指令,将 LR 中保存的返回地址加载到 PC,恢复到正常程序流。
2.3 示例:异常返回
SVC_Handler:        ; 异常处理程序
    ...
    MOV PC, LR       ; 恢复程序计数器,返回到异常发生的位置

在特定的异常模式下(例如 IRQ 模式、FIQ 模式等),LR 会有专门的名称:

  • R14_svc:在 SVC 模式下的链接寄存器,存储异常返回地址。
  • R14_irq:在 IRQ 模式下的链接寄存器。
  • R14_fiq:在 FIQ 模式下的链接寄存器。

总结

在 ARM 中,LR 寄存器的两种主要用途为:

  1. 函数调用的返回地址: 存储函数调用后的返回地址,用于函数返回时跳转回调用点。
  2. 异常的返回地址: 存储异常发生时的返回地址,用于异常处理完成后恢复程序执行。

这使得 LR 寄存器在程序流控制、异常处理和中断管理中都扮演着至关重要的角色。

细说程序计数器 R15的值

在 ARM 架构中,程序计数器(PC) 也就是 R15,是一个特殊的寄存器,用于存储当前正在执行的指令的地址。R15 不仅用于控制程序流,还有一些特殊的行为和特性,以下是详细解释:


1. R15(PC)的基本用途

  • 程序计数器指向当前正在执行的指令的地址,也就是下一条将要执行的指令的地址。
  • 在 ARM 体系结构中,R15 被直接与程序流控制相关联,修改 R15 的值意味着改变程序的执行路径。

2. R15(PC)值的特殊特性

  • 指向下一条指令:R15 总是包含当前正在执行的指令的地址 + 8 字节偏移(ARM 状态)或 4 字节偏移(Thumb 状态)。

  • 在 ARM 模式下,R15 会自动加上偏移量,这个偏移是由于指令预取队列的存在。ARM 体系结构设计允许指令在实际执行之前就被预取到指令缓存中。

    • ARM 模式(32-bit): 当前正在执行的指令的地址 + 8 字节。
    • Thumb 模式(16-bit): 当前正在执行的指令的地址 + 4 字节。

    这就是为什么我们不能直接从 R15 读取到正在执行的指令地址,而是需要调整偏移来获取准确的指令地址。


3. 修改 R15 的值

R15 作为程序计数器,修改它的值直接影响程序的执行流。常见的修改方式包括:

  • 跳转指令(Branch)

    • B(Branch):通过修改 R15 来实现无条件跳转。
    • BL(Branch with Link):跳转并保存返回地址(通过 LR 寄存器)。
    • BX(Branch and Exchange):跳转并交换指令集(ARM/Thumb)。
  • 函数调用返回

    • 在函数调用(通过 BL)或异常处理完成后,修改 R15 为返回地址以恢复执行。
  • 异常和中断返回

    • 当发生中断或异常时,LR 寄存器会保存异常发生时的地址,而通过 MOV PC, LRBX LR 来恢复到异常发生点。

4. R15(PC)在不同模式下的行为

ARM 处理器支持多种模式,包括用户模式和特权模式(如中断模式、异常模式等)。在这些模式下,R15(PC)具有不同的行为:

  • 在用户模式下:

    • R15 直接指向程序计数器的当前地址,表示下一条将要执行的指令。
  • 在特权模式下(如 FIQ、IRQ、Supervisor 模式等):

    • R15 被用来存储返回地址。在发生异常时,R15 会保存当前的指令地址(即异常发生时的地址),以便异常处理完成后可以恢复执行。

    • 例如,在 IRQ 模式下,程序计数器 R15 会指向当前正在执行的指令地址加上偏移量(如加 8 字节),而在异常返回时,LR(链接寄存器)保存的是该返回地址,处理完成后通过 MOV PC, LR 恢复到原来的地址。


5. R15(PC)在汇编中的使用

在汇编代码中,程序通常通过 BBLBX 等指令来修改 R15 的值,实现跳转和函数调用。

5.1 分支跳转
B target        ; 无条件跳转到 target 地址
BL target       ; 跳转到 target 地址,并将返回地址存储到 LR
5.2 函数返回
MOV PC, LR      ; 使用 LR 中存储的返回地址返回
5.3 异常返回
BX LR           ; 使用 LR 恢复程序流,返回到异常发生点

6. 总结

  • R15(PC) 是 ARM 中控制程序流的关键寄存器,存储当前正在执行的指令的地址(加上偏移)。
  • 修改 R15 的值直接影响程序的执行路径,可以用于跳转、函数调用、异常返回等操作。
  • 在 ARM 和 Thumb 模式下,R15 会加上偏移量来配合预取指令队列的存在,使得我们通常会看到 R15 的值比实际执行的指令地址大一些。

 

PC+8的问题

在 ARM 架构中,PC 寄存器显示的值实际正在执行的指令地址之间有一个固定的偏移量,通常是 8 字节。这意味着,PC 寄存器的值总是比当前正在执行的指令地址大 8 字节。

这个偏移的原因:

ARM 处理器的 PC 寄存器(程序计数器)实际上指向的是当前指令的 地址+8。这个偏移量是 ARM 架构设计的一部分,具体原因如下:

  • 流水线处理: ARM 处理器通常是 3-阶段流水线 或更高效的流水线架构。在这种架构下,处理器需要一些时间来取指、解码和执行指令。因此,PC 在执行指令时通常已经提前指向下一条指令的地址。

  • 取指时的延迟: ARM 处理器在取指(fetch)时,通常会先取下一条指令。因此,PC 显示的地址并不是当前正在执行的指令地址,而是当前指令地址的 偏移 8 字节

举个例子:

假设当前执行的指令地址为 0x1000,即 PC 应该指向 0x1000

  • 实际上,在 ARM 中,PC 寄存器的值通常会显示为 0x1008,即当前指令地址加 8 字节。
    0x1000:  MOV R0, #1   ; 执行当前指令
    0x1004:  ADD R1, R0, R0
    0x1008:  BL  func      ; 例如这条是分支指令

在这个例子中,指令 BL func 的地址是 0x1008,这时 PC 寄存器的值会显示为 0x1008,但是实际上 CPU 在执行的是 0x1000 地址的指令。

总结:

在 ARM 中,PC 寄存器的值通常比实际执行的指令地址大 8 字节,因为它指向的地址是下一条指令的地址。这是 ARM 处理器流水线和取指过程的一个特性。

Thumb 模式
  • Thumb 模式(16 位模式)下,PC 会指向当前执行指令的地址 加 4 字节,因为 Thumb 指令集每条指令占用 2 字节,因此两条指令会占用 4 字节。

 

举例说明

为了更清楚地说明 PC(程序计数器)和 LR(链接寄存器)是如何在 ARM 架构中工作的,将从头到尾分析一个详细的例子。这个例子将展示在 用户模式 下执行程序时,如何通过指令的执行、函数调用以及中断处理时,PCLR 的值变化。

 

场景:

  1. 执行一段简单的程序,使用 BL func 进行函数调用。
  2. 详细说明每条指令执行时 PC 和 LR 的变化

程序代码:

start:
    MOV R0, #5        ; 0x1000
    MOV R1, #10       ; 0x1004
    BL func           ; 0x1008
    MOV R2, #20       ; 0x100C
    B end             ; 0x1010

func:
    MOV R3, #15       ; 0x2000
    MOV PC, LR        ; 0x2004

end:
    NOP               ; 0x1014

执行分析:

假设我们正在 ARM 模式 下执行该程序,并且通过流水线机制和指令预取的行为来分析 PCLR 的值。

1. 执行 MOV R0, #5(0x1000)
  • 当前执行的指令地址: PC = 0x1000  
  • 在 ARM 模式下,PC 显示的是当前执行指令地址 加 8 字节。因此,PC = 0x1000 + 8 = 0x1008(即下一条指令的地址)。
2. 执行 MOV R1, #10(0x1004)
  • 当前执行的指令地址: PC = 0x1004
  • 由于流水线机制,PC 会指向下一条指令的地址 加 8,因此 PC = 0x1004 + 8 = 0x100C
3. 执行 BL func(0x1008)
  • BL 指令作用: 跳转到 func 函数的起始地址,并将返回地址(PC + 4)保存到 LR 中。
    • PC 会修改为 func 函数的起始地址,例如 PC = 0x2000func 的地址)。
    • LR 会保存跳过 BL func 之后的地址,即 LR = 0x100C

此时的寄存器值:

  • PC = 0x2000(跳转到 func 函数的地址)。
  • LR = 0x100CBL 指令后面要跳回的位置)。
4. 执行 MOV R3, #15(0x2000)
  • 当前执行的指令地址: PC = 0x2000
  • 由于流水线机制,PC 会自动向前推进,指向下一条指令的地址 加 8 字节,因此 PC = 0x2000 + 8 = 0x2008
5. 执行 MOV PC, LR(0x2004)
  • MOV PC, LR:将 LR 中保存的返回地址加载到 PC 中。LR 中的值是 0x100C,所以 PC = 0x100C
  • 程序返回到 MOV R2, #20,即跳回到函数调用后的位置。

此时的寄存器值:

  • PC = 0x100C(恢复到 MOV R2, #20 处,继续执行)。
  • LR = 0x100C(保持返回地址,直到下一次调用)。
6. 执行 MOV R2, #20(0x100C)
  • 当前执行的指令地址: PC = 0x100C
  • 由于流水线机制,PC 会自动向前推进,指向下一条指令的地址 加 8 字节,因此 PC = 0x100C + 8 = 0x1014
7. 执行 B end(0x1010)
  • 当前执行的指令地址: PC = 0x1010
  • B end 指令跳转到 end 标签,PC 会被修改为跳转目的地的地址,假设 end 标签的地址为 0x1014

此时的寄存器值:

  • PC = 0x1014(跳转到 end 处)。

寄存器值汇总:

步骤操作PC 地址LR 地址
执行 MOV R0, #5当前指令地址0x1000未改变
执行 MOV R1, #10当前指令地址0x1004未改变
执行 BL func跳转到 func 函数0x20000x100C
执行 MOV R3, #15当前指令地址0x20000x100C
执行 MOV PC, LR恢复到 MOV R2, #200x100C0x100C
执行 MOV R2, #20当前指令地址0x100C0x100C
执行 B end跳转到 end 标签0x10140x100C

总结:

  • PC 的值:在 ARM 模式下,PC 总是指向当前执行的指令地址 加 8 字节,因为 ARM 处理器采用指令预取机制,使得 PC 显示为当前指令的地址加上 8 字节的偏移量。
  • LR 的值:在 BL 指令执行时,LR 保存的是跳过 BL 指令后的地址,即 PC + 4。这使得 LR 能够在函数调用返回时提供正确的返回地址。

 

为了更清楚地展示中断时 PCLR(以及其他寄存器)的变化,我们将通过一个 中断处理 的例子来分析。

假设我们有一个简单的场景,其中一个中断触发后,ARM 处理器进入中断处理程序(ISR),并在中断服务完成后返回。

中断的基本流程:

  1. 当一个外部中断(如外部设备产生的 IRQ)发生时,处理器会保存当前的执行状态(包括 PCLR 等寄存器)并跳转到 中断向量表 中的入口地址,进入中断处理程序。
  2. 中断服务例程(ISR)执行完后,通过 BX LR 或者 MOV PC, LR 指令将 LR 中保存的返回地址加载回 PC,恢复到中断发生前的程序位置。

程序代码:

为了简单起见,我们假设在这个示例中,外部中断发生后,程序会跳转到一个名为 ISR_Handler 的中断服务程序。以下是我们假设的代码,展示中断发生时的寄存器变化:

start:
    MOV R0, #0        ; 0x1000  设定初始值
    MOV R1, #10       ; 0x1004  
    MOV R2, #20       ; 0x1008

    ; 中断处理发生前的代码...
    B main_loop       ; 0x100C

main_loop:
    NOP               ; 0x1010  主循环
    NOP               ; 0x1014
    ; 模拟外部中断发生
    NOP               ; 0x1018

ISR_Handler:
    ; 处理中断的代码
    MOV R3, #99       ; 0x2000  保存数据
    MOV PC, LR        ; 0x2004  中断返回

执行流程分析:

1. 程序初始化:

程序从 start 标签开始执行。处理器在运行时使用 PC 寄存器指向当前指令的地址。

  • PC = 0x1000 执行 MOV R0, #0
  • PC = 0x1004 执行 MOV R1, #10
  • PC = 0x1008 执行 MOV R2, #20
  • PC = 0x100C 执行 B main_loop,跳转到 main_loop 标签。

此时,程序进入主循环,继续执行 main_loop 中的指令。

2. 中断发生:

在运行过程中,假设外部设备触发了一个中断(IRQ)。当中断触发时,处理器会保存当前执行状态,并跳转到 中断向量表 中的中断处理程序入口。通常情况下,ARM 处理器会使用 IRQ 中断向量

  • 中断发生时:
    • 当前 PC(执行 B main_loop 后的地址)会被 保存到 LR(链接寄存器)中。
    • 处理器会跳转到中断向量表中定义的 ISR(中断服务例程)入口。假设入口地址是 0x2000

此时,寄存器的变化如下:

  • LR 保存当前地址LR = 0x1018(即 main_loop 中断发生时的地址,下一条指令地址)。
  • PC 跳转到中断处理程序PC = 0x2000(进入 ISR_Handler)。
3. 中断服务例程 ISR 执行:

当执行 ISR_Handler 中的指令时:

  • PC = 0x2000 执行 MOV R3, #99,中断服务例程的处理代码。
  • PC = 0x2004 执行 MOV PC, LR,将保存的返回地址从 LR 恢复到 PC
4. 中断返回:
  • MOV PC, LR 执行时,LR 中的值会被加载回 PC,恢复到中断发生前的程序状态。
  • LR = 0x1018(即 main_loop 中断发生时的地址)。
  • PC = 0x1018,程序跳回到中断发生时的地址,继续执行 main_loop 后的指令。
最终寄存器值:
  • PC = 0x1018(恢复到中断前的执行地址)。
  • LR = 0x1018(LR 在进入 ISR 时保存了当前的 PC 值,之后恢复时保持不变)。

寄存器值汇总:

步骤操作PC 地址LR 地址
执行 MOV R0, #0当前指令地址0x1000未改变
执行 MOV R1, #10当前指令地址0x1004未改变
执行 MOV R2, #20当前指令地址0x1008未改变
执行 B main_loop跳转到 main_loop0x100C未改变
执行 NOP(在主循环)当前指令地址0x1010未改变
执行 NOP(在主循环)当前指令地址0x1014未改变
中断发生保存当前 PC + 4LR,跳转到中断入口0x20000x1018
执行 MOV R3, #99当前指令地址0x20000x1018
执行 MOV PC, LR恢复 PC(跳回中断前)0x10180x1018
执行 NOP(恢复后)当前指令地址0x10180x1018

总结:

  • 当中断发生时,PC 会指向当前指令的地址加 4(ARM 的中断向量表会根据中断类型将 PC 跳转到合适的处理程序)。同时,LR 会保存当前 PC + 4(中断前的下一条指令地址),以便在中断结束后恢复。

  • ISR 中,处理器执行完中断服务后,通过 MOV PC, LR 将保存的返回地址(即 LR 中的值)加载到 PC 寄存器中,从而跳回中断发生前的执行位置,恢复程序的正常执行。

 

### STM32 中 LR PC 寄存器的功能与使用 #### 1. 链接寄存器 (Link Register, LR) 链接寄存器(也称为 R14),主要用于保存子程序返回地址。当执行 `BL` 或者 `BLX` 指令时,即发生过程调用的情况下,当前的程序计数器(PC)值会被复制到 LR 中作为后续跳转回原位置的基础[^3]。 此外,在 Cortex-M3 架构下,LR 还承担着额外的任务——它最底部的一位用来指示 Thumb 状态。这意味着如果该位为零,则表示接下来应解释成 16-bit 的 Thumb 指令;反之则代表即将解析的是混合有 32-bit 扩展指令集的代码片段[^1]。 ```c // 示例:函数调用前后 LR 的变化 void function_call_example(void){ // 函数内部... } int main(){ __asm volatile ("bl %0\n\t" : "=r"(function_call_example)); // 此处 LR 已经更新为main之后的地址 } ``` #### 2. 程序计数器 (Program Counter, PC) 程序计数器(通常指代 R15),负责指向正在被执行机器码的位置,并随着每次指令周期递增以追踪下一个待取指的目标地址。对于 ARM 处理器而言,由于采用流水线结构,实际上 PC 总是指向距离当前位置两条指令后的内存单元。 值得注意的是,在某些情况下,比如异常处理期间或是调试过程中,程序员可能需要手动调整 PC 来改变控制流走向。然而一般开发场景里并不推荐这样做,除非确实有必要绕过正常流程或实现特殊逻辑。 ```assembly ; 修改 PC 实现简单跳转 MOV PC, #new_address ; 直接赋新址给 PC 完成交叉跳转 BX target_register ; 利用 BX 指令间接转移至目标寄存器所含地址 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dlz0836

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值