链接器如何决定代码从哪里开始执行?(嵌入式)

How does the linker decide where the code execution will start from? [Embedded]

本文关键字:开始 执行 嵌入式 代码 何决定 决定 链接      更新时间:2023-10-16

作为嵌入式C编程的初学者,我非常好奇每一个(在我的经验中)程序执行如何从main()函数开始?这就像链接器识别main()并将"特殊"函数的地址放入复位向量指向的地址中。

通常链接器脚本创建一个特殊的部分,该部分映射到重置向量,并包含一个跳转/goto指令到C启动代码,该代码反过来调用main()。

C定义了在"托管"环境中运行的代码和在"独立"环境中运行的代码的不同规范。大多数程序员在他们的整个职业生涯中都不需要处理一个独立的环境,但大多数例外是那些使用嵌入式编程、内核编程、引导加载程序和其他运行在裸机上的软件的程序员。

在宿主环境中,C指定程序从调用main()开始执行。这并不排除系统在调用之前执行的初步设置,但这超出了规范的范围。C编译器和/或链接器负责安排这一切发生;细节取决于实现。

另一方面,在独立实现中,程序入口点以实现选择的方式确定。可能没有main()函数,如果有,那么它的签名不需要与在托管环境中运行的程序所允许的签名相匹配。

决定的不是链接器,而是处理器。在上电时,指令指针被设置为预定义的内存地址,通常与复位中断向量相同。然后链接器通过将分支指令放置到该地址的启动代码中来启动。

链接器链接一个模块用于处理器和运行时环境初始化。那个模块是从重置矢量进入的。在gcc工具链中,该模块通常称为crt0。0,从源文件crt0构建。S(汇编代码)。你的工具链可能会有所不同,但某些启动代码将被链接,并且源应该可用于自定义。

启动代码通常会执行硬件初始化,例如为所需的时钟速度配置锁相环,以及在使用外部存储器时初始化内存控制器。C运行时初始化需要设置堆栈指针,初始化全局静态数据,以及可能的运行时库初始化-例如堆和工作室初始化。对于c++,它还调用任何全局静态对象的构造函数。最后调用main()

注意,并不是链接器知道main();这只是运行时启动模块中一个未解析的链接。如果你的程序没有main(),它将链接失败。

您当然可以修改启动代码以使用main()以外的其他符号,但是main()是由语言标准定义为入口点的。

一些应用程序框架或环境可能没有main();例如,在RTOS VxWorks中,应用程序从usrAppInit()开始,但实际上这只是因为main()是在VxWorks库中定义的。

链接器根据程序集源代码中的指令或链接器脚本中的指令定位启动代码;工具链可能不同。

在ARM Cortex-M设备上,初始堆栈指针在vector表中定义并自动加载;因此,这些设备可以直接从重置中运行C代码(尽管在某种程度上有限的环境中),并且允许用C而不是汇编语言编写大部分运行时环境初始化。

每个处理器和工具链是不同的。但是,通常将它们设置在从重置向量到达运行时库的入口点(很多时候是_start)的地方。运行时库准备处理器状态,清除.bss内存,初始化.data内存,可能设置堆,并调用一些调用以允许自定义启动,然后调用所有全局构造函数(如果是c++),最后跳转到main()。

它是硬件需求、工具链假设、运行时库和系统代码的混合。你可以删减很多,因为C语言唯一真正的要求就是你有一个堆栈。其余部分是库代码,您可能会使用,也可能不会使用。

为了满足标准或至少是程序员的期望,在main之前,您需要清除bss,编译时初始化变量(例如带有=什么的全局变量),c库和其他有趣的东西。所以你就有了鸡生蛋还是蛋生鸡的问题,你怎么能用C代码来满足这些假设和要求,又怎么能用C代码来满足这些要求。你不。还有其他代码,并不罕见的汇编,但可能来自C,其中的假设已知是不正确的。有时称为引导代码。不管这是一个嵌入式系统还是一个运行在操作系统上的应用程序。在该"程序"的第一个指令之间有一些粘合。如果你反汇编gnu工具创建的东西,你可以看到这个执行路径在一个名为_start和main的标签之间。其他工具链可能会也可能不会以不同的方式命名它们的入口点。

在微控制器或情况下,你可能是裸机(pc上的bios,启动rtos/os的启动代码)的最低限度,如果你不关心C的一些要求/假设,加载堆栈指针和分支到main是你所需要的。将bss归零和将。data从flash复制到ram中,是您需要接近C语言要求的接下来两件事,您会发现这些都是您在某些嵌入式系统中需要的步骤。

也可能是其他处理器,但是arm cortex-m硬件有能力加载堆栈指针并分支到一个地址(重置总是分支到一个地址或从某个已知地址运行代码),而且中断系统为你保存状态,所以你不需要在用C编写的中断服务例程周围包装asm(或者做一些编译器特定的声明,做同样的事情)(这是下一个问题,无论如何你都需要问,1)复位到C代码2)中断到C代码),所以中断向量表可以有地址直接到C函数。这是那个产品线的一个很好的特点。

使用工具链反汇编器并检查从入口点到main()的代码…过去的一些工具链肯定会在看到main()时做出假设,并添加额外的代码。所以有时你会看到一些其他的C函数名被用作第一个C函数,以避免工具链链接到其他东西。

Clifford一语中的,虽然链接器只是在寻找未解决的符号,一个是main,一个是gnu工具链,另一个是_start。它会链接它已经知道的东西或者你在命令行中提供的东西,直到所有的标签都被解析。