powerpcuboot链接脚本大改造

kerneler_ kerneler_     2023-02-22     570

关键词:

在做完了linux由arm处理器核移植到ppc处理器核的工作后,还需要进行uboot的移植,之前对uboot的分析文章都是基于arm平台,感兴趣的朋友可以看看,链接如下:
http://blog.csdn.net/column/details/uboot-note.html
借这次ppc移植工作,也学习了ppc uboot的相关知识,顺道写几篇文章记录下。
工作背景是公司处理器由arm处理器核cortex A8换为ppc处理器核ppc460s,现在处于FPGA仿真验证阶段,SOC的外设控制器都没有变化.

uboot版本号:2014.04
平台:powerpc460s

根据之前arm版本uboot的移植经验,一次完整的uboot移植我觉得可以分为3步:
(1)根据需求,对uboot进行配置,链接等方面的修改,加入必须的板级支持函数(先为空函数即可),保证uboot可以编译链接通过。
(2)由start.s开始,按照uboot启动流程对调用到的各个关键函数进行功能调试。保证uboot正常启动进入命令行。
(3)对各个模块驱动单独调试。

因此我修改boards.cfg,修改配置文件,确定代码段首地址,内存首地址等基本要素。期望能够直接编过。
但是看了ppc460s处理器的链接脚本后,我觉得问题比我想象中要复杂一点。
根据我的实际需求需要对ppc的链接脚本进行改造,其实就是将uboot镜像中各个section进行重新布局。今天趁十一假期,来记录下对ppc uboot链接脚本的改造过程。
首先来分析下原版uboot针对ppc460s处理器各个section的布局,ppc460s属于ppc4xx系列,因此链接脚本是arch/powerpc/cpu/ppc4xx/u-boot.lds。结合arch/powerpc/cpu/ppc4xx/start.S等启动代码,我画出了ppc4xx系列处理器uboot段布局图如下。

bootpg和resetvec位于4G地址空间顶端的4k空间,其余段则位于指定的地址。虚线表明了处理器上电后执行流程。
这里就需要简单说明下ppc4xx系列处理器的一点特性,ppc4xx系列处理器很大的一个特点是MMU常开,上电即开启。
上电后ppc4xx有一个shadow TLB将地址空间最顶端的4k进行映射,具体映射到哪里,这就要看处理器内部逻辑如何填该TLB,以及外部总线的地址仲裁如何布局物理地址空间,这点询问SOC的IC设计人员就可以知道。
初上电的ppc4xx处理器只能访问顶端4K空间,并且上电取指地址是0xfffffffc,因此在针对ppc4xx系列处理器,bootloader的设计一般就是0xfffffffc处为跳转指令,跳转至4k起始地址执行,在该4k代码中对需要进行访问的地址空间进行TLB映射,保证之后主代码所在物理地址的映射,最后跳转到主代码段执行。
可以看出uboot的设计就是遵循这个特点,上电首先执行jump _start_440,跳转到4k起点执行,初始化TLB之后jump _start,进入代码段执行。
因此uboot完全可以作为ppc4xx处理器的bootloader来使用,上电直接执行uboot。

但是对于我们公司处理器,却产生了一些问题,问题如下:
(1)公司处理器逻辑将顶端4k空间映射到片内64k的ROM中,该ROM存放公司开发的bootloader,uboot太大放不进去
(2)uboot实际相当于一个二级bootloader,由公司一级bootloader加载uboot启动,uboot进行必要的配置和参数准备,再去启动kernel
(3)公司处理器的需求是uboot直接运行在内存中,仅仅起一个承上启下的作用,为kernel传参。
从公司处理器的需求来看,所需要的uboot并不是一个完整的bootloader,而仅仅是在内存中运行,为kernel准备参数,加载启动kernel就可以。这样uboot的段布局就需要改造一下。

根据这个需求,对uboot的链接布局我提出来的改进方案如下:
(1)顶部跳转和地址空间映射的任务由一级bootloader完成,uboot仅需要直接在内存中运行即可,因此考虑将bootpg以及resetvec去掉。
(2)原代码段执行是由_start_440–>_start跳转来切入的,现在需要手动指定uboot入口为_start。

针对第一条改进方案,起初考虑过保留bootpg,但是由于bootpg在地址空间的顶端4k,根据实际的处理器地址空间分布,我的代码段启动地址CONFIG_SYS_TEXT_BASE是在内存的0x80e80000,需要注意的是这个地址是一个虚拟地址,因为公司一级bootloader中已经完成了地址空间的映射,都是平映射。但是这样uboot最终链接生成u-boot.bin时,就会有1G大小,这是因为最高位置的段bootpg和最低位置的段text之间间隔了将近1G,生成镜像时这之间的空隙是需要预留出来的,来保证加载是能够将各个段加载正确位置。

这里就想到一个问题,难道别的ppc4xx处理器,如果使用uboot作为一级bootloader,text段在内存中,而内存地址距离地址空间顶端很远,编译出来的u-boot.bin就会非常大吗?
查看别的ppc4xx处理器的相关代码,发现解决方法是在4k代码中将内存映射到接近顶端4k的地址,同时修改CONFIG_SYS_TEXT_BASE为该地址。这样编译链接出来的u-boot.bin就不大了。

因此我上面的考虑,有一个很重要的前提是我在4k代码中为需要使用的地址空间做了平映射。公司处理器的一级bootloader中的确是这样做的。

这样想来,对于我的uboot,bootpg段还有一种解决方法,是保留bootpg,修改其中TLB映射,将内存代码段地址映射到接近顶端4k的位置,并且修改CONFIG_SYS_TEXT_BASE。
但是由于一级bootloader的存在,已经将跳转以及TLB映射工作做完,所以uboot并不需要4k代码,并且uboot本身不算很大,平映射完全满足需求,为了移植简单快捷,我还是采用将bootpg等段删掉的方法。

因此我将arch/powerpc/cpu/ppc4xx/u-boot.lds中如下代码注掉。

#if 0 
#ifndef CONFIG_SPL
#ifdef CONFIG_440
  .bootpg RESET_VECTOR_ADDRESS - 0xffc :
  
    arch/powerpc/cpu/ppc4xx/start.o (.bootpg)

    /*
     * PPC440 board need a board specific object with the
     * TLB definitions. This needs to get included right after
     * start.o, since the first shadow TLB only covers 4k
     * of address space.
     */
#ifdef CONFIG_INIT_TLB
    CONFIG_INIT_TLB (.bootpg)
#else
    CONFIG_BOARDDIR/init.o  (.bootpg)
#endif
   :text = 0xffff
#endif

  .resetvec RESET_VECTOR_ADDRESS :
  
    KEEP(*(.resetvec))
   :text = 0xffff

  . = RESET_VECTOR_ADDRESS + 0x4;

  /*
   * Make sure that the bss segment isn't linked at 0x0, otherwise its
   * address won't be updated during relocation fixups.  Note that
   * this is a temporary fix.  Code to dynamically the fixup the bss
   * location will be added in the future.  When the bss relocation
   * fixup code is present this workaround should be removed.
   */
#if (RESET_VECTOR_ADDRESS == 0xfffffffc)
  . |= 0x10;
#endif
#endif /* CONFIG_SPL */
#endif
  __bss_start = .;
  .bss (NOLOAD)       :
  
   *(.bss*)
   *(.sbss*)
   *(COMMON)
   :bss

  . = ALIGN(4);
  __bss_end = . ;
  PROVIDE (end = .);

将u-boot.lds中的bootpg resetvec段定义注释掉。这里需要注意的一点是bss段。在最后再进行讨论。
修改链接脚本后,还需要将start.S中bootpg段代码以及resetvec.S中resetvec段代码也注释掉,这里就不列了。

到这里,整个bootpg resetvec段就去掉了,原来u-boot的执行是按照由上电入口地址0xfffffffc开始,一路跳转进入_start,进入text代码段执行的。
现在没有了bootpg resetvec,u-boot.bin的入口地址就需要指定下了。
链接器ld可以通过参数-Ttext指定text段起始地址,通过参数-e指定程序入口地址。在uboot的Makefile中,如下:

MKIMAGEFLAGS_u-boot.kwb = -n $(srctree)/$(CONFIG_SYS_KWD_CONFIG:"%"=%) \\
    -T kwbimage -a $(CONFIG_SYS_TEXT_BASE) -e $(CONFIG_SYS_TEXT_BASE)

默认是uboot入口地址与代码段起始地址一致。但是根据上面u-boot段分布图可以看出,text段真正的入口地址是在_start,前面0x2100大小的代码是版本字符串以及异常向量表。
因此需要指定下uboot的入口地址。我的修改方法是在u-boot.lds中加入如下代码。

ENTRY(_start)

指定入口地址在_start,_start地址是text段起始地址加上0x2100.
对链接脚本进行如上修改之后,编译通过uboot还需要一些板级支持函数,对于powerpc平台,如initsdram board_early_init_r等,可以先为空函数,待功能调试时再完善,最终uboot编译通过。

通过readelf查看生成的u-boot,如下:

zk@server2:~/u-boot$ powerpc-linux-readelf -h u-boot
ELF Header:
  Magic:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, big endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           PowerPC
  Version:                           0x1
  Entry point address:               0x80e82100
  Start of program headers:          52 (bytes into file)
  Start of section headers:          600904 (bytes into file)
  Flags:                             0x18000, relocatable, relocatable-lib
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         20
  Section header string table index: 17

可以看出,入口地址正是我指定的_start地址,再查看各个段的分布,如下:

zk@server2:~/u-boot$ powerpc-linux-readelf -S u-boot
There are 20 section headers, starting at offset 0x92b48:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        80e80000 000074 017510 00  AX  0   0  4
  [ 2] .rodata           PROGBITS        80e97510 017584 005717 00   A  0   0  4
  [ 3] .reloc            PROGBITS        80e9cd00 01cd74 0014b8 04 WAX  0   0  4
  [ 4] .data             PROGBITS        80e9e1b8 01e22c 001120 00  WA  0   0  4
  [ 5] .u_boot_list      PROGBITS        80e9f2d8 01f34c 0004f8 00  WA  0   0  4
  [ 6] .bss              NOBITS          80e9f800 01f844 002798 00  WA  0   0  4
  [ 7] .debug_line       PROGBITS        00000000 01f844 00c63c 00      0   0  1
  [ 8] .debug_info       PROGBITS        00000000 02be80 0338e8 00      0   0  1
  [ 9] .debug_abbrev     PROGBITS        00000000 05f768 00b114 00      0   0  1
  [10] .debug_aranges    PROGBITS        00000000 06a880 0018d8 00      0   0  8
  [11] .comment          PROGBITS        00000000 06c158 000027 01  MS  0   0  1
  [12] .gnu.attributes   LOOS+ffffff5    00000000 06c17f 000012 00      0   0  1
  [13] .debug_frame      PROGBITS        00000000 06c194 0046dc 00      0   0  4
  [14] .debug_str        PROGBITS        00000000 070870 0063f9 01  MS  0   0  1
  [15] .debug_loc        PROGBITS        00000000 076c69 01934f 00      0   0  1
  [16] .debug_ranges     PROGBITS        00000000 08ffb8 002ac8 00      0   0  8
  [17] .shstrtab         STRTAB          00000000 092a80 0000c7 00      0   0  1
  [18] .symtab           SYMTAB          00000000 092e68 004080 10     19 533  4
  [19] .strtab           STRTAB          00000000 096ee8 0031b5 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

代码段起始于0x80e80000.都符合了我的要求。
这样针对公司处理器的需求,对u-boot链接脚本的改造就完成了。

总结下,在改造过程中我觉得有2个地方值得思考。
1 bss段的几个疑问
在修改u-boot.lds去掉bootpg resetvec时,我注意到bss定义位于resetvec之上,resetvec已经到了地址空间的顶点,难道bss段超出了4G
后来实际编译我才发现,根据u-boot.lds中的定义,resetvec位于0xfffffffc时,bss位于(0xfffffffc+4)| 0x10的位置,地址就是0x10.如果resetvec定义在其他位置,则bss就在resetvec之上了。
根据编译生成的elf文件u-boot,获取到各个段的大小,与u-boot.bin比较,如下:

zk@server2:~/u-boot$ size u-boot
   text    data     bss     dec     hex filename
 123103    5656   10136  138895   21e8f u-boot
zk@server2:~/u-boot$ ls -lh u-boot.bin
-rwxr-xr-x 1 zk git 126k 2015-10-01 15:22 u-boot.bin

计算下可以发现,u-boot.bin基本等于text与data段之和,问题来了,bss段哪里去了

在网上查找资料才知道,原来在最终的程序镜像中,是没有bss段。
为什么去掉bss段,想来想去,我的理解是bss段中包括了未初始化以及初始化为0的全局变量和静态变量,该段的数据全部为0,但是占据空间大,为了减小镜像尺寸,才在objcopy生成镜像时将bss段删掉。
而根据uboot代码可以知道,对于uboot这样的裸编程序来说,在其启动过程中会开辟bss段,并对bss段进行清空,来保证其中数据全部为0.
这样就不会影响后续的全局变量和静态变量的使用了。

那么我的疑问又来了,在其开辟和清空bss段的过程中,是如何知道bss段的地址范围的。

这个问题想来倒是也好解答,在编译链接过程中,我们就可以确定下来bss段的起止地址,在u-boot.lds中用__bss_start和__bss_end来表示了。
并且该起止地址记录到了u-boot这个elf文件的段表中,也就是上面readelf看到的段表列表的结果,
最终生成u-boot.bin时去掉了bss,也的确是应该去掉,占据空间大还全部为0,完全可以在程序运行时再根据__bss_start和__bss_end来分配清空就可以。

总结下,uboot中bss段的生成过程可以分为如下步骤:
(1)链接脚本中定义bss段地址范围__bss_start __bss_end。
(2)编译链接elf时,根据链接脚本确定下__bss_start __bss_end的绝对地址,记录在elf文件的段表中。
(3)elf objcopy生成u-boot.bin时,去掉bss段。
(4)加载u-boot.bin启动运行,根据__bss_start __bss_end开辟bss段,并全部清空为0。
(5)后续运行代码中访问未初始化和初始化为0的全局变量以及静态变量则会访问到bss段中。

一句话,bss段在uboot生成镜像时删掉,在运行时动态分配清空,从而达到减小镜像尺寸的目的。

2 程序起始地址与入口地址的关系
从这次修改ppc u-boot连接脚本就可以看出来,程序起始地址与入口地址不是一个概念,完全可以不相等。由于u-boot.lds中将text段的定义放在了最开始的位置,所以text段的起始地址就是整个uboot镜像的起始地址了,代码段在最前面,这也是最常用的链接方式。程序入口地址可以通过链接器的参数-e来指定。

脚本改造(代码片段)

##########################Jenkins脚本改造#############################BUILD_ID=DONTKILLME#服务名称SNAME=yutang-study-center-apiNAME=study-center-apiSBAG=yutang-study-center-api-1.0.0-SNAPSHOT.jarSPORT=9003SNO 查看详情

柱体改造 2 包括大括号

】柱体改造2包括大括号【英文标题】:Postbodyretrofit2includecurlybrackets【发布时间】:2019-11-1311:34:50【问题描述】:我想在改造中发布带有正文的请求,其中括号还有另一个括号期待正文请求:"attributes":"data":"FOO... 查看详情

如何从 API 显示图像并在单击图像后将用户发送到视频链接? Kotlin,改造

】如何从API显示图像并在单击图像后将用户发送到视频链接?Kotlin,改造【英文标题】:HowtodisplayanimagefromAPIandsendtheusertovideolinkafterclickingtheimage?Kotlin,Retrofit【发布时间】:2021-08-0122:22:28【问题描述】:我正在尝试在recyclerview中... 查看详情

layedit第三次改造

原文链接:http://www.bianbingdang.com/article_detail/105.html作为技术人员,在网上写代码,常常涉及代码块的编写。一个好的代码块,让页面美化很多。由于本站使用layui作为前端开发,layui自带的编辑器layedit的功能确实有些欠缺。随后... 查看详情

大模型狂欢背后:ai基础设施的“老化”与改造工程(代码片段)

作者|RiverRiddle、EricJohnson、AbdulDakak翻译|胡燕君、杨婷机器学习模型逐渐发展成人们口中的“庞然大物”。全球顶尖的科技公司纷纷踏上“军备竞赛”之路,立志训练出规模最大的模型(MUM、OPT、GPT-3、Megatron... 查看详情

使用 443 端口进行改造

...议如何设置我的BASEURLWITH443PORT?【问题讨论】:试试这个链接:***.com/a/4 查看详情

laravel怎么改造分页类,给他加上首页和尾页

参考技术A这个简单,你直接在模板上搞个首页链接,和尾页链接就好了。还有一个方案是,你用paginate(15)返回数据,你会发现这个数据会有每个页面的数据,当前页数,总页数,你根据这些数据来写html代码,就可以了 查看详情

链接脚本解析

1.概述链接器的作用主要是对符号的解析以及将符号与地址进行绑定。要实现这个功能需要依赖链接脚本,链接脚本大多数情况下用来链接输入文件,并生成目标文件。编译器的“-T”参数就是用来指定链接脚本的。2.链接脚本需... 查看详情

智慧医疗管理系统解决方案:医药电商系统实现智能化改造

现在的互联网智慧医疗系统拥有强大的技术优势,支持连接政府、医疗服务机构、医药研发与流通、康养等,构建医疗大健康产业云生态,助力数字化升级。医疗系统平台开发服务商依托基础设施能力、人才优势与大... 查看详情

mfcui源码分析一

????一直在寻找一款lua作为界面开发语言脚本框架,类似于web开发,使用html做界面,lua作为脚本语言进行行为控制,htmlayout是不二选择,但是其搭配的默认脚本并不是lua,这里找到一个开源的框架,将htmlayout进行了改造,使其支... 查看详情

链接器中——链接脚本

链接脚本链接器根据说明具体的原则完成具体的工作?答案是:链接脚本。1链接脚本的意义链接脚本用于描述链接器处理目标文件和库文件的方式1.合并各个目标文件中的段2.重定位各个段的起始地址3.重定位各个符号的最终地... 查看详情

链接脚本基本语法(代码片段)

...翻译自:BuiltinFunctions(LD)加上了一些我自己的备注。链接脚本控制每次链接。这样的脚本是用链接器命令语言编写的。链接脚本的主要目的是描述如何将输入文件中的各个section(节)映射到输出文件中,并控制输... 查看详情

基于redis实现分布式锁,分析解决锁误删情况及利用lua脚本解决原子性问题并改造锁(代码片段)

(目录)上一篇博文部分:优惠卷秒杀分布式锁1、基本原理和实现方式对比分布式锁:分布式锁的核心思想就是:分布式锁他应该满足一些什么样的条件呢?常见的分布式锁有三种:2、Redis分布式锁的实现核心思路实现分布式锁时... 查看详情

链接器脚本

  参考:《程序员的自我修养:链接、装载与库》      什么是链接脚本?链接脚本就是程序链接时的参考文件,其主要目的是描述如何把输入文件中的段(SECTION)映射到输出文件中,并控制输出文件... 查看详情

无标题链接脚本基本语法(代码片段)

...翻译自:BuiltinFunctions(LD)加上了一些我自己的备注。链接脚本控制每次链接。这样的脚本是用链接器命令语言编写的。链接脚本的主要目的是描述如何将输入文件中的各个section(节)映射到输出文件中,并控制输... 查看详情

链接脚本(linkerscripts)语法和规则解析(翻译自官方手册)(代码片段)

原链接:链接脚本(LinkerScripts)语法和规则解析(翻译自官方手册)_BSP-路人甲的博客-CSDN博客_链接脚本语法 为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个子章节标题和引用使用的都是官方手册... 查看详情

链接脚本与重定位(代码片段)

目录链接脚本与重定位总结链接脚本格式COMM段BSS段elf和bin文件获得链接脚本的值重定位1-直接指定数据段位置(代码黑洞)2-分散加载(数据段)3-全局重定位(一体式)BL跳转指令bss段处理汇编处理C处理位置无关码title:链接脚... 查看详情

链接脚本与重定位(代码片段)

目录链接脚本与重定位总结链接脚本格式COMM段BSS段elf和bin文件获得链接脚本的值重定位1-直接指定数据段位置(代码黑洞)2-分散加载(数据段)3-全局重定位(一体式)BL跳转指令bss段处理汇编处理C处理位置无关码title:链接脚... 查看详情