面灵AI→

嵌入式面试:链接脚本改过吗,为什么改完程序起不来

时间
2026-10
来源
牛客网

《面试题目》

  1. 链接脚本你改过吗?
  2. 改了哪几个段?为什么改完程序起不来?
  3. 复位后到 main 之间发生了什么?
  4. 向量表、链接脚本、段初始化、栈布局分别在启动过程中起什么作用?
  5. 一问 BootLoader、启动流程和内存布局,基础断档会暴露在哪?
  6. HardFault 现场是怎么定位的?

《参考解析》

先分清段,再谈「为什么起不来」

一个典型的固件镜像至少要管五个段:.isr_vector(向量表)、.text(代码)、.rodata(常量、字符串、查表)、.data(有初值的全局/静态变量)、.bss(未初始化或零初始化的全局/静态变量)。两个容易忽略的:C++ 的全局构造表(.init_array)和栈/堆(通常不放在镜像里,只在 RAM 里划区间)。

「改完起不来」绝大多数是加载地址(LMA)与运行地址(VMA)搞混。.data 的运行地址在 RAM,但初值存在 Flash 里,启动代码必须按链接脚本给出的 LMA 把它搬到 VMA,再清零 .bss。如果改了 .data 的区间却没同步改启动代码里的搬运长度,或者把 .data 放到了不该放的位置,表现就是全局变量初值全错、外设配置寄存器写了个垃圾值,程序在 main 之前或刚进 main 就挂。第二种常见原因是向量表偏移:把 .isr_vector 挪了位置但没设 SCB->VTOR,一进中断就跳到错误地址。第三种是栈:把栈放在 RAM 末尾却没留够空间,或者 _estack 符号没跟着改,一压栈就踩到别的数据。

启动流程按顺序讲

上电后 CPU 从向量表第一个字取初始 MSP、第二个字取 Reset_Handler 地址,然后跳过去。Reset_Handler 里依次做:关中断、初始化时钟与必要外设、把 .data 从 LMA 拷到 VMA、.bss 清零、调用 C 库初始化(__libc_init_array,C++ 的全局对象在这里构造)、最后 main。讲过一遍之后要能回答反问:为什么 .bss 不用存在 Flash 里(初值全是 0,启动时清零即可,省 Flash)、为什么 .data 不能直接在 Flash 里跑(Flash 只读,变量要可写)、栈指针为什么在进 main 之前就必须有效(任何函数调用都要压栈)。

BootLoader 与内存布局

带 BootLoader 的板子要讲清两件事:一是跳转前的准备——关中断、关外设、把 MSP 设成 App 向量表第一个字、设 VTOR 为 App 向量表地址,再跳到 App 的 Reset_Handler;二是分区表——Boot 区、App 区、参数/升级标志区、备份区各自的地址与大小,必须与链接脚本里 App 的起始地址一致,不一致就会出现「Boot 跳过去,App 一进中断就跑飞」。升级场景还要讲 A/B 双分区或备份回滚:新固件校验通过前不改升级标志,掉电后能回到旧版本。

HardFault 现场怎么定位

先看 LR 的值判断是从线程模式还是中断里进的,从对应的栈(MSP/PSP)取异常栈帧里的 PC、LR、xPSR,PC 指向的就是出错指令;再看 CFSR/HFSR/MMFAR/BFAR 判断类型——非法地址访问、非对齐访问、除零、还是取指总线错误。实战里最高频的是空指针和野指针解引用、以及栈溢出踩到别的区域。讲到这里能补一句「我会在 HardFault handler 里把栈帧打印到串口再复位」,就说明真调过。

按追问链准备

这类问题的分水岭不是知不知道名词,而是能不能说出「不用它会怎样、用错了什么现象、从哪查」。拿链接脚本举例,完整的追问链是:它解决什么问题(把不同属性的数据放进不同的存储区)→ 不改会怎样(默认分散加载可能把常量放进 RAM,RAM 不够用)→ 改错什么现象(.data 初值错、中断跑飞、栈溢出)→ 怎么查(看 map 文件的段地址与大小、打印 _sdata/_edata 符号、单步跟 Reset_Handler)→ 项目里哪段代码对应(启动文件里的搬运循环、VTOR 设置、分区表宏定义)。这五问走通一遍,比背十页教材有用。