深入理解计算机系统篇之链接(6):PIC可执行目标文件与RAM运行实现

Posted by WHX on December 7, 2025

深入理解计算机系统篇之链接(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;
}