建设厅工作证查询网站微信公众号网页怎么制作
2026/4/2 5:29:01 网站建设 项目流程
建设厅工作证查询网站,微信公众号网页怎么制作,站点推广是什么意思,企业如何进行网络营销解释为什么即使加载到 0x80000000#xff0c;程序仍从 TCM 启动。这涉及 bin 文件格式、链接脚本、绝对地址编码和 bootaux 的工作机制。 一、bin 文件的结构 1. bin 文件是什么#xff1f; .bin 是纯二进制文件#xff0c;包含#xff1a; 机器码#xff08;指令#xf…解释为什么即使加载到0x80000000程序仍从 TCM 启动。这涉及 bin 文件格式、链接脚本、绝对地址编码和bootaux的工作机制。一、bin 文件的结构1. bin 文件是什么.bin是纯二进制文件包含机器码指令数据常量、变量初始值地址信息硬编码的绝对地址2. 向量表Vector Table的结构ARM Cortex-M 程序的开头是向量表结构如下地址偏移 内容 说明 ───────────────────────────────────────────── 0x00000000 __initial_sp 初始栈指针MSP 0x00000004 Reset_Handler 复位处理函数地址 ← 关键 0x00000008 NMI_Handler NMI中断处理函数地址 0x0000000C HardFault_Handler 硬错误处理函数地址 ... ... 其他中断处理函数向量表的前两个条目__initial_sp初始栈指针值Reset_Handler程序入口地址PC 的初始值3. 关键点地址是硬编码的编译时链接器会把函数地址写入向量表。例如TCM版本编译时// 链接脚本MIMX8ML8xxxxx_cm7_ram.ldMEMORY{m_interrupts:ORIGIN0x00000000,...// TCM地址m_text:ORIGIN0x00000400,...}// Reset_Handler函数被链接到地址 0x000004CD// 向量表中写入0x000004CDDDR版本编译时// 链接脚本MIMX8ML8xxxxx_cm7_ddr_ram.ldMEMORY{m_interrupts:ORIGIN0x80000000,...// DDR地址m_text:ORIGIN0x80000400,...}// Reset_Handler函数被链接到地址 0x800004CD// 向量表中写入0x800004CD二、链接脚本的作用1. 链接脚本决定地址链接脚本告诉链接器代码放在哪个地址数据放在哪个地址函数地址如何计算2. 地址编码到机器码中编译过程源代码 (hello_world.c) ↓ [编译] 目标文件 (.o) - 包含未解析的符号 ↓ [链接] ← 链接脚本在这里起作用 可执行文件 (.elf) - 所有地址已确定 ↓ [objcopy] 二进制文件 (.bin) - 纯二进制地址已硬编码链接时链接器会读取链接脚本确定每个段的地址计算所有函数和变量的绝对地址将这些地址写入机器码3. 示例函数调用// 源代码voidmain(void){PRINTF(hello);}// 编译后的机器码简化// 假设PRINTF函数在地址0x00001234TCM版本// 或者地址0x80001234DDR版本// 调用PRINTF的指令BL0x00001234← TCM版本硬编码的TCM地址// 或BL0x80001234← DDR版本硬编码的DDR地址三、为什么加载到0x80000000仍从 TCM 启动1. 实际发生的过程ext4load mmc2:2 0x80000000 /home/root/ecspi_loopback.bin这一步做了什么从 eMMC 读取 bin 文件将文件内容按字节顺序写入 DDR 的0x80000000bin 文件内容TCM版本地址偏移 内容十六进制 含义 ───────────────────────────────────────────── 0x0000 0x20020000 __initial_sp (栈指针) 0x0004 0x000004CD Reset_Handler地址 ← 这是TCM地址 0x0008 0x00000501 NMI_Handler地址 ... 0x04CD 0xB510 Reset_Handler的机器码 0x04CE 0x4801 ... ...加载到0x80000000后内存内容DDR地址 内容 含义 ───────────────────────────────────────────── 0x80000000 0x20020000 __initial_sp 0x80000004 0x000004CD Reset_Handler地址 ← 还是TCM地址 0x80000008 0x00000501 NMI_Handler地址 ...2.bootaux命令的工作机制bootaux 0x80000000bootaux做了什么// bootaux的伪代码简化voidbootaux(uint32_taddress){// 1. 从指定地址读取向量表uint32_t*vector_table(uint32_t*)address;// 2. 读取初始栈指针uint32_tinitial_spvector_table[0];// 0x80000000处的内容// 3. 读取Reset_Handler地址这是关键uint32_treset_handlervector_table[1];// 0x80000004处的内容// 对于TCM版本的binreset_handler 0x000004CDTCM地址// 对于DDR版本的binreset_handler 0x800004CDDDR地址// 4. 设置M7的栈指针M7_MSPinitial_sp;// 5. 设置M7的程序计数器PCM7_PCreset_handler;// ← 跳转到这个地址执行// 6. 启动M7核start_M7_core();}3. 问题所在对于 TCM 版本的 bin 文件步骤1: ext4load mmc 2:2 0x80000000 ecspi_loopback.bin ↓ 将bin文件内容复制到DDR的0x80000000 步骤2: bootaux 0x80000000 ↓ 从0x80000004读取Reset_Handler地址 ↓ 读取到0x000004CD这是TCM地址 ↓ 设置PC 0x000004CD ↓ M7从TCM的0x000004CD开始执行即使 bin 文件在 DDR 的0x80000000向量表中硬编码的地址仍是 TCM 地址所以 PC 会跳回 TCM。四、地址映射关系1. M7 的地址空间M7本地地址空间M7视角 系统地址空间A53/U-Boot视角 ───────────────────────── ─────────────────────────── 0x00000000 (ITCM) → 0x007E0000 0x20000000 (DTCM) → 0x1F800000 0x40000000 (DDR区域1) → 0x40000000 0x80000000 (DDR区域2) → 0x800000002. 为什么0x000004CD能执行M7 执行时使用本地地址空间0x000004CD在 M7 本地地址空间中是 ITCM即使从系统地址0x7E0000加载M7 执行时仍按本地地址0x000004CD访问 ITCM3. 地址不匹配的问题U-Boot加载位置0x80000000系统地址DDR区域2 ↓ bin文件内容向量表中Reset_Handler 0x000004CDTCM地址 ↓ bootaux读取PC 0x000004CD ↓ M7执行从0x000004CDTCM开始执行 ↓ 但是代码实际在DDR的0x80000000不在TCM ↓ 结果程序可能崩溃或执行错误代码五、为什么 TCM 版本能工作1. 正确的加载流程ext4load mmc2:2 0x48000000 hello_world.bin# 临时加载到DDRcp.b 0x48000000 0x7e0000${filesize}# 复制到TCM系统地址bootaux 0x7e0000# 从TCM启动过程步骤1: ext4load → 0x48000000DDR临时缓冲区 步骤2: cp.b → 0x7e0000TCM的系统地址 ↓ 地址映射0x7E0000系统→ 0x00000000M7本地 ↓ 代码被复制到M7的ITCM0x00000000开始 步骤3: bootaux 0x7e0000 ↓ 从0x7e0004读取Reset_Handler 0x000004CD ↓ PC 0x000004CD ↓ M7从ITCM的0x000004CD执行 ← 代码就在这里 ↓ 成功2. 为什么需要cp.b因为ext4load只能加载到 DDR0x48000000TCM 版本的程序必须运行在 TCM需要将代码从 DDR 复制到 TCM六、DDR 版本如何工作1. DDR 版本的链接脚本// MIMX8ML8xxxxx_cm7_ddr_ram.ld MEMORY { m_interrupts : ORIGIN 0x80000000, LENGTH 0x00000400 m_text : ORIGIN 0x80000400, LENGTH 0x001FFC00 }编译后向量表地址0x80000000Reset_Handler 地址0x800004CDDDR 地址2. DDR 版本的 bin 文件内容地址偏移 内容十六进制 含义 ───────────────────────────────────────────── 0x0000 0x80200000 __initial_sp (DDR中的栈) 0x0004 0x800004CD Reset_Handler地址 ← DDR地址 0x0008 0x80000501 NMI_Handler地址 ...3. DDR 版本的启动流程ext4load mmc2:2 0x80000000 ecspi_loopback_ddr.bin bootaux 0x80000000过程步骤1: ext4load → 0x80000000DDR区域2 ↓ 代码直接加载到DDR的0x80000000 步骤2: bootaux 0x80000000 ↓ 从0x80000004读取Reset_Handler 0x800004CD ↓ PC 0x800004CD ↓ M7从DDR的0x800004CD执行 ← 代码就在这里 ↓ 成功七、核心原理总结1. bin 文件包含硬编码地址链接时链接器根据链接脚本确定所有地址这些地址被硬编码到机器码中bin 文件是纯二进制不包含重定位信息2. 地址必须匹配加载地址 ≠ 运行地址 → 程序无法运行 加载地址 运行地址 → 程序可以运行3. 为什么不能混用TCM版本的bin - 链接地址0x00000000TCM - 向量表中Reset_Handler0x000004CDTCM地址 - 必须加载到0x7e0000系统地址映射到TCM - 不能加载到0x80000000DDR地址不匹配 DDR版本的bin - 链接地址0x80000000DDR - 向量表中Reset_Handler0x800004CDDDR地址 - 必须加载到0x80000000DDR地址 - 不能加载到0x7e0000TCM地址不匹配4. 类比理解想象bin文件是一张地图 TCM版本的bin文件 Reset_Handler在TCM的0x000004CD号房间 即使你把地图放在DDR的0x80000000号房间 地图上写的还是去TCM的0x000004CD号房间 所以程序还是会去TCM执行 DDR版本的bin文件 Reset_Handler在DDR的0x800004CD号房间 地图放在DDR的0x80000000号房间 地图上写去DDR的0x800004CD号房间 所以程序在DDR执行八、如何验证方法1查看 bin 文件内容# 使用hexdump或xxd查看bin文件的前几个字节hexdump -C ecspi_loopback.bin|head-5# TCM版本会看到# 00000000 00 00 20 20 cd 04 00 00 ... ← Reset_Handler 0x000004CD# DDR版本会看到# 00000000 00 00 20 80 cd 04 00 80 ... ← Reset_Handler 0x800004CD方法2查看链接脚本# TCM版本使用MIMX8ML8xxxxx_cm7_ram.ld → m_interrupts:ORIGIN0x00000000# DDR版本使用MIMX8ML8xxxxx_cm7_ddr_ram.ld → m_interrupts:ORIGIN0x80000000方法3查看编译输出# 编译时查看map文件# TCM版本Reset_Handler地址在0x0000xxxx# DDR版本Reset_Handler地址在0x8000xxxx九、总结原因bin 文件中的地址是链接时硬编码的由链接脚本决定。为什么加载到0x80000000仍从 TCM 启动向量表中的Reset_Handler地址是 TCM 地址0x000004CDbootaux读取后设置 PC 为该地址因此跳回 TCM。原理链接脚本决定代码的链接地址链接器将地址硬编码到机器码bin 文件包含这些硬编码地址bootaux从向量表读取入口地址并设置 PC如果地址不匹配程序无法正确执行因此必须使用与链接地址匹配的加载地址TCM 版本加载到 TCMDDR 版本加载到 DDR。解释编译和链接的作用、原理和过程。[4 tools called]解释编译和链接的作用、原理和过程。一、整体流程概览源代码文件 (.c, .h, .S) ↓ [编译 Compilation] ↓ 目标文件 (.o) - 包含机器码但地址未确定 ↓ [链接 Linking] ↓ 可执行文件 (.elf) - 所有地址已确定 ↓ [objcopy] ↓ 二进制文件 (.bin) - 纯二进制可直接运行二、编译Compilation的作用1. 编译做什么编译将源代码.c、.S转换为目标文件.o生成机器码但地址尚未确定。2. 编译的输入和输出输入源代码文件.c、.S头文件.h编译器选项-O2、-g、-mcpucortex-m7等输出目标文件.o包含机器码、符号表、重定位信息3. 编译过程详解步骤1预处理Preprocessing// hello_world.c#includefsl_debug_console.h// 预处理会展开这个头文件#includead7606.hintmain(void){AD7606_Init();// 调用AD7606_Init函数return0;}预处理后简化// 展开头文件内容// fsl_debug_console.h的内容被插入// ad7606.h的内容被插入intmain(void){AD7606_Init();// 函数声明来自ad7606.hreturn0;}步骤2编译Compilation编译器将 C 代码转换为汇编代码// C代码intmain(void){AD7606_Init();return0;}转换为汇编简化main: push {lr} ; 保存返回地址 bl AD7606_Init ; 调用AD7606_Init地址未知 mov r0, #0 ; return 0 pop {pc} ; 返回步骤3汇编Assembly汇编器将汇编代码转换为机器码bl AD7606_Init ; 汇编指令转换为机器码简化机器码0xF000F000 (BL指令的编码) 目标地址AD7606_Init ← 这是符号地址还不知道4. 目标文件.o的结构目标文件包含1) 机器码段.text// hello_world.c编译后的.obj文件.text段 地址偏移 机器码 对应的源代码 ──────────────────────────────────────0x00000xB500push{lr}0x00020xF000F000bl AD7606_Init ← 地址是占位符0x00060x2000mov r0,#00x00080xBD00pop{pc}2) 符号表Symbol Table符号表 符号名 类型 地址相对 说明 ────────────────────────────────────────────── main 函数0x0000定义在本文件 AD7606_Init 函数???未定义需要从其他文件找 PRINTF 函数???未定义需要从库中找3) 重定位表Relocation Table重定位表 位置 类型 符号名 说明 ──────────────────────────────────────────────0x0004R_ARM_CALL AD7606_Init 需要填入AD7606_Init的地址0x0010R_ARM_CALL PRINTF 需要填入PRINTF的地址5. 为什么地址未确定编译时编译器不知道其他文件中的函数在哪里库函数在哪里全局变量在哪里最终程序会放在哪个内存地址因此编译只能生成相对地址的机器码记录需要重定位的位置记录未解析的符号三、链接Linking的作用1. 链接做什么链接将多个目标文件.o合并成一个可执行文件.elf完成解析所有符号函数、变量确定所有地址重定位代码和数据合并所有段2. 链接的输入和输出输入多个目标文件.o库文件.a链接脚本.ld输出可执行文件.elf包含所有代码和数据地址已确定3. 链接过程详解步骤1收集所有目标文件链接器收集 hello_world.c.obj ad7606.c.obj pin_mux.c.obj board.c.obj fsl_ecspi.c.obj (来自SDK库) fsl_gpio.c.obj (来自SDK库) startup_MIMX8ML8_cm7.S.obj ...步骤2读取链接脚本// MIMX8ML8xxxxx_cm7_ram.ld MEMORY { m_interrupts : ORIGIN 0x00000000, LENGTH 0x00000400 m_text : ORIGIN 0x00000400, LENGTH 0x0001FC00 m_data : ORIGIN 0x20000000, LENGTH 0x00020000 } SECTIONS { .interrupts : { *(.isr_vector) } m_interrupts .text : { *(.text) } m_text .data : { *(.data) } m_data }链接脚本告诉链接器代码放在0x00000400TCM数据放在0x20000000DTCM向量表放在0x00000000步骤3符号解析Symbol Resolution链接器查找所有符号的定义符号查找过程 1. 查找 AD7606_Init - 在 hello_world.c.obj 中未定义只是调用 - 在 ad7606.c.obj 中找到了地址待定 2. 查找 PRINTF - 在所有.obj文件中未定义 - 在库文件 libc.a 中找到了 3. 查找 Reset_Handler - 在 startup_MIMX8ML8_cm7.S.obj 中找到了步骤4地址分配Address Assignment链接器根据链接脚本分配地址地址分配过程 1. 向量表.interrupts段 起始地址0x00000000 - __initial_sp: 0x00000000 - Reset_Handler: 0x00000004 - NMI_Handler: 0x00000008 ... 2. 代码段.text段 起始地址0x00000400 - Reset_Handler函数0x000004CD - SystemInit函数0x00000501 - AD7606_Init函数0x00001234 - main函数0x00001500 - PRINTF函数库0x00002000 3. 数据段.data段 起始地址0x20000000 - 全局变量0x20000000 ...步骤5重定位Relocation链接器修改机器码中的地址重定位过程 1. hello_world.c.obj中的调用 原始机器码bl AD7606_Init (地址未知) ↓ 链接器计算AD7606_Init 0x00001234 ↓ 修改机器码bl 0x00001234 (地址已确定) 2. hello_world.c.obj中的调用 原始机器码bl PRINTF (地址未知) ↓ 链接器计算PRINTF 0x00002000 ↓ 修改机器码bl 0x00002000 (地址已确定)步骤6合并段Section Merging链接器将所有目标文件的段合并合并过程 所有.obj文件的.text段 → 合并到最终.elf的.text段 所有.obj文件的.data段 → 合并到最终.elf的.data段 所有.obj文件的.bss段 → 合并到最终.elf的.bss段四、实际例子例子1函数调用源代码// hello_world.c#includead7606.hintmain(void){AD7606_Init();// 调用其他文件中的函数return0;}// ad7606.cvoidAD7606_Init(void){// 初始化代码}编译后// hello_world.c.obj符号表 main:定义地址0x0000相对地址 重定位表 位置0x0004:需要填入AD7606_Init的地址 机器码0x0000:push{lr}0x0002:bl???← AD7606_Init的地址未知0x0006:mov r0,#00x0008:pop{pc}// ad7606.c.obj符号表 AD7606_Init:定义地址0x0000相对地址 机器码0x0000:push{r4,lr}0x0002:...初始化代码...0x0050:pop{r4,pc}链接后// hello_world.elf链接器的工作1.符号解析-AD7606_Init在ad7606.c.obj中找到2.地址分配根据链接脚本-main函数0x00001500-AD7606_Init函数0x000012343.重定位-hello_world.c.obj中0x0002处的bl指令-修改为bl0x00001234最终机器码0x1500:push{lr}0x1502:bl0x00001234← 地址已确定0x1506:mov r0,#00x1508:pop{pc}0x1234:push{r4,lr}← AD7606_Init的代码0x1236:...初始化代码...0x1284:pop{r4,pc}例子2全局变量源代码// ad7606.cAD7606_FIFO_T g_tAD7606;// 全局变量voidAD7606_Init(void){g_tAD7606.usRead0;// 使用全局变量}// hello_world.cexternAD7606_FIFO_T g_tAD7606;// 声明外部变量intmain(void){g_tAD7606.usWrite0;// 使用全局变量return0;}编译后// ad7606.c.obj符号表 g_tAD7606:定义大小16字节.bss段未初始化数据 预留16字节空间地址未定// hello_world.c.obj符号表 g_tAD7606:未定义需要从其他文件找 重定位表 位置0x0010:需要填入g_tAD7606的地址 机器码 ldr r0,g_tAD7606 ← 地址未知 mov r1,#0str r1,[r0]链接后// hello_world.elf链接器的工作1.符号解析-g_tAD7606在ad7606.c.obj中找到2.地址分配根据链接脚本-.bss段起始0x20000000-g_tAD76060x200000003.重定位-hello_world.c.obj中的ldr指令-修改为ldr r0,0x20000000最终机器码 ldr r0,0x20000000← 地址已确定 mov r1,#0str r1,[r0].bss段0x20000000 预留16字节给g_tAD7606五、为什么需要两个步骤1. 分离编译的好处好处1提高编译效率如果只有一个文件修改 - 只需要重新编译这个文件 - 其他文件不需要重新编译 - 只需要重新链接 如果所有文件一起编译 - 任何文件修改都要重新编译所有文件 - 效率很低好处2模块化开发不同的人可以 - 同时开发不同的.c文件 - 各自编译成.obj文件 - 最后一起链接 就像搭积木 - 每个人做自己的积木块.obj文件 - 最后组装在一起链接好处3代码复用SDK库文件 - fsl_ecspi.c → fsl_ecspi.obj → libdriver.a - fsl_gpio.c → fsl_gpio.obj → libdriver.a 你的程序 - hello_world.c → hello_world.obj - ad7606.c → ad7606.obj 链接时 - 只需要链接用到的库函数 - 不需要的库函数不会被包含2. 链接的必要性必要性1地址必须确定编译时 - 不知道其他文件的函数在哪里 - 不知道库函数在哪里 - 不知道最终运行地址 链接时 - 读取链接脚本知道内存布局 - 可以计算所有地址 - 可以修改机器码中的地址必要性2符号必须解析编译时 - 只知道函数名符号 - 不知道函数在哪里 链接时 - 在所有.obj文件中查找符号 - 在库文件中查找符号 - 如果找不到报错undefined reference六、链接脚本的作用1. 链接脚本决定什么// MIMX8ML8xxxxx_cm7_ram.ld MEMORY { m_interrupts : ORIGIN 0x00000000, LENGTH 0x00000400 m_text : ORIGIN 0x00000400, LENGTH 0x0001FC00 m_data : ORIGIN 0x20000000, LENGTH 0x00020000 }链接脚本告诉链接器代码放在哪里0x00000400数据放在哪里0x20000000每个段的大小限制2. 为什么需要链接脚本因为不同MCU有不同的内存布局不同应用需要不同的内存分配链接器需要知道如何组织代码和数据3. 链接脚本如何影响地址TCM版本的链接脚本 m_text: ORIGIN 0x00000400 ← TCM地址 链接结果 - Reset_Handler: 0x000004CD - AD7606_Init: 0x00001234 - main: 0x00001500 DDR版本的链接脚本 m_text: ORIGIN 0x80000400 ← DDR地址 链接结果 - Reset_Handler: 0x800004CD - AD7606_Init: 0x80001234 - main: 0x80001500七、完整的编译链接流程实际例子编译 hello_world步骤1预处理arm-none-eabi-gcc -E hello_world.c -o hello_world.i结果展开所有#include和宏定义步骤2编译arm-none-eabi-gcc -c hello_world.c -o hello_world.o结果生成hello_world.o包含机器码地址未定符号表重定位表步骤3链接arm-none-eabi-gcc hello_world.o ad7606.o... -T MIMX8ML8xxxxx_cm7_ram.ld -o hello_world.elf结果生成hello_world.elf包含所有代码地址已确定所有数据地址已确定符号表用于调试步骤4生成bin文件arm-none-eabi-objcopy -O binary hello_world.elf hello_world.bin结果生成hello_world.bin纯二进制文件八、常见问题1. 为什么会有 “undefined reference” 错误原因 - 链接时找不到某个符号的定义 - 比如调用了AD7606_Init但链接时找不到这个函数 解决方法 - 确保包含定义了该函数的.obj文件 - 或者链接包含该函数的库文件2. 为什么会有 “multiple definition” 错误原因 - 同一个符号在多个文件中定义了 - 比如g_tAD7606在ad7606.c和hello_world.c中都定义了 解决方法 - 只在一个文件中定义 - 其他文件用extern声明3. 为什么链接脚本很重要原因 - 链接脚本决定了程序的地址布局 - 地址必须匹配实际的硬件内存布局 - 地址不匹配会导致程序无法运行九、总结编译.c → .o作用将源代码转换为机器码生成符号表和重定位信息需要源代码文件头文件编译器选项做了什么预处理展开头文件编译C → 汇编汇编汇编 → 机器码输出目标文件.o包含机器码但地址未确定链接.o → .elf作用合并多个目标文件解析所有符号确定所有地址需要多个目标文件.o库文件.a链接脚本.ld做了什么符号解析查找所有符号的定义地址分配根据链接脚本分配地址重定位修改机器码中的地址段合并合并所有段输出可执行文件.elf包含所有代码和数据地址已确定为什么需要两个步骤提高效率只重新编译修改的文件模块化不同文件可以独立开发代码复用可以链接库文件地址确定链接时才能确定最终地址这就是为什么即使把 TCM 版本的 bin 文件加载到0x80000000程序还是会从 TCM 启动——因为链接时已经将地址硬编码为 TCM 地址了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询