【问题标题】:C startup code is only written in assembly confusionC 启动代码只写汇编混淆
【发布时间】:2021-08-18 01:00:30
【问题描述】:

我了解C启动代码是用于初始化C运行环境,初始化静态变量,设置堆栈指针等,最后分支到main()。

他们说这只能用汇编语言编写,因为它是特定于平台的。但是,这不能还是用C写,针对特定平台编译吗?

函数调用当然是不可能的,因为我们“很可能”在那个阶段没有设置堆栈指针。我仍然看不到其他主要原因。提前致谢。

【问题讨论】:

  • 严格符合 C 代码不能初始化处理器寄存器(例如帧指针),因为 C 标准没有提供任何引用它们的方法。
  • @EricPostpischil 除了堆栈指针问题,还有什么其他严格的原因不能使用 C 代码行来编写 C 启动例程?
  • 除了堆栈之外,您可能还必须使用 C 根本不支持的特殊指令进行初始化(例如切换模式、设置中断、内存模型等)
  • 我认为 C 编译器可以很好地维护每个平台的启动程序集 sn-ps,然后按需放置它们,但 sn-ps 只能在一个地方使用,因此库(标准库)是更适合他们的地方(?)。

标签: c assembly embedded startup


【解决方案1】:

只有在以下情况下才能用 C 语言编写启动代码:

  1. 实现提供了所有必要的内在函数来设置无法使用标准 C 设置的硬件功能
  2. 提供将代码和数据片段按特定顺序放置在特定位置的机制(例如 gcc 对 ld 链接器脚本的支持)。

如果两个条件都满足,就可以用C语言编写启动代码了。

我为 Cortex-M 微控制器使用我自己用 C 语言编写的启动代码(而不是芯片供应商提供的启动代码),因为 ARM 提供了具有所有需要的内联汇编功能的 CMSIS 头文件,而基于 gcc 的工具链为我提供了完整的内存布局控制。

【讨论】:

  • 回答的最后一部分:你说的是STM32吗? ST 在​​汇编中提供启动文件。可以有一个具体的原因吗?我知道有些供应商在 C 中提供它们。将它们迁移到 C 有什么好处?
  • 简单。我的启动比标准启动要复杂得多。用 C 比用汇编更容易编写和维护
  • @Tagli 供应商为 STM32 提供的启动只是部分组装(实际上很少)。 SystemInit() 例如是 C 代码。
  • @Tagli ... 汇编器只不过是跳转到 SystemInit() 然后跳转到 __main(),汇编器文件的大部分是向量表,主要是弱链接默认值可以用 C 代码覆盖。在汇编程序中这样做更简洁,并且不依赖于编译器之间不同的语言扩展和编译器指令。虽然汇编程序的语法和指令当然也不同
  • @Tagli 是的,你可以用 C 语言编写它。最好也添加放置在其他部分的数据的初始化。 __libc_init_array 也用于 C 中,当您使用函数属性时,__attribute__((constructor)) 将在调用 main 之前执行具有该属性的函数。
【解决方案2】:

实际上,用 C 语言编写早期启动代码的大部分问题是缺少结构正确的堆栈。这比不能进行函数调用更糟糕。 C 编译器生成的所有机器代码都假定存在一个堆栈,该堆栈由 ABI 指定的寄存器指向,可随时用于临时存储。改变这个假设的工作量很大,以至于编译器需要完成第二个“后端”——这比继续在汇编中手动编写早期启动代码要多得多。

早期的bootstrap 代码,从开机启动机器,还必须执行一些通常不能从C 访问的特殊操作,例如配置中断和虚拟内存。并且它可能必须处理没有在它链接的地址加载的代码,或者没有处理重定位表,或其他类似的问题;这些也打破了 C 编译器所做的普遍假设(例如,它可以随时注入对 memcpy 的调用)。

尽管如此,大部分用户模式 ​​C 库的启动代码实际上将用 C 编写,这正是您所想的原因。没有人愿意为每个受支持的 ISA 一遍又一遍地编写更多的汇编代码,而不是绝对必要的。

【讨论】:

  • 感谢您的回复。配置中断和虚拟内存部分,为什么通常无法实现它是C?我不明白这个。
  • @Engineer999 它涉及不能从 C 中访问的机器指令,例如“移入/移出控制寄存器”和“启用中断”。它还可能涉及在 C 中无法强制执行的优化约束,例如x86 规则,启用分页的 MOV CR0 必须立即后跟 FAR JMP。 其中一些可以使用程序集插入来处理,但根据更大的上下文,它可能会或可能不会在可移植性或可维护性方面为您带来太多好处。
【解决方案3】:

最小的 C 运行时环境需要一个堆栈和一个到起始地址的跳转。在大多数架构上设置堆栈指针需要汇编代码。一旦堆栈可用,就可以运行从 C 源代码生成的代码。

ARM Cortex-M 设备在复位时从向量表中加载堆栈指针和起始地址,因此实际上可以直接启动到从 C 源代码生成的代码。

在其他架构上,最小的汇编要求是设置一个堆栈指针,并跳转到起始地址。此后,可以用 C(甚至 C++)编写其他启动任务。此类启动代码负责建立完整的 C 运行时,因此不得假设静态初始化或库初始化(例如没有堆或文件系统),这些都是必须由启动代码完成的事情。

从这个意义上说,您可以运行从 C 源代码生成的代码,但是在调用 main() 之前,环境并不严格符合要求,因此存在一些限制。

即使使用汇编代码,也不必是汇编中的整个启动代码。

【讨论】:

  • 感谢您的回复。就你提到的 Cortex-M 设备的向量表而言,中断向量表中的条目是可编程的,还是固定的?
  • @Engineer999 ,它们是可编程的,但由于它们位于闪存上,这样做不太实用。但是,可以将它们移动到 RAM 中以使其易于修改。这需要一些硬件支持,我不确定是否所有平台都支持。
  • @Tagli 所以通常 ARM Cortex-M 设备带有一个固定的堆栈位置地址,它永远不会“正常”修改?以及中断处理程序地址..
  • @Engineer999 Cortex M3 和 M4 有一个名为 VTOR 的寄存器,它允许您将向量表放置在 FLASH 或 RAM 中的几乎任何位置(具有一些对齐约束)。我不确定供应商对VTOR 的实施是强制性的还是可选的。对于 STM32F0(Cortex M0),没有VTOR,但它们可以将 0x00 重新映射到 0x20'000'000(RAM 的开始)。默认情况下,0x00 映射到 0x8'000'000(闪存开始)。
  • @Engineer999 这是一个新问题和特定于架构的问题。问一个新问题。在 ARM Cortex-M 上,有一个向量表地址寄存器可以设置来重定位向量表。这可能是启动代码的一部分或在应用程序中完成。如果你在 RAM 中找到它,你当然可以在运行时修改中断处理程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 2019-09-07
相关资源
最近更新 更多