【问题标题】:How to run each "function" in a code section?如何在代码段中运行每个“函数”?
【发布时间】:2020-11-21 17:53:56
【问题描述】:

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

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

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

问题是,一旦第一个返回,我不知道如何转到集合中的下一个函数。或者可能有函数在最后不生成“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 assembly linker arm embedded


    【解决方案1】:

    不要把函数本身放在一个特殊的部分,而是把指向函数的指针放在这个特殊的部分:

    void disk_driver_init()  {
        dev_register(&diskDevice);
    }
    
    void __attribute__((section(".driverinit")))(* const disk_driver_init_ptr)() = disk_driver_init;
    

    你需要一些东西来标记部分的结尾,所以把它单独放在一个文件中,让它成为你链接的最后一个目标文件:

    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++;
        }
    }
    

    请注意,此目标文件必须位于第一个,或者至少在链接时将函数指针放入.driverinit 的任何其他目标文件之前。

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

    【讨论】:

    • 啊,是的,这太棒了!感谢您的建议!
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 这基本上就是我想要做的。我需要将函数添加到该数组中,为此,我需要调用将它们添加到数组中的函数。如果可能的话,我会尽量避免从单个文件中包含和引用所有函数。
      • @RockyPulley:我认为您通常会安排链接器在构建时将它们添加到该数组中(IDK 如何或我会回答,但也许将.word funcname 组装到正确的部分),而不是在运行时运行函数来修改初始化函数指针数组。
      • 您将名称放入数组中,链接器将它们填充为正确代码的地址。名称就像任何其他指针一样只是指针。我告诉我的学生一个指针只是一个态度很大的无符号长整数,在这种情况下是一个具有参数类型、数字、位置和返回类型签名的函数。所以,'strlen' 是一个指向函数的指针,它只接受一个字符指针并返回一个 size_t: size_t (*strlen)( char * ),您可以将它与其他接受一个 char 参数的函数放在一个数组中指针并返回一个 size_t。
      猜你喜欢
      • 2013-07-01
      • 2014-12-04
      • 2022-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-17
      • 2021-05-26
      • 2018-02-16
      相关资源
      最近更新 更多