ARM架构与编程(基于I.MX6ULL): 代码重定位(八)(下)

简介: ARM架构与编程(基于I.MX6ULL): 代码重定位(八)

4.数据段重定位


参考手册:Using LD, the GNU linker


4.1. 怎么获得各个段的信息


数据复制3要素:源、目的、长度。

怎么知道某个段的加载地址、链接地址、长度?


4.1.1 怎么确定源?


可以用ADR伪指令获得当前代码的地址,对于这样的代码:

.text
.global  _start
_start: 
    ......
    adr r0, _start


adr是伪指令,它最终要转换为真实的指令。它怎么获得_start代码的当前所处地址呢?

实际上,adr r0, _start指令的本质是r0 = pc - offset,offset是在链接时就确定了。


4.1.2 怎么确定目的地址?


也就是怎么确定链接地址?可以用LDR伪指令。

对于这样的代码:

.text
.global  _start
_start: 
    ......
    ldr r0, =_start


ldr是伪指令,它最终要转换为真实的指令。它怎么获得_start的链接地址呢?

_start的链接地址在链接时,由链接脚本确定。


4.1.3 如何获得更详细的信息


在链接脚本里可以定义各类符号,在代码里读取这些符号的值。

比如对于下面的链接脚本,可以使用__bss_start、__bss_end得到BSS段的起始、结束地址:

__bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;


上述代码里,有一个".“,它被称为"Location Counter”,表示当前地址:可读可写。

它表示的是链接地址。

. = 0xABC;       /* 设置当前地址为0xABC */ 
_abc_addr = . ;  /* 设置_abc_addr等于当前地址 */
. = . + 0x100;   /* 当前地址增加0x100 */
. = ALIGN(4);    /* 当前地址向4对齐 */


注意:"Location Counter"只能增大,不能较小。


4.2. 编写程序重定位数据段


4.2.1 修改链接脚本


我们故意只重定位数据段,在后面的课程再来重定位代码段并引入更多知识。

数据段要被复制到哪去?需要在链接脚本里确定一下:增加了__data_start

SECTIONS {
    . = 0xC0200000;   /* 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 */
    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }
    . = ALIGN(4);
    __rodata_start = .;
    .rodata : { *(.rodata) }
    . = ALIGN(4);
    .data : { *(.data) }
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}


4.2.2 编写程序


修改start.S:

ldr r0, =__data_start   /* 目的: 链接地址 */
/* 计算data段的当前地址:
 * _start的链接地址 - _start的当前地址 = __rodata_start的链接地址 - rodata段的当前地址
 * data段的当前地址 = __rodata_start的链接地址 - (_start的链接地址 - _start的当前地址)
 */
ldr r0, =__rodata_start
ldr r2, =_start  /* link addr */
adr r3, _start   /* load addr */
sub r2, r2, r3
sub r1, r0, r2   /* 源 */
ldr r3, =__bss_start
sub r2, r3, r0
bl memcpy    /* r0: 目的, r1: 源, r2:长度 */


5.清除BSS段


5.1. C语言中的BSS段


程序里的全局变量,如果它的初始值为0,或者没有设置初始值,这些变量被放在BSS段里。

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;  // 放在BSS段
int g_B;      // 放在BSS段


BSS段并不会放入bin文件中,否则也太浪费空间了。

在使用BSS段里的变量之前,把BSS段所占据的内存清零就可以了。


5.2. 清除BSS段


5.2.1 BSS段在哪?多大?


在链接脚本中,BSS段如下描述:

SECTIONS {
    . = 0xC0200000;   /* 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 */
    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }
    . = ALIGN(4);
    __rodata_start = .;
    .rodata : { *(.rodata) }
    . = ALIGN(4);
    .data : { *(.data) }
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}


BSS段的起始地址、结束地址,使用__bss_start和__bss_end来获得,它们是链接地址。


5.2.2 怎么清除BSS段


ldr r0, =__bss_start   /* 目的 */
mov r1, #0             /* 值 */
ldr r2, =__bss_end     
sub r2, r2, r1         /* 长度 */
bl memset              /* r0: 目的, r1: 值, r2: 长度 */


6.代码段重定位


6.1. 代码段不重定位的后果


谁执行了数据段的重定位?

谁清除了BSS段?

都是程序自己做的,也就是代码段那些指令实现的。

代码段并没有位于它的链接地址上,并没有重定位,为什么它也可以执行?


因为重定位之前的代码是使用位置无关码写的,后面再说。


如果代码段没有重定位,则不能使用链接地址来调用函数:


汇编中


ldr  pc, =main   ; 这样调用函数时,用到main函数的链接地址,如果代码段没有重定位,则跳转失败


C语言中

void (*funcptr)(const char *s, unsigned int val);
funcptr = put_s_hex;
funcptr("hello, test function ptr", 123);


6.2. 代码段重定位


6.2.1 代码段在哪?多大?


这要看链接脚本,对于MPU的程序,代码段、数据段一般是紧挨着排列的。

所以重定位时,干脆把代码段、数据段一起重定位。


链接脚本

SECTIONS {
    . = 0xC0200000;   /* 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 */
    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }
    . = ALIGN(4);
    __rodata_start = .;
    .rodata : { *(.rodata) }
    . = ALIGN(4);
    .data : { *(.data) }
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}


对于这样的代码:

.text
.global  _start
_start:


确定目的

ldr r0, =_start


确定源

adr  r1, _start


确定长度

ldr r3, =__bss_start
sub r2, r3, r0


6.2.2 怎么重定位


ldr r0, =_start
adr  r1, _start
ldr r3, =__bss_start
sub r2, r3, r0
bl memcpy


6.3 为什么重定位之前的代码也可以正常运行?


因为重定位之前的代码是使用位置无关码写的:


只使用相对跳转指令:b、bl


不只用绝对跳转指令:

ldr pc, =main


不访问全局变量、静态变量、字符串、数组


重定位完后,使用绝对跳转指令跳转到XXX函数的链接地址去

bl main         // bl相对跳转,程序仍在原来的区域运行
ldr pc, =main   // 绝对跳转,跳到链接地址去运行
ldr r0, =main   // 更规范的写法,支持指令集切换
blx r0


7.重定位的纯C函数实现


7.1. 怎么得到链接脚本里的值


对于这样的链接脚本,怎么得到其中的__bss_start和 __bss_end:

SECTIONS {
    . = 0xC0200000;   /* 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 */
    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }
    . = ALIGN(4);
    __rodata_start = .;
    .rodata : { *(.rodata) }
    . = ALIGN(4);
    .data : { *(.data) }
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}


7.1.1 汇编代码


ldr  r0, =__bss_start
ldr  r1, =__bss_end


7.1.2 C语言


方法1

声明为外部变量,使用时需要使用取址符:

extern unsigned int __bss_start;
extern unsigned int __bss_end;
unsigned int len;
len = (unsigned int)&__bss_end - (unsigned int)&__bss_start;
memset(&__bss_start, 0, len);


方法2

声明为外部数组,使用时不需要使用取址符:

extern char __bss_start[];
extern char __bss_end[];
unsigned int len;
len = __bss_end - __bss_start;
memset(__bss_start, 0, len);


7.2. 怎么理解上述代码


对于这样的C变量:

int g_a;


编译的时候会有一个符号表(symbol table),如下:

Name Address
g_a xxxxxxxx


对于链接脚本中的各类Symbol,有2中声明方式:

extern unsigned int __bss_start;     // 声明为一般变量
extern char __bss_start[];           // 声明为数组


不管是哪种方式,它们都会保存在符号表里,比如:

Name Address
g_a xxxxxxxx
__bss_start yyyyyyyy


对于int g_a变量

使用&g_a得到符号表里的地址。

对于extern unsigned int __bss_start变量

要得到符号表中的地址,也是使用&__bss_start。

对于extern char __bss_start[]变量

要得到符号表中的地址,直接使用__bss_start[],不需要加&

为什么?`__bss_start本身就表示地址啊

相关文章
|
1月前
|
数据采集 机器学习/深度学习 大数据
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
这篇文章详细介绍了C3D架构在行为检测领域的应用,包括训练和测试步骤,使用UCF101数据集进行演示。
43 1
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
|
22天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
63 10
|
1月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
71 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
30天前
|
Docker 容器
docker:记录如何在x86架构上构造和使用arm架构的镜像
为了实现国产化适配,需将原x86平台上的Docker镜像转换为适用于ARM平台的镜像。本文介绍了如何配置Docker buildx环境,包括检查Docker版本、安装buildx插件、启用实验性功能及构建多平台镜像的具体步骤。通过这些操作,可以在x86平台上成功构建并运行ARM64镜像,实现跨平台的应用部署。
582 2
|
1月前
|
编解码 弹性计算 应用服务中间件
阿里云服务器Arm计算架构解析:Arm计算架构云服务器租用收费标准价格参考
阿里云服务器架构分为X86计算、Arm计算、高性能计算等多种架构,其中Arm计算架构以其低功耗、高效率的特点受到广泛关注。本文将深入解析阿里云Arm计算架构云服务器的技术特点、适用场景以及包年包月与按量付费的收费标准与最新活动价格情况,以供选择参考。
|
1月前
|
机器学习/深度学习 弹性计算 编解码
阿里云服务器计算架构X86/ARM/GPU/FPGA/ASIC/裸金属/超级计算集群有啥区别?
阿里云服务器ECS提供了多种计算架构,包括X86、ARM、GPU/FPGA/ASIC、弹性裸金属服务器及超级计算集群。X86架构常见且通用,适合大多数应用场景;ARM架构具备低功耗优势,适用于长期运行环境;GPU/FPGA/ASIC则针对深度学习、科学计算、视频处理等高性能需求;弹性裸金属服务器与超级计算集群则分别提供物理机级别的性能和高速RDMA互联,满足高性能计算和大规模训练需求。
|
1月前
|
设计模式 人工智能 算法
编程之旅:从代码到架构的感悟
【9月更文挑战第33天】在编程的世界里,代码不仅是实现功能的工具,更是连接思想与现实的桥梁。本文将通过个人的编程经历,分享从编写第一行代码到设计系统架构的旅程,探索编程背后的哲学和技术演变。我们将一起思考,如何在代码的海洋中找到自己的航向,以及在这个过程中如何不断成长和适应变化。
|
1月前
|
存储 Docker 容器
ARM架构鲲鹏主机BClinux离线安装docker步骤
下载并安装适用于ARM架构的Docker CE二进制文件,解压后移动至/usr/bin目录。创建docker组,配置systemd服务脚本(docker.service、docker.socket、containerd.service),重载systemd配置,启动并启用docker服务。编辑daemon.json配置存储驱动、镜像加速地址等,最后拉取所需镜像。
44 0
|
1月前
|
机器学习/深度学习 大数据 PyTorch
行为检测(一):openpose、LSTM、TSN、C3D等架构实现或者开源代码总结
这篇文章总结了包括openpose、LSTM、TSN和C3D在内的几种行为检测架构的实现方法和开源代码资源。
42 0
|
1月前
|
NoSQL MongoDB Docker
求助,有没有大神可以找到arm64架构下mongodb的3.6.8版本的docker镜像?
在Docker Hub受限的情况下,寻求适用于ARM架构的docker镜像资源或拉取链接,以便在x86架构上获取;内网中的机器为ARM架构,因此优先请求适合ARM的Docker镜像或Dockerfile,非常感激您的帮助。