如何 运行 每个 "function" 在代码部分?

How to run each "function" in a code section?

我希望能够声明函数,以便将它们添加到特定的代码部分,然后它们都在我的启动函数之前执行。像这样:

void __attribute__((__section__(".driverinit"))) disk_driver_init()  {
    dev_register(&diskDevice);
}

目的是将它们中的每一个都添加到一个数组中,以便可以处理其中包含的函数。我知道我可以在全球范围内添加这些功能,但如果可以的话,我会尽量避免这种情况。

问题是,一旦第一个returns我不知道如何进入集合中的下一个功能。或者可能有函数在最后不生成“ret”并失败。不确定这是否可能。

这是反汇编,我希望它 运行 代码部分中的每个函数,然后如果可能的话跳转到末尾的 _start...

Disassembly of section .driverinit:

0000000000000000 <disk_driver_init>:
   0:   a9bf7bfd        stp     x29, x30, [sp, #-16]!
   4:   910003fd        mov     x29, sp
   8:   d0000040        adrp    x0, a000 <FLAG_APP_CMD+0x138>
   c:   9128e000        add     x0, x0, #0xa38
  10:   9400020f        bl      84c <dev_register>
  14:   d503201f        nop
  18:   a8c17bfd        ldp     x29, x30, [sp], #16
  1c:   d65f03c0        ret
  20:   14000002        b       28 <_start>
  24:   d65f03c0        ret

Disassembly of section .text.boot:

0000000000000028 <_start>:
  28:   d53800a0        mrs     x0, mpidr_el1
  2c:   92401c00        and     x0, x0, #0xff
  30:   b4000040        cbz     x0, 38 <startup_proc>

有人可以指引我正确的方向吗?

从某种意义上说,代码在函数之前运行的唯一情况是在 C++ 中,当为堆或 local/automatic 主对象变量(不是指针,堆上的真实对象作为全局或静态对象)触发构造函数时对象,或 automatic/local 到子例程,包括开头的 main()))。在 C/Assembly 世界中,某些东西必须先调用函数,然后再调用 start()。在 C 语言中,如果函数指针都具有相同的签名并遍历数组 运行,则可以将它们放入数组中。您可以在 start() 上放置一个包装器,或者在调用 start() 时调用准备例程,可能是为了响应 NULL 指针留在未初始化或丢弃的项目的位置。

与其将函数本身放在一个特殊的部分,不如将指向函数的指针放在这个特殊部分中:

void disk_driver_init()  {
    dev_register(&diskDevice);
}

void __attribute__((section(".driverinit")))(* const disk_driver_init_ptr)() = disk_driver_init;

您需要一些东西来标记该部分的结尾,因此请将其单独放在一个文件中,并将其作为您 link 的最后一个目标文件:

void __attribute__((section(".driverinit")))(* const driverinit_end)() = 0;

然后你可以像这样调用所有的驱动初始化函数:

void __attribute__((section(".driverinit")))(* const driverinit_start)() = 0;

void
call_driver_init_fns(void) {
    void (* const *fn)() = &driverinit_start + 1;
    while (fn != &driverinit_end) {
        (*fn)();
        fn++;
    }
}

请注意,当 linking.

时,此目标文件需要位于第一个,或者至少在任何其他将函数指针放入 .driverinit 的目标文件之前。

这基本上就是 C++ 构造函数的工作方式。您可以通过使用为 .driverinit 部分的开始和结束提供符号的自定义 linker 脚本来避免浪费 space _start_end 变量.还有 GCC constructor attribute,但如果你没有使用标准运行时,我猜你没有,你将不得不弄清楚它在你的平台上是如何工作的,并自己实现启动代码。

您需要做的是修改“C 运行时”(CRT),即调用 main() 之前执行的启动代码。我写了一些关于如何为嵌入式系统编写自己合适的 CRT 的指南 here

在内存设置完成之前,您无法可靠地调用任何 C 代码:至少在设置堆栈指针之前不能(ARM 通过读取地址 0 及以后的默认项自动执行此操作)。某些代码可能需要完成 MMU 初始化,或者 .data.bss 初始化。

如何做到这一点当然是系统和编译器特定的。低端 Cortex M 的设置并不那么先进,它们不使用虚拟内存等,所以根据我的记忆,MMU 设置的方式应该不会太多,除非你出于某种原因使用自定义内存映射等。您可以看看您当前的 CRT 是如何做的,然后在您自己的 CRT 中做同样的事情。