U-Boot学习(4):u-boot.lds链接脚本分析

在之前的文章中有介绍U-Boot的编译流程,但我们知道,不同的存储介质可能会接在不同的接口上,如NOR Flash、EMMC和SDRAM等内存的接口是不同的,而不同的接口对应CPU就会映射到不同的内存中。所以如果我们需要运行U-Boot的话,我们就应该根据映射的内存,然后将程序链接到指定的位置。

文章目录

  • 1 链接脚本分析
    • 1.1 语法介绍
    • 1.2 u-boot.lds分析
  • 2 链接脚本的使用
  • 3 链接U-Boot到别的地址

1 链接脚本分析

1.1 语法介绍

在分析之前,建议先学习lds链接脚本的语法,可以参考我的这一篇文章:lds链接脚本基础与例子分析。

SECTIONS {...secname start ALIGN(align) (NOLOAD) : AT ( ldadr ){ contents } >region :phdr =fill...
}
  • secnamecontents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段中。
  • start:段重定位地址,也称为VMA,即运行地址。如果代码中有位置相关的指令,程序在运行时,这个段必须放在这个地址上。
  • ALIGN(align):虽然start指定了运行地址,但是仍可以使用BLOCK(align)来指定对齐的要求一这个对齐的地址才是真正的运行地址。
  • (NOLOAD):用来告诉加载器,在运行时不用加载这个段。这个选项只有在有操作系统的情况下才有意义。
  • AT (ldadr):指定这个段在编译出来的映象文件中的地址,称为LMA,即加载地址。若不指定默认加载地址等于运行地址。通过这个选项,可以控制各段分别保存在输出文件中不同的位置。
  • >region :phdr =fill:没用到,不作介绍。

1.2 u-boot.lds分析

u-boot的链接脚本即目录下的u-boot.lds

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{. = 0x00000000;. = ALIGN(4);.text :{*(.__image_copy_start)*(.vectors)arch/arm/cpu/armv7/start.o (.text*)}.__efi_runtime_start : {*(.__efi_runtime_start)}.efi_runtime : {*(.text.efi_runtime*)*(.rodata.efi_runtime*)*(.data.efi_runtime*)}.__efi_runtime_stop : {*(.__efi_runtime_stop)}.text_rest :{*(.text*)}. = ALIGN(4);.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }. = ALIGN(4);.data : {*(.data*)}. = ALIGN(4);. = .;. = ALIGN(4);.u_boot_list : {KEEP(*(SORT(.u_boot_list*)));}. = ALIGN(4);.efi_runtime_rel_start :{*(.__efi_runtime_rel_start)}.efi_runtime_rel : {*(.rel*.efi_runtime)*(.rel*.efi_runtime.*)}.efi_runtime_rel_stop :{*(.__efi_runtime_rel_stop)}. = ALIGN(4);.image_copy_end :{*(.__image_copy_end)}.rel_dyn_start :{*(.__rel_dyn_start)}.rel.dyn : {*(.rel*)}.rel_dyn_end :{*(.__rel_dyn_end)}.end :{*(.__end)}_image_binary_end = .;. = ALIGN(4096);.mmutable : {*(.mmutable)}.bss_start __rel_dyn_start (OVERLAY) : {KEEP(*(.__bss_start));__bss_base = .;}.bss __bss_base (OVERLAY) : {*(.bss*). = ALIGN(4);__bss_limit = .;}.bss_end __bss_limit (OVERLAY) : {KEEP(*(.__bss_end));}.dynsym _image_binary_end : { *(.dynsym) }.dynbss : { *(.dynbss) }.dynstr : { *(.dynstr*) }.dynamic : { *(.dynamic*) }.plt : { *(.plt*) }.interp : { *(.interp*) }.gnu.hash : { *(.gnu.hash) }.gnu : { *(.gnu*) }.ARM.exidx : { *(.ARM.exidx*) }.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}

我们简单地分析一下这个链接脚本:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
  • OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 指定了输出文件的格式为 elf32-littlearm
  • OUTPUT_ARCH(arm) 指定了输出文件的体系结构为 ARM。

1、ENTRY(_start)

ENTRY(_start) 指定了程序的入口点为 _start的值指向的地址。这个肯定就是我们的中断向量表的地址了,我们可以在arch/arm/lib中发现这个标号,果然在中断向量表定义之前:

在这里插入图片描述

接着往下看

SECTIONS
{. = 0x00000000;. = ALIGN(4);   #地址4字节对齐,由于前面指定为0,所以已经对齐.text :{*(.__image_copy_start)*(.vectors)arch/arm/cpu/armv7/start.o (.text*)}....image_copy_end :{*(.__image_copy_end)}...}
  • .为定位器符号,不指定默认为0。存放了某个段后,定位器符号会往后移动这个段的大小长度,所以后面text段就从0地址开始放。

下面的text段包括__image_copy_start段、vectors段和arch/arm/cpu/armv7/start.o文件的代码段。最后定义了一个__image_copy_end段。

  • 其它的各个段就不具体分析了,实际上就是在这里定义了,然后在程序中可以通过 _*attribute_((section*(name)))等关键字链接一些特定的函数、变量、文件到特定的段中。

2、__image_copy_start__image_copy_end

看名字就知道这两个标号用于拷贝操作,分别保存拷贝的起始地址和终止地址,也就是说我们需要拷贝代码,拷贝的范围就是所有的声明在这两个标号中间的段。我们知道链接脚本中的标号是可以直接在C语言中用extern关键字获取的,但这里直接声明了一个段,我们应该如何获取这个地址呢?我们搜索一下:

在这里插入图片描述

发现在sections.c中有如下两个声明:

char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));

也就是说定义了两个大小为0的数组,但是将它们链接到了__image_copy_start__image_copy_end段中,所以这两个变量的地址就对应于我们要拷贝的地址。实际上我们可以在链接脚本中声明:

__image_copy_start = .;
__image_copy_end = .;

然后在C语言中通过extern来获取,这样起到的效果也是一样的。

extern unsigned int __image_copy_start;
extern unsigned int __image_copy_end;

代码的拷贝操作在relocate.c文件中实现。

3、vectorsarch/arm/cpu/armv7/start.otext

前面的__image_copy_start的数组为0,只是起到一个标号的作用,并不占据内存,接下来的链接脚本定义了vectorstext段,看一下u-boot.map文件:

在这里插入图片描述

可以看到紧接着的正是.vectorsarch/arm/cpu/armv7/start.otext段。

我们可以在vectors.S文件中看到.vectors段的声明:

在这里插入图片描述

也就是说在程序的开头放的是向量表的首地址,同时我们注意到第一条指令是b reset,也就是后面就直接跳转到reset标号中执行并不再返回了。

紧接着就是我们的程序,程序就保存在arch/arm/cpu/armv7/start.S文件中。如下图所示,正是reset标号函数的实现:

在这里插入图片描述

主要就是进入特权模式对系统做一些初始化。具体完成了什么操作,在后续的文章中会详细的分析,本篇文章就不介绍了。

2 链接脚本的使用

在没有链接脚本的情况下,我们可以通过arm-linux-ld类似的指令的参数指定代码段、数据段和bss段的地址:

-Ttext startaddr    #直接指定代码段地址
-Tdata startaddr    #直接指定数据段地址
-Tbss startaddr     #直接指定bss段地址

例子:

arm-linux-gcc -c -o link.o link.s
arm-linux-ld -Ttext 0x00000000 link.o -o link_elf_0x00000000//启动后PC=0x00000000
arm-linux-ld -Ttext 0x30000000 link.o -o link_elf_0x30000000//启动后PC=0x30000000

而在有链接脚本后,我们同样可以通过-T来指定链接脚本:

arm-linux-ld -T u-boot.lds -o link_elf_lds link.o

但是这里我们主要是想解决一个疑问,在前面的u-boot.lds中一开始的起始标号为0,但最终程序链接到了0x87800000处,这是怎么回事呢?这是因为我们可以通过参数对这个地址做一个调整,这样链接脚本中的地址实际上就是一个相对的地址。来看一个例子:

LDSCRIPT := /u-boot.lds
LDFLAGS += -T $(LDSCRIPT) -Ttext 0x87800000all: u-bootu-boot: $(OBJECTS)$(LD) $(LDFLAGS) -o $@ $(OBJECTS)# 其他 Makefile 规则...

LDSCRIPT变量指定了链接脚本的路径,然后通过LDFLAGS将其传递给链接器。另外,-Ttext 0x87800000参数指定了链接器的起始地址为0x87800000。所以我们只要在Makefile中再定义一下-Ttext到0x87800000就可以了,来看一下U-Boot的Makefile中的对应的定义:

在这里插入图片描述

其中CONFIG_SYS_TEXT_BASE就是0x87800000,这个值来源于.config文件,具体参考下面。

3 链接U-Boot到别的地址

前面我们知道,默认的配置中,U-Boot链接到了0x87800000处,所以我们就用grep "87800000" * -nr在目录搜索一下,发现在.config中有定义一个宏定义:

在这里插入图片描述

也就是说U-Boot的链接地址实际上是在xxx_defconfig文件中定义的,这个宏定义在Makefile中被读取,在编译的时候就会将Text段链接到这个位置。所以我们只需要更改这个值就能改变U-Boot的链接地址了。

这里我们将其修改为0x87900000,编译后打开u-boot.map文件看一下向量表的映射地址:

在这里插入图片描述

果然已经改到0x87900000了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

介绍下Redis?Redis有哪些数据类型?

一、Redis介绍 Redis全称(Remote Dictionary Server)本质上是一个Key-Value类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性…

Matlab深度学习进行波形分割(二)

🔗 运行环境:Matlab 🚩 撰写作者:左手の明天 🥇 精选专栏:《python》 🔥 推荐专栏:《算法研究》 🔐#### 防伪水印——左手の明天 ####🔐 💗 大家…

02.部署LVS-DR群集

技能展示: 了解LVS-DR群集的工作原理 会构建LVS-DR负载均衡群集 2.1 LVS-DR 集群 LVS-DR( Linux Virtual Server Director Server )工作模式,是生产环境中最常用的一种工作模式。 2.1.1.LVS-DR 工作原理 LVS-DR 模式&…

react、Vue打包直接运行index.html不空白方法

react vue 在根目录下创建 vue.config.js 文件,写入 module.exports {publicPath: ./, }

【SpringBoot框架篇】35.kafka环境搭建和收发消息

kafka环境搭建 kafka依赖java环境,如果没有则需要安装jdk yum install java-1.8.0-openjdk* -y1.下载安装kafka kafka3.0版本后默认自带了zookeeper,3.0之前的版本需要单独再安装zookeeper,我使用的最新的3.6.1版本。 cd /usr/local wget https://dlcdn.apache.…

C语言——编译和链接

(图片由AI生成) 0.前言 C语言是最受欢迎的编程语言之一,以其接近硬件的能力和高效性而闻名。理解C语言的编译和链接过程对于深入了解其运行原理至关重要。本文将详细介绍C语言的翻译环境和运行环境,重点关注编译和链接的各个阶段…

蓝桥杯AcWing学习笔记 8-2数论的学习(下)

蓝桥杯 我的AcWing 题目及图片来自蓝桥杯C AB组辅导课 数论(下) 蓝桥杯省赛中考的数论不是很多,这里讲几个蓝桥杯常考的知识点。 约数个数定理 我们如何去求一个数的约数个数呢? N N N分解质因数的结果: N P 1 α…

kubeSphere DevOps部署vue项目

devops部署vue项目 🌔环境说明🌏创建DevOps工程🌏填写流水线信息🌏创建流水线 🌔部署应用所需脚本JenkinsfileDockerfile 🌔脚本一些参数如何设置说明🌏deploy.yaml中的:imagePullSecrets:name属…

部署 LVS-DR 群集

本章内容: -了解LVS-DR群集的工作原理 -会构建LVS-DR负载均衡群集 2.1 LVS-DR 集群 LVS-DR ( Linux Virtual Server Director Server )工作模式,是生产环境中最常用的一 种工作模式。 2.1.1 . LVS-DR 工作原理 …

JVM运行时数据区(下篇)

紧接上篇:JVM运行时数据区(上篇)-CSDN博客 堆 一般Java程序中堆内存是空间最大的一块内存区域。创建出来的对象都存在于堆上。 栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态…

记录Qt和opencv 新环境配置过程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Qt是什么?二、Qt的版本三、安装步骤1.下载Qt2.双击安装包.exe开始安装3. 需要登陆才能继续安装,没有的就用邮箱注册账号4.注意安装路…

linux创建文件

创建文件夹: mkdir folder_name其中,folder_name是想要创建的文件夹的名称。 例如,如果想在当前目录下创建一个名为 "my_folder" 的文件夹,可以运行以下命令: mkdir my_folder如果想在特定路径下创建文件…

element-ui el-table表格勾选框条件禁用,及全勾选按钮禁用, 记录

项目场景: 表格的部分内容是可以被勾选的,部分内容是不可以被勾选的 使用的是 “element-plus”: “^2.2.22”, 以上应该都是兼容的 问题描述 要求el-table表格中,部分内容不可以被勾选,全选框在没有可选内容时,是禁…

RK3566RK3568安卓11隐藏状态栏带接口

文章目录 前言一、创建全局变量二、设置应用添加隐藏导航栏按钮三、添加按钮功能四、动态隐藏还有显示功能五、创建系统导航栏广播接口总结 前言 关于Android系统的状态栏,不同的客户有不同的需求: 有些客户需要永久隐藏状态栏,有些客户需要在设置显示中…

Flask框架小程序后端分离开发学习笔记《1》网络知识

Flask框架小程序后端分离开发学习笔记《1》网络知识 Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。 一、网址组成介绍 协议:http,https (https是加密的http)主机:g.cn zhihu.com之类的网址…

通义灵码 - 免费的阿里云 VS code Jetbrains AI 编码辅助工具

系列文章目录 前言 通义灵码,是阿里云出品的一款基于通义大模型的智能编码辅助工具,提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研发智能问答、异常报错排查等能力,并针对阿里云 SDK/OpenAPI 的使用…

【Java 设计模式】创建型之建造者模式

文章目录 1. 定义2. 应用场景3. 代码实现4. 应用示例结语 在软件开发中,建造者模式是一种创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常包括一个指导者(Director&…

如何在 SwiftUI 中实现音频图表

文章目录 前言DataPoint 结构体BarChartView 结构体ContentView 结构体实现协议实现线图总结 前言 在可访问性方面,图表是复杂的事物之一。iOS 15 引入了一项名为“音频图表”的新功能。 下面我们将学习如何通过使用 accessibilityChartDescriptor 视图修饰符为任…

远程开发之vscode端口转发

远程开发之vscode端口转发 涉及的软件forwarded port 通过端口转发,实现在本地电脑上访问远程服务器上的内网的服务。 涉及的软件 vscode、ssh forwarded port 在ports界面中的port字段,填需要转发的IP:PORT,即可转发远程服务器中的内网端…

十、Three场景实现多个物体的合并

Three场景实现多个物体的合并 目的 产品需求是让物体的光柱墙包含一个多边形的区域,二而我的多边形只能使用原型,方向,多边形。那么再研究的时候就需要将这些多边形合并成为一个形状,那么就行实现了。 原先的图形 如上图,是两个mesh组成的。首先寻找mesh合并的方法。 第…