我正在树莓派3(没有蓝牙)上构建内核。 我的内核使用arm汇编语言(32位)和c语言,而uboot引导我的内核。
我找到了中断向量表,并将其应用于这样的代码。
.globl _ram_entry
_ram_entry:
bl kernel_init
b _ram_entry //
ldr pc,=print_mem1
b print_mem1
b print_mem1
b print_mem2
b print_mem3
b print_mem4
b print_mem1
b print_mem2
b print_mem3
b print_mem4
#define svc_stack 0xa0300000
#define irq_stack 0xa0380000
#define sys_stack 0xa0400000
.global kernel_init
kernel_init:
ldr r0,=0x00080008
mov r1,#0x0000
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
bl main
b _ram_entry
.global print_mem1
print_mem1:
bl print_c_mem1
.global print_mem2
print_mem2:
bl print_c_mem2
.global print_mem3
print_mem3:
bl print_c_mem3
.global print_mem4
print_mem4:
bl print_c_mem4
_ram_entry从0x00080008开始,这是我的中断向量表。 当我打印内存时,0x00具有bl kernel_init。 所有中断处理程序都只打印简单数字。
但是,如果我像这样的主代码一样使用swi,则调用重置处理程序。
int main()
{
R_GPIO_REGS * gp_regs= (R_GPIO_REGS*)GPIO_BASE_ADDRESS;
gp_regs->GPFSEL[1] =0x1000000;
uart_init();
printf("hellow world\n");
vector_memory_dump();
unsigned int destrst=0xea020000;
unsigned int destirq=0xea020000;
unsigned int destswi=0xea020000;
PUT32(MEMZERO,destrst);
PUT32(MEMY,destirq);
PUT32(MEMSWI,destswi);
vector_memory_dump();
//asm("b 0x04");
asm("swi 0"); //which call swi handler on 0x08. I thought.
while(1)
{
gp_regs->GPSET[0]=0x40000;
}
return 0;
}
出什么问题了?
答案 0 :(得分:1)
所以从标签开始,我假设这是树莓派pi3,处于aarch32模式,可能是HYP模式。注意,我非常感谢您直接或间接阅读/借用我的一些代码。
使用您的代码,从这里开始:
ldr r0,=0x00080008
mov r1,#0x0000
从技术上讲,这不是一个错误,但是有点忽略了该副本的作用。
b print_mem1
b print_mem1
b print_mem2
b print_mem3
b print_mem4
b print_mem1
b print_mem2
b print_mem3
b print_mem4
与这些结合,然后是一个问题。因为它们取决于位置,并且使工具链为您创建表格然后进行复制的整个想法都丢失了。
Disassembly of section .text:
00080000 <_ram_entry>:
80000: eb00000a bl 80030 <kernel_init>
80004: eafffffd b 80000 <_ram_entry>
80008: e59ff074 ldr pc, [pc, #116] ; 80084 <print_c_mem4+0x4>
8000c: ea000013 b 80060 <print_mem1>
80010: ea000012 b 80060 <print_mem1>
80014: ea000012 b 80064 <print_mem2>
80018: ea000012 b 80068 <print_mem3>
8001c: ea000012 b 8006c <print_mem4>
80020: ea00000e b 80060 <print_mem1>
80024: ea00000e b 80064 <print_mem2>
80028: ea00000e b 80068 <print_mem3>
8002c: ea00000e b 8006c <print_mem4>
当我组装然后拆卸时,ldr pc是这样做的正确方法,但是在错误的地方着陆会显示0x80084,它前面是84-8 = 0x7C,在前面是1111100 0x1F寄存器,用于进行复制以获得到目前为止...
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
32个寄存器,复制0x80字节。从技术上讲,它可以覆盖第一个向量,也许可以覆盖第二个,但是当然不能覆盖swi向量。
(再次)查看arm文档时(armv7-ar,因为它是aarch32或armv7-a兼容模式)0x00000008是超级用户/ svc / swi呼叫的入口点。
因此,您需要一条从0x00000008到所需地址/标签的指令。
因此,如果您返回到本示例或您从中借鉴/学习的任何示例。
.globl _start
_start:
ldr pc,reset_handler
ldr pc,undefined_handler
ldr pc,swi_handler
ldr pc,prefetch_handler
ldr pc,data_handler
ldr pc,unused_handler
ldr pc,irq_handler
ldr pc,fiq_handler
reset_handler: .word reset
undefined_handler: .word hang
swi_handler: .word hang
prefetch_handler: .word hang
data_handler: .word hang
unused_handler: .word hang
irq_handler: .word irq
fiq_handler: .word hang
reset:
mov r0,#0x80000
mov r1,#0x0000
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
Disassembly of section .text:
00080000 <_stack>:
80000: e59ff018 ldr pc, [pc, #24] ; 80020 <reset_handler>
80004: e59ff018 ldr pc, [pc, #24] ; 80024 <undefined_handler>
80008: e59ff018 ldr pc, [pc, #24] ; 80028 <swi_handler>
8000c: e59ff018 ldr pc, [pc, #24] ; 8002c <prefetch_handler>
80010: e59ff018 ldr pc, [pc, #24] ; 80030 <data_handler>
80014: e59ff018 ldr pc, [pc, #24] ; 80034 <unused_handler>
80018: e59ff018 ldr pc, [pc, #24] ; 80038 <irq_handler>
8001c: e59ff018 ldr pc, [pc, #24] ; 8003c <fiq_handler>
00080020 <reset_handler>:
80020: 00080040 andeq r0, r8, r0, asr #32
00080024 <undefined_handler>:
80024: 00080058 andeq r0, r8, r8, asr r0
00080028 <swi_handler>:
80028: 00080058 andeq r0, r8, r8, asr r0
0008002c <prefetch_handler>:
8002c: 00080058 andeq r0, r8, r8, asr r0
00080030 <data_handler>:
80030: 00080058 andeq r0, r8, r8, asr r0
00080034 <unused_handler>:
80034: 00080058 andeq r0, r8, r8, asr r0
00080038 <irq_handler>:
80038: 0008005c andeq r0, r8, ip, asr r0
0008003c <fiq_handler>:
8003c: 00080058 andeq r0, r8, r8, asr r0
00080040 <reset>:
80040: e3a00702 mov r0, #524288 ; 0x80000
80044: e3a01000 mov r1, #0
80048: e8b003fc ldm r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
8004c: e8a103fc stmia r1!, {r2, r3, r4, r5, r6, r7, r8, r9}
80050: e8b003fc ldm r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
80054: e8a103fc stmia r1!, {r2, r3, r4, r5, r6, r7, r8, r9}
00080058 <hang>:
80058: eafffffe b 80058 <hang>
0008005c <irq>:
8005c: eafffffe b 8005c <irq>
这都迫使入口点的8个单词从异常处理程序表中启动,并在接下来的8个单词之后立即放置这些地址以供pc相对访问,因此您需要复制16个单词以使汇编器完成工作为您而不必计算这些东西。 32个字,每条4个指令8个寄存器,即32个字。或者,如果您希望使用8套指令,每套指令也可以使用4个字。
这就是您采用整个方法的目的
80008: e59ff018 ldr pc, [pc, #24] ; 80028 <swi_handler>
00080028 <swi_handler>:
80028: 00080058
使该工具为您完成工作
该怎么办?
.globl _start
_start:
ldr pc,reset_handler
ldr pc,undefined_handler
ldr pc,swi_handler
ldr pc,prefetch_handler
ldr pc,data_handler
ldr pc,unused_handler
b irq
ldr pc,fiq_handler
reset_handler: .word reset
undefined_handler: .word hang
swi_handler: .word hang
prefetch_handler: .word hang
data_handler: .word hang
unused_handler: .word hang
irq_handler: .word irq
fiq_handler: .word hang
reset:
mov r0,#0x80000
mov r1,#0x0000
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
hang:
b hang
irq:
b irq
我明白了
80018: ea00000f b 8005c <irq>
代替此
80018: e59ff018 ldr pc, [pc, #24] ; 80038 <irq_handler>
后者是说从pc + 24读取的PC在这种情况下比该PC早8,因此指令地址+ 32即指令地址+ 0x20。
还有这个
80018: ea00000f b 8005c <irq>
是说要跳转到指令地址之前的地址0x44
现在让我们从另一个基址中反汇编,例如,对象(而不是链接的elf二进制文件)是一个很好的选择
00000000 <_start>:
0: e59ff018 ldr pc, [pc, #24] ; 20 <reset_handler>
4: e59ff018 ldr pc, [pc, #24] ; 24 <undefined_handler>
8: e59ff018 ldr pc, [pc, #24] ; 28 <swi_handler>
c: e59ff018 ldr pc, [pc, #24] ; 2c <prefetch_handler>
10: e59ff018 ldr pc, [pc, #24] ; 30 <data_handler>
14: e59ff018 ldr pc, [pc, #24] ; 34 <unused_handler>
18: ea00000f b 5c <irq>
1c: e59ff018 ldr pc, [pc, #24] ; 3c <fiq_handler>
注意所有其他机器码,在此指令之前将字0x20字节装入PC。
分支在程序计数器前面表示分支0x44字节。
我们使用工具链制作了表格
00080020 <reset_handler>:
80020: 00080040 andeq r0, r8, r0, asr #32
00080024 <undefined_handler>:
80024: 00080058 andeq r0, r8, r8, asr r0
00080028 <swi_handler>:
80028: 00080058 andeq r0, r8, r8, asr r0
0008002c <prefetch_handler>:
8002c: 00080058 andeq r0, r8, r8, asr r0
00080030 <data_handler>:
80030: 00080058 andeq r0, r8, r8, asr r0
00080034 <unused_handler>:
80034: 00080058 andeq r0, r8, r8, asr r0
00080038 <irq_handler>:
80038: 0008005c andeq r0, r8, ip, asr r0
0008003c <fiq_handler>:
8003c: 00080058 andeq r0, r8, r8, asr r0
如果我们将0x40字节从0x80000复制到0x00000,那么当它在0x18处读取从0x38读取的机器代码并将其放在程序计数器中时,它将得到0008005c,这是正确的位置
但是如果找到
18: ea00000f b 5c <irq>
这意味着分支到没有处理程序的0x5c。
除了未设置堆栈指针以及代码如何将其设置为swi以外,但无论如何,如果您构建了此指针
80008: e59ff074 ldr pc, [pc, #116] ; 80084 <print_c_mem4+0x4>
8000c: ea000013 b 80060 <print_mem1>
80010: ea000012 b 80060 <print_mem1>
80014: ea000012 b 80064 <print_mem2>
80018: ea000012 b 80068 <print_mem3>
8001c: ea000012 b 8006c <print_mem4>
80020: ea00000e b 80060 <print_mem1>
80024: ea00000e b 80064 <print_mem2>
80028: ea00000e b 80068 <print_mem3>
8002c: ea00000e b 8006c <print_mem4>
或类似的东西,因为您的print_mems不仅是占位符,而且可以使此示例构建为此答案。但仍然是pc的相对分支。
并且您从0x80008复制了一段时间到0x00000,那么最后一条指令位于地址0x00000008,它是svc / swi处理程序。
80010: ea000012 b 80060 <print_mem1>
一个分支到print_mem1,但是它不会靠近print_mem1的任何地方,因为它将在0x00000之后分支一些字节,这将与您真正希望它登陆的地址相距0x80008字节。
现在所有这些,如果您在arm文档中搜索HVBAR,您将发现不必执行任何复制操作,您可以在内存中设置异常表并更改处理器运行时的基址。发生异常(重置除外)。但请注意,低5位必须为零,因此0x80008将不起作用。因此,在代码中使用.balign,在其中建立表,使用标签获取该地址并将其粘贴在HVBAR中,然后可以使用branch而不是ldr pc。对于armv6和更早的版本,需要进行复制或构建表,因为除用于高地址的处理器带外,向量必须位于0x00000000。对于armv7和许多cortex-ms,您可以将表移动/指向其他地址(直到重置)。
很高兴理解我演示的复制技巧,但是您必须正确使用它才能起作用。这不是一个不常见的解决方案。请注意另一种完成此操作的方法,您可以在此处执行以下操作:
.globl _start
_start:
b 0x80000
b 0x80004
b 0x80008
b 0x8000C
链接为0x0000时
00000000 <_start>:
0: ea01fffe b 80000 <_stack>
4: ea01fffe b 80004 <*ABS*0x80004>
8: ea01fffe b 80008 <*ABS*0x80008>
c: ea01fffe b 8000c <*ABS*0x8000c>
因此,此机器码ea01fffe表示相对于该指令的地址跳转到0x80000,因此,您可以只写从0x00000000开始的前8个字来代替副本,然后处理器将跳转到您的0x80000表。如果您想在0x80008上构建它,那么让这些工具为您完成工作:
.globl _start
_start:
b 0x80008
b 0x8000c
b 0x80010
b 0x80014
如预期的那样,立即数是单词数,0x8是两个单词,将2加到1fffe中得到0x20000
00000000 <_start>:
0: ea020000 b 80008 <*ABS*0x80008>
4: ea020000 b 8000c <*ABS*0x8000c>
8: ea020000 b 80010 <*ABS*0x80010>
c: ea020000 b 80014 <*ABS*0x80014>
我们也知道pc在前面2,所以当在地址0执行时,以这种方式使用pc是8,我们想转到0x80008,即pc前面的0x80000,指令中的立即数以字,所以前面是0x20000个字。
所以不是副本
ldr r0,=0xEA020000
ldr r1,=0x00000000
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
str r0,[r1],#4
或其他一些解决方案,将分支的正确位置填充到这8个位置。
编辑
让工具为我们做到这一点的另一种方法:
.text部分的反汇编:
00080000 <_stack>:
80000: e59ff018 ldr pc, [pc, #24] ; 80020 <reset_handler>
80004: e59ff018 ldr pc, [pc, #24] ; 80024 <undefined_handler>
80008: e59ff018 ldr pc, [pc, #24] ; 80028 <swi_handler>
8000c: e59ff018 ldr pc, [pc, #24] ; 8002c <prefetch_handler>
80010: e59ff018 ldr pc, [pc, #24] ; 80030 <data_handler>
80014: e59ff018 ldr pc, [pc, #24] ; 80034 <unused_handler>
80018: e59ff018 ldr pc, [pc, #24] ; 80038 <irq_handler>
8001c: e59ff018 ldr pc, [pc, #24] ; 8003c <fiq_handler>
是我们可以使用e59ff018填充内存的前8个字,然后在需要它们的某个时候可以稍后填充地址,然后在创建中断之前使用处理程序的地址填充0x00000038,可以使用C或ASM管他呢。可以每次更改处理程序,在执行svc / swi指令之前,将0xe59ff018放在内存中的0x00000008处,并将swi处理程序的地址放在0x00000028处,在0x00000028处更改处理程序,然后重试。