Skip to content

MDK编译过程

理解芯片的原理, 制作IAP等

image-20230727130401312

编译: 得到.o文件, 只要内容是源文件编译得到的机器码, 数据以及调试信息

链接: 把.o文件链接成为.axf或.elf文件

格式转换: 一般来说Windows以及Linux使用链接器直接生成可执行映像文件elf之后内核根据该文件信息进行加载就可以了, 在单片机需要转化为.bin或者.hex文件, 交给下载器下载到flash或者ROM中

image-20230727133044085

调用的编译器以及所在的文件夹

image-20230727133417692

armar: .o文件打包成lib文件

armasm: 编译汇编文件

armcc: 编译C文件以及C++文件, 每一个源文件都会有一个独立的.o文件

armlink: 链接对象文件, 附带各个域的大小说明

fromelf: 生成hex文件, 在选项中选择是否生成

image-20230727134003561

编译过程中出现的错误以及警告, 构建消耗的时间

程序的存储

Program Size: Code=3404 RO-data=336 RW-data=40 ZI-data=1024

说明了各个域的大小

Code: 生成的代码, 会下载到flash中

ROdata: 只读数据, 程序用到这些数据这些数据是不能修改的, const变量, 存储在flash中

RW-data: 初始值不是0的可读写的数据, 运行的时候常驻于RAM, 比如初始化的全局变量

ZI-data: 初始值为0的值, 可以读写, 不需要在flash中储存

栈空间以及堆空间: 在函数内部定义的变量属于栈空间, 使用malloc分配的变量属于堆空间, 都是属于ZI-data, 空间初始化为0, 没有使用malloc的话会进行优化, 在.s文件中初始化有最大值

image-20230727143535304

RO不需要加载到SRAM, 运行的时候加载到内存, 直接从Flash加载之后运行, 但是由于没有MMC所以不能跑Linux

在执行主体代码前,会先执行一段加载代码,它把 RW 节数据从 ROM 复制到 RAM,并且在 RAM 加入 ZI 节,ZI 节的数据都被初始化为 0。

当程序存储到 STM32 芯片的内部 FLASH 时 (即 ROM 区),它占用的空间是 Code、RO-data 及 RW-data 的总和

image-20230727145319016

stm32的RAM大小是64K

image-20230727145817288

编译过程

在文件的输出的文件夹里面

image-20230727155424096

编译器优化水平, 一般使用优化1, 优化水平过高会导致程序出错

调节的时候会使用下面的指令进行控制armcc

bash
-c --cpu Cortex-M3 -D__MICROLIB -g -O1 --apcs=interwork -I ../../Libraries/CMSIS -I ../../User -I ../../Libraries/FWlib/inc

image-20230727155859305

-c: 只编译不链接

--cpu: 选择CPU

-D: 进入点

  • armasm

image-20230727160154457

这里设置的是汇编的编译器, armasm.exe

  • armlink

image-20230727160249013

这里是armlink的操作, 这里使用默认的配置进行链接, 会被记录在sct文件, 它把各个 O 文件链接组合在一起生成 ELF 格式的 AXF 文件

sct
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

生成的sct文件

  • armar

image-20230727161018282

是否生成lib文件, 看不见源代码

  • fromelf

image-20230727161226119

生成hex文件

image-20230727161310594

添加一些自己的指令, 在各个步骤之前执行的指令, 可以在这里添加文件生成bin文件, 这里需要axf文件

image-20230727162256617

MDK的执行路径值在项目存放的的位置

image-20230727163057196

杂项文件

image-20230727163303711

uvprojx: 整个工程的结构,如芯片类型、工程包 含了哪些源文件等内容

uvoptx 文件记录了工程的配置选项,如下载器的类型、变量跟踪配置、断点位置以及当前已打开 的文件等等

uvguix 文件记录了 MDK 软件的 GUI 布局,如代码编辑区窗口的大小、编译输出提示窗口的位置 等等。在窗口关闭的时候会进行刚更新

image-20230727163836013

dep, 包含的文件信息的记录的时候, 比如说包含的头文件

.d, 某一个文件包含的头文件等信息

crf, MDK跳转的时候需要的文件

axf, 编译之后生成的文件, 再下载的时候需要这一个文件

hex, bin是axf的简化版

具体文件解析

ELF文件

可执行链接格式, 用于记录目标文件内容, linux下可以直接运行

主要是有三种类型

  • 可重定位文件

包含基础代码以及文件, 但是没有包含绝对地址, 适合和其他文件链接创建可执行文件或共享目标文件, 一般是.o文件或者.obj文件

  • 可执行文件

适合执行文件

包含的代码和数据都有固定的地址, 可以由系统加载到内存执行, 文件一般由链接器根据重定位文件链接而成, 主要是组织各个可重定位文件, 给代码以及数据打上地址标签, 固定在程序内部, 链接之后代码数据不能再次链接

MDK生成的.elf文件.axf文件,

  • 共享目标文件

.lib文件, 可以继续参与链接, 加入到可执行文件中, linux下面的.so文件

image-20230727192803059

中间代表的是 armlink 链接器,在它的右侧是输入链接器的 *.o 文件,左侧是它输出的 *axf 文件。

于都使用 ELF 文件格式,.o 与.axf 文件的结构是类似的,它们包含 ELF 文件头、程 序头、节区 (section) 以及节区头部表。

  • ELF 文件头用来描述整个文件的组织,例如数据的大小端格式,程序头、节区头在文件中 的位置等。
  • 程序头告诉系统如何加载程序,例如程序主体存储在本文件的哪个位置,程序的大小,程 序要加载到内存什么地址等等。MDK 的可重定位文件 *.o 不包含这部分内容
  • 节区是 *.o 文件的独立数据区域,它包含提供给链接视图使用的大量信息,如指令 (Code)、 数据 (RO、RW、ZI-data)、符号表 (函数、变量名等)、重定位信息等,例如每个由 C 语言定 义的函数在 *.o 文件中都会有一个独立的节区
  • 存储在最后的节区头则包含了本文件节区的信息,如节区名称、大小等等。

image-20230727195513356

选了以后就会每一个函数都生成一个节区

bash
Other Output Formats:
       --elf         ELF
       --text        Text Information

                Flags for Text Information
                -v          verbose
                -a          print data addresses (For images built with debug)
                -c          disassemble code
                -d          print contents of data section
                -e          print exception tables
                -g          print debug tables
                -r          print relocation information
                -s          print symbol table
                -t          print string table
                -y          print dynamic segment contents
                -z          print code and data size information

image-20230727203334919

检查.o文件的内容可以使用fromlef --text -v 文件名.o > 输入的文件名

bash
PS E:\a学习\1.stm\A盘(资料盘)\1-程序源码_教程文档\1-[野火]《STM32库开发实战指南》(标准库源码)【优先学习】\0.书籍源码\41-PWR—电源管理\PWR—PVD监控\Output> fromelf.exe -v --text .\stm32f10x_gpio.o > text.txt
bash
========================================================================
//节区头
** ELF Header Information
	# 文件名
    File Name: .\stm32f10x_gpio.o
	# 机器的类型, 大小端, 版本号, 操作系统, , 文件类型 
    Machine class: ELFCLASS32 (32-bit)
    Data encoding: ELFDATA2LSB (Little endian)
    Header version: EV_CURRENT (Current version)
    Operating System ABI: none
    ABI Version: 0
    File Type: ET_REL (Relocatable object) (1)
    Machine: EM_ARM (ARM)

    Entry offset (in SHF_ENTRYSECT section): 0x00000000
    Flags: None (0x05000000)

    ARM ELF revision: 5 (ABI version 2)

    Header size: 52 bytes (0x34)
    # 程序头的大小, 节区头部表的大小
    Program header entry size: 0 bytes (0x0)
    Section header entry size: 40 bytes (0x28)
	# 进入的地址
    Program header entries: 0
    Section header entries: 344
	# 在程序中的位置
    Program header offset: 0 (0x00000000)
    Section header offset: 400496 (0x00061c70)

    Section header string table index: 341

========================================================================
bash
========================================================================

** Section #1
	# 节区名字
    Name        : i.GPIO_AFIODeInit
    # 程序的定义信息, 格式含义都是程序解释
    Type        : SHT_PROGBITS (0x00000001)
    # 节区在执行过程中占用内存, 有可以执行的机器指令
    Flags       : SHF_ALLOC + SHF_EXECINSTR (0x00000006)
    # 地址, 设置为0表示没有分配
    Addr        : 0x00000000
    # 文件中的偏移(这时候的存储位置)
    File Offset : 52 (0x34)
    # 大小
    Size        : 22 bytes (0x16)
    Link        : SHN_UNDEF
    Info        : 0
    # 字节对齐
    Alignment   : 2
    Entry Size  : 0


====================================
bash
PS E:\a学习\1.stm\A盘(资料盘)\1-程序源码_教程文档\1-[野火]《STM32库开发实战指南》(标准库源码)【优先学习】\0.书籍源码\41-PWR—电源管理\PWR—PVD监控\Output> fromelf.exe -v --text .\Template.axf > text1.txt
bash
========================================================================

** Program header #0

    Type          : PT_LOAD (1)
    File Offset   : 52 (0x34)
    # 物理地址以及虚拟地址
    Virtual Addr  : 0x08000000
    Physical Addr : 0x08000000
    # 在flash中的大小, 这个是Code + RO-data + R-data, 实际上会小, 因为编译器会优化
    Size in file  : 1516 bytes (0x5ec)
    # 加载到内存中的大小
    Size in memory: 2540 bytes (0x9ec)
    Flags         : PF_X + PF_W + PF_R + PF_ARM_ENTRY (0x80000007)
    Alignment     : 8


========================================================================
# ROM的大小
** Section #1

    Name        : ER_IROM1
    Type        : SHT_PROGBITS (0x00000001)
    Flags       : SHF_ALLOC + SHF_EXECINSTR (0x00000006)
    # 这就是flash的地址
    Addr        : 0x08000000
    File Offset : 52 (0x34)
    Size        : 1516 bytes (0x5ec)
    Link        : SHN_UNDEF
    Info        : 0
    Alignment   : 4
    Entry Size  : 0


====================================
# 这个是栈和堆的空间
** Section #2

    Name        : RW_IRAM1
    Type        : SHT_NOBITS (0x00000008)
    Flags       : SHF_ALLOC + SHF_WRITE (0x00000003)
    Addr        : 0x20000000
    File Offset : 1568 (0x620)
    Size        : 1024 bytes (0x400)
    Link        : SHN_UNDEF
    Info        : 0
    Alignment   : 8
    Entry Size  : 0


====================================

分散加载代码

__scatterload函数把文件copy到RAM中对应的位置, 是链接器自动生成的, 会被___main函数调用, __main函数会被Reset_Handler调用

想要使用外部SRAM需要在__main之前初始化

HEX和bin文件

代码数据的文件, CooCex可以使用bin文件, 这个是DAP仿真器的配套软件

  • HEX

是Intel公司制定的一种ASCII文本记录器或常量数据文件格式, 实际上就是一些数字

把这些数据存储到ROM里面, 大多数下载器都支持这种格式, 格式是:llaaaatt[dd...]cc

image-20230728105626279

  • : 一条记录的开头
  • ll: 记录之后主体记录的长度
  • aaaa: 要保存的地址
  • tt: 数据的类型

image-20230728105909767

:020000040800F2

02, 两个字节, 0000是地址, 04表示扩展地址, 0800表示扩展的线性地址位置, 第一行代表的就是地址0x0800 0000, 之后的地址会在这一条的地址上面

  • dd: 表示一个字节的数据, 一条记录有多个数据
  • cc: 校验, 对前面所有的数取和, 对256取模结果的补码
  • bin文件

最直接的代码映像, 记录的内容直接下载到flash, 根据芯片的类型进行选择下载的位置, 实际上就是hex文件的存储的数据

image-20230728105626279

image-20230728113645226

image-20230728113754483

先设置堆栈的指针, 之后设置sp指针运行开始的位置, 中断向量表

htm文件

这里面有各个函数的调用

image-20230728114728974

最深一层的栈调用, 这个是静态调用, 但是如果有递归函数的时候就不能确定了, 定义的栈空间最好要大一倍, 使用指针调用的话也不能使用

Listing目录下的文件

  • .map文件

image-20230728134135540

选择包含的内容

链接器生成的文件, 主要是交叉编译的信息, 查看该文件可以了解工程文件各个符号之间的引用以及工程的Code, RO-data, RW-data以及ZI-data的详细信息, 主要包含节区的跨文件引用, 删除无用节区, 符号映像表, 存储器映像索引以及映像组件大小

这里记录了没有使用的函数以及使用的函数, 没有的函数会进行删除

image-20230728134511670

记录文件调用

image-20230728130136781

没有使用的堆区也进行了删除

image-20230728141029329

变量的位置以及大小

image-20230728131226697

文件各个分段的大小

sct文件

image-20230728141341900

选择sct文件, 这里是不使用外部的

在构建工程的时候MDK根据芯片的信息以及FLASH和SRAM的概况生成sct文件, 分散加载文件, 在这里可以控制文件在RAM里面的加载的位置

之前可以使用__attribute__来指定把文件储存到外部SRAM, 使用分散加载文件就不需要了

**注: **stm32使用的是NOR Flash, 可以随意访问, 所以代码段直接存放在NOR Flash, 在其他的使用NAND Flash的代码里面, 分散文件会把加载区设置为NAND-FLASH文件程序的位置, 程序实际运行的位置在SRAM中, 链接器就会生成配套的分散加载代码, 把它加载到外部的SRAM中, 内核从SRAM运行

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region加载域,文件保存的地方
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address执行域,执行的区域
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data这里是RW以及ZI
   .ANY (+RW +ZI)
  }
}

分配了Code, RO-data, RW-data, ZI-data

主要包含加载域以及执行域

最外层的是加载域, 第二层的是执行域

image-20230728144036373

image-20230728144816525

image-20230728160758667

  • 模块选择样式

可以用于选择.o或者是.lib文件作为输入节区, 直接使用文件名或者使用通配符"*", 比如*.o, 也可以使用.ANY或单独的"*"选择所有的.o和.lib文件, .ANY的优先级最低, 选择剩下的文件

  • 输入节区样式

image-20230728161519261

在启动文件设置的节区的名字,

  • 输入节区属性

选择样式中的不同内容, 描述符之前有一个+, 使用空格或者逗号隔开

image-20230728162041527

  • 输入节区特性

使用+FIRST或+LAST选择储存的位置, 一般重要的节区在头部, 校验之类的在尾部

  • 特殊符号

*(lnRoot$$Section)是链接器支持的特殊符号, 选择所有的标准库里面的要求存储到root区域的节区, 比喻__main.o、__scatter*.o等内容

修改

image-20230728163438360

image-20230728163553131

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00008000  {  ; RW data
   .ANY (+RW +ZI)
  }
  RW_IRAM2 0x20008000 0x00008000  {
   .ANY (+RW +ZI)
  }
}

image-20230728163831741

image-20230728163849266

选择文件的位置

image-20230728164001377

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00008000  {  ; RW data
   .ANY (+RW +ZI)
  }
  RW_IRAM2 0x20008000 0x00008000  {
    bsp_led.o (+RW)
   .ANY (+RW +ZI)
  }
}

实际应用

  • 自动分配变量到指定的空间

image-20230728174329071

DAK会自动选择比较大的RAM先进行使用

由于速度的原因, 一般优先使用内部的SRAM

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; RW data
	*.o(STACK)
   .ANY (+RW +ZI)
  }
  RW_ERAM1 0x20005000 0x00007000  {
   *.o(HEAP)
  }
}
  • 在外部空间存放大的数据
c
uint8_t test5[1024] __attribute__((section("EXRAM"))) = {1, 2, 3};
c
LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; RW data
	*.o(STACK)
   .ANY (+RW +ZI)
  }
  RW_ERAM1 0x20005000 0x00007000  {
  *.o(EXRAM)
   *.o(HEAP)
  }
}

    Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x080005e0, Size: 0x00000410, Max: 0x00005000, ABSOLUTE)

    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20000000   0x080005e0   0x00000006   Data   RW         3110    .data               main.o
    0x20000006   0x080005e6   0x00000002   PAD
    0x20000008   0x080005e8   0x00000004   Data   RW         3260    .data               mc_w.l(mvars.o)
    0x2000000c   0x080005ec   0x00000004   Data   RW         3261    .data               mc_w.l(mvars.o)
    0x20000010        -       0x00000400   Zero   RW            1    STACK               startup_stm32f10x_hd.o


    Execution Region RW_ERAM1 (Exec base: 0x20005000, Load base: 0x080005f0, Size: 0x00000600, Max: 0x00007000, ABSOLUTE, COMPRESSED[0x0000000c])

    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20005000   COMPRESSED   0x00000400   Data   RW         3111    EXRAM               main.o
    0x20005400        -       0x00000200   Zero   RW            2    HEAP                startup_stm32f10x_hd.o

使用外部SRAM

  1. 在启动文件之中初始化SRAM放在__main之前
  2. 更改sct文件

stack一定要在内部SRAM, 因为在调用SystemInit的时候还没有初始化外部SRAM空间, 固件库也有提供不使用变量的函数, 需要定义宏