xv6-riscv 有这样一行代码:
// kernel/start.c
void start() {
...
// requires gcc **-mcmodel=medany**
w_mepc((uint64)main)
...
}
对应的,Makefile 里有这个:
CFLAGS += -mcmodel=medany
然后我就不禁要问了,我拿个 main 函数的地址,关这个 cmodel 啥事?网上翻了一圈后,就有了这篇文章。
关于 -mcmodel=medany
,简单地说,他是指定编译器如何生成访问全局变量指令的命令,cmodel 即是 code model 的意思。相对的,还有另一种模式是 medlow(默认的模式):
-
medany
:生成位置无关的代码,全局变量的地址通过auipc
生成(即他是 PC relative 的)。由于auipc
的限制,全局变量的地址必须在 PC ± 2G 以内 -
medlow
:low 模式,地址通过lui
生成,这限制了地址范围为 [0, 2G]
具体可以参考这篇文章。
理解了 code model 以后,我们就可以确信地说,拿个 main 函数的地址,跟 code model 其实没半毛钱关系。或者说,这不是使用 medany
的根本原因。
那么,要不我们试试把这个 flag 去掉?遗憾的是,如果你真的去掉了这个 flag,链接的时候你会发现有一堆错误:
kernel/start.o: in function `timerinit':
xv6-riscv/kernel/start.c:76:(.text+0x34): relocation truncated to fit: R_RISCV_HI20 against `.LANCHOR0'
kernel/console.o: in function `consoleread':
xv6-riscv/kernel/console.c:87:(.text+0x84): relocation truncated to fit: R_RISCV_HI20 against `.LANCHOR0'
......
意思是,函数 xxx 里面,某个需要重定位的地址超过了 R_RISCV_HI20
能够支持的范围,即超过了 2G(因为默认是 medlow
模式)。原因是,我们在链接的时候,把 text 段放在了 0x80000000
(= 2^31 = 2G),我们所有的代码都超过了 2G:
# kernel/kernel.ld
SECTIONS
{
/*
* ensure that entry.S / _entry is at 0x80000000,
* where qemu's -kernel jumps.
*/
. = 0x80000000;
.text : {
*(.text .text.*)
. = ALIGN(0x1000);
_trampoline = .;
*(trampsec)
. = ALIGN(0x1000);
ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
PROVIDE(etext = .);
}
...
也因此,我们才需要用 medany
。
你可以简单验证一下,比方说,把 kernel.ld 里的 0x80000000 改成 0x40000000。这样即使用默认的
medlow
,也能够编译成功(但是运行就不行了,qemu 默认会执行 0x80000000 处的代码)。
网友评论