以下的程序清单,为一个内核模块的源码。sass
#define __SYLIXOS_KERNEL #include <SylixOS.h> #include <module.h> /* * SylixOS call module_init() and module_exit() automatically. */ int module_init (void) { printk("hello_module init!\n"); return 0; } void module_exit (void) { }
反汇编以后的内容以下所示。函数
kmTest.ko: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 <module_init>: /* * SylixOS call module_init() and module_exit() automatically. */ int module_init (void) { 0: a9bf7bfd stp x29, x30, [sp, #-16]! 4: 910003fd mov x29, sp printk("hello_module init!\n"); 8: 90000000 adrp x0, 0 <module_init> c: 91000000 add x0, x0, #0x0 10: f9400000 ldr x0, [x0] 14: 94000000 bl 0 <API_LogPrintk> return 0; 18: 52800000 mov w0, #0x0 // #0 } 1c: a8c17bfd ldp x29, x30, [sp], #16 20: d65f03c0 ret 24: d503201f nop ... 0000000000000030 <module_exit>: void module_exit (void) { } 30: d503201f nop 34: d65f03c0 ret
从以上反汇编结果可知,printk函数调用会被汇编为BL指令,而且跳转的目的地址为0,这是由于实际的跳转地址会在动态加载时进行调整。3d
94000000 bl 0 <API_LogPrintk>
查阅ARMv8手册,BL指令的结构以下图所示。 按照该结构可知,BL指令最大的跳转范围为4×226 = 256MB,即±128MB。可是“实际需跳转位置”与“当前指令位置”的地址偏移颇有可能超过该范围。因此在动态加载时,须要修改这条指令的实现,使得其具备跳转到整个64位地址空间的能力。code
一般的作法是采用跳转表进行实现。 跳转表使用的方式以下图所示,其中“跳转表所在的位置”与“当前指令位置”的地址偏移范围为±128MB以内,所以,能够首先从当前位置跳转到跳转表中的某一个表项。 orm
BR跳转指令使用寄存器进行跳转,那么该指令具体264地址空间跳转的能力,所以跳转表能够借助该指令进行实现。blog
字节 | 指令内容 |
---|---|
[16:19] | movn x16, #0x…. |
[12:15] | movk x16, #0x…., lsl #16 |
[08:11] | movk x16, #0x…., lsl #32 |
[04:07] | movk x16, #0x…., lsl #48 |
[00:03] | br x16 |
由于MOV指令不能一次将一个64位数移入寄存器,因此必须将移位操做分为四步完成,如上表所示。 | |
此时,在动态加载时,按照以下方式进行跳转: |
一、 将原来的BL指令中目的跳转位置,调整为跳转表对应表项的位置;v8
二、 跳转表会将实际跳转地址更新到X16寄存器中;源码
三、 经过BR指令跳转到实际的目标地址。it