程序\段\符号,分别是什么?是指什么?
程序是指最终生成的可执行文件(如.elf、.bin、.hex),它包含了所有编译后的代码和数据。链接脚本通过控制这三者,实现了对程序内存布局的精确控制。理解这些概念是掌握嵌入式开发的关键。符号是程序中所有有名字的实体,包括函数名、变量名、标签等,每个符号都对应一个内存地址。段是程序中具有相同属性的内容集合,编译器会自动将代码和数据分类到不同的段中。
·
程序、段、符号的概念详解
一、程序 (Program)
程序是指最终生成的可执行文件(如.elf、.bin、.hex),它包含了所有编译后的代码和数据。
// main.c - 源代码
int global_var = 100; // 全局变量
const char msg[] = "Hello"; // 常量字符串
void func(void) { // 函数
static int count = 0; // 静态变量
count++;
}
int main(void) {
int local = 10; // 局部变量
func();
return 0;
}
编译后,这个程序会包含:
- 代码:main()和func()的机器指令
- 数据:global_var、msg、count的值
- 符号信息:函数名、变量名及其地址
二、段 (Section)
段是程序中具有相同属性的内容集合,编译器会自动将代码和数据分类到不同的段中。
标准段的分类:
// 示例代码
const int rodata_var = 123; // → .rodata段(只读数据)
int data_var = 456; // → .data段(已初始化数据)
int bss_var; // → .bss段(未初始化数据)
void code_func(void) { } // → .text段(代码)
编译后的段结构:
# 使用objdump查看段
arm-none-eabi-objdump -h program.o
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000128 00000000 00000000 00000034 2**2 # 代码段
1 .data 00000010 00000000 00000000 0000015c 2**2 # 初始化数据
2 .bss 00000004 00000000 00000000 0000016c 2**2 # 未初始化数据
3 .rodata 00000008 00000000 00000000 00000170 2**2 # 只读数据
主要段的用途表:
| 段名 | 内容 | 存储位置 | 特点 |
|---|---|---|---|
| .text | 程序代码(函数) | Flash | 只读、可执行 |
| .rodata | 只读数据(const常量、字符串字面量) | Flash | 只读、不可执行 |
| .data | 已初始化全局/静态变量 | Flash→RAM | 可读写、需要初始化 |
| .bss | 未初始化全局/静态变量 | RAM | 可读写、启动时清零 |
| .stack | 栈空间(局部变量、函数调用) | RAM | 运行时动态使用 |
| .heap | 堆空间(malloc分配) | RAM | 运行时动态分配 |
三、符号 (Symbol)
符号是程序中所有有名字的实体,包括函数名、变量名、标签等,每个符号都对应一个内存地址。
符号示例:
// C代码
int global_var = 100; // 符号: global_var
void func(void) { } // 符号: func
static int static_var = 50; // 符号: static_var (局部符号)
// 链接脚本生成的符号
extern uint32_t _estack; // 栈顶地址
extern uint32_t _sidata; // data段在Flash中的起始地址
extern uint32_t _sdata; // data段在RAM中的起始地址
查看符号表:
# 使用nm查看符号
arm-none-eabi-nm program.elf
# 输出示例:
08000100 T main # T=代码段符号,地址0x08000100
08000200 T func # 函数符号
20000000 D global_var # D=已初始化数据符号
20000004 B bss_var # B=BSS段符号
08000400 R rodata_var # R=只读数据符号
20001000 A _estack # A=绝对地址符号
符号类型说明:
- T/t:代码段符号(大写=全局,小写=局部)
- D/d:已初始化数据段符号
- B/b:BSS段(未初始化数据)符号
- R/r:只读数据段符号
- A:绝对符号(固定地址)
- U:未定义符号(需要链接)
常见符号类型标识
| 标识 | 含义 | 说明 |
|---|---|---|
| A | Absolute | 绝对符号,地址固定不会被链接器改变 |
| B | BSS section | 未初始化数据段(大写=全局) |
| b | BSS section | 未初始化数据段(小写=局部) |
| D | Data section | 已初始化数据段(大写=全局) |
| d | Data section | 已初始化数据段(小写=局部) |
| T | Text section | 代码段(大写=全局函数) |
| t | Text section | 代码段(小写=局部/static函数) |
| R | Read-only data | 只读数据段(大写=全局) |
| r | Read-only data | 只读数据段(小写=局部) |
| W | Weak symbol | 弱符号(全局) |
| w | Weak symbol | 弱符号(局部) |
| U | Undefined | 未定义符号,需要从其他文件链接 |
| V | Weak object | 弱对象符号 |
| ? | Unknown | 未知类型 |
W 表示弱符号,这是一个可以被强符号覆盖的符号定义。
弱符号的特点:
- 可被覆盖:如果其他地方定义了同名的强符号,链接器会使用强符号
- 提供默认实现:通常用于提供默认的函数实现
- 避免链接错误:即使没有定义也不会导致链接失败
示例分析:
080053c4 W ADC_IRQHandler
080053c4 W BDMA_Channel0_IRQHandler
这些都是中断处理函数的弱符号,它们:
- 都指向同一个地址
080053c4 - 是STM32启动文件提供的默认中断处理函数
- 通常是一个空的死循环
四、实际例子:完整流程
1. 源代码
// led.c
#include <stdint.h>
// 全局变量 - 会成为符号
uint32_t led_count = 0; // → .data段
uint32_t led_state; // → .bss段
const uint32_t led_delay = 1000; // → .rodata段
// 函数 - 会成为符号
void LED_Init(void) { // → .text段
// 初始化代码
}
void LED_Toggle(void) { // → .text段
led_count++;
led_state = !led_state;
}
2. 编译后的目标文件
# 编译
arm-none-eabi-gcc -c led.c -o led.o
# 查看段
arm-none-eabi-objdump -h led.o
# 结果:
# .text 000000a0 (包含LED_Init和LED_Toggle的代码)
# .data 00000004 (包含led_count的初始值)
# .bss 00000004 (为led_state预留空间)
# .rodata 00000004 (包含led_delay的值)
3. 链接脚本控制布局
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
/* 将.text段放入FLASH */
.text :
{
*(.text) /* 所有的代码 */
*(.text*) /* LED_Init, LED_Toggle等函数 */
} >FLASH
/* 将.rodata段放入FLASH */
.rodata :
{
*(.rodata) /* led_delay等常量 */
} >FLASH
/* 将.data段运行时放在RAM,但初始值存在FLASH */
.data :
{
_sdata = .; /* 创建符号标记起始 */
*(.data) /* led_count等已初始化变量 */
_edata = .; /* 创建符号标记结束 */
} >RAM AT>FLASH
/* 将.bss段放在RAM */
.bss :
{
_sbss = .; /* 创建符号标记起始 */
*(.bss) /* led_state等未初始化变量 */
_ebss = .; /* 创建符号标记结束 */
} >RAM
}
4. 最终内存布局
FLASH (0x08000000):
├── .text段
│ ├── 0x08000100: LED_Init (函数代码)
│ └── 0x08000150: LED_Toggle (函数代码)
├── .rodata段
│ └── 0x08000200: led_delay = 1000
└── .data段初始值
└── 0x08000204: led_count = 0
RAM (0x20000000):
├── .data段 (运行时)
│ └── 0x20000000: led_count (从Flash复制)
└── .bss段
└── 0x20000004: led_state (清零)
符号表:
LED_Init = 0x08000100 (函数地址)
LED_Toggle = 0x08000150 (函数地址)
led_delay = 0x08000200 (常量地址)
led_count = 0x20000000 (变量地址)
led_state = 0x20000004 (变量地址)
_sdata = 0x20000000 (data段起始)
_edata = 0x20000004 (data段结束)
五、自定义段的例子
// 在C代码中创建自定义段
__attribute__((section(".mycode")))
void special_func(void) {
// 这个函数会被放到.mycode段
}
__attribute__((section(".mydata")))
uint32_t special_var = 123; // 这个变量会被放到.mydata段
// 在链接脚本中处理自定义段
SECTIONS
{
.mycode :
{
*(.mycode)
} >ITCMRAM AT>FLASH /* 放到ITCM执行 */
.mydata :
{
*(.mydata)
} >DTCMRAM /* 放到DTCM访问 */
}
总结
- 程序:是最终的可执行文件,包含所有代码和数据
- 段:是程序内部的组织单位,将相同属性的内容分组(代码段、数据段等)
- 符号:是程序中所有命名实体(函数、变量)及其地址的映射关系
链接脚本通过控制这三者,实现了对程序内存布局的精确控制。理解这些概念是掌握嵌入式开发的关键。
更多推荐
所有评论(0)