本篇我们主要来看一下第一个Commit里面的Makefile
, alice.ld
以及最开始代码startup.S
.
这里会介绍一下查看反汇编的方法以及写ARM汇编的小技巧。
Makefile & Monitor
我这里使用了Makefile来进行构建,关于Makefile的教程有很多, 由于我也不准备构建多复杂的系统,所以Makefile写的会比较简单。 这里给两个链接以供参考:
里面的规则等等都比较简单,这里主要说一下启动Qemu的一些区别:
1 | qemu: $(OS).bin |
make qemu
会直接编译并运行,make qemu-gdb
和make qemu-telnet
则分别让
qemu将1234端口开放给gdb与telnet;
QEMU Monitor可以让我们在系统挂住的时候查看一些寄存器的状态
nowait
的作用是为了让QEMU在monitor连上之前自己也会往下跑,
因为我们主要是为了查看最后系统hung住时的寄存器状态.
GDB的使用我们以后再说~
Link Script
链接脚本的语法讲解网上也非常多,这里提供一个链接供参考:Linker Script.
这里主要看一下我们的地址:
1 | ENTRY(entry) |
我后面打算让Kernel从 0x80000000
开始映射,所以把kernel最开始的代码放到了
0x80000000
地址上,
这里引入一个start_address
的原因是因为不同的平台会默认让系统从不同的地址启动.
为了让后面映射地址时方便计算而引入的。
也就是说,当我们的虚拟地址映射建立好之后,
内核的第一行代码应该是从0x80000000 + start_address
开始的。
Bin & Elf
接下来就是我们的第一行代码了,ARM平台上当内核镜像被放到内存中之后, CPU会从加载的第一条指令直接开始执行。 这也就要求我们编译完内核镜像后去掉开头的elf header;
我们用gcc编译完的镜像本身是elf格式的,这个格式有一个header:
xxd alice.bin > alice.bin.dump
xxd alice.elf > alice.elf.dump
1 | /* alice.bin.dump */ |
用xxd
查看16进制,会发现alice.bin的内容也就是我们内核实际的内容非常简单,
开头的 3302
就是我们的 0x233
的小端写法,与我们的反汇编一致,开头就是指令;
那么alice.elf.dump呢?
...
4096 0000fff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
4097 00010000: 3302 00e3 efbe adde 0000 4126 0000 0061 3.........A&...a
4098 00010010: 6561 6269 0001 1c00 0000 0543 6f72 7465 eabi.......Corte
4099 00010020: 782d 4139 0006 0a07 4108 0109 022a 0144 x-A9....A....*.D
...
发现我们的代码3302 00e3
是从第二个页(4097)开始的, 第一个页是elf自己加的东西;
在后面也还有一些跟体系结构相关的东西(Cortex-A9). 所以elf文件本身有自己的格式,
但是ARM设备是从第一条指令直接执行的, 所以我们要用objcopy把我们要的东西从
elf文件里面copy出来!
再次看makefile:
1 | ... |
可以看到, 我们的alice.bin实际上就是
arm-none-eabi-objcopy -O binary alice.elf alice.bin
生成的.
但是我们的objdump又需要用到elf格式,这样它才认识里面的debug symbol,
才能生成方便我们调试的反汇编。
First Instruction
现在终于可以来看看我们的第一条指令了:
1 | .global entry |
内容非常简单,就是把0x233放到r0里面,
这里.word 0xdeadbeef
就是在代码段里面插入一个错误的指令,
当CPU执行完mov r0, #0x233
后就会尝试解析0xdeadbeef
,
由于解析失败,所以就会卡在这里。
Register Info
使用 make qemu-telnet
并且在另一个shell连上, 我们来看一下寄存器信息:
1 | alice@MacAlice ‹ 9d021c0 › : ~/Codes/alice-os |
R0
和我们想的一样,内容就是233, 启动寄存器这里也一并介绍一下:
Reg | Alias | Purpose |
---|---|---|
R0 - R3 | - | Caller Saved General Purpose |
R4 - R8 | - | Callee Saved General Purpose |
R9 | - | Might Callee Saved General Purpose |
R10 | - | Callee Saved General Purpose |
R11 | FP | Frame Pointer / Callee Saved General Purpose |
R12 | IP | Intra Procedural Call (Used by Dynamic Link) |
R13 | SP | Stack Pointer |
R14 | LR | Link Register (Save Return Address) |
R15 | PC | Program Counter |
PSR | - | Program Status Register |
Caller / Callee 相关的可以看一下aapcs calling convention. 主要是说函数调用时, r0-r3我们可以随便用; R13是我们的栈, R14存的返回地址, R15是是PC.
但是我们发现,本应该存返回地址的R14里面存的是什么呢:0x60010008
也就是说,现在CPU认为我们之前是从60010008跑到了异常的地方.
(我们执行了0xdeadbeef, CPU会产生异常,PC会变,
由于现在还没设置异常向量表,所以跳到了一个奇怪的地址: 0xeb25134
.
同时LR保存之前PC的地址: 0x60010008
).
这也就是说我们的系统一开始是从 0x60010000
开始运行的?!
下一次我们来验证一下我们的猜想!