深入理解计算机系统篇之链接(6):PIC可执行目标文件与RAM运行实现
1.摘要
在学习了PIC位置无关代码后,想要做一个位置无关代码的APP跑起来的测试,通过添加编译选项变成PIC代码,然后存入Flash中,编写Boot加载该APP程序,看看效果如何
2.APP
APP程序很简单,用cubeMX生成后直接用cmake进行编译,不过cmake文件有几行需要修改,首先是开启位置无关代码的编译选项
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
然后注释掉
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Map=${CMAKE_PROJECT_NAME}.map -Wl,--gc-sections")
这段是给最终生成的可执行文件的链接器添加两个选项:生成 map 文件、并启用垃圾回收无用段。因为会报错所以注释掉了。
3.Boot
跳转函数:
static int load_elf_from_flash(uint32_t flash_addr)
{
/* ELF头部指针指向闪存地址 */
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)flash_addr;
/* 校验ELF魔数 */
if (!(ehdr->e_ident[0] == 0x7F && ehdr->e_ident[1] == 'E' &&
ehdr->e_ident[2] == 'L' && ehdr->e_ident[3] == 'F')) {
printf("Validation of ELF magic number failed!\r\n");
return -1;
}
/* 校验程序头表的偏移和数量 */
if (ehdr->e_phoff == 0 || ehdr->e_phnum == 0) {
printf("Validation of ELF program header failed!\r\n");
return -2;
}
/* 程序头表加载地址 */
const Elf32_Phdr *phdr = (const Elf32_Phdr *)(flash_addr + ehdr->e_phoff);
/* 对于每个PT_LOAD,加载可加载段到指定内存地址 */
for (Elf32_Half i = 0; i < ehdr->e_phnum; ++i) {
if (phdr[i].p_type != PT_LOAD) continue;
uint32_t dst = phdr[i].p_vaddr;
uint32_t src = flash_addr + phdr[i].p_offset;
uint32_t filesz = phdr[i].p_filesz;
uint32_t memsz = phdr[i].p_memsz;
/* 校验目的地址和大小 */
if (dst == 0 || filesz > memsz) return -3;
uint32_t sp;
__asm volatile ("MRS %0, MSP" : "=r" (sp));
/* 拷贝段数据到目标地址 */
memcpy((void *)dst, (const void *)src, filesz);
__asm volatile ("MRS %0, MSP" : "=r" (sp));
/* 清零未初始化数据区 */
if (memsz > filesz) {
memset((void *)(dst + filesz), 0, (size_t)(memsz - filesz));
}
}
/*加载之后,准备跳转到应用程序*/
/* 找到应用程序的复位向量地址
* 如果有多个PT_LOAD段,取最低的p_vaddr作为向量表地址
*/
uint32_t lowest_vaddr = UINT32_MAX;
for (Elf32_Half i = 0; i < ehdr->e_phnum; ++i) {
if (phdr[i].p_type != PT_LOAD) continue;
if (phdr[i].p_vaddr < lowest_vaddr) lowest_vaddr = phdr[i].p_vaddr;
}
if (lowest_vaddr == UINT32_MAX) return -4;
/* 检查向量表内容 */
uint32_t *vec = (uint32_t *)lowest_vaddr;
uint32_t initial_sp = vec[0];
uint32_t app_handler = vec[1];
/* 验证初始堆栈指针地址是否在SRAM范围内 */
if ((initial_sp & 0xFF000000U) != 0x20000000U) {
/* Not a RAM stack pointer? still may be ok; skip strict check */
printf("Validation of initial SP failed!\r\n");
}
/* 关闭中断 */
__disable_irq();
/* 设置主堆栈指针(先设置 MSP,保险) */
__set_MSP(initial_sp);
/* 设置向量表偏移寄存器(VTOR) */
SCB->VTOR = lowest_vaddr;
__DSB();
__ISB();
__enable_irq(); // 可选:在跳转前重新启用中断
/* 跳转到应用程序复位处理函数(确保 Thumb 位=1) */
typedef void (*app_reset_t)(void);
app_reset_t app_start = (app_reset_t)((app_handler & ~1u) | 1u);
app_start();
return 0;
}