当前位置: 首页 > news >正文

免费医疗网站模板湛江网站建设招聘

免费医疗网站模板,湛江网站建设招聘,html婚纱网站源码,搜索引擎广告优化#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《理解ARM架构》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 目录 #x1f3d3;引出重定位#x1f3d3;散列文件#x1f3d3;可读可写数据段重定位#… 作者一只大喵咪1201 专栏《理解ARM架构》 格言你只管努力剩下的交给时间 目录 引出重定位散列文件可读可写数据段重定位清除BSS段代码段重定位相对跳转 实现代码段重定位 纯C函数实现重定位总结 书接上文中的 问题为什么每个函数中都得创建一个uart1结构体局部变量而不是创建全局变量供这些函数使用呢 引出重定位 继续看这张图为什么只有被const修饰的ConstChar变量能正常输出其他三个变量输出的就是乱码的呢 如上图本喵为了方便看汇编代码将C代码只保留打印变量部分。 打印g_Char这个没有被const修饰的全局变量时可以看到使用汇编指令LDR r0,[pc,#80]从0x20000000地址处读取值赋给寄存器R0。 打印g_ConstChar这个被const修饰的全局变量时可以看到使用汇编指令MOVVS r0,#0x42直接将B的ASCII码值赋给了寄存器R0。 都是在R0拿到数值以后才调用putchar函数来输出R0中的值这里R0是用来传参的。 但是此时g_Char全局变量所在地址0x20000000并没有值因为整个代码中只有读取这个地址的指令并没有给这个地址赋值的指令所以CPU读取g_Char地址处读取到的就是乱码。 而g_ConstChar是直接拿到的立即数这个立即数是存放在Flash中的是在烧录的时候写入的。 加载地址和链接地址 我们将程序写好编译链接完成以后将其烧录到开发板中此时程序保存在Flash中而对应Flash中的地址被叫做加载地址。 而编译器在链接时会给变量分配一个地址这个地址叫做链接地址对应于RAM中的地址。 加载地址可以理解为真实物理地址而链接地址可以理解为虚拟地址CPU在执行代码时使用的是虚拟地址也就是链接地址。 如上图要想程序正常运行就需要在执行mymain函数之前将Flash中的A复制到RAM的数据段处让CPU能在链接地址处找到相应的值。 保存在ROM上的全局变量的值在使用前要复制到内存这就是数据段重定位把代码移动到其他位置这就是代码重定位。 程序运行时应该位于它的链接地址处因为 使用函数地址时用的是函数的链接地址所以代码段应该位于链接地址处去访问全局变量、静态变量时用的是变量的链接地址所以数据段应该位于链接地址处 但是 程序一开始时可能并没有位于它的链接地址 比如对于STM32F103程序被烧录器烧写在Flash上这个地址称为加载地址比如对于IMX6ULL/STM32MP157片内ROM根据头部信息把程序读入内存这个地址称为“加载地址” 当加载地址 链接地址时就需要重定位。 谁来做重定位 程序本身它把自己复制到链接地址去一开始程序可能并不位于它的链接地址上它自己都不在链接地址为什么它可以执行重定位的操作 因为重定位的代码是使用“位置无关码”写的。 什么叫位置无关码这段代码扔在任何位置都可以运行跟它所在的位置无关 怎么写出位置无关码 跳转使用相对跳转指令不能使用绝对跳转指令。 只能使用branch指令(比如BL mymain)不能给PC直接赋值比如LDR pc, mymain 不要访问全局变量、静态变量。不使用字符串。 散列文件 怎么做重定位和清除BSS段 核心操作复制。 复制的三要素源、目的、长度 怎么知道代码段/数据段保存在哪(加载地址)怎么知道代码段/数据段要被复制到哪(链接地址)怎么知道代码段/数据段的长度 对于BSS段怎么知道BSS段的地址范围起始地址、长度 这一切信息都在keil中使用散列文件(Scatter File)来描述。 顾名思义散列就是分散排列的意思在STM32F103这类资源紧缺的单片机芯片中 代码段保存在Flash上直接在Flash上运行(当然也可以重定位到内存里)数据段保存在Flash上使用前被复制到内存里 从Keil5的帮助中找到Arm® Compiler for Embedded Reference Guide.pdf文件里面有散列文件的详细介绍本喵这里大致讲解一下。 如上图一个散列文件由一个或多个Load region(加载域)组成加载域中含有一个或多个Execution region(可执行域)可执行域中含义一个或多个input section(输入段)。 加载域描述了Flash中一块区域的位置包括该区域中有什么内容及位置(加载地址)而且还可以控制这些内容放置在哪里(加载地址)。 可执行域描述了RAM中程序运行时该区域包含的内容(输入段)并且可以控制区域所在的位置(链接地址)。 输入部分描述了可执行域中包含哪些数据类型。 这样来看对于不同域的作用还是一头雾水下面直接看本喵写的代码中生成的散列文件。 如上图所示在当前工程文件中的Objects目录下有一个后缀为.sct的文件这个就是散列文件。 如上图就是散列文件里的内容LR_IROM1是加载域的名称0x08000000是加载域的加载地址也就是Flash中代码存放的起始地址后面是加载域的大小。 第一个可执行域 加载域中有两个可执行域ER_IROM1是第一个可执行域的名称起始地址是0x08000000(链接地址)里面包含多个输入段信息该地址和加载地址重合所以这个可执行域不用重定位。 *.o 所有objects文件就是链接之前的二进制目标文件。*所有objects文件和库在一个散列文件中只能使用一个*。.ANY等同于*优先级比*低在一个散列文件的多个可执行域中可以有多个.ANY。 *.o (RESET, First)表示将所有objects文件中的RESET域放在可执行的起始(First)位置。 如上图所示启动文件中的汇编代码其中 AREA RESET规定了绿色框中的汇编代码是RESET域所以散列文件中第一个可执行域中首先放置绿色框中的代码。 板子一上电以后就从__Vectors处的DCD开始执行代码这部分也恰好是该可执行域中RESET域的代码。然后才会调用跳转到Rset_Handler处开始执行。 *(InRoot$$Sections)是如果我们写了main函数编译器自己会执行的一套东西这里本喵写的是mymain函数为的就是避开这个东西所以这里不用管它是什么。 .ANY (RO)表示所有objects文件和库中的只读数据段放在这个可执行域中挨着前面的输入段放置和我们前面分析的一样只读数据段并不需要重定位。 .ANY (XO)并没有涉及到也不用管它。 第二个可执行域 RW_IRAM1是第二个可执行域的名称起始地址是0x20000000(链接地址)位于RAM中里面包含一个输入段信息这个执行域需要进行重定位。 .ANY (RW ZI)表示所有objects文件和库中读写数据段和BSS或者ZI段放在这个可执行域中。 可执行域的起始地址就是链接地址。 可以看到不同数据段所处的位置是由散列文件通过可执行域中的输入段决定的而且从散列文件中可以得到重定位需要的三要素源目的长度了。 可读可写数据段重定位 在调用mymain函数使用数据段前将可读可写数据段的初始值从加载地址复制到链接地址。 如上图所示定义一个复制函数mymemcpy传入目的地址起始地址以及数据长度将数据从起始地址复制到目的地址。 在启动文件的汇编代码中在使用BL mymain之前使用BL mymemcpy调用该函数实现数据段重定位。 我们知道汇编代码调用C函数时通过r0~r3寄存器来实现传参的但是在调用时我们怎么知道起始地址和目的地址以及长度呢 不用去散列文件里查找具体的地址值keil5的帮助手册中提供了获取这几个参数的方式 如上图所示是加载地址的符号只需要将region_name替换成我们要操作的域即可如Load$$RW_IRAM1$$Base表示的就是RW_IRAM1可执行域的加载地址也就是我们需要的源地址。 如上图所示是获取可执行域的起始地址同样进行部分替换即可得到目的地址(链接地址)。 如上图在启动文件的汇编代码中声明拷贝函数链接地址数据个数加载地址然后将其赋值给R0~R2进行传参再调用mymemcpy进行数据段的拷贝最后再带调用mymain打印四个变量。 如上图此时g_Char也成功的打印出了值说明此时该地址并是乱码了而是A的ASCII码说明我们的数据段重定位成功了。 清除BSS段 如上图初始值为0的全局变量g_A和无初始值的全局变量g_B也并不是乱码了而且空0输出的结果是空说明此时这两个变量的链接地址处也有值。 奇怪了我们只是进行了可读可写数据段的重定位为什么BSS段也被清零了呢这是因为编译器进行了优化BSS段的数据只有两个太少了没有必要专门去清除BSS段直接把它两归为可读可写数据段一并处理了。 如上图将BSS段的这两个变量改成数组此时编译器就不会优化了可以看到输出的数组第一个元素的值是乱码说明BSS段地址处存放的就是乱码需要我们进行清零操作。 如上图所示是获得BSS段(ZI段)的链接地址(基地址)、长度的符号同样需要进行部分替换。 如上图定义一个mymemset清除函数传入目的地址要设置的值以及长度就将这部分数据设置成对应的值。 如上图所示汇编代码在红色框声明ZI段的起始地址以及长度然后在蓝色框中使用R0~R2传参给R1赋值0再使用BL mymemset调用清除函数将BSS段清零0。 如上图此时打印出的就是空说明BSS段存放的是0也说明我们清除BSS段成功了。 代码段重定位 如上图在默认散列文件中代码段的load address execution address 也就是加载地址和执行地址(链接地址)一致所以无需重定位。 有时候我们需要把程序复制到内存里运行比如 想让程序执行得更快需要把代码段复制到内存里。程序很大保存在片外SPI Flash中SPI Flash上的代码无法直接执行需要复制到内存里。 这时候需要修改散列文件把代码段的可执行域放在内存里那么程序运行时就需要尽快把代码段重定位到内存。 如上图对我们的散列文件进行修改 可执行域ER_IROM1 加载地址为0x08000000可执行地址为0x20000000两者不相等。 可执行域RW_IRAM1地址只有0 加载地址紧跟着ER_IROM1的加载地址。可执行地址紧跟着ER_IROM1的可执行地址。 相对跳转 此时散列文件已经被修改了让代码段的链接地址不等于加载地址我们在启动文件中先不进行代码段重定位直接让板子去执行 如上图这是编译后的反汇编文件可以看到Reset_Handler的起始地址是0x20000008说明代码段的起始地址就是这个但是这是将散列文件中代码段可执行区域的链接地址改成0x20000000的结果。 说明Reset_Handler距离代码段起始地址的偏移量是8那么我们将编译好的程序烧录到开发板中以后它的加载地址是从0x08000000开始的所以Reset_Handler的加载地址就是0x08000008。 如上图在程序一上电开始执行__Vectors处的第二个DCD位置将Reset_Handler改成0x08000009让程序从加载地址开始执行。 为什么是0x08000009而不是0x08000008呢因为最低位bit0要为1表示从这里开始使用的是Thumb指令集。 程序上电后必须从加载地址开始执行否则无法进行重定位等操作。所以不能直接使用Reset_Handler因为它此时的链接地址是0x20000008。 然后其他代码不变仍然使用BL mymain调用mymain函数将程序烧录到开饭板中 如上图此时程序仍然可以正常运行。程序的链接地址不在散列文件中修改了吗意味着mymain的链接地址也变了啊此时RAM中并没有代码为什么还能正常执行呢 能正常调用mymain的原因是使用了BL相对跳转指令。 如上图代码被烧录到开发板中后位于Flash中而此时程序又是从0x08000008处开始执行的mymain函数距离这个位置的偏移量是固定的所以使用BL相对跳转指令可以直接跳转到mymain函数中。 相对跳转指令只看当前位置和目标位置的偏移量。 而数据段和BSS段都已经处理好了所以程序可以正常执行只是在Flash中执行和我们预期在RAM中执行不符。 实现代码段重定位 如上图所示使用绝对跳转的方式去调用mymain函数先获取mymain函数的地址到R0寄存器中此时获取到的是链接地址然后再使用BLX跳转到链接地址处。 使用BLX中的X表示Thumb指令集。 如上图此时就不会有任何现象了因为mymain函数的链接地址处并没有代码。 所以此时就需要实现代码段的重定位了 IMPORT |Image$$ER_IROM1$$Base| IMPORT |Image$$ER_IROM1$$Length| IMPORT |Load$$ER_IROM1$$Base|同样使用这几个符号来确定代码段的加载地址和链接地址此时要将名字都替换成ER_IROM1表示这是代码段而不是数据段RW_IRAM1。 如上图所示在汇编代码中声明代码段的源地址目的地址长度然后使用R0~R2进行传参然后也是调用mymemcpy将Flash中的代码赋值到RAM中的链接地址处。 如上图此时就可以正常执行了可以输出对应的数据说明代码段重定位成功了。 纯C函数实现重定位 在汇编中实现重定位需要使用寄存器来传参目的源长度三要素有没有办法只调用一个C函数就能实现呢有的。 使用C函数实现重定位的难点在于怎么得到各个域的加载地址(源)、链接地址(目的)、长度。 在C函数中可以直接使用汇编中表示地址的符号 方法1声明为外部变量使用时需要使用取址符 extern int Image$$ER_IROM1$$Base; extern int Load$$ER_IROM1$$Base; extern int Image$$ER_IROM1$$Length;mymemcpy(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;mymemcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, Load$$ER_IROM1$$Base);为什么声明了就可以用了呢 对于int g_A这样的变量在编译的时候会生成一个符号表里面包含变量名和变量地址这个变量是我们定义的。 而表示加载地址链接地址长度的符号是keil5的散列文件定义的变量所以我们在使用前使用extern声明一下在编译的时候就会和我们自己的变量共同组成符号表 NameAddressg_axxxxxxxxImage$$ER_IROM1$$Baseyyyyyyyy 由于符号表中存放的是变量名和其地址所以我们在访问自己定义的变量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本身就表示地址。 如上图所示定义系统重定位函数SystemInit在函数体内部使用extern声明我们所需要的加载地址链接地址以及长度等要素变量。 在使用mymemcpy等函数进行重定位时传参extern声明的这几个变量都需要取地址。 如上图也可以将这些变量声明为数组数组名的本质就是一个地址所以在调用mymemcpy等函数重定位时传参时不需要取地址。 如上图在汇编函数中声明一下系统重定位函数然后设置好栈因为会用到栈再调用SystemInit进行代码段可读可写数据段的重定位和ZI段的清0。 如上图此时仍然可以正常运行说明纯C方式的重定位成功。 既然使用到的是extern变量的地址那么直接声明成指针类型行不行呢 如上图直接使用extern声明为int*类型经过本喵实验证明在调用mymemcpy等函数时直接传入声明的指针变量是不可以的必须再对声明的指针取地址再传参。 因为这里使用的是这几个变量本身的值如果声明为指针类型的话它虽然是一个指针但是它表示的地址数值却是我们需要的如果直接对指针解引用访问到的并不是我们需要的这个值。 所以需要再对指针变量取地址让其成为一个二级指针此时解引用二级指针得到的一级指针的值才是我们需要的地址才能直接使用。 总结 平时使用CubeMX或者直接移植固件库时并不会注意到需要重定位而实现重定位在keil5中主要依赖散列文件它描述了不同可执行域的加载地址和链接地址给重定位提供了三要素。
http://www.yutouwan.com/news/282877/

相关文章:

  • 网站关键词突然没有排名了ppt设计主题
  • 泰安专业网站开发公司h5自适应网站建设是什么意思
  • 网站如何强制修改主页 源码广州智能科技有限公司
  • 学校网站建设意义网站建设如何定价
  • iis网站服务器安全隐患分析网站开发注册个体工商
  • 江西专业网站建设定制细分网站
  • 网站改标题不改版 kwordpress图片主题 瀑布流经典
  • 网站策划素材做网站如何获得阿里巴巴投资
  • 南宁保洁网站建设浙江高端网站建设公司
  • 哪个网站上做ppt比较好公司官网怎么制作
  • 做兼职设计去哪个网站广西模板十大名牌排名榜
  • 网站基础建设巴巴商友圈互联网行业是干什么的
  • 成都网站推广经理标签wordpress
  • 衡水移动网站建设报价网站降权了怎么办
  • 网站开发洲际wordpress百度插件下载
  • 商城网站设计需要哪些技术成全视频免费观看在线看收索
  • 创建网站的工作流程wordpress中文版 显示英文
  • 网站建设框架图提高网站浏览量
  • 宁波网站制作公司官网网站内容如何优化
  • 网站 建设初步无法连接wordpress
  • 雄安新区网站建设昆明市网站推广
  • 网站开发需求文档模板带er图服饰 视频 网站建设
  • 网站如何导流量wordpress前台多张缩略图
  • 做特产网站手机网站建设经典教程
  • 营销网站建设大全注册网站查询系统
  • 模板网站建设哪家专业江苏企业网站建设
  • 做学校和企业对接的网站个人网站备案 网站服务内容
  • 购物网站开发课程设计图片常采用gif或jpeg格式
  • 昆明网站建设 技术支持百度手机助手安卓版
  • 搭建网站要什么显卡建行官网网站