为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: 
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 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))
  1. 反汇编
    将elf文件反汇编成asm文件

arm-none-eabi-objdump -S fw.elf >>fw.asm

  1. 选择跳出地址
    根据前一个patch计算当前patch的的首地址
0xa0038b74 -->   0xc000672c    跳转指令0xbddaf1cd    
//0xc00066c8+23*4+8 = 0xc000672c  
0xc000672c 和 0xa020672c 是同一个位置,但是b 跳转的时候不能跳太远,所以要跳到  0xa020672c 

a0038b72:   2802        cmp r0, #2
a0038b74:   d924        bls.n   a0038bc0 
入口地址必须是4字节对齐的(以0 4 8 c结尾的地址),所以我选择
a0038b74 作为入口,跳到patch 的ram 地址 0xc000672c;
然而我们实际跳转的bl指令跳不了很远,所以先调到0xa020672c,再跳到0xc000672c。
  1. 汇编指令的计算
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   

0xc0006740: 2805        cmp r0, #5
0xc0006742: d001        beq.n    0xa0206748 
beq:(如果之前比较相等就执行 beq指令,如果不等继续执行吓一条)
所以代码的意思是:r0=5,那么跳到  0xa0206748 ,否则跳到0xc0006744


0xc0006744:    0xf632 ba17         //a0038b76
0xc0006748:  -->  0xf632 ba3a   //0xa0038bc0   
  1. 最后生成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

你可能感兴趣的:(为ROM版芯片打patch)