遍历补丁表

在实际打补丁时,我们会利用前面插图中求出的偏移量给所有的位置打补丁。这是通过调用符号 _fixuppv_table 实现的,此时就需要用到 r8 中保存的偏移量了:从一个名为 _pvtable 读入五个符号至 r3 ~ r7 中,这五个符号都需要直接引用物理内存地址,接下来用上面说过的方法来增强它们(这就是为何这个表前面有个 .long):

__fixup_pv_table:


adrr0, 1f


ldmiar0, {r3-r7}


mvnip, #0


subsr3, r0, r3@ PHYS_OFFSET - PAGE_OFFSET


addr4, r4, r3@ adjust table start address


addr5, r5, r3@ adjust table end address


addr6, r6, r3@ adjust __pv_phys_pfn_offset address


addr7, r7, r3@ adjust __pv_offset address


movr0, r8, lsr #PAGE_SHIFT@ convert to PFN


strr0, [r6]@ save computed PHYS_OFFSET to __pv_phys_pfn_offset


(...)


b__fixup_a_pv_table





1: .long.


   .long__pv_table_begin


   .long__pv_table_end


2: .long__pv_phys_pfn_offset


   .long__pv_offset

这段代码使用第一个值(加载到了 r3 中)计算物理内存的偏移量,然后将其加到其他寄存器上,这样 r4 ~ r7 都直接指向各个标签的物理内存地址。所以 r4 指向保存了 _pvtable_begin 的物理内存地址,r5 指向 _pvtable_end,r6 指向 _pvphys_pfn_offset,r7 指向 _pvofffset。如果是C语言,这些地址都将是 u32 * ,即指向32位整数。

_pvphys_pfn_offset 特别重要,它的含义是给物理地址打补丁成虚拟地址时需要的偏移量,所以我们首先通过 mov r0, r8, lsr #PAGE_SHIFT,利用前面计算出的 r8 (内核内存相对于0的偏移量,本例中为 0x10000000)对其执行右移操作,然后利用 str r0, [r6] 将结果写入实际保存该变量的位置。这个值在内核启动的前期不会用到,但后面的虚拟内存管理需要用到它。

接下来调用 _fixupa_pv_table,它会遍历从 r4 到 r5 的每个地址(这个表格中保存的指针指向了需要打补丁的指令),然后利用一个自定义的二进制补丁程序依次打补丁,该程序可以将ARM或THUMB2的指令(指令类型在编译时确定)转换成带有一个立即偏移量的指令,该立即偏移量表示的是物理地址到虚拟地址的偏移量。这段代码非常复杂,包含了许多奇怪的操作,用来处理大头字节序。

注意每次内核加载模块的时候也会经历这个步骤,因此该步骤必须知道新的模块是否需要在物理地址和虚拟地址之间进行转换!所以,所有模块的ELF文件必须包含一个相同类型的 .pv_table 节,而且每次加载模块的时候都会调用这个汇编循环。