gcc for arm 工具链使用(一)


前言

  本文主要描述 gcc for arm 工具链各个组件及的使用:首先会讲解各个组件的基本功能,然后通过一个示例来说明如何编译链接、如何写ld文件、如何写makefile。本文最后会给出一个通用的 makefile。本文虽然是针对 gcc for arm 写的,但同样适用于其它平台的 gcc。


提示:以下是本篇文章正文内容,下面案例可供参考

一、编译链接过程

  GNU 工具链主要包括汇编器 as、C 编译器 gcc、C++编译器 g++、链接器 ld、二进制转换工具 objcopy 和反汇编的工具 objdump 等。编译基于 Arm 平台的非 linux 程序的工具链位arm-none-eabi-*。下图显示了从源文件的编写到生成用户APP程序的整个过程。
aaaaa
图1.1 用户程序的编译链接流程>

APP 的生成大致需要程序编写、编译和链接三个过程,可以参见上图。有以下两点需要说明:

  • 对于汇编文件而言,文件后缀为 s 时只能使用 GNU ASM 中的指令、伪指令、伪操作等;后缀为 S 时,允许使用 C 语言中的预处理指令”#”,包括宏定义、文件包含和条件编译。
  • arm-none-eabi-gcc/g++不仅可以实现编译的功能,在添加附加参数的情况下,它也能够实现 as 和 ld 的功能。

下面的章节是对面向 Arm 裸机平台的 GNU 工具的使用介绍。
表1.1 GNU for arm 的工具链
在这里插入图片描述


二、编译器驱动程序arm-none-eabi-gcc的使用

gcc/g++可以执行预编译、编译、汇编和链接的功能,可以通过overall options来决定具体完成哪些功能。本节主要是描述针对Arm平台的编译器参数设置。输入文件、输出文件和预编译/编译/链接/汇编用选项是作为gcc/g++的操作数出现的,不同操作数的顺序不影响最终结果。

1. 处理器架构选项

对于 cortex-M 的内核,对应的处理器构架相关的选项如下(既可以第一行也可以第二行):

对于 cortex-m3
-mthumb -mcpu=cortex-m3 或
-mthumb -march=armv7-m

对于 cortex-m4
-mthumb -mcpu=cortex-m4 或
-mthumb -march=armv7-m

对于 cortex-m7
-mthumb -mcpu=cortex-m7 或
-mthumb -march=armv7-m

2. C 标准库的选择

arm-none-eabi 工具链带有两个基于 newlib 的库可供选择(newlibnewlib_nano),其中 newlib_nano 库对代码尺寸做了优化。默认情况下使用的是 newlib 库,如果想要使用 newlib-nano 库,则需要在编译和链接时使用下列选项,也就是说下面的选项既是编译用选项,也是链接用选项。

--specs=nano.specs

3. 语言标准类常用选项

-ansi     -std=standard
-fcond-mismatch
-fsigned-char     -funsigned-char     -fno-signed-char     -fno-unsigned-char
-fsigned-bitfields     -funsigned-bitfields     -fno-signed-bitfields     -fno-unsigned-bitfields
-fabi-version=n (C++专用)
-fno-asm

  1. 编译器使用的 C 语言标准(默认值可以查询相应的 gcc 手册)
C 语言版本 参数设置
ANSI C 标准 -ansi 或 –std=c90 或 -std=89
ISO C99 标准 -std=c99
ISO C11 标准 -std=c11
GNU 扩展 C90 -std=gnu90
GNU 扩展 C99 -std=gnu99
GNU 扩展 C11 -std=gnu11
  1. 编译器使用的 C++语言标准(默认值可以查询相应的 gcc 手册)
C++语言版本 参数设置
C++98 标准 -ansi 或 -std=c++98
C++03 标准 -std=c++03
C++11 标准 -std=c++11
GNU 扩展 C++98 -std=gnu++98
GNU 扩展 C++11 -std=gnu++11
  1. -fcond-mismatch,允许三元操作符x?a:b 中的ab 的类型不同(只适用于 C 语言)。
  2. -fsigned-char-fno-unsigned-char-funsigned-char-fno-signed-char,前两者者表示 char:=signed char,后者表示 char:=unsigned char
  3. -fsigned-bitfields-fno-unsigned-bitfields-fno-signed-bitfields-funsigned-bitfields 表示结构体中的位域的符号,前两者表示为有符号数,后两者表示为无符号数。默认为有符号数。
  4. 只适用于 C++的-fabi-version=n 参数,选择 n=0 最为保险。
  5. -fno-asm,不将 asminlinetypeof 作为关键词使用,要使用相同的功能需要__asm____inline____typeof__

4. Overall 类选项(控制 gcc/g++的输出类型)

-x lang
–x none
-c -S -E -o file
-pipe

在这里插入图片描述

5. 诊断信息类选项(控制诊断信息的格式)

  警告信息的开启都是通过显式调用-W 开头的连续字符串实现,而通过显式调用以-Wno-开始的连续字符串来
实现关闭相应的警告选项,例如:-Wimplicit-Wno-implicit
在这里插入图片描述
  当选项中-Wextra-Wextra 使能的某警告的反例混用(比如-Wno-missing-field-initializers)时,则效果是使能了-Wextra 中除了该警告的其它所有警告。例如:-Wextra -Wno-missing-field-initializers 就是使能-Wextra 中除了-Wmissing-field-initializers 以外的所有警告。
常用的诊断信息类选项有:

-fmessage-length=n
-fsyntax-only
-w
-Werror
-Wall
-Wextra
-Wunused
-Wconversion

  对于具体的警告类型和出错类型的使能和禁用,根据实际情况查找 gcc 手册。一般情况下,作如下设置即可:

-fmessage-length=0   -Wall   -Wextra

下面分别为 集中 IDE 环境中的 warning 信息页

Code::blocks中设定的常用warning选项
在这里插入图片描述
CDT中设定的常用warning选项
在这里插入图片描述
Dev-C++中设定的常用warning选项
在这里插入图片描述
通过在 cmd 中输入下面内容可以获取默认情况下各个 warning 选项的开关状态(下图为 GNU for Arm 4.9 2015q2中默认warning设置):

arm-none-eabi-gcc   -Q   --help=warning

在这里插入图片描述

6. 编译优化类参数选项

  没有指定任何优化选项的情况下,编译器的目标是减少编译时间和保证 debug 能产生期望的结果。此时,各
语句之间是彼此独立的。只有开启以’-O’开始的优化等级,编译器才会进行优化;也就是说,仅仅使用优化标志
并不能启用优化。下面仅列出优化等级和几个常用的选项,具体每个优化等级开启哪一些优化选项,参见gcc手册
在这里插入图片描述
 对于每一种优化等级下开启哪一些优化选项可以通过下面的指令查看。

arm-none-eabi-gcc   -Q   -O1/O2/O3/Os/O0   --help=optimizers

7. 预编译控制参数常用选项

  预编译指令的调用方式有两种:arm-none-eabi-gccarm-none-eabi-cpp

arm-none-eabi-gcc -E 等价于 arm-none-eabi-cpp

在这里插入图片描述

8. 链接用参数常用选项

  链接时,目标文件只会从它后面的.a 文件搜寻相应的未定义符号,所以在编译链接表达式中需要把*.a 文件
写在其它文件后面(下面都为使用 gcc 链接时的选项)。
在这里插入图片描述


三、 二进制工具集 binutils 的使用

1. 链接器工具 arm-none-eabi-ld

  对于链接,也可以直接使用 arm-none-eabi-ld,此时的选项比使用 arm-none-eabi-gcc 要多,这些选项中的一
部分可以通过-Xlinker option-Wl,option 传递。下面表格就是使用 arm-none-eabi-ld 进行链接时的选项。先看3点说明:

1️⃣ 对于单字母选项,如果后面有参数的话,既可以将选项跟单字母选项写一块,又可以分开。
2️⃣ 对于多字母选项,一般情况下,前面可以是单下划线或双下划线,作用相同。但是小写字母 o 开头的多字母选项除外,它只能为双下划线开头。
3️⃣ 对于多字母选项,--option value--option=value 等价。
在这里插入图片描述

2. 二进制格式转换工具 arm-none-eabi-objcopy

arm-none-eabi-copy 的作用是将一种格式的目标文件转换为另一种格式的目标文件。一般情况下(使用了 gcc
for arm 内置库)使用 arm-none-eabi-gcc 或者arm-none-eabi-ld 最终生成的目标文件的格式为elf32-littlearm,在调试完成后需要将其转换为 hex 文件或者 bin 文件,这个时候就会用到 arm-none-eabi-copy
在这里插入图片描述
调用格式为:

arm-none-eabi-objcopy   options   inputfile   outputfile

  从原理上来说,可以把任意一种 bfd 文件格式转换为另一种 bfd 文件格式,并将多个文件转换后的文件链接
为一个文件。所以,可以将各种资源文件(比如图片、字库等)合并为一个 bin/hex 文件(比如可以将一个程序的源代码放在其二进制代码中),每个资源文件有一个独立的段名,使用时根据段名提取出来就可以了。

3. 目标文件查看工具 arm-none-eabi-objdump

arm-none-eabi-objdump 用于显示目标文件的信息,通过各种不同的选项控制显示何种信息。以下为常用的几
种选项,更多选项参见 binutils.pdf 中的第 5 章。
在这里插入图片描述
调用格式如下,>txtfile 为将输出重定向到文本文件txtfile

arm-none-eabi-objdump   options   inputfile1   inputfile2……   > txtfile

4. 目标文件大小查看工具 arm-none-eabi-size

  各种基于 gcc 的 IDE 编译完成后显示的目标文件大小信息就是通过调用 arm-none-eabi-size 来实现的,其选
项只有少数的几个,如下表。
在这里插入图片描述
调用格式:

arm-none-eabi-size   options   file1   file2……

5. 静态库工具 arm-none-eabi-ar

  在 GCC 中,类似于 windows 下的静态库被称为归档(archive)。用来创建、修改归档和从归档中提取模块的工具为 ar(arm 裸机平台下为 arm-none-eabi-ar),它将一些文件(模块)按照特定的结构组织成的一个文件,各个原始文件的内容、模式(访问权限)、时间戳、属主、组等属性都保留在库文件中,并且从归档中能够提取构成归档的源文件(-x)。arm-none-eabi-ar 的选项分为三种:操作选项、操作修饰符选项和通用修饰符选项。其中,操作选项一次只能使用一个,如表 3.5。
在这里插入图片描述
调用格式([]为可选项,‘ * ’表示*为字符,不带‘’的表示变量):

arm-none-eabi-ar   [‘-’]p[mod]   [relpos]   [count]   archive   [member…]

6. 静态库工具 arm-none-eabi-nm

  在 GCC 中, nm 工具主要是用于查找目标文件、库文件(库文件实际上就是目标文件的打包)、elf 格式可执行文件中的特定符号,这些符号主要是函数名和全局变量名。这个工具通常可以快速定位一些链接问题。比如一个工程链接的时候提示某个函数找不到。则通过该工具在所有库中去查看未定义的函数到底在那个库里面,从而将该库添加到 makefile 中。
在这里插入图片描述
在这里插入图片描述
  调用格式如下:(>txtfile 为将输出重定向到文本文件 txtfile[*]为可选项,如果不给出objfile...,则默认会查看a.out 中的符号,objfile...表示一次可以给出多个文件,不带选项时只会打印出常用选项)

arm-none-eabi-nm   [options]   [objfile…]   >txtfile

7 静态库工具 arm-none-eabi-addr2line

addr2line 工具用于将地址解析为源文件中的文件名和行号,根据可执行文件中的地址或者在可重定位目标文
件段中的偏移地址,利用调试信息找出与该地址对应的文件名和行号。addr2line 有两种工作方式,一种是在命令行中指定 16 进制的地址,然后 addr2line 显示该地址对应的文件名和行号;另一种是 addr2line 从标准输入(cmd命令行)获取 16 进制的地址,然后打印出文件名和行号(第二种方式可以使用管道 pipe 技术)。
在这里插入图片描述
建议的调用格式(具体使用情况参见 编译链接的过程):

arm-none-eabi-addr2line   [options]e   objects   [addr1   addr2   addr3……]


四、 一个简单的示例

  一个不含有 main 函数的简单 C 语言工程组成如下:

1️⃣ 2 个 C 文件:Cordic.cLED.C
2️⃣ 3 个头文件:Cordic.hLED.hos_type.h
3️⃣ 调用 C 语言标准数学库 math.h1 中的 sqrt 函数、浮点处理算法和数据类型转换算法。

1. 源代码

Cordic.c 的代码:

#include "Cordic.h" 
/**   
 *Cordic 算法求解 cos 和 sin
 *----------------------------------*/ 
void cordic(s16 theta, float * sine, float * cosine)   
{
    
     
  s16 cordic_ctab[15] = {
    
    11520, 6801, 3593, 1824, 916, 458, 229, 115, 57, 29, 14, 7, 4, 2, 1};    //    角度为 Q8 格式 
  register u8 k = 0 ; 
  register u8 minus_cos = 0;         
  s16 x = 0x26DD; 
  s16 y = 0; 
  s16 z ; 
  s8  d ; 
  s16 tx, ty, tz; 
  
  if((theta<=270)&&(theta>90)) 
  {
    
     
    theta = 180 - theta; 
    minus_cos = 1; 
  } 
  z = theta<<8; 
  do{
    
     
    d = (*((s8*)(&z)))>>7;         
    tx = x - (((y>>k) ^ d) - d);       
                ty = y + (((x>>k) ^ d) - d); 
                tz = z - ((cordic_ctab[k] ^ d) - d); 
                x = tx;y = ty; z = tz;   
  } while(++k<15); 
  *cosine = (minus_cos?-x:x)/16384.0f; *sine = y/16384.0f; 
} 

LED.c 的代码:

#include "LED.h" 
#include <math.h> 
#include "Cordic.h" 
#define PERIPH_BASE                       ((uint32_t)0x40010000)   
#define APBPERIPH_BASE                    PERIPH_BASE   
#define GPIOA_BASE                        (APBPERIPH_BASE + 0x2000)   
#define GPIOA                             ((GPIO_TypeDef *)GPIOA_BASE)   
#define LED_ON                            GPIOA->GpioDATA[255]=0x01   
#define LED_OFF                           GPIOA->GpioDATA[255]=0x00 
 
int app_init(void);     
 
typedef int (*pfn)(void); 

__attribute__((section(".ssg_header"))) 
pfn ssg_header[]    =   
{
    
     
	(pfn)0x50415353, 
  	0, 
  	app_init 
}; 
 
int app_init(void)   
{
    
     
  	return 1; 
} 
 
u32 test_led(u32 psys, u32 sec)   
{
    
       
     _sys * sys=(void*)psys;   
     u32 bakms=sys->RunTimeMs;   
     float f_tmp; 
     float f_tmp1; 
           
     cordic(sec, &f_tmp, &f_tmp1); 
     f_tmp = sqrt(f_tmp); 
         
     while(sys->RunTimeMs-bakms< sec*1000)
     {
    
    
     	if(sys->RunTimeMs%100>50)   
     		LED_ON;   
        else   
            LED_OFF;   
     }   
     return f_tmp;
}    

Cordic.h 的代码:

#ifndef __CORDIC_H__ 
#define __CORDIC_H__ 
 
#include "os_type.h" 
void cordic(s16 theta, float * sine, float * cosine); 
#endif 

LED.h 的代码:

#include "os_type.h" 
 
typedef      u32      uint32_t    ;   
#define    __IO      volatile 
typedef struct _tsRTCTIMECB /*  时间格式  */   
{
    
       
  u16      Year;          	/*  阳历年份  - [2000,2099] */                       
  u8        Month;        	/*  某年的几月份  - [0,11] */                   
  u8        Week;          	/*  星期几  - [0,6] */                               
  u8        Day;            /*  某月的几号  - [1,31] */       
  u8        Days_amt; 		/*  当前月份的天数  - [28,31] */       
  u8        Hour;          	/*  几点  - [0,23] */                           
  u8        Minute;      	/*  几分  - [0,59] */                       
  u8        Second;      	/*  几秒  - [0,59] */                       
} tsRtcTimeCb;   
 
typedef struct{
    
       
      u32 RunTimeMs;   
      u32 SleepTimes;   
      u32 LockTimes;   
      tsRtcTimeCb now;   
      u32 flash_size;   
      u32 flash_jedecid;   
      u8    flash_uid[8];   
} _sys;   
 
typedef struct   
{
    
       
  __IO uint32_t                          GpioDATA[256];              // @0x00-0x3FC   
  __IO uint32_t                          GpioDIR;                    // @0x400   
  __IO uint32_t                          GpioIS;                     // @0x404   
  __IO uint32_t                          GpioIBE;                    // @0x408   
  __IO uint32_t                          GpioIEv;                    // @0x40C   
  __IO uint32_t                          GpioIE;                     // @0x410   
  __IO const uint32_t              GpioRIS;                          // @0x414   
  __IO const uint32_t              GpioMIS;                          // @0x418
    __IO uint32_t                          GpioIC;                   // @0x41C   
  __IO uint32_t                          GpioAFSEL;                  // @0x420   
     
  const uint32_t fill0[119];                                         // @0x424-0x5FC   
 
  __IO uint32_t                          GpioITCR;                   // @0x600   
  __IO uint32_t                          GpioITIP1;                  // @0x604   
  __IO uint32_t                          GpioITIP2;                  // @0x608   
  __IO uint32_t                          GpioITOP1;                  // @0x60C   
  __IO const uint32_t              GpioITOP2;                        // @0x610   
  __IO uint32_t                          GpioITOP3;                  // @0x614   
     
  const uint32_t fill1[622];                                         // @0x618-0xFCC   
     
  __IO const uint32_t reservedID[4];                          		 // @0xFD0-0xFDC   
 
  __IO const uint32_t GpioPeriphID0;                          		// @ 0xFE0   
  __IO const uint32_t GpioPeriphID1;   
  __IO const uint32_t GpioPeriphID2;   
  __IO const uint32_t GpioPeriphID3;   
  __IO const uint32_t GpioPCellID0;   
  __IO const uint32_t GpioPCellID1;   
  __IO const uint32_t GpioPCellID2;   
  __IO const uint32_t GpioPCellID3;   
} GPIO_TypeDef; 

os_type.h 的代码:

#ifndef __OS_TYPE_H 
#define __OS_TYPE_H 
 
#ifndef __C51__ 
  typedef char * sfr; 
  typedef char * sbit; 
  #define INT_UART0 
  #define INT_TIMER0 
  #define INT_TIMER2 
  #define xdata 
  #define xptr 
#else 
  #define INT_UART0 interrupt 4 
  #define INT_TIMER0 interrupt 1 
  #define INT_TIMER2 interrupt 5 
  #define xptr xdata * 
#endif 
 
#define BIT(a) (1<<a) 
#define BIT4B(a,b,c,d) (1<<a|1<<b|1<<c|1<<d)
#define BIT8B(a,b,c,d,e,f,g,h) (BIT4B(a,b,c,d) | BIT4B(e,f,g,h)) 
 
typedef unsigned    long     handle; 
typedef unsigned    char     BYTE; 
typedef unsigned    long     u32 ; 
typedef unsigned    short    u16 ; 
typedef unsigned    char     u8 ; 
typedef signed      long     s32 ; 
typedef signed      short    s16 ; 
typedef signed      char     s8 ; 
typedef unsigned    char     bool; 
typedef struct {
    
    s32 x;  s32 y;} _uiPointI; 
typedef struct {
    
    s16 x,y,w,h;} _uiRect; 
 
#define false      0 
#define true      (~0) 
   
#define False    0 
#define True      (~False) 
 
#ifndef NULL  
#define NULL      0 
#endif   
 
#endif      //__OS_TYPE_H 

sections.lds 的代码:

SECTIONS {
    
     
     . = 0x20000000; 
    .app_header : {
    
     
    *(.ssg_header) 
    } 
    .text : {
    
     
               	*(.text .text.*)      
               	*(.rodata .rodata.* .constdata .constdata.*) 
        		*(vtable) 
        		KEEP(*(.eh_frame*)) 
        		*(.glue_7) 
                *(.glue_7t) 
    } 
    .data : {
    
     
                *(.data)       
    } 
    .bss : {
    
     
                *(.bss)       
    } 
    _end = . ; 
}

2. 编译链接的过程

  目录结构为:

工程目录:E:\cm3_app1

src:

Cordic.c 
  LED.c 

Include:

Cordic.h 
os_type.h 
LED.h 

  在 gcc 工具链中,我们使用的*gcc*g++命令,被称作 compiler driver(编译驱动程序)。它们本身并不做任何具体的编译工作,而是通过解析命令行参数,调用具体的编译器和其它工具来驱动整个编译过程。无论使用*gcc 或者*g++,我们都可以通过-v 参数显示整体构建过程。在这个过程中我们会看到对于真正的编译器 ccl(c compiler)或者 cclplus(c++ compiler)的调用,以及对于 as(汇编器)和 collect2(连接器)的调用。

Step 1. 对 C 文件编译

在这里插入图片描述
  上表中,S1 部分为编译驱动程序,S2 部分为 cortex-m3 处理器对应的选项,S3 部分指明标准 C 库使用newlib-nano 库(如果不指定,则使用的是 newlib 库),S4 部分为优化等级,S5 部分为 debug 等级,S6 部分指明只编译不链接(即只调用预编译器、编译器和汇编器),S7 部分指明输出文件的名称(带相对/绝对路径),S8 部分指明用户头文件的搜索路径,S9 部分指明需要编译的源文件。

Step 2. 将所有.o 文件链接成目标文件

在这里插入图片描述
  上表中,S1、S2、S4、S5、S6 如 Step 1 所示,S3 指明链接时生成 map 文件(链接专用参数只能通过-W1或者-Xlinker 来传递给 arm-none-eabi-g++),S7 指明链接时不使用系统提供的 startup 文件,S8 指明使用的链接脚本文件,S9 指明输出文件名(无论指定什么样的后缀,其输出的总是 arm-elf 格式的文件),S10 指明进行链接的所有文件。另外,如果使用到第三方的库(静态库或者动态库),则需要使用-L 和/或-l 指令指定其路径和名称。

Step 3. 将 arm-elf 格式的文件转换为 hex 或者 bin 文件

在这里插入图片描述
  上表中,S1 为 binutils 中的格式转换工具,S2S3 用于删除一些用于调试的段和一些符号、重定位信息,S4 指明输出格式(如果要输出 bin 文件,则为-O binary),S5为输入文件,S6为输出文件。利用 arm-none-eabi-objcopy可以实现对目标文件的任意剪裁。

Step 4. 显示最终目标文件的大小信息

在这里插入图片描述
  上表中,S1 为 binutils 中的目标文件大小显示工具,S2 为显示格式(一般 IDE 都是用-B 格式),S3 为需要分析的目标文件,使用该命令行命令之后,其显示如下所示。需要注意一点,该指令不适用于.bin 文件。
在这里插入图片描述

Step 5. 对目标文件进行反汇编

在这里插入图片描述
  上表中,S1 为咪表文件查看工具,S2 表示对所有段进行反汇编,S3 表示源代码与反汇编代码混合显示(编译时有 debug 选项的情况下才会显示源代码),S4 为目标文件,S5 为将输出重定向到 LED.s 文件。

Step 6. 将目标代码地址解析为源代码中 文件+函数+行号

在这里插入图片描述
  上表中,前面带→的为用户输入的指令,不带的为指令执行后的结果。S1 为代码解析工具,S2 为使用的选项(等价于-f –p –a –e,这里-e 选项必须为最后一个),S3 为可执行目标代码文件,S4 为代码地址;D1 为代码地址(-a 选项使然),D2 为函数名,l中的 at为“在……之中”,D3D2 所归属的源文件全路径名称,D4 为代码地址在源文件中的行号。如果该工具不足以完成调试中的代码跟踪功能,可以进一步参考 IBM 网站的一篇文章,网址为:http://www.ibm.com/developerworks/cn/linux/l-graphvis/
  上面的过程仅仅是一次编译链接的过程,实际应用中不可能每次都将所有文件全编译一遍,通常的做法是仅将做出修改的文件进行编译。就本例而言,其源文件依赖关系如图 4.1。
在这里插入图片描述
在这里插入图片描述
  其中,math.h 为系统头文件,一般不会改变,所以不予考虑。根据源文件的依赖关系,可以得到编译过程中的各个文件之间的依赖关系,如图 4.2 所示。可见,如果仅仅是 LED.c 和/或LED.h 做了改动,则不需要重新编译生成 Cordic.o。gcc 中有将这种依赖关系输出到文件的指令。
在这里插入图片描述
  从上面可以看出,LED.o的依赖文件并没有包含 Cordic.c 文件,这是因为对于函数 test_led 调用函数 cordic,在LED.o 中不会包含函数 cordic 的真正地址,而只会有一个占位地址;到了链接阶段才会将占位地址替换为真正的函数 cordic 的地址。所以,实际上不用考虑 LED.o 对于 Cordic.c的依赖。如果不使用makefile 脚本来进行编译、链接,则需要依赖文件和各文件的修改时间来判定那个文件需要编译。

3. ld 文件对目标代码结构的影响

  先通过 arm-none-eabi-objdump 来看一下生成的 LED.out 的构成,指令如下:

arm-none-eabi-objdump   -x   LED.out

  上面的命令行指令用于查看 LED.out 的文件头信息,从而了解其大致构造。

在这里插入图片描述
  对 LED.out 进行反汇编
在这里插入图片描述
可以看到以下几点:

  1. 文件格式为 elf32-littlearm
  2. 加载和运行的地址都为 0x20000000,大小为 0x00001530
  3. 共有 14 个 sections(其实还有三个表段.shstrtab,.symtab,.strtab),并给出每个段的大小、装载和运行地址、对齐方式,.debug 开头的段为调试时使用的,.comment 段为编译器版本信息,.ARM.attributes 段为处理器相关信息。调试完成下载时可以将调试信息、编译器信息、处理器信息、表段等都删除掉,只保留代码和数据。
  4. .app_header段为 DATA 类型,这是由其定义方式决定的,如果使用__asm__(".word 0x50415353");这种内联汇编定义,则会将其归类为 CODE 类型。
  5. 注意到,.rodata、.constdata段及其子段都显式的包含在.text 段中,在不指定的情况下,会出现几个与.rodata、.constdata 相关的段。可以参见后面修改之后的 sections1.lds 及其连接之后对应的段结构。
  6. 从修改前后的 sections.lds 可以看出,如果不将.rodata、.constdata 作为.text 段的输入段,则他们回座位独立的段出现,并且其类型为 CODE。也就是说,因为.text 的默认属性为 CODE,所以当将类型为 DATA 的段作为其输入段时,这些输入段在最终的输出目标文件中也会变成CODE类型。由此,进一步修改sections1.lds为 sections2.lds,以便将.app_header(输入段为 .ssg_header ) 的类型改为 CODE 类型。

sections1.lds 的代码:

SECTIONS {
    
     
     . = 0x20000000; 
    .app_header : {
    
     
    			*(.ssg_header) 
    } 
    .text : {
    
     
               	*(.text)      
    } 
    .data : {
    
     
                *(.data)       
    } 
    .bss : {
    
     
                *(.bss)       
    } 
    _end = . ; 
}

sections1.lds 对应的段结构
在这里插入图片描述
sections2.lds 的代码:

SECTIONS {
    
     
    . = 0x20000000; 
    .text : {
    
     
                    *(.ssg_header) 
                    *(.text .text.*)      
                    *(.rodata .rodata.* .constdata .constdata.*) 
        			*(vtable) 
       	 			KEEP(*(.eh_frame*)) 
        			*(.glue_7) 
                    *(.glue_7t) 
            } 
    .data : {
    
     
                    *(.data)       
    } 
    .bss : {
    
     
                    *(.bss)       
    } 
    _end = . ; 
} 

Sections2.lds 对应的段结构
在这里插入图片描述
Sections2.lds 对应的反汇编
在这里插入图片描述
To be continue…
篇幅太长,另开一篇


五、 ld/lds 链接脚本文件的书写规则


六、 Makefile 简要教程


七、 一个通用 Makefile 的实现

To be continue…

猜你喜欢

转载自blog.csdn.net/fdcp123/article/details/114537469