4、RA系列启动流程逆向:Reset Handler定位、SystemInit函数分析、时钟配置与PLL初始化逆向

好,咱们进入第四章。这一章我打算聊聊RA系列MCU上电之后,到底是怎么「活」过来的。说白了,就是启动流程的逆向分析。

你拿到一个固件,直接扔进IDA里,看到的第一段代码往往不是main函数。那是什么?是Reset Handler。它是CPU复位后执行的第一条指令。我刚开始逆向RA芯片时,就犯过一个低级错误——直接搜main入口,结果发现根本跑不通。后来才明白,不把启动流程捋清楚,后面的外设分析全是空中楼阁。

4.1 Reset Handler的定位与识别

RA系列基于Cortex-M33或M23内核。复位后,CPU从向量表的起始地址加载SP和PC。向量表默认放在0x00000000,但实际固件里,它可能被重映射到Flash或SRAM的某个偏移处。

怎么定位Reset Handler?我一般用两种方法:

  • 方法一:直接读向量表。在二进制文件开头,偏移0x0004处存放的就是Reset Handler的地址。注意,Cortex-M的向量表里,地址的最低位必须是1(表示Thumb模式)。如果看到0x10000401这样的值,去掉最低位,实际入口就是0x10000400。
  • 方法二:特征码扫描。RA的Reset Handler开头通常有一段初始化系统时钟、拷贝数据段、清零BSS段的代码。我习惯搜`LDR R0, =__main`或`BL SystemInit`这类指令的机器码。

实战要点:我在逆向某款RA4M2的Bootloader时,发现向量表被刻意偏移到了0x00002000。如果你按默认地址0x00000000去解析,读到的全是无效数据。所以,第一步先确认芯片的BOOT引脚电平,或者直接搜ROM里的向量表重映射寄存器配置。

下面是一个典型的RA系列启动流程概览,我画了张图帮你理清脉络:

RA系列MCU启动流程概览 RA系列MCU启动流程概览 上电复位 / 外部复位 加载向量表 (0x00000000) 跳转 Reset Handler SystemInit() 时钟配置 / PLL初始化 数据段拷贝 / BSS清零 跳转 main() 注意:向量表可能被重映射 实际入口地址需去掉LSB

4.2 SystemInit函数深度分析

SystemInit是RA启动流程里的关键一环。它由Reset Handler调用,主要干三件事:

  1. 配置Flash等待周期——根据目标频率设置Flash读取时序。
  2. 初始化时钟树——选择振荡器、配置PLL、设置系统时钟源。
  3. 更新系统滴答定时器——为操作系统或延时函数提供基准。

逆向时怎么找到SystemInit?我通常直接在IDA里搜字符串"SystemInit",或者搜对`R_SYSTEM_SystemInit`的调用。如果固件被strip过,那就搜特征指令序列——比如对`SCB->VTOR`的写操作。

个人习惯:我会在SystemInit函数入口下断点,然后单步跟踪。重点关注它对`R_SYSTEM->SCKCR`和`R_SYSTEM->PLLCR`这两个寄存器的操作。这两个寄存器直接决定了系统时钟频率。

下面是一个典型的SystemInit反汇编片段(伪代码形式):

void SystemInit(void) {
    // 1. 设置Flash等待周期
    // 根据目标频率,写入FACI寄存器
    FLASH->FACI = 0x05;  // 等待周期数

    // 2. 配置主振荡器
    R_SYSTEM->MOSCCR = 0x00;  // 使能主振荡器
    while (!(R_SYSTEM->OSCSF & 0x01)); // 等待稳定

    // 3. 配置PLL
    R_SYSTEM->PLLCR = 0x0A;   // 设置倍频系数
    R_SYSTEM->PLLCCR = 0x01;  // 选择PLL时钟源
    R_SYSTEM->PLLCR2 = 0x01;  // 使能PLL
    while (!(R_SYSTEM->OSCSF & 0x10)); // 等待PLL锁定

    // 4. 切换系统时钟
    R_SYSTEM->SCKCR = 0x02;   // 选择PLL输出作为系统时钟
}

4.3 时钟配置与PLL初始化逆向

时钟配置是RA逆向里最容易踩坑的地方。为什么?因为RA的时钟树太灵活了——你可以从主振荡器、副振荡器、内部高速振荡器、内部中速振荡器里选一个作为PLL的输入。而且PLL的倍频系数、分频系数都是可编程的。

我逆向时钟配置时,会重点关注以下几个寄存器:

寄存器名称 地址偏移 作用 逆向关注点
MOSCCR 0x40020000 主振荡器控制 是否使能、是否旁路
PLLCR 0x40020010 PLL倍频配置 倍频系数(通常为8-24倍)
PLLCCR 0x40020014 PLL时钟源选择 0=主振荡器, 1=内部高速
SCKCR 0x40020020 系统时钟选择 最终系统时钟源
OSCSF 0x40020024 振荡器状态标志 等待稳定/锁定

我曾经踩过的坑:有一次逆向RA6M5的固件,发现PLL死活锁不住。单步跟踪了半天,才发现问题出在PLLCCR寄存器——固件把时钟源选成了内部高速振荡器,但内部高速振荡器根本没使能。所以,逆向时钟配置时,一定要检查振荡器使能位和稳定标志位的顺序。

PLL输出频率的计算公式很简单:

PLL输出 = PLL输入 × 倍频系数 / 分频系数

举个例子,如果主振荡器是8MHz,PLL倍频系数设为10,分频系数设为1,那么PLL输出就是80MHz。RA系列通常支持最高100MHz或200MHz的系统时钟,具体看型号。

逆向时,我会在固件里搜对PLLCR的写操作。如果看到`LDR R0, =0x40020010`后面跟着`STR R1, [R0]`,那基本就是PLL配置了。然后根据写入的值反推倍频系数。

4.4 实战:从固件中提取时钟配置

咱们来走一遍实际逆向的步骤。假设你手里有一个RA4W1的固件,想搞清楚它的系统时钟是多少。

  1. 定位Reset Handler:在0x00000004处读取向量,得到Reset Handler入口地址。
  2. 找到SystemInit调用:在Reset Handler里搜`BL SystemInit`指令。
  3. 分析SystemInit:进入SystemInit,找到对`R_SYSTEM->MOSCCR`的写操作。
  4. 提取PLL参数:找到对`PLLCR`的写操作,记录写入的值。
  5. 计算最终频率:根据PLL输入频率和倍频系数,算出系统时钟。

关键技巧:RA的时钟配置代码通常放在一个叫`clock_init()`或`SystemClock_Config()`的函数里。如果你在固件里搜不到SystemInit,可以搜这些函数名。另外,RA的FSP(Flexible Software Package)生成的代码里,时钟配置会调用`R_SYSTEM_SystemClockConfig()`,这个函数名在二进制里通常不会被strip掉。

嗯,这一章的内容差不多就这些。启动流程逆向是固件分析的基础,把Reset Handler和SystemInit吃透了,后面的外设识别才能顺风顺水。你想想看,如果连芯片跑在多少频率都不知道,你怎么分析UART的波特率配置?怎么算定时器的溢出时间?

我个人习惯,每次拿到一个新固件,第一件事就是画一张启动流程图,把每个函数调用和寄存器配置都标出来。这张图就是后续分析的「地图」。你也试试看。