网站使用mip后效果怎么样,php网站建设案例教程视频,郑州有名的做网页的公司,建立公司网站需要多少钱文章目录 一、段的概念以及重定位的引入1.1 问题的引入1.2 段的概念1.3 重定位 二、如何实现重定位2.1 程序中含有什么#xff1f;2.2 谁来做重定位#xff1f;2.3 怎么做重定位和清除BSS段#xff1f;2.4 加载地址和链接地址的区别 三、散列文件使用与分析3.1 重定位的实质… 文章目录 一、段的概念以及重定位的引入1.1 问题的引入1.2 段的概念1.3 重定位 二、如何实现重定位2.1 程序中含有什么2.2 谁来做重定位2.3 怎么做重定位和清除BSS段2.4 加载地址和链接地址的区别 三、散列文件使用与分析3.1 重定位的实质: 移动数据3.2 散列文件示例3.2.1 示例代码3.2.2 散列文件语法 3.3 散列文件解析3.4 怎么获得region的信息3.4.1 可执行域的信息3.4.2 加载域的信息3.4.3 汇编代码里怎么使用这些信息3.4.4 C语言里怎么使用这些信息1.方法12.方法2 四、清除BSS段(ZI段)4.1 C语言中的BSS段4.2 清除BSS段4.2.1 BSS段的位置和大小4.2.2 怎么清除BSS段1.汇编码2.C语言 五、代码段重定位5.1 加载地址等于链接地址5.2 加载地址不等于链接地址5.3 代码段不重定位的后果5.4 代码段重定位5.4.1 代码段的位置和大小5.4.2 怎么重定位1.汇编代码2.C语言代码 5.5 为什么重定位之前的代码也可以正常运行 五、重定位的纯C函数实现5.1 难点5.2 怎么理解上述代码 一、段的概念以及重定位的引入
1.1 问题的引入
复制之前的串口程序修改为”01_uart_question“添加全局变量把它打印出来看看会发生什么事。
#include uart.hchar g_char1 A;
const char g_char2 B;void delay(int d)
{while(d--);
}int main()
{char c;uart_init();putchar(l);putchar(j);putchar(g);putchar(6);putchar(6);putchar(6);putchar(\n);putchar(\r);putchar(g_char1);putchar(g_char2);while (1){c getchar();putchar(c);putchar(\n);putchar(\r);putchar(c1); putchar(\n);putchar(\r); }return 0;
}可以看见我们在定义了g_char1和g_char2定义为常量后分别打印时g_char1显示的是乱码而g_char2显示正常。
为了研究该问题我们写了一个string.c和string.h里面是打印函数我们将该文件包含进该工程中查看g_char1和g_char2的地址。 由图可见g_char2指向ROM且表现为只读所以能被成功打印而g_char1指向内存RAM该区域是可读可写的而我们对于内存未进行赋值所以当访问到g_char1指向的地址时就会打印乱码该内存中的任意值。
1.2 段的概念
代码段、只读数据段、可读可写的数据段、BSS段。
char g_Char A; // 可读可写不能放在ROM上应该放在RAM里
const char g_Char2 B; // 只读变量可以放在ROM上
int g_A 0; // 初始值为0干嘛浪费空间保存在ROM上没必要
int g_B; // 没有初始化干嘛浪费空间保存在ROM上没必要所以程序分为这几个段
代码段(RO-CODE)就是程序本身不会被修改可读可写的数据段(RW-DATA)有初始值的全局变量、静态变量需要从ROM上复制到内存只读的数据段(RO-DATA)可以放在ROM上不需要复制到内存BSS段或ZI段 初始值为0的全局变量或静态变量没必要放在ROM上使用之前清零就可以未初始化的全局变量或静态变量没必要放在ROM上使用之前清零就可以 局部变量保存在栈中运行时生成堆一块空闲空间使用malloc函数来管理它malloc函数可以自己写
1.3 重定位
保存在ROM上的全局变量的值在使用前要复制到内存这就是数据段重定位。
想把代码移动到其他位置这就是代码重定位。
二、如何实现重定位
2.1 程序中含有什么
代码段如果它不在链接地址上就需要重定位只读数据段如果它不在链接地址上就需要重定位可读可写的数据段如果它不在链接地址上就需要重定位BSS段不需要重定位因为程序里根本不保存BSS段使用前把BSS段对应的空间清零即可
2.2 谁来做重定位
程序本身它把自己复制到链接地址去一开始程序可能并不位于它的链接地址上为什么它可以执行重定位的操作 因为重定位的代码是使用“位置无关码”写的 什么叫位置无关码这段代码扔在任何位置都可以运行跟它所在的位置无关怎么写出位置无关码 跳转使用相对跳转指令不能使用绝对跳转指令 只能使用branch指令(比如bl main)不能给PC直接复制比如ldr pc, main 不要访问全局变量、静态变量不使用字符串
2.3 怎么做重定位和清除BSS段
核心复制复制的三要素源、目的、长度 怎么知道代码段/数据段保存在哪(加载地址)怎么知道代码段/数据段要被复制到哪(链接地址)怎么知道代码段/数据段的长度 怎么知道BSS段的地址范围起始地址、长度这一切 在keil中使用散列文件(Scatter File)来描述在GCC中使用链接脚本(Link Script)来描述
2.4 加载地址和链接地址的区别
程序运行时应该位于它的链接地址处因为
使用函数地址时用的是函数的链接地址所以代码段应该位于链接地址处去访问全局变量、静态变量时用的是变量的链接地址所以数据段应该位于链接地址处
但是 程序一开始时可能并没有位于它的链接地址
比如对于STM32F103程序被烧录器烧写在Flash上这个地址称为加载地址比如对于IMX6ULL/STM32MP157片内ROM根据头部信息把程序读入内存这个地址称为“加载地址”
当加载地址 链接地址时就需要重定位。
三、散列文件使用与分析
3.1 重定位的实质: 移动数据
把代码段、只读数据段、数据段移动到它的链接地址处mcmcpy。 也就是复制 数据复制的三要素源、目的、长度。 数据保存在哪里加载地址 数据要复制到哪里链接地址 长度
这3要素怎么得到 在keil中使用散列文件来描述。 散列分散排列 是的在STM32F103这类资源紧缺的单片机芯片中
代码段保存在Flash上直接在Flash上运行(当然也可以重定位到内存里)数据段保存在Flash上使用前被复制到内存里
3.2 散列文件示例
3.2.1 示例代码
复制”01_uart_question“改名为”02_uart_sct“。
在 Keil 中进行如下配置 会生成一个.sct结尾的文件使用Nope打开
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************LR_IROM1 0x08000000 0x00080000 { ; load region size_regionER_IROM1 0x08000000 0x00080000 { ; load address execution address*.o (RESET, First).ANY (RO).ANY (XO)}RW_IRAM1 0x20000000 0x00010000 { ; RW data.ANY (RW ZI)}
}3.2.2 散列文件语法
一个散列文件由一个或多个Load region(加载域)组成
load_region_description ::load_region_name (base_address | ( offset)) [attribute_list] [max_size]{execution_region_description}Load region中含有一个或多个Execution region(可执行域)
Execution region语法如下
execution_region_description ::exec_region_name (base_address | offset) [attribute_list] [max_size | length]{input_section_description*}Execution region中含有一个或多个Input section Input section语法如下
input_section_description ::
module_select_pattern [ ( input_section_selector ( ,
input_section_selector )* ) ]
input_section_selector ::input_section_attr |
input_section_pattern |
input_section_type |
input_symbol_pattern |
section_properties3.3 散列文件解析 *.o 所有objects文件
*所有objects文件和库在一个散列文件中只能使用一个*
.ANY等同于*优先级比*低在一个散列文件的多个可执行域中可以有多个.ANY
.ANY (RO)所有.o文件、库文件的只读数据段
.ANY (XO)所有.o文件、库文件的可执行数据段
.ANY (RW ZI)所有.o文件、库文件的可读可写段和BSS段
3.4 怎么获得region的信息
3.4.1 可执行域的信息 3.4.2 加载域的信息 3.4.3 汇编代码里怎么使用这些信息
Load$$region_name$$Base //下面修改时候要注意region_name(区域名)通过上图可知可执行域1的源地址和目的地址一致所以能够正常执行不需要重定位而可执行域2的源地址紧随1后但其目的地址与源地址不同所以需要对可执行域进行重定位。所以需要将区域名字修改成 RW_IRAM1 。
示例代码如下 string.c中的memcpy函数
void mcmcpy(void *dest, void *src, unsigned int len)
{unsigned char *pcDest;unsigned char *pcSrc;while (len--){*pcDest *pcSrc; //让目的地与源地址相等后面两者长度知道len0完成复制pcSrc;pcDest;}
}IMPORT |Image$$RW_IRAM1$$Base|
IMPORT |Image$$RW_IRAM1$$Length|
IMPORT |Load$$RW_IRAM1$$Base|
IMPORT memcpyLDR R0, |Image$$RW_IRAM1$$Base| ; DEST
LDR R1, |Load$$RW_IRAM1$$Base| ; SORUCE
LDR R2, |Image$$RW_IRAM1$$Length| ; LENGTH
BL memcpy修改start.s让目的地址与源地址一样后编译烧录可以发现A被成功打印。 3.4.4 C语言里怎么使用这些信息
1.方法1
声明为外部变量。 注意使用时需要使用取址符
extern int Image$$RW_IRAM1$$Base;
extern int Load$$RW_IRAM1$$Base;
extern int Image$$RW_IRAM1$$Length;memcpy(Image$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length, Load$$RW_IRAM1$$Base);2.方法2
声明为外部数组。 注意使用时不需要使用取址符
extern char Image$$RW_IRAM1$$Base[];
extern char Load$$RW_IRAM1$$Base[];
extern int Image$$RW_IRAM1$$Length;memcpy(Image$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length, Load$$RW_IRAM1$$Base);四、清除BSS段(ZI段)
4.1 C语言中的BSS段
程序里的全局变量如果它的初始值为0或者没有设置初始值这些变量被放在BSS段里也叫ZI段。
char g_Char A;
const char g_Char2 B;
int g_A 0; // 放在BSS段
int g_B; // 放在BSS段BSS段并不会放入bin文件中否则也太浪费空间了。 在使用BSS段里的变量之前把BSS段所占据的内存清零就可以了。 注意对于keil来说一个本该放到BSS段的变量如果它所占据的空间小于等于8字节自己keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时才会放到BSS段。 int g_A[3] {0, 0}; // 放在BSS段
char g_B[9]; // 放在BSS段int g_A[2] {0, 0}; // 放在data段
char g_B[8]; // 放在data段4.2 清除BSS段
4.2.1 BSS段的位置和大小
在散列文件中BSS段(ZI段)在可执行域RW_IRAM1中描述
LR_IROM1 0x08000000 0x00080000 { ; load region size_regionER_IROM1 0x08000000 0x00080000 { ; load address execution address*.o (RESET, First)*(InRoot$$Sections).ANY (RO).ANY (XO)}RW_IRAM1 0x20000000 0x00010000 { ; RW data.ANY (RW ZI)}
}BSS段(ZI段)的链接地址(基地址)、长度使用下面的符号获得
4.2.2 怎么清除BSS段
1.汇编码
string.c中的memset函数
void memset(void *dest, unsigned char val, unsigned int len)
{unsigned char *pcDest dest;while (len --){*pcDest val;pcDest;}
}IMPORT |Image$$RW_IRAM1$$ZI$$Base| ;IMPORT在这里相当于定义
IMPORT |Image$$RW_IRAM1$$ZI$$Length|
IMPORT memsetLDR R0, |Image$$RW_IRAM1$$ZI$$Base| ; DEST
MOV R1, #0 ; VALVAL为0 并赋给dest 完成清零操作
LDR R1, |Image$$RW_IRAM1$$ZI$$Length| ; Length
BL memset调用memset函数后可见放入BSS段成功被清除为0 2.C语言
方法1 声明为外部变量使用时需要使用取址符
extern int Image$$RW_IRAM1$$ZI$$Base;
extern int Image$$RW_IRAM1$$ZI$$Length;memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length);方法2 声明为外部数组使用时不需要使用取址符
extern char Image$$RW_IRAM1$$ZI$$Base[];
extern int Image$$RW_IRAM1$$ZI$$Length[];memset(Image$$RW_IRAM1$$ZI$$Base[], 0, Image$$RW_IRAM1$$ZI$$Length);五、代码段重定位
5.1 加载地址等于链接地址
在默认散列文件中代码段的load address execution address 也就是加载地址和执行地址(链接地址)一致所以无需重定位
LR_IROM1 0x08000000 0x00080000 { ; load region size_regionER_IROM1 0x08000000 0x00080000 { ; load address execution address*.o (RESET, First)*(InRoot$$Sections).ANY (RO).ANY (XO)}RW_IRAM1 0x20000000 0x00010000 { ; RW data.ANY (RW ZI)}
}5.2 加载地址不等于链接地址
有时候我们需要把程序复制到内存里运行比如
想让程序执行得更快需要把代码段复制到内存里程序很大保存在片外SPI Flash中SPI Flash上的代码无法直接执行需要复制到内存里
这时候需要修改散列文件把代码段的可执行域放在内存里。 那么程序运行时需要尽快把代码段重定位到内存。 散列文件示例
LR_IROM1 0x08000000 0x00080000 { ; load region size_regionER_IROM1 0x20000000 { ; load address ! execution address*.o (RESET, First).ANY (RO).ANY (XO)}RW_IRAM1 0 { ; RW data.ANY (RW ZI)}
}上面的散列文件中
可执行域ER_IROM1 加载地址为0x08000000可执行地址为0x20000000两者不相等板子上电后从0x08000000处开始运行需要尽快把代码段复制到0x20000000 可执行域RW_IRAM1 加载地址紧跟着ER_IROM1的加载地址0可执行地址紧跟着ER_IROM1的可执行地址0需要尽快把数据段复制到可执行地址处
数据段的重定位我们做过实验 如果代码段不重定位的话会发生什么事 修改完上面的散列文件发现程序编译烧录后在Mobe软件上毫无反应说明程序发生崩溃。我们进行解析 我们程序运行时在start.s中第一条指令是从不是从Reset_Handler开始执行而是从__Vectors对于__Vectors的第一个数据DCD是用来设置栈但是我们并不需要第一个数据我们自己手动设置了栈LDR SP, (0x200000000x10000)对于第二个数据CPU会在DCD中取出Reset_Handler这个值并将其作为函数地址跳转过去。为了了解函数地址我们打开反汇编代码可以看见其对应的地址为20000009而我们的代码烧写在Flash上时在开始是0CPU一上电会用第一个值设置栈但我们没有使用我们后面自己去设置栈在第二个位置取出该值将其作为函数地址跳过去执行注意这里的20000009中的bit0并不使用bit0只是用来表示跳过去后的指令为Tumb指令集会跳到Reset Handler对应的20000008但在20000008上我们并未对该内存进行任何操作此时如果PC跳到位置则会崩溃由第三张图可见Reset_Handler对应的是08000008所以我们需要在20000009的位置放入08000009设置成09是因为bit(0)等于1表示后面执行的是Tumb指令集。修改完毕后程序得以正常执行成功清除ss段 5.3 代码段不重定位的后果
不能使用链接地址来调用函数 汇编中 ldr pc, main ; 这样调用函数时用到main函数的链接地址如果代码段没有重定位则跳转失败C语言中 void (*funcptr)(const char *s, unsigned int val);
funcptr put_s_hex;
funcptr(hello, test function ptr, 123);5.4 代码段重定位
5.4.1 代码段的位置和大小
在散列文件中代码段在可执行域ER_IROM1中描述
LR_IROM1 0x08000000 0x00080000 { ; load region size_regionER_IROM1 0x08000000 0x00080000 { ; load address execution address*.o (RESET, First)*(InRoot$$Sections).ANY (RO).ANY (XO)}RW_IRAM1 0x20000000 0x00010000 { ; RW data.ANY (RW ZI)}
}代码段的链接地址(基地址)、长度使用下面的符号获得 代码段的加载地址使用下面的符号获得 5.4.2 怎么重定位
1.汇编代码
把代码段从Flash复制到内存中
IMPORT |Image$$ER_IROM1$$Base| ;IMPORT在这里相当于定义
IMPORT |Image$$ER_IROM1$$Length|
IMPORT |Load$$ER_IROM1$$Base|LDR R0, |Image$$ER_IROM1$$Base| ; DEST
LDR R1, |Load$$ER_IROM1$$Base| ; SORUCE
LDR R2, |Image$$ER_IROM1$$Length| ; LENGTH
BL memcpy注意第二段代码已经进行重定位而第一段代码还位于Flash上为什么该段代码还没位于他的链接地址上却可以正常执行因为该段代码使用的是位置无关码。所谓位置无关就是放在任何地方都能得以执行跟他的位置没有任何关系。关键在于跳转他使用的是相对跳转指令而第二段代码使用的是链接地址跳转指令。
2.C语言代码
方法1
声明为外部变量使用时需要使用取址符
extern int Image$$ER_IROM1$$Base;
extern int Load$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, Load$$ER_IROM1$$Base);方法2
声明为外部数组使用时不需要使用取址符
extern char Image$$ER_IROM1$$Base[];
extern char Load$$ER_IROM1$$Base[];
extern int Image$$ER_IROM1$$Length;memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, Load$$ER_IROM1$$Base);5.5 为什么重定位之前的代码也可以正常运行
因为重定位之前的代码是使用位置无关码写的 只使用相对跳转指令B、BL 不只用绝对跳转指令 LDR R0, main
BLX R0不访问全局变量、静态变量、字符串、数组 重定位完后使用绝对跳转指令跳转到XXX函数的链接地址去 BL main ; bl相对跳转程序仍在Flash上运行LDR R0, main ; 绝对跳转跳到链接地址去就是跳去内存里执行
BLX R0五、重定位的纯C函数实现
5.1 难点
难点在于怎么得到各个域的加载地址、链接地址、长度。
方法1
声明为外部变量使用时需要使用取址符
extern int Image$$ER_IROM1$$Base;
extern int Load$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, Load$$ER_IROM1$$Base);方法2
声明为外部数组使用时不需要使用取址符
extern char Image$$ER_IROM1$$Base[];
extern char Load$$ER_IROM1$$Base[];
extern int Image$$ER_IROM1$$Length;memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, Load$$ER_IROM1$$Base);5.2 怎么理解上述代码
对于这样的C变量
int g_a;编译的时候会有一个符号表(symbol table)如下
NameAddressg_axxxxxxxx
对于散列文件中的各类Symbol有2中声明方式
extern int Image$$ER_IROM1$$Base; // 声明为一般变量包括int、char还有指针
extern char Image$$ER_IROM1$$Base[]; // 声明为数组不管是哪种方式它们都会保存在符号表里比如
NameAddressg_axxxxxxxxImage E R I R O M 1 ER_IROM1 ERIROM1Baseyyyyyyyy
对于int g_a变量 使用g_a得到符号表里的地址。 对于extern int Image$$ER_IROM1$$Base变量 要得到符号表中的地址也是使用Image$$ER_IROM1$$Base。 对于extern char Image$$ER_IROM1$$Base[]变量 要得到符号表中的地址直接使用Image$$ER_IROM1$$Base不需要加为什么mage$$ER_IROM1$$Base本身就表示地址啊
示例代码 在start.s中定义SystemInt并调用跳转
PRESERVE8THUMB; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLYEXPORT __Vectors__Vectors DCD 0 DCD 0x08000009 ; Reset HandlerAREA |.text|, CODE, READONLY; Reset handler
Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT mymainIMPORT SystemInit ;定义SystemInitLDR SP, (0x200000000x10000)BL SystemInit ;跳转SystemInit;BL mymainLDR R0, mymainBLX R0ENDPEND创建一个init.c在其中写入c函数的重定位包括int、char和指针
#include string.h#if 0
void SystemInit()
{ extern int Image$$ER_IROM1$$Base;extern int Image$$ER_IROM1$$Length;extern int Load$$ER_IROM1$$Base;extern int Image$$RW_IRAM1$$Base;extern int Image$$RW_IRAM1$$Length;extern int Load$$RW_IRAM1$$Base;extern int Image$$RW_IRAM1$$ZI$$Base;extern int Image$$RW_IRAM1$$ZI$$Length;/* 代码段重定位 */memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length); //目的地址、源地址、长度/* 数据段重定位 */memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length); //目的地址、源地址、长度/* 清除BSS段 */memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length); //源地址、清除为0、长度
}
#endif#if 0
void SystemInit()
{ extern int Image$$ER_IROM1$$Base[];extern int Image$$ER_IROM1$$Length[];extern int Load$$ER_IROM1$$Base[];extern int Image$$RW_IRAM1$$Base[];extern int Image$$RW_IRAM1$$Length[];extern int Load$$RW_IRAM1$$Base[];extern int Image$$RW_IRAM1$$ZI$$Base[];extern int Image$$RW_IRAM1$$ZI$$Length[];/* 代码段重定位 */memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length); //目的地址、源地址、长度/* 数据段重定位 */memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length); //目的地址、源地址、长度/* 清除BSS段 */memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length); //源地址、清除为0、长度
}
#endifvoid SystemInit()
{ extern int *Image$$ER_IROM1$$Base;extern int *Image$$ER_IROM1$$Length;extern int *Load$$ER_IROM1$$Base;extern int *Image$$RW_IRAM1$$Base;extern int *Image$$RW_IRAM1$$Length;extern int *Load$$RW_IRAM1$$Base;extern int *Image$$RW_IRAM1$$ZI$$Base;extern int *Image$$RW_IRAM1$$ZI$$Length;/* 代码段重定位 */memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length); //目的地址、源地址、长度/* 数据段重定位 */memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length); //目的地址、源地址、长度/* 清除BSS段 */memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length); //源地址、清除为0、长度
}三者结果一致