为ROM版芯片打patch
前言
国内大部分蓝牙芯片原厂的controller都是购买的IP(Intellectual Property),因为IP公司出售的controller代码已经十分稳定。所以会将controller做成ROM版,以降低成本。而芯片Tape out后难免会有一些BUG,这样ARM crotex M4 的code-patch ability for ROM system updates function 就有了用武之地!
技术路线
我们预先在lds文件中流出256Bytes 空间专门留给Host来打patch:
.flashpatch (NOLOAD) :
{
KEEP(*(.flashpatch))
. = 0x100;
. = ALIGN(4);
} > RAM
这样虽然蓝牙子系统是ROM版,不能重新烧写程序,但是我们可以在Host端编写patch,然后写到这片预留好的patch空间中。
这样每当执行错误代码段,就可以跳到patch空间执行正确代码,再跳回到到正常的代码。
于是出现了一个棘手的问题:
预留的patch空间有限,我们不可能将错误的代码块修改后反汇编成机器码,全部写入到patch空间中。因此patch的跳出地址和返回跳入地址的选择尤为关键!
打patch常用的汇编指令
**1. nop **
控制指令用来补齐
2. beq
beq指定是跳转指令,但是跳转要满足一定的条件,
例:
cmp R1,#0
beq Label
即当R1和0相等的时候程序跳到标号Label处执行
cmp.w sl, #4 f1ba 0f04
beq.n d001
beq.n 的计算: 从当前pc指针跳到下面指令 N行
那么 (N-4) 就是pc指针偏移量,
(N-4) /2 就是指令后2位!
注:所以在打patch 的时候,我们可以先用beq.n 这种指令跳到patch 下面的位置,然后再跳出!
例:
a020011c: f1ba 0f04 cmp.w sl, #4
a0200120: d002 beq.n a0200128: <ld_clock_isr+0xdd6>
a0200122: bf00
跳出 1
a0200124: 0xba1cf607 b. a0007560
跳出 2
a0200128: 0xbd13f607 b. a0007b52
3. b \ bl
B或BL指令引起处理器转移到“子程序名”处开始执行。
两者的不同之处在于BL指令在转移到子程序执行之前,将其下一条指令的地址拷贝到R14(LR,链接寄存器)。由于BL指令保存了下条指令的地址,因此使用指令“MOV PC ,LR”即可实现子程序的返回。
而B指令则无法实现子程序的返回,只能实现单纯的跳转。用户在编程的时候,可根据具体应用选用合适的子程序调用语句。
4. ldr
LDR指令的格式:
LDR{条件} 目的寄存器 <存储器地址>
作用:将 存储器地址 所指地址处连续的4个字节(1个字)的数据传送到目的寄存器中。
示例
a0200110: 4b06 ldr r3, [pc, #24]
LDR指令的寻址方式比较灵活,实例如下:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将 R1+R2的值存入R1。
LDR R0,[R1],#8 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+8的值存入R1。
LDR R0,[R1,R2]! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将R1+R2的值存入R1。
LDR R0,[R1,LSL #3] ;将存储器地址为R1*8的字数据读入寄存器R0。
LDR R0,[R1,R2,LSL #2] ;将存储器地址为R1+R2*4的字数据读入寄存器R0。
LDR R0,[R1,,R2,LSL #2]! ;将存储器地址为R1+R2*4的字数据读入寄存器R0,并将R1+R2*4的值存入R1。
LDR R0,[R1],R2,LSL #2 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+R2*4的值存入R1。
LDR R0,Label ;Label为程序标号,Label必须是当前指令的-4~4KB范围内。
要注意的是
LDR Rd,[Rn],#0x04 ;这里Rd不允许是R15。
另外LDRB 的指令格式与LDR相似,只不过它是将存储器地址中的8位(1个字节)读到目的寄存器中。
LDRH的指令格式也与LDR相似,它是将内存中的16位(半字)读到目的寄存器中。
LDR R0,=0xff
这里的LDR不是arm指令,而是伪指令。这个时候与MOVE很相似,只不过MOV指令后的立即数是有限制的。
这个立即数必须是0X00-OXFF范围内的数经过偶数次右移得到的数,所以MOV用起来比较麻烦,因为有些数不那么容易看出来是否合法。
5. pop
POP <reglist> t
a0023b18: bd38 pop {r3, r4, r5, pc}
我打个一个patch
先看一下c代码,在原本的if语句中添加了2个限制条件
if ((((llc_env[conhdl]->chnl_assess_interfere_cnt[chnl])*100)>
(llm_get_chnl_assess_interfere_per_thres_bad()*
(llc_env[conhdl]->chnl_assess_pkt_cnt[chnl]))) &&
(llm_util_check_map_validity(&llc_env[conhdl]->n_ch_map.map[0],
LE_CHNL_MAP_LEN)>2)&&
//patch需要加入的2个限制条件
(llc_env[conhdl]->role == ROLE_MASTER)&&
(ke_state_get(dest_id) == LLC_MAP_UPD_WAIT_INSTANT))
- 反汇编
将elf文件反汇编成asm文件
arm-none-eabi-objdump -S fw.elf >>fw.asm
- 选择跳出地址
根据前一个patch计算当前patch的的首地址
0xa0038b74 --> 0xc000672c 跳转指令0xbddaf1cd
//0xc00066c8+23*4+8 = 0xc000672c
0xc000672c 和 0xa020672c 是同一个位置,但是b 跳转的时候不能跳太远,所以要跳到 0xa020672c
a0038b72: 2802 cmp r0, #2
a0038b74: d924 bls.n a0038bc0 <lld_data_ind_handler+0x290>
入口地址必须是4字节对齐的(以0 4 8 c结尾的地址),所以我选择
a0038b74 作为入口,跳到patch 的ram 地址 0xc000672c;
然而我们实际跳转的bl指令跳不了很远,所以先调到0xa020672c,再跳到0xc000672c。
- 汇编指令的计算
0xc000672c :d90c bls.n 0xc0006748 // a0038bc0
0xc000672e :bf00 nop
bls(Branch if Lower or the Same),指令的意思是如果(cmp r0, #2)
判断结果是小于等于:那么就跳 0c 这个偏移(跳到0xa0206748,再从 0xa0206748,跳回原来的代码),
否则继续往下一条走。这个偏移的计算如下:(0xc0006748 - 0xc000672c - 4)/2 =0x0c
解释:2个内存地址的偏移再 - 4,是因为arm 的流水线机制,取指令和执行指令的间隔是4个机器周期,
而这种跳转的指令都是2字节对齐,所以除以2。
0xc0006730: f859 3025 ldr.w r3, [r9, r5, lsl #2]
0xc0006734: f893 30c5 ldrb.w r3, [r3, #197] ; 0xc5
0xc0006738: b933 cbnz r3 0xa0206748
0xc000673a: 9806 ldr r0, [sp, #24]
解释:CBNZ (Compare and Branch on Non-Zero and Compare
and Branch on Zero compares the value in a register with zero,
and conditionally branches forward a constant value. They do not affect the condition flags.)
跳转目标必须在指令之后的 4 到 130 个字节之内。
等同于: CMP Rn, #0
BNE label
0xc000673c: f5fa fd5a bl a00011f4 <ke_state_get>
0xc0006740: 2805 cmp r0, #5
0xc0006742: d001 beq.n 0xa0206748
beq:(如果之前比较相等就执行 beq指令,如果不等继续执行吓一条)
所以代码的意思是:r0=5,那么跳到 0xa0206748 ,否则跳到0xc0006744
0xc0006744: 0xf632 ba17 //a0038b76
0xc0006748: --> 0xf632 ba3a //0xa0038bc0
- 最后生成patch 机器码
const uint32_t verd_patch7_ins_data[] =
{
0xbf00d90c,
0x3025f859,
0x30c5f893,
0x9806b933,
0xfd5af5fa,
0xd0012805,
0xba17f632,
0xba3af632
};
const BTDRV_PATCH_STRUCT verd_ins_patch7 =
{
7,
BTDRV_PATCH_ACT,
sizeof(verd_patch7_ins_data),
0x00038b74,
0xbddaf1cd,
0xc000672c,
(uint8_t *)verd_patch7_ins_data
};
2017年8月22日23:01:03