- 1. 处理器架构选项
- 2. C 标准库的选择
- 3. 语言标准类常用选项
- 4. Overall 类选项(控制 gcc/g++ 的输出类型)
- 5. 诊断信息类选项(控制诊断信息的格式)
- 6. 编译优化类参数选项
- 7. 预编译控制参数常用选项
- 8. 链接用参数常用选项
- 1. 链接器工具 arm-none-eabi-ld
- 2. 二进制格式转换工具 arm-none-eabi-objcopy
- 3. 目标文件查看工具 arm-none-eabi-objdump
- 4. 目标文件大小查看工具 arm-none-eabi-size
- 5. 静态库工具 arm-none-eabi-ar
- 6. 静态库工具 arm-none-eabi-nm
- 7 静态库工具 arm-none-eabi-addr2line
- 1. 源代码
- 2. 编译链接的过程
- 3. ld 文件对目标代码结构的影响
本文主要描述 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 程序的整个过程。
图 1.1 用户程序的编译链接流程 >
APP 的生成大致需要程序编写、编译和链接三个过程,可以参见上图。有以下两点需要说明:
- 对于汇编文件而言,文件后缀为 s 时只能使用 GNU ASM 中的指令、伪指令、伪操作等;后缀为 S 时,允许使用 C 语言中的预处理指令”#”,包括宏定义、文件包含和条件编译。
arm-none-eabi-gcc/g++
不仅可以实现编译的功能,在添加附加参数的情况下,它也能够实现 as 和 ld 的功能。
下面的章节是对面向 Arm 裸机平台的 GNU 工具的使用介绍。
表 1.1 GNU for arm 的工具链
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
的库可供选择(newlib
和 newlib_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
- 编译器使用的 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 |
- 编译器使用的 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 |
-fcond-mismatch
,允许三元操作符x?a:b
中的a
和b
的类型不同(只适用于 C 语言)。-fsigned-char
、-fno-unsigned-char
、-funsigned-char
和-fno-signed-char
,前两者者表示char:=signed char
,后者表示char:=unsigned char
。-fsigned-bitfields
、-fno-unsigned-bitfields
、-fno-signed-bitfields
、-funsigned-bitfields
表示结构体中的位域的符号,前两者表示为有符号数,后两者表示为无符号数。默认为有符号数。- 只适用于 C++ 的
-fabi-version=n
参数,选择n=0
最为保险。 -fno-asm
,不将asm
、inline
和typeof
作为关键词使用,要使用相同的功能需要__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-gcc
和 arm-none-eabi-cpp
。
arm-none-eabi-gcc -E 等价于 arm-none-eabi-cpp
8. 链接用参数常用选项
链接时,目标文件只会从它后面的. a 文件搜寻相应的未定义符号,所以在编译链接表达式中需要把*.a 文件
写在其它文件后面(下面都为使用 gcc 链接时的选项)。
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.c
和LED.C
2️⃣ 3 个头文件:Cordic.h
、LED.h
和os_type.h
3️⃣ 调用 C 语言标准数学库math.h1
中的sqrt
函数、浮点处理算法和数据类型转换算法。
1. 源代码
Cordic.c 的代码:
#include "Cordic.h"
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};
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;
u8 Month;
u8 Week;
u8 Day;
u8 Days_amt;
u8 Hour;
u8 Minute;
u8 Second;
} 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];
__IO uint32_t GpioDIR;
__IO uint32_t GpioIS;
__IO uint32_t GpioIBE;
__IO uint32_t GpioIEv;
__IO uint32_t GpioIE;
__IO const uint32_t GpioRIS;
__IO const uint32_t GpioMIS;
__IO uint32_t GpioIC;
__IO uint32_t GpioAFSEL;
const uint32_t fill0[119];
__IO uint32_t GpioITCR;
__IO uint32_t GpioITIP1;
__IO uint32_t GpioITIP2;
__IO uint32_t GpioITOP1;
__IO const uint32_t GpioITOP2;
__IO uint32_t GpioITOP3;
const uint32_t fill1[622];
__IO const uint32_t reservedID[4];
__IO const uint32_t GpioPeriphID0;
__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
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:
Include:
Cordic.c
LED.c
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 中的格式转换工具,S2
和 S3
用于删除一些用于调试的段和一些符号、重定位信息,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
为 “在……
之中”,D3
为D2
所归属的源文件全路径名称,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 进行反汇编
可以看到以下几点:
- 文件格式为
elf32-littlearm
; - 加载和运行的地址都为
0x20000000
,大小为0x00001530
; - 共有 14 个
sections
(其实还有三个表段.shstrtab
,.symtab
,.strtab
),并给出每个段的大小、装载和运行地址、对齐方式,.debug
开头的段为调试时使用的,.comment
段为编译器版本信息,.ARM.attributes
段为处理器相关信息。调试完成下载时可以将调试信息、编译器信息、处理器信息、表段等都删除掉,只保留代码和数据。 .app_header
段为DATA
类型,这是由其定义方式决定的,如果使用__asm__(".word 0x50415353")
; 这种内联汇编定义,则会将其归类为CODE
类型。- 注意到,
.rodata、.constdata
段及其子段都显式的包含在.text
段中,在不指定的情况下,会出现几个与.rodata、.constdata
相关的段。可以参见后面修改之后的sections1.lds
及其连接之后对应的段结构。 - 从修改前后的 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…
篇幅太长,另开一篇
To be continue…
https://blog.csdn.net/fdcp123/article/details/114537469#t30