通过链接文件和Start.S学习armv7

目录

一,整体架构分析

1,内存区域划分

2,处理器模式栈分配

二,关键部分详细解析

1.输出格式与入口

2,代码段 (.text)

3,初始化调用段 (.initcall)

4,退出调用段 (.exitcall)

5,内核符号表 (.ksymtab)

[6,ROM 磁盘 (.romdisk)](#6,ROM 磁盘 (.romdisk))

7,数据段的巧妙设计 (.data_shadow 和 .data)

[8,ARM 特定段](#8,ARM 特定段)

[9,BSS 段 (.bss)](#9,BSS 段 (.bss))

10,多模式栈分配 (.stack)

[11,DMA 和堆区域](#11,DMA 和堆区域)

12,调试信息段

三,启动流程分析

阶段1:硬件启动

阶段2:启动代码 (start.o)

阶段3:初始化调用链

四,reset_handler处理

五,undefined_handler处理

六,irq_handler处理


一,整体架构分析

1,内存区域划分

主内存 (ram)

DMA 区域 (dma)

堆内存 (heap)

2,处理器模式栈分配

Undefined 模式栈: 256KB (0x40000)

Abort 模式栈: 256KB (0x40000)

IRQ 模式栈: 256KB (0x40000)

FIQ 模式栈: 256KB (0x40000)

SVC 模式栈: 1MB (0x100000)

二,关键部分详细解析

1.输出格式与入口

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")

生成 ELF 32位 格式,默认小端字节序,支持大端作为备用

入口点为 _start(通常是启动代码)

2,代码段 (.text)

.text :

{

PROVIDE(__image_start = .);# 镜像起始地址

PROVIDE(__text_start = .);

.obj/arch/arm32/mach-feifei/start.o (.text)# 启动代码必须放在最前面

*(.text*)# 所有代码段

*(.init.text)

*(.exit.text)

*(.glue*)

*(.note.gnu.build-id)

PROVIDE(__text_end = .);

} > ram

3,初始化调用段 (.initcall)

.initcall ALIGN(8) :

{

PROVIDE(__initcall_start = .);

KEEP(*(.initcall_0.text))

KEEP(*(.initcall_1.text))

KEEP(*(.initcall_2.text))

KEEP(*(.initcall_3.text))

KEEP(*(.initcall_4.text))

KEEP(*(.initcall_5.text))

KEEP(*(.initcall_6.text))

KEEP(*(.initcall_7.text))

KEEP(*(.initcall_8.text))

KEEP(*(.initcall_9.text))

PROVIDE(__initcall_end = .);

} > ram

级别 0: 最底层硬件初始化(时钟、内存控制器)

级别 1: 核心子系统

...

级别 9: 应用程序级初始化

KEEP() 确保即使未被引用也不会被优化掉

4,退出调用段 (.exitcall)

.exitcall ALIGN(8) :

{

PROVIDE(__exitcall_start = .);

KEEP(*(.exitcall_9.text))

KEEP(*(.exitcall_8.text))

KEEP(*(.exitcall_7.text))

KEEP(*(.exitcall_6.text))

KEEP(*(.exitcall_5.text))

KEEP(*(.exitcall_4.text))

KEEP(*(.exitcall_3.text))

KEEP(*(.exitcall_2.text))

KEEP(*(.exitcall_1.text))

KEEP(*(.exitcall_0.text))

PROVIDE(__exitcall_end = .);

} > ram

与初始化相反的顺序(9→0),符合资源释放的依赖关系(后初始化的先释放)

5,内核符号表 (.ksymtab)

.ksymtab ALIGN(8) :

{

PROVIDE(__ksymtab_start = .);

KEEP(*(.ksymtab.text))

PROVIDE(__ksymtab_end = .);

} > ram

用途:内核符号导出,可能用于:

模块动态加载

系统调用表

调试信息

6,ROM 磁盘 (.romdisk)

.romdisk ALIGN(8) :

{

PROVIDE(__romdisk_start = .);

KEEP(*(.romdisk))

PROVIDE(__romdisk_end = .);

} > ram

.rodata ALIGN(8) :

{

PROVIDE(__rodata_start = .);

*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))

PROVIDE(__rodata_end = .);

} > ram

将文件系统映像(如 initramfs)链接到内核镜像中,实现单一镜像启动

7,数据段的巧妙设计 (.data_shadow 和 .data)

.data_shadow ALIGN(8) :

{

PROVIDE(__data_shadow_start = .);

PROVIDE(__data_shadow_end = (. + SIZEOF(.data)));

PROVIDE(__image_end = .);

} > ram

.data : AT(ADDR(.data_shadow))

{

PROVIDE(__data_start = .);

*(.data*)

. = ALIGN(8);

PROVIDE(__data_end = .);

} > ram

工作原理:

ROM 中:.data_shadow 区域(保存初始化数据的副本)

RAM 中:.data 区域(运行时实际位置)

启动时:需要将 .data_shadow 的内容复制到 .data

内存布局:

.text

.rodata

.data_shadow ← 初始化数据在镜像中的存储位置

.ARM.*

.bss

.stack

.data ← 运行时数据在RAM中的实际位置(物理上在.stack之后)

AT(ADDR(.data_shadow)) 表示:

.data 段的 加载地址(LMA)= .data_shadow 的地址

.data 段的 运行地址(VMA)= 实际RAM位置

启动代码需要做:

// 将初始化数据从ROM复制到RAM

memcpy(__data_start, __data_shadow_start,

__data_end - __data_start);

8,ARM 特定段

.ARM.exidx ALIGN(8) :

{

PROVIDE (__exidx_start = .);

*(.ARM.exidx*)# 异常处理索引表(栈展开)

PROVIDE (__exidx_end = .);

} > ram

.ARM.extab ALIGN(8) :

{

PROVIDE (__extab_start = .);

*(.ARM.extab*)# 异常处理展开表

PROVIDE (__extab_end = .);

} > ram

用途:C++ 异常处理、栈回溯、调试

9,BSS 段 (.bss)

.bss ALIGN(8) (NOLOAD) :

{

PROVIDE(__bss_start = .);

*(.bss*)# 未初始化全局变量

*(.sbss*)# 未初始化的公共变量

*(COMMON)

. = ALIGN(8);

PROVIDE(__bss_end = .);

} > ram

NOLOAD:不占用镜像空间,但运行时需要清零

启动代码需要:

memset(__bss_start, 0, __bss_end - __bss_start)

10,多模式栈分配 (.stack)

.stack ALIGN(8) (NOLOAD) :

{

PROVIDE(__stack_start = .);

PROVIDE(__stack_und_start = .);

. += STACK_UND_SIZE;

PROVIDE(__stack_und_end = .);

. = ALIGN(8);

PROVIDE(__stack_abt_start = .);

. += STACK_ABT_SIZE;

PROVIDE(__stack_abt_end = .);

. = ALIGN(8);

PROVIDE(__stack_irq_start = .);

. += STACK_IRQ_SIZE;

PROVIDE(__stack_irq_end = .);

. = ALIGN(8);

PROVIDE(__stack_fiq_start = .);

. += STACK_FIQ_SIZE;

PROVIDE(__stack_fiq_end = .);

. = ALIGN(8);

PROVIDE(__stack_srv_start = .);

. += STACK_SRV_SIZE;

PROVIDE(__stack_srv_end = .);

. = ALIGN(8);

PROVIDE(__stack_end = .);

} > ram

11,DMA 和堆区域

.dma ALIGN(8) (NOLOAD) :

{

PROVIDE(__dma_start = ORIGIN(dma));

PROVIDE(__dma_end = ORIGIN(dma) + LENGTH(dma));

} > dma

.heap ALIGN(8) (NOLOAD) :

{

PROVIDE(__heap_start = ORIGIN(heap));

PROVIDE(__heap_end = ORIGIN(heap) + LENGTH(heap));

} > heap

物理地址固定:直接使用内存区域的原点(ORIGIN)

NOLOAD:不加载到镜像中

用途:

DMA:外设直接内存访问缓冲区

Heap:动态内存分配(malloc/free)

12,调试信息段

将所有调试段放到地址 0,表示不加载到目标系统,仅用于调试

.stab 0 : { *(.stab) }# 符号表

.stabstr 0 : { *(.stabstr) }

.stab.excl 0 : { *(.stab.excl) }

.stab.exclstr 0 : { *(.stab.exclstr) }

.stab.index 0 : { *(.stab.index) }

.stab.indexstr 0 : { *(.stab.indexstr) }

.comment 0 : { *(.comment) }

.debug_abbrev 0 : { *(.debug_abbrev) }# debug_*表示DWARF 调试信息

.debug_info 0 : { *(.debug_info) }

.debug_line 0 : { *(.debug_line) }

.debug_pubnames 0 : { *(.debug_pubnames) }

.debug_aranges 0 : { *(.debug_aranges) }

三,启动流程分析

阶段1:硬件启动

CPU 从复位向量开始执行

跳转到 _start(在 start.o 中)

阶段2:启动代码 (start.o)

assembly

_start:

// 1. 设置异常向量表

// 2. 初始化关键硬件(时钟、内存控制器)

// 3. 设置各模式栈指针

ldr sp, =__stack_srv_end // 设置SVC模式栈

// 4. 清零BSS段

ldr r0, =__bss_start

ldr r1, =__bss_end

mov r2, #0

bl memset

// 5. 复制.data段

ldr r0, =__data_start // 目标:RAM中的.data

ldr r1, =__data_shadow_start // 源:ROM中的.data_shadow

ldr r2, =__data_end

sub r2, r2, r0 // 计算大小

bl memcpy

// 6. 调用初始化函数

ldr r0, =__initcall_start

ldr r1, =__initcall_end

bl call_initcalls // 按顺序调用initcall_0到9

// 7. 跳转到main

bl main

阶段3:初始化调用链

// 系统初始化顺序

initcall_0: // 最早

  • 内存控制器初始化

  • 缓存初始化

  • 基本串口输出

initcall_1:

  • 时钟系统

  • 电源管理

initcall_2:

  • 中断控制器

  • 定时器

initcall_3:

  • 设备树解析

  • 平台设备

... // 中间各级

initcall_9: // 最晚

  • 文件系统

  • 应用程序启动

四,reset_handler处理

reset:

/* Enter svc mode cleanly and mask interrupts */

mrs r0, cpsr

eor r0, r0, #0x1a

tst r0, #0x1f

bic r0, r0, #0x1f

orr r0, r0, #0xd3

bne 1f

orr r0, r0, #0x100

adr lr, 2f

msr spsr_cxsf, r0

.word 0xe12ef30e /* msr elr_hyp, lr */

.word 0xe160006e /* eret */

1: msr cpsr_c, r0

2: nop

/* Set vector base address register */

ldr r0, =_start

mcr p15, 0, r0, c12, c0, 0

mrc p15, 0, r0, c1, c0, 0

bic r0, #(1<<13)

mcr p15, 0, r0, c1, c0, 0

/* Enable neon/vfp unit */

mrc p15, 0, r0, c1, c0, 2

orr r0, r0, #(0xf << 20)

mcr p15, 0, r0, c1, c0, 2

isb

mov r0, #0x40000000

vmsr fpexc, r0

/* Initialize stacks */

mrs r0, cpsr

bic r0, r0, #0x1f

orr r1, r0, #0x1b

msr cpsr_cxsf, r1

ldr sp, _stack_und_end

bic r0, r0, #0x1f

orr r1, r0, #0x17

msr cpsr_cxsf, r1

ldr sp, _stack_abt_end

bic r0, r0, #0x1f

orr r1, r0, #0x12

msr cpsr_cxsf, r1

ldr sp, _stack_irq_end

bic r0, r0, #0x1f

orr r1, r0, #0x11

msr cpsr_cxsf, r1

ldr sp, _stack_fiq_end

bic r0, r0, #0x1f

orr r1, r0, #0x13

msr cpsr_cxsf, r1

ldr sp, _stack_srv_end

/* Copyself to link address */

adr r0, _start

ldr r1, =_start

cmp r0, r1

beq 1f

ldr r0, _image_start

adr r1, _start

ldr r2, _image_end

sub r2, r2, r0

bl memcpy

1: nop

/* Copy data section */

ldr r0, _data_start

ldr r3, _image_start

ldr r1, _data_shadow_start

sub r1, r1, r3

adr r3, _start

add r1, r1 ,r3

ldr r2, _data_shadow_start

ldr r3, _data_shadow_end

sub r2, r3, r2

bl memcpy

/* Clear bss section */

ldr r0, _bss_start

ldr r2, _bss_end

sub r2, r2, r0

mov r1, #0

bl memset

/* Call _main */

ldr r1, =_main

mov pc, r1

_main:

mov r0, #1;

mov r1, #0;

bl main

b _main

五,undefined_handler处理

undefined_handler:

ldr sp, _stack_und_end

sub sp, sp, #72

stmia sp, {r0 - r12}

add r8, sp, #60

mrs r1, cpsr

mrs r2, spsr

orr r2, r2, #0xc0

msr cpsr_c, r2

mov r0, r0

stmdb r8, {sp, lr}

msr cpsr_c, r1

sub lr, lr, #4

str lr, [r8, #0]

mrs r6, spsr

str r6, [r8, #4]

str r0, [r8, #8]

mov r0, sp

bl gdbserver_handle_exception

ldmia sp, {r0 - r12}

mov r0, r0

ldr lr, [sp, #60]

add sp, sp, #72

movs pc, lr

六,irq_handler处理

中断发生时保存被中断任务的上下文

.macro irq_save_regs

sub sp, sp, #72@在 IRQ 模式栈上分配 72 字节,此时 CPU 已自动切换到 IRQ 模式

stmia sp, {r0 - r12}@保存通用寄存器 :r0-r12 到栈中位置 0-48

add r8, sp, #60@r8 = sp + 60,指向将要保存 lr_irq 的位置

stmdb r8, {sp, lr}^

str lr, [r8, #0]

mrs r6, spsr@保存 SPSR

str r6, [r8, #4]@保存SPSR的 位置:[r8+4] = [sp+64]

str r0, [r8, #8]@实际上保存的是当前 r0 的值到

mov r0, sp将栈指针保存到 r0,作为参数传递给 C 语言中断处理函数

.endm

  1. sub sp, sp, #72
  • 分配栈帧:在 IRQ 模式栈上分配 72 字节

  • 当前模式:此时 CPU 已切换到 IRQ 模式(由硬件自动完成)

  1. stmia sp, {r0 - r12}
  • 保存通用寄存器:r0-r12 到栈中位置 0-48

  • 关键点 :保存的是中断发生时的寄存器值

  1. add r8, sp, #60
  • 计算指针r8 指向栈帧中特殊寄存器区域(偏移60)

  • r8 = sp + 60,指向将要保存 lr_irq 的位置

  1. stmdb r8, {sp, lr}^
  • 最复杂的指令! 需要分解理解:

    • stmdb:存储多个,先减地址后存储(Decrement Before)

    • {sp, lr}^^ 表示存储被中断模式的 SP 和 LR

    • 实际操作

      1. r8 = r8 - 8(因为要存2个寄存器,每个4字节)

      2. 存储 sp_usr[r8](即 [sp+52]

      3. 存储 lr_usr[r8+4](即 [sp+56]

  1. str lr, [r8, #0]
  • 保存 IRQ 模式的 LR :此时的 lrlr_irq

  • ARM 中断机制 :发生 IRQ 时,硬件自动将 pc+4 保存到 lr_irq

  • 保存位置:[r8] = [sp+60]

  1. mrs r6, spsrstr r6, [r8, #4]
  • 保存 SPSR:被中断模式的 CPSR

  • SPSR 包含:处理器模式、中断使能位、条件标志等

  • 保存位置:[r8+4] = [sp+64]

  1. str r0, [r8, #8]
  • 这里保存 r0 寄存器在 add r8, sp, #60 之后的当前值
  1. mov r0, sp
  • 设置参数 :将栈指针保存到 r0

  • 目的:作为参数传递给 C 语言中断处理函数

  • C 函数可以通过 r0 访问保存的寄存器上下文

中断返回时恢复上下文

.macro irq_restore_regs

ldmia sp, {r0 - lr}^

mov r0, r0@延迟操作,确保流水线完成清空

ldr lr, [sp, #60]@恢复中断返回地址 从栈中加载 lr_irq

add sp, sp, #72

subs pc, lr, #4

.endm

  1. ldmia sp, {r0 - lr}^
  • 恢复通用寄存器和用户模式寄存器

    • ^ 表示恢复到用户模式的寄存器

    • 从栈中恢复 r0-r12、sp_usr、lr_usr

  • 实际恢复的寄存器

    • r0-r12:通用寄存器

    • sp:用户模式栈指针(实际上被忽略?)

    • lr:用户模式链接寄存器

  1. mov r0, r0
  • 空操作:看似无用,实则有重要目的!

  • 作用确保上一条 ldmia 完成

  • ARM 架构特性:ldm 指令有延迟效应,下一条指令可能还在使用旧寄存器值

  • mov r0, r0 创建一个依赖,确保流水线清空

  1. ldr lr, [sp, #60]
  • 恢复中断返回地址 :从栈中加载 lr_irq

  • 注意 :此时的 lr 是 IRQ 模式的链接寄存器

  1. add sp, sp, #72
  • 释放栈帧:恢复 IRQ 模式栈指针
  1. subs pc, lr, #4
  • 中断返回:关键指令!

  • subs:带标志设置的减法

  • pc:目标寄存器是程序计数器

  • lr :包含中断返回地址(pc+4

  • #4 :减去4,返回到被中断的指令

  • 自动操作subs pc, ... 还会自动将 SPSR 复制到 CPSR

irq:

ldr sp, _stack_irq_end#栈是从高地址向低地址方向的

irq_save_regs#中断发生时保存被中断任务的上下文

bl interrupt_handle_exception#c语言irq中断处理函数

irq_restore_regs#中断返回时恢复上下文

相关推荐
DN金猿2 小时前
使用ubuntu安装nginx时报错
linux·nginx·ubuntu
小赵还有头发2 小时前
安装Ceres与glog
linux·学习·无人机·ceres·glog
Engineer邓祥浩3 小时前
设计模式学习(16) 23-14 命令模式
学习·设计模式·命令模式
负二代0.03 小时前
Linux下的网络管理
linux·网络
zhangrelay3 小时前
ROS云课三分钟-cmake默认版本和升级-260120
笔记·学习
m0_555762903 小时前
STM32H7 + VS Code + OpenOCD + STLink 完整配置教程
stm32·单片机·嵌入式硬件
s_daqing3 小时前
ubuntu(arm)安装redis
linux·redis·ubuntu
飞来客isdn3 小时前
关于单片机与上位机串口通信的问题
单片机·嵌入式硬件·mcu
为何创造硅基生物3 小时前
STM32 串口的中断,空闲中断DMA
stm32·单片机·嵌入式硬件