8051架构速览:内部RAM、特殊功能寄存器(SFR)、程序存储器映射、中断系统
做固件逆向,说白了就是跟芯片的“五脏六腑”打交道。8051这玩意儿虽然老,但它的架构设计非常经典。你搞懂了它的内存布局和中断机制,反汇编出来的代码就不再是天书了。
我个人习惯,拿到一个8051的固件,第一件事不是急着反汇编,而是先看它的存储器映射。这就像你去一个陌生的城市,先看地图一样。今天我们就来把这张“地图”画清楚。
2.1 内部RAM:那128个字节的“战场”
8051的内部RAM只有128字节,地址从0x00到0x7F。你别看它小,这里可是程序运行的“主战场”。我把它分成三块来讲:
- 工作寄存器区(0x00-0x1F):共32字节,分成4组(Bank0~Bank3),每组8个寄存器(R0~R7)。CPU当前用哪一组,由PSW寄存器里的RS1、RS0两位决定。反汇编时看到MOV R0, #0x55,你得知道这个R0到底映射到哪个物理地址。
- 位寻址区(0x20-0x2F):共16字节,128位。每个位都有独立的地址(0x00~0x7F)。这是8051的独门绝技,很多控制逻辑都靠它实现。我在逆向一个老式空调控制器时,发现所有按键状态都映射在这个区域,用位操作指令直接读写,效率极高。
- 通用RAM区(0x30-0x7F):剩下的80字节,给用户随便用。堆栈通常也设在这里。
避坑指南:我曾经在分析一个通信协议栈时,发现堆栈指针SP被初始化到了0x60。结果程序跑着跑着就死机了。后来一查,是堆栈向下增长时,把0x5F处的关键数据给覆盖了。记住,8051的堆栈是向上增长的(压栈时SP先加1),而且默认复位后SP=0x07,刚好在工作寄存器区。很多新手工程师忘了重新设置SP,导致寄存器数据被堆栈冲掉。
2.2 特殊功能寄存器(SFR):控制芯片的“开关面板”
SFR的地址范围是0x80到0xFF,一共128个字节。但8051实际只用了其中一部分。这些寄存器控制着定时器、串口、I/O口、中断等所有外设。反汇编时,看到直接地址操作(比如MOV 0x90, #0xFF),你就要立刻反应过来——这是在操作P1口(0x90是P1的地址)。
我整理了一张常用SFR的速查表,做逆向时经常用到:
| 地址 | 符号 | 功能描述 | 可位寻址 |
|---|---|---|---|
| 0x80 | P0 | 端口0 | 是 |
| 0x81 | SP | 堆栈指针 | 否 |
| 0x87 | PCON | 电源控制寄存器 | 否 |
| 0x88 | TCON | 定时器控制寄存器 | 是 |
| 0x89 | TMOD | 定时器模式寄存器 | 否 |
| 0x8A | TL0 | 定时器0低字节 | 否 |
| 0x8B | TL1 | 定时器1低字节 | 否 |
| 0x8C | TH0 | 定时器0高字节 | 否 |
| 0x8D | TH1 | 定时器1高字节 | 否 |
| 0x90 | P1 | 端口1 | 是 |
| 0x98 | SCON | 串口控制寄存器 | 是 |
| 0x99 | SBUF | 串口数据缓冲器 | 否 |
| 0xA0 | P2 | 端口2 | 是 |
| 0xA8 | IE | 中断使能寄存器 | 是 |
| 0xB0 | P3 | 端口3 | 是 |
| 0xB8 | IP | 中断优先级寄存器 | 是 |
| 0xD0 | PSW | 程序状态字 | 是 |
| 0xE0 | ACC | 累加器 | 是 |
| 0xF0 | B | B寄存器 | 是 |
小技巧:SFR中地址能被8整除的(如0x80、0x88、0x90...),都支持位寻址。反汇编时看到SETB 0x97,你查一下表就知道这是在置位P1.7。这个规律能帮你快速定位代码在操作哪个外设。
2.3 程序存储器映射:代码到底在哪儿跑?
8051采用哈佛架构,程序存储器和数据存储器是分开的。程序存储器(ROM/Flash)的地址空间是64KB(0x0000~0xFFFF)。但有几个特殊地址你得记住:
- 0x0000:复位入口。CPU上电或复位后,第一条指令从这里开始取。
- 0x0003:外部中断0(INT0)的中断服务程序入口。
- 0x000B:定时器0溢出中断入口。
- 0x0013:外部中断1(INT1)入口。
- 0x001B:定时器1溢出中断入口。
- 0x0023:串口中断入口。
每个中断入口之间只间隔8个字节。你想想看,8个字节能写几条指令?通常放一条LJMP指令跳转到真正的中断处理函数。反汇编时,看到0x0003处是LJMP 0x0150,你就知道外部中断0的处理函数在0x0150。
注意:有些芯片厂商(比如STC)会扩展程序存储器,或者把ISP引导码放在高地址区。我遇到过一款芯片,复位后先跳到0x1C00执行引导程序,然后再跳回0x0000。如果你直接反汇编0x0000开始的代码,会发现逻辑不完整——因为引导程序初始化了一些关键寄存器。
2.4 中断系统:CPU的“紧急通道”
8051有5个中断源(标准版),每个都有固定的入口地址和优先级。中断系统由两个核心寄存器控制:IE(中断使能)和IP(中断优先级)。
IE寄存器的位定义如下:
| 位 | 符号 | 功能 |
|---|---|---|
| EA | IE.7 | 全局中断使能(总开关) |
| ES | IE.4 | 串口中断使能 |
| ET1 | IE.3 | 定时器1中断使能 |
| EX1 | IE.2 | 外部中断1使能 |
| ET0 | IE.1 | 定时器0中断使能 |
| EX0 | IE.0 | 外部中断0使能 |
反汇编时,看到SETB 0xA8(IE的地址),你就知道这是在开中断。如果看到CLR 0xA8,那就是关中断。我经常通过扫描这些指令来定位中断相关的代码。
中断优先级默认是:INT0 > 定时器0 > INT1 > 定时器1 > 串口。但你可以通过IP寄存器调整。每个中断有两个优先级:高(1)和低(0)。如果两个同优先级的中断同时触发,硬件会按自然优先级顺序响应。
实战经验:我在逆向一个工业控制器的固件时,发现它的定时器0中断优先级被设成了高,而外部中断0是低。但奇怪的是,外部中断0的处理函数里竟然调用了延时函数。这导致定时器0频繁打断外部中断的处理,最终造成数据丢失。嗯,这明显是一个设计缺陷。通过反汇编,我很快就定位到了问题所在。
2.5 架构总览图
下面这张图是我自己画的8051架构总览,把上面讲的内容串起来。你盯着看几分钟,应该就能建立起整体印象。
这张图里,CPU核心在中间,左边是内部RAM,右边是SFR,下面是程序存储器和中断系统。箭头表示CPU可以访问这些模块。你反汇编时看到的每一条指令,最终都会落到这些模块的某个地址上。
嗯,8051的架构其实不复杂,但细节很多。我个人觉得,搞懂内部RAM的分区和SFR的地址映射,是逆向分析的第一步。下次你拿到一个固件,先别急着反汇编,打开它的存储器映射表,对照着看代码,你会发现很多规律。