程序、段、符号的概念详解

一、程序 (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 表示弱符号,这是一个可以被强符号覆盖的符号定义。

弱符号的特点:

  1. 可被覆盖:如果其他地方定义了同名的强符号,链接器会使用强符号
  2. 提供默认实现:通常用于提供默认的函数实现
  3. 避免链接错误:即使没有定义也不会导致链接失败

示例分析:

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访问 */
}

总结

  • 程序:是最终的可执行文件,包含所有代码和数据
  • :是程序内部的组织单位,将相同属性的内容分组(代码段、数据段等)
  • 符号:是程序中所有命名实体(函数、变量)及其地址的映射关系

链接脚本通过控制这三者,实现了对程序内存布局的精确控制。理解这些概念是掌握嵌入式开发的关键。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐