GCC编译四步和LDS脚本

news2024/10/6 22:58:40

GCC编译四步

1、预处理:宏定义替换之类的工作

2、编译非汇编:将源代码经过词法分析、语法分析、语义分析转为汇编代码的过程

3、汇编:将汇编代码转为具体二进制机器码的过程(此时由于还没有进行链接,所以虽然是二进制代码也不可直接执行)

4、链接:将目标文件中链接到的其他目标二进制文件进行整合成最终可执行二进制文件

这里说整合是因为有两种链接方式

动态链接:最终形成的可执行二进制文件只包含需要链接的目标二进制文件的位置,本身自己的二进制文件并不包含目标二进制文件,最终形成的二进制文件较小,利于目标二进制库进行更新,不用二次编译(只要位置和接口都没变),但是自己单独不可运行。

静态链接:将需要的目标二进制文件打包进自己的二进制文件中,其实就是位置替换,将出现目标二进制文件的位置用目标二进制文件进行位置替换。可独立执行,生成的二进制文件较大,每次目标二进制库更新后,都需要重新进行编译(如果要使用新功能的话)。

text段、data段和bss段

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWctbXkuY3Nkbi5uZXQvdXBsb2Fkcy8yMDEzMDEvMDEvMTM1NzAwNzA1NV82ODQ0LmdpZg

一个程序的3个基本段:text段,data段,bss段。

text段在内存中被映射为只读,但.data和.bss是可写的。

text段:就是放程序代码的,编译时确定,只读;

data段:存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量和赋初值的静态变量存放在这个区域,常量也存放在这个区域;

bss段:定义而没有赋初值的全局变量和静态变量,放在这个区域;

bss(可读可写)

bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。

text(只读)

text段是程序代码段,在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。

data(可读可写)

data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。

栈(stack):保存函数的局部变量和参数。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。

堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

Lds

介绍

链接器:把一个或多个输入文件合并成一个输出文件,输入文件是目标文件或者链接脚本文件,输出文件是目标文件(库文件)或者可执行文件,链接器从链接脚本读完一个 section 后,将定位器符号的值增加该 section 的大小。

链接脚本:链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的各部分在程序地址空间内的布局,地址空间包括 ROM 和 RAM。

链接器总是使用链接脚本的,如果你不提供,则链接器会使用一个缺省的脚本,这个脚本是被编译进链接器可执行文件的。

https://img-blog.csdnimg.cn/8dd3696198914c169a69028d42b8cee1.png

关键字详解

ENTRY(进入)

ENTRY(main)

ENTRY(MultibootEntry)

ENTRY关键字用于定义应用程序的入口点,即输出文件中的第一条可执行指令。该关键字接受链接程序/内核入口点的符号名作为单个参数。所提供的符号名指向的代码将是。ELF和PE二进制文件中的文本部分。

ENTRY(SYMBOL) :将符号SYMBOL的值设置成入口地址。

入口地址(entry point)是指进程执行的第一条用户空间的指令在进程地址空间的地址

ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)

1、ld命令行的-e选项

2、链接脚本的ENTRY(SYMBOL)命令

3、如果定义了start符号, 使用start符号值

4、如果存在.text section, 使用.text section的第一字节的位置值

5、使用值0

OUTPUT_FORMAT(输出格式)

OUTPUT_FORMAT(elf64-x86-64)

OUTPUT_FORMAT("pe-i386")

OUTPUT_FORMAT指令只接受一个参数。它指定可执行文件的输出格式。要了解系统binutils和GCC支持哪些输出格式,可以使用objdump-i命令。

STARTUP (启动)

STARTUP(Boot.o)

STARTUP(crt0.o)

启动需要一个参数。它是要链接到可执行文件开头的文件。对于userland项目,这通常是crt0。o或crtbegin。o、 对于内核,通常是包含程序集样板的文件启动堆栈,在某些情况下是GDT之类的,然后调用kmain()。

SEARCH_DIR(搜索目录)

SEARCH_DIR(Directory)

这将为您的库搜索目录添加路径。-nostlib标志将导致在该路径中找到的任何库被有效忽略。我不知道为什么,这似乎就是ld的工作原理。它将链接器脚本指定的搜索目录视为标准目录,因此会忽略它们,而不使用默认的libs和此类标志

INPUT(输入)

INPUT(File1.o File2.o File3.o ...)

INPUT

(

       File1.o

       File2.o

       File3.o

       ...

)

INPUT是一个“链接器脚本中”替换项,用于将对象文件添加到命令行。您通常会指定类似于ld File1的内容。o文件2。o、 可以使用输入部分在链接器脚本中执行此操作。

OUTPUT (输出)

OUTPUT(Kernel.bin)

OUTPUT命令指定要生成的文件作为链接过程的输出。这是最终创建的二进制文件的名称。此命令的效果与-o filename命令行标志的效果相同,后者会覆盖它。

MEMORY (记忆存储)

MEMORY

{

    ROM (rx) : ORIGIN = 0, LENGTH = 256k

    RAM (wx) : org = 0x00100000, len = 1M

}

MEMORY声明一个或多个内存区域,其属性指定该区域是否可以写入、读取或执行。这主要用于不同地址空间区域可能包含不同访问权限的嵌入式系统。

上面的示例脚本告诉链接器有两个内存区域:

a) “ROM”从地址0x00000000开始,长度为256kB,可以读取和执行。

b) “RAM”从地址0x00100000开始,长度为1MB,可以写入、读取和执行。

SECTIONS命令

SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).

该命令格式如下:

SECTIONS

{

SECTIONS-COMMAND

SECTIONS-COMMAND

}

SECTION-COMMAND有四种:

(1) ENTRY命令

(2) 符号赋值语句

(3) 一个输出section的描述(output section description)

(4) 一个section叠加描述(overlay description)

如果整个连接脚本内没有SECTIONS命令, 那么ld将所有同名输入section合成为一个输出section内, 各输入section的顺序为它们被连接器发现的顺序.如果某输入section没有在SECTIONS命令中提到, 那么该section将被直接拷贝成输出section。

KEEP (保持)

链接器脚本中的KEEP语句将指示链接器保留指定的节,即使其中没有引用任何符号。此语句在链接器脚本的节中使用。当在链接时执行垃圾收集时,这一点就变得很重要,在链接命令行内使用了选项 -gc-sections 后,链接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制让链接器保留一些特定的 section,可用 KEEP() 关键字达此目的。说的通俗易懂就是:防止被优化。

该语句常见于针对ARM体系结构的链接器脚本中,用于将中断向量表放置在偏移量0x00000000处。如果没有这个指令,代码中可能不会显式引用的表将被删除。

SECTIONS

{

       .text :

       {

              KEEP(*(.text.ivt))

              *(.text.boot)

              *(.text*)

       } > ROM

       /** ... **/

}

PROVIDE

为在任何链接目标中没有定义但是被引用的一个符号,而在链接脚本定义一个符号。 PROVIDE(symbol = expression)。提供定义‘_exfun’的例子:

SECTIONS

{

  .text :

    {

      *(.text)

      _exfun = .;

      PROVIDE(_exfun = .);

    }

}

如果程序定义了’ _exfun ‘(带有前导下划线),链接器将给出重复定义错误。另一方面,如果程序定义了’ exfun‘(没有前导下划线),链接器会默认使用程序中的定义。如果程序引用了’ exfun '但没有定义它,链接器将使用链接器脚本中的定义。

PROVIDE指令将考虑定义一个普通符号,即使这样的符号可以与PROVIDE将创建的符号组合在一起。当考虑构造函数和析构函数列表符号时,这一点尤其重要,因为它们通常被定义为普通符号。

TYPE

每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型。它可以为以下五种值

NOLOAD 该section在程序运行时,不被载入内存。

DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来。这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。

AT( LAM_ADDR )

section包含两个地址:VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址)。默认情况下 LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

用关键字 AT()指定,括号内包含表达式,表达式的值用于设置LMA。如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。这个属性主要用于构件ROM境象。

一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定)。

例子,

SECTIONS

{

.text 0×1000 : {_etext = . ;*(.text);  }

.mdata 0×2000 :

AT ( ADDR (.text) + SIZEOF (.text) )

{ _data = . ; *(.data); _edata = . ; }

.bss 0×3000 :

{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}

}

c程序如下:

extern char _etext, _data, _edata, _bstart, _bend;

char *src = &_etext;

char *dst = &_data;

while (dst rom }

REGION

这个region就是前面说的MEMORY命令定义的位置信息。

ALIGN

表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。

输入section描述

输入section描述基本语法:

FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)

FILENAME文件名,可以是一个特定的文件的名字,也可以是一个字符串模式。

SECTION名字,可以是一个特定的section名字,也可以是一个字符串模式。

具体示例解析:

1、*(.text) :表示所有输入文件的.text section

2、(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors section。

3、data.o(.data) :表示data.o文件的.data section

4、data.o :表示data.o文件的所有section

5、*(.text .data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第一个文件的.data section,第二个文件的.text section,第二个文件的.data section,…

6、*(.text) *(.data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第二个文件的.text section,…,最后一个文件的.text section,第一个文件的.data section,第二个文件的.data section,…,最后一个文件的.data section

下面看连接器是如何找到对应的文件的。

当FILENAME是一个特定的文件名时,连接器会查看它是否在连接命令行内出现或在INPUT命令中出现。

当FILENAME是一个字符串模式时,连接器仅仅只查看它是否在连接命令行内出现。

注意:如果连接器发现某文件在INPUT命令内出现,那么它会在-L指定的路径内搜寻该文件。

字符串模式内可存在以下通配符:

* :表示任意多个字符

? :表示任意一个字符

[CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z

:表示引用下一个紧跟的字符

在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外。

任何一个文件的任意section只能在SECTIONS命令内出现一次。

看如下例子

SECTIONS {

.data : { *(.data) }

.data1 : { data.o(.data) }

}

data.o文件的.data section在第一个OUTPUT-SECTION-COMMAND命令内被使用了,那么在第二个OUTPUT-SECTION-COMMAND命令内将不会再被使用,也就是说即使连接器不报错,输出文件的.data1 section的内容也是空的。

ASSERT(断言)

ASSERT(exp, message)

内建函数

lds中有以下一些内建函数:

ABSOLUTE(EXP) :转换成绝对值

ADDR(SECTION) :返回某section的VMA值。

ALIGN(EXP) :返回定位符’.'的按照EXP进行对齐后的修调值,对齐后的修调值算法为:(. + EXP – 1) & ~(EXP – 1)

BLOCK(EXP) :如同ALIGN(EXP),为了向前兼容。

DEFINED(SYMBOL) :如果符号SYMBOL在全局符号表内,且被定义了,那么返回1,否则返回0

LOADADDR(SECTION) :返回三SECTION的LMA

MAX(EXP1,EXP2) :返回大者

MIN(EXP1,EXP2) :返回小者

NEXT(EXP) :返回下一个能被使用的地址,该地址是EXP的倍数,类似于ALIGN(EXP)。除非使用了MEMORY命令定义了一些非连续的内存块,否则NEXT(EXP)与ALIGH(EXP)一定相同

SIZEOF(SECTION) :返回SECTION的大小。当SECTION没有被分配时,即此时SECTION的大小还不能确定时,连接器会报错

SIZEOF_HEADERS :返回输出文件头部的字节数。这些信息出现在输出文件的开始处。当设置第一个段的开始地址时,你可以使用这个数字。如果你选择了加速分页,当产生一个ELF输出文件时,如果链接器脚本使用SIZEOF_HEADERS内建函数,连接器必须在它算出所有段地址和长度之前计算程序头部的数值。如果连接器后来发现它需要附加程序头,它将报告一个“not enough room for program headers”错误。为了避免这样的错误,你必须避免使用SIZEOF_HEADERS函数,或者你必须修改你的连接器脚本去避免强制连接器去使用附加程序头,或者你必须使用PHDRS命令去定义你自己的程序头

Symbols (象征)

可以在链接器脚本中定义任意符号。这些符号被添加到程序的符号表中。表中的每个符号都有一个名称和一个关联的地址。链接器脚本中已赋值的符号将被赋予外部链接,并可在程序代码中作为指针访问。

floating_point = 0;

SECTIONS

{

  .text :

    {

      *(.text)

      _etext = .;

    }

  _bdata = (. + 3) & ~ 3;

  .data : { *(.data) }

}

在上面的示例中,符号浮点被定义为零。符号_etext被定义为最后一个字符后面的地址。文本输入部分。符号_bdata被定义为以下地址:。文本输出部分向上对齐到4字节边界。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/650263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Context Prior for Scene Segmentation--CVPR, 2020

Context Prior for Scene Segmentation–CVPR, 2020 文章目录 Context Prior for Scene Segmentation--CVPR, 2020一、背景介绍二、方法介绍1.A的生成2.Affinity Loss3.如何从 X X X获取P4.Y操作 一、背景介绍 问题:现阶段,不少语义分割方法所限于卷积结…

快速部署合同管理模板:低代码实现高效率

在现代商业环境中,合同管理是企业日常运营中至关重要的一环。合同是企业与外部实体之间约定的法律文件,合够帮助企业有效管理合同的全生命周期,包括合同创建、审批、签署、执行和归档,以提高合同管理的效率和准确性。 随着企业数…

软件测试面试题(大全)

1.B/S架构和C/S架构区别 B/S 只需要有操作系统和浏览器就行,可以实现跨平台,客户端零维护,维护成本低,但是个性化能力低,响应速度较慢 C/S响应速度快,安全性强,一般应用于局域网中,因…

【操作系统】操作系统最全的总结,5万字干货

文章目录 前言搞清楚几个问题 一、认识操作系统二、计算机硬件三、进程和线程1、进程2、进程模型3、进程的创建4、进程的终止5、进程的层次结构6、UNIX 进程体系7、Windows 进程体系8、进程状态9、进程的实现10、线程11、线程的使用12、经典的线程模型13、线程系统调用14、POSI…

Revit轴网问题:创建标高看不到原来的轴网和轴网转化

一、Revit中创建的标高看不见原先的轴网怎么解决 (1)在Revit中绘制的轴网会默认超过最高标高一定距离,若新绘制的标高会在这距离之上,进入新绘制的“标高3”平面会发现看不到(1至6号轴网)。 (2)进入东立面,拖动轴网往上移动即可。 进入南、北…

MySQL 读写分离代理(Mycat2)

作者:田逸 作者亲自尝试过的开源MySQL读写分离工具有Amoeba、MySQL Proxy、Mycat等,经过仔细测试对比,在某个实际项目中选用Mycat作为MySQL数据库读写分离的代理工具。Mycat当前的最新版本为Mycat2,可从http://dl.mycat.org.cn/2…

60、基于51单片机无线蓝牙温度上下限控制加热系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+开题报告+任务书+元器件清单等)

摘 要 随着人们生活水平的提高,对生活环境的要求也越来越高,家用电器越来越趋向于自动控制控制乃至于智能控制,针对目前家庭的实际需要,自动控制水温报警系统比较方便实用,本文就通过51系列单片机来实现一种家用自动控…

VS2013 如何创建动态库和使用

创建动态库具体的步骤是:(以DLL为例) 1、创建一个win32项目 2、选择应用程序类型:DLL; 附加选项:导出符号,勾上; 3、点击完成,就会生成动态库 4、 由于是导出库&#xf…

[细读经典]Megatron论文和代码详细分析(1)

[细读经典]Megatron论文和代码详细分析(1) 导航: 迷途小书僮:[细读经典]Megatron论文和代码详细分析(2)102 赞同 41 评论文章正在上传…重新上传取消 前言 作为一款支持multi-node,multi-GPU的可以直接用来训练GPT3等世界上超大规模的自然…

【C++学习】VScode配置C/C++开发环境

VSCode是一个高级编辑器,只能用来写C/C/Python/Java等代码,不能直接编译这些代码。所以,我们需要搭建编译和调试环境,本文以C/C为例,使用MinGW-w64,将其移植到Windows平台的一个gcc编译器。下面具体介绍如何…

基于Java蜀都天香酒楼网站系统设计实现(源码+lw+部署文档+讲解等)

博主介绍: ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ 🍅 文末获取源码联系 🍅 👇🏻 精…

第二章_基于redis实现分布式锁

基本实现 借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setnx命令,只有一个客户端可以成功,返回1(true);其他的客户端返回0(false…

微信小程序WE分析----事件分析

目录 web分析-小程序 事件分析概述 1.新增事件管理 事件参数说明 (1) 填写事件配置 (2)小程序添加上报代码:将上报代码添加到小程序中 (3)测试事件数据上报:测试事件上报的数据是否正确。 属性管理 字典管理 新增事件分析 创建事件分析 添加事件指…

Java+Swing+mysql员工工资管理系统2.0

JavaSwingmysql员工工资管理系统2.0 一、系统介绍二、功能展示1.用户登陆2.主页3.员工工资查询4.员工工资添加5.员工工资修改6.员工工资删除 三、系统实现1.salary.java 四、其它系统五、获取源码 一、系统介绍 该系统实现了简单的增删查改、用户登陆、员工工资查询、员工工资…

美联储缩表意味着什么?

What does the Feds balance sheet reduction mean? 这里的表是资产负债表,Balance sheet. 美联储(Federal Reserve)作为全球影响力最大的央行,其在货币政策上做出的一些调整,可能就会引起全球经济和金融市场动荡&am…

项目测试排期的正确方法是什么?

测试排期是项目排期里面的一部分,所以了解项目排期对整体产品的全貌会有一个宏观的认知,甘特图能很好的体现项目排期,里面包含了参与角色和每个角色对应的排期。项目参与者和项目责任人都可以清晰的看到项目当前进展和项目耗时等。 甘特图可…

智能监控系统:在线培训考试系统的保障

随着互联网技术的不断发展,越来越多的培训机构和教育机构采用在线学习和考试的方式进行教学。然而,考试中的作弊问题也随之产生,给教育质量和学术诚信带来了挑战。为了解决这一问题,许多在线培训考试系统引入了智能监控系统。 智…

邓铎:探索书法艺术的新境界

中国书画院院士邓铎,是一位在书法艺术领域拥有深刻理解和丰富实践经验的老者。他的作品随心所欲,个性鲜明,具备独特的审美品味和艺术手法,更有重要的理论创新,让书法艺术大放光彩。 邓铎的书法作品在形式上追求“形似象…

【无标题】面试常考算法(3):二叉树遍历(创建、遍历、销毁)

这部分不够熟悉的话,面试直接递归就行。不过实际中虽然递归在某些情况下可以提供简洁和优雅的解决方案,但可能占用大量的内存空间和导致额外时间开销,所以还是尽量使用非递归。因为每次递归调用时,函数的局部变量和参数都需要在栈…

迭代器模式(十九)

相信自己,请一定要相信自己 上一章简单介绍了访问者模式(十八), 如果没有看过, 请观看上一章 一. 迭代器模式 引用 菜鸟教程里面迭代器模式介绍: https://www.runoob.com/design-pattern/iterator-pattern.html 迭代器模式(Iterator Pattern&#xff…