匿名
未登录
登录
百问网嵌入式Linux wiki
搜索
查看“第013课 代码重定位”的源代码
来自百问网嵌入式Linux wiki
名字空间
页面
讨论
更多
更多
页面选项
Read
查看源代码
历史
←
第013课 代码重定位
因为以下原因,您没有权限编辑本页:
您所请求的操作仅限于该用户组的用户使用:
用户
该页面已被保护以防止编辑和其他操作。
您可以查看与复制此页面的源代码。
=第001节_段的概念_重定位的引入 = S3C2440的CPU可以直接给SDRAM发送命令、给Nor Flash发送命令、给4K的片上SDRAM发送命令,但是不能直接给Nand Flsh发送命令 假如把程序烧写到Nand Flsh上,即向Nand Flsh烧入''' bin''' 文件,CPU是无法从Nand Flsh中取代码执行的。 为什还可以使用NAND启动? # 上电后,Nand启动硬件会自动把Nand Flsh前4K复制到SRAM; # CPU从0地址运行SRAM; 如果我的程序大于4K怎么办?<br> 前4K的代码需要把整个程序读出来放到SDRAM(即代码重定位)。 如果从Nor Flash启动,会出现什么问题?<br> 将拨动开关拨到Nor Flash启动时,此时CPU认为的''' 0地址 '''在Nor Flash上面,片内内存SRAM的基地址就变成了0x40000000(Nand启动时片内内存SRAM的基地址基地址是0),由于Nor Flash特性:'''可以像内存一样读,但不能像内存直接写''',因此需要把全局变量和静态变量重定位 放到SDRAM里。<br> 例如执行如下几条汇编指令 MOV R0, #0 LDR R1, [R0] @读有效 STR R1, [R0] @写无效 当程序中含有需要写的全局变量或静态变量时,假如是在Nand Flash可以正常操作,如果是在Nor Flash,修改无效。因此我们需要把全局变量和静态变量重定位 放到SDRAM <syntaxhighlight lang="c" > #include "s3c2440_soc.h" #include "uart.h" #include "init.h" char g_Char = 'A'; //定义一个全局变量 const char g_Char2 = 'B'; //定义固定的全局变量 int g_A = 0; int g_B; int main(void) { uart0_init(); while (1) { putchar(g_Char); /*让g_Char输出*/ g_Char++; /* nor启动时, 此代码无效 */ delay(1000000); } return 0; } </syntaxhighlight> 编译运行查看是否有效果 查看sdram.dis文件 发现data数据段放在了0x00008474这个地址导致 程序太大 在makefile中加入这么一句话 arm-linux-ld -Ttext 0 ''' -Tdata 0x700 ''' start.o led.o uart.o init.o main.o -o sdram.elf 16进制的700就是十进制的2048 这时我们的bin文件就变为2049 烧写程序: 烧写在NORFlash 和 烧写在NANDFlash观察这两种的效果。 设置成NANDFlash启动没有问题 显示ABCDE... 设置成NORFlash启动显示AAA... 对于NOR启动时<code>g_Char++; /* nor启动时, 此代码无效 */</code> <syntaxhighlight lang="c" > Disassembly of section .data: 00000700 <__data_start>: 700: Address 0x700 is out of bounds. //数据段 Disassembly of section .rodata: //放在只读数据段内 00000474 <g_Char2>: //const char g_Char2 = 'B'; 474: Address 0x474 is out of bounds. Disassembly of section .bss: //bss段 00000804 <g_A>: //int g_A = 0; 804: 00000000 andeq r0, r0, r0 00000808 <g_B>: //int g_B; 808: 00000000 andeq r0, r0, r0 Disassembly of section .comment: </syntaxhighlight> 一个程序里面有 * .text 代码段 * .data 数据段 * rodata 只读数据段(const全局变量) * bss段 (初始值为0,无初始值的全局变量) * commen 注释 其中bss段和commen 注释不保存在bin文件中。 = 第002节_链接脚本的引入与简单测试 = 前面程序运行,发现从Nand Flash启动和从Nor Flash启动表现是不一样的。 设置成Nand Flash启动没有问题 显示ABCDE... 设置成NOor Flash启动则显示AAA... 这是什么原因呢? * 假如现在是Nor启动: [[File:chapter13_lesson2_001.jpg|700px]] Nor Flash就被认为是0地址,g_Char被放在0x700后面。CPU上电后从0地址开始执行,它能读取Nor Flash上的代码,打印出A,当进行g_Char++的时候,写操作操作无效,下次读取的数据仍然是A。 * 假如现在是Nand启动: [[File:chapter13_lesson2_002.jpg|700px]] 上电后,Nand Flash前4K代码就被自动的复制到SRAM里面,SRAM是CPU认为的0地址。CPU上电后从0地址开始执行,它读取SRAM上的代码,并g_Char++修改变量,下次读取的数据就依次增加了。 为了解决Nor Flash里面的变量不能写的问题,我们把变量所在的数据段放在SDRAM里面,看行不行。 修改Makefile 指定数据段为0x30000000 -Tdata 0x30000000: arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf 这样的话编译出来的bin文件 从0地址 到 0x30000000地址 文件大小有700多MB,代码段和数据段直接有间隔,称之为黑洞。<br> [[File:chapter13_lesson2_003.jpg|400px]] 解决黑洞有两个办法: *第一个方法 # 把数据段的g_Char和代码段靠在一起; # 烧写在Nor Flash上面; # 运行时把g_char(全局变量)复制到SDRAM,即0x3000000位置(重定位); *第二个方法 # 让文件直接从0x30000000开始,全局变量在0x3......; # 烧写Nor Flash上 0地址处; # 运行会把整个代码段数据段(整个程序)从0地址复制到SDRAM的0x30000000(重定位); 这两个方法的区别是前者只重定位了数据段,后者重定位了数据段和代码段。 参考文档 [http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html Using LD, the GNU linker] 第一种办法如何实现 修改Makefile的代码段地址,使用链接脚本sdram.lds指定。 #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf 链接脚本的语法: <syntaxhighlight lang="c" > SECTIONS { ... secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) { contents } >region :phdr =fill ... } </syntaxhighlight> 我们需要依次排列 代码段、只读数据段、数据段、.bss段、.common。 其中数据段放在0x700,但运行时在0x3000000: <syntaxhighlight lang="c" > SECTIONS { .text 0 : { *(.text) }//所有文件的.text .rodata : { *(.rodata) } //只读数据段 .data 0x30000000 : AT(0x700) { *(.data) } //放在0x700,但运行时在0x3000000 .bss : { *(.bss) *(.COMMON) }//所有文件的bss段,所有文件的.COMMON段 } </syntaxhighlight> 重新编译后烧写bin文件,发现启动后显示乱码。原因是我们从0x30000000处获取g_Char,但在这之前,并没有在0x30000000处准备好数据。因此需要重定位数据段,将0x700的数据移动到0x30000000处,在start.S加入: bl sdram_init /* 重定位data段 */ mov r1, #0x700 ldr r0, [r1] mov r1, #0x30000000 str r0, [r1] bl main 上面的这种方法,只能复制0x700处的一位数据,不太通用,下面写一个更加通用的复制方法: 链接脚本修改如下: <syntaxhighlight lang="c" > SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x700) { data_load_addr = LOADADDR(.data); data_start = . ;//等于当前位置 *(.data) //等于数据段的大小 data_end = . ;//等于当前位置 } .bss : { *(.bss) *(.COMMON) } } </syntaxhighlight> 修改start.S <syntaxhighlight lang="c" > bl sdram_init /* 重定位data段 */ ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */ ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */ ldr r3, =data_end /* data段结束地址 */ cpy: ldrb r4, [r1] //从r1读到r4 strb r4, [r2] //r4存放到r2 add r1, r1, #1 //r1+1 add r2, r2, #1 //r2+1 cmp r2, r3 //r2 r3比较 bne cpy //如果不等则继续拷贝 bl main </syntaxhighlight> =第003节_链接脚本的解析 = 链接脚本的语法 <syntaxhighlight lang="c" > SECTIONS { ... secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) { contents } >region :phdr =fill ... } </syntaxhighlight> 解释: secname :段名 start :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr) AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr { contents } 的内容: start.o //内容为start.o文件 *(.text)所有的代码段文件 start.o *(.text)文件 '''elf文件格式''' 1 链接得到elf文件,含有地址信息(load addr) 2 使用加载器 :: 2.1 对于裸板是JTAG调试工具 :: 2.2 对于APP,加载器也是APP 把elf文件解析读入内存的加载地址 3 运行程序 4 如果loadaddr != runtimeaddr程序本身要重定位 核心程序运行时应该位于 runtimeaddr(reloate addr)或者链接地址 '''bin文件''' 1 elf生成bin文件 2 硬件机制启动 3 如果bin文件所在位置 不等于runtimeaddr ,程序本身实现重定位 bin文件/elf文件都不保存bss段 这些都是初始值为0 或者没有初始化的全局变量 程序运行时把bss段对应的空间清零 做个实验,把全局变量g_A以16进制打印出来 <syntaxhighlight lang="c" > /* 0xABCDEF12 */ void printHex(unsigned int val) { int i; unsigned char arr[8]; /* 先取出每一位的值 */ for (i = 0; i < 8; i++) { arr[i] = val & 0xf; val >>= 4; /* arr[0] = 2, arr[1] = 1, arr[2] = 0xF */ } /* 打印 */ puts("0x"); for (i = 7; i >=0; i--) { if (arr[i] >= 0 && arr[i] <= 9) putchar(arr[i] + '0'); else if(arr[i] >= 0xA && arr[i] <= 0xF) putchar(arr[i] - 0xA + 'A'); } } </syntaxhighlight> <syntaxhighlight lang="c" > //打印初始值为0的变量 int g_A = 0; int g_B; int main(void) { uart0_init(); puts("\n\rg_A = "); printHex(g_A); puts("\n\r"); </syntaxhighlight> 上述代码,没有清理bss段 g_A等于莫名奇妙的值 并不等于0 所以需要清理bss段 修改lds链接文件 <syntaxhighlight lang="c" > SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x700) { data_load_addr = LOADADDR(.data); data_start = . ; *(.data) data_end = . ; } bss_start = .; //bss开始地址是当前位置 .bss : { *(.bss) *(.COMMON) } bss_end = .; //bss结束地址也是当前位置 } </syntaxhighlight> 修改start.s,清除bss段 <syntaxhighlight lang="c" > /* 清除BSS段 */ ldr r1, =bss_start ldr r2, =bss_end mov r3, #0 clean: strb r3, [r1] add r1, r1, #1 cmp r1, r2 bne clean bl main halt: b halt </syntaxhighlight> 现在的代码全局变量就是为0,通过几行代码,就可以少几十个甚至上千个全局变量的存储空间。 = 第004节_拷贝代码和链接脚本的改进 = 本节进行拷贝代码的改进和链接脚本的改进。 前面重定位时,需要ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDRAM里面。 <syntaxhighlight lang="c" > cpy: ldrb r4, [r1] /*首先从flash读出一个字节*/ strb r4, [r2] /*让后把数据写到SDRAM*/ add r1, r1, #1 add r2, r2, #1 cmp r2, r3 bne cpy </syntaxhighlight> JZ2440上的Nor Flash是16位,SDRAM是32位。 假设现在需要复制16byte数据, 采用ldrb命令每次只能加载1byte,因此CPU需要发出16次命令,内存控制器每次收到命令后,访问硬件Nor Flash,因此需要访问硬件16次; 同理,访问SDRAM时,CPU需要执行strb 16次,内存控制器每次收到命令后,访问硬件SDRAM,也要16次,这样总共访问32次。 现在对其进行改进,使用ldr从Nor Flash中读,ldr命令每次加载4字节数据,因此CPU只需执行4次,但由于Nor Flash是16位的,内存控制器每次收到CPU命令后,需要拆分成两次访问,因此需要访问硬件8次; 使用str写SDRAM,CPU只需执行4次,内存控制器每次收到命令后,直接硬件访问32位的SDRAM,因此这里只需要4次,这样总共访问只需要12次。 在整个操作中,花费时间最长的就是硬件访问,改进后代码,减少了硬件访问的次数,极大的提高了效率。<br> [[File:chapter13_lesson4_001.jpg|700px]] 根据上面原理修改代码,修改start.S: <syntaxhighlight lang="c" > cpy: ldr r4, [r1] str r4, [r2] add r1, r1, #4 //r1加4 add r2, r2, #4 //r2加4 cmp r2, r3 //如果r2 =< r3继续拷贝 ble cpy /* 清除BSS段 */ ldr r1, =bss_start ldr r2, =bss_end mov r3, #0 clean: str r3, [r1] add r1, r1, #4 cmp r1, r2 //如果r1 =< r2则继续拷贝 ble clean bl main </syntaxhighlight> 然后编译烧写,发现启动后没有输出字符。修改主程序,尝试以整数格式输出字符,发现输出的数从0开始,应该是 全局变量被破坏了。 屏蔽掉start.S里面的清理命令,测试是否是清除bss段是清除了全局变量。 <syntaxhighlight lang="c" > clean: //str r3, [r1] //注释掉此句话,str不仅把bss段清除,把全局变量这些也清除了 add r1, r1, #4 cmp r1, r2 ble clean bl main </syntaxhighlight> 屏蔽后,正常输出,锁定了问题大致位置。查看反汇编文件,原来是没有向4取整。 修改链接脚本让bss段,使用ALIGN(4)向4取整。 <syntaxhighlight lang="c" > SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x700) { data_load_addr = LOADADDR(.data); . = ALIGN(4); data_start = . ; *(.data) data_end = . ; } . = ALIGN(4);//让当前地址向4对齐 bss_start = .; .bss : { *(.bss) *(.COMMON) } bss_end = .; } </syntaxhighlight> 现在重新编译烧写,测试结果正常。 再次查看反汇编文件,发现现在bss段以4字节对齐,清理bss段也是正常的。 <syntaxhighlight lang="c" > Disassembly of section .bss: 30000004 <g_A>: 30000004: 00000000 andeq r0, r0, r0 30000008 <g_B>: 30000008: 00000000 andeq r0, r0, r0 Disassembly of section .comment: </syntaxhighlight> 同样的问题也会出在代码重定位这里,如何保证data段起始地址也是向4对齐呢? 也是使用'''ALIGN(4)'''向4取整。 <syntaxhighlight lang="c" > SECTIONS { . = 0x30000000; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(.COMMON) } _end = .; } </syntaxhighlight> Uboot是裸机的集大成者,可以参考uboot链接脚本也是类似的。 = 第005节_代码重定位与位置无关码 = 一个程序,由代码段、只读数据段、数据段、bss段等组成。 程序一开始可以烧在Nor Flash上面,运行时代码段仍可以在Nor Flash运行,但对于数据段,就必须把数据段移到SDRAM中,因为只要在SDRAM里面,数据段的变量才能被写操作,把程序从一个位置移动到另一个位置,把这个过程就称为'''重定位'''。 前面的例子,我们只是重定位了数据段,这里我们再尝试重定位整个代码。 先梳理下把整个程序复制到SDRAM需要哪些技术细节:<br> 1. 把程序从Flash复制到运行地址,链接脚本中就要指定运行地址为SDRAM地址;<br> 2. 编译链接生成的bin文件,需要在SDRAM地址上运行,但上电后却必须先在0地址运行,这就要求重定位之前的代码与位置无关(是位置无关码);<br> 参考Uboot修改链接脚本: <syntaxhighlight lang="c" > SECTIONS { . = 0x30000000; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(.COMMON) } _end = .; } </syntaxhighlight> 现在我们写的这个链接脚本,称为'''一体式'''链接脚本,对比前面的'''分体式'''链接脚本区别在于代码段和数据段的存放位置是否是分开的。 例如现在的一体式链接脚本的代码段后面依次就是只读数据段、数据段、bss段,都是连续在一起的。 分体式链接脚本则是代码段、只读数据段,中间相关很远之后才是数据段、bss段。 我们以后的代码更多的采用'''一体式'''链接脚本,原因如下:<br> 1. 分体式链接脚本适合单片机,单片机自带有flash,不需要再将代码复制到内存占用空间。而我们的嵌入式系统内存非常大,没必要节省这点空间,并且有些嵌入式系统没有Nor Flash等可以直接运行代码的Flash,就需要从Nand Flash或者SD卡复制整个代码到内存;<br> 2. JTAG等调试器一般只支持一体式链接脚本; 修改start.S段 <syntaxhighlight lang="c" > /* 重定位text, rodata, data段整个程序 */ mov r1, #0 ldr r2, =_start /* 第1条指令运行时的地址 */ ldr r3, =__bss_start /* bss段的起始地址 */ cpy: ldr r4, [r1] str r4, [r2] add r1, r1, #4 add r2, r2, #4 cmp r2, r3 ble cpy /* 清除BSS段 */ ldr r1, =__bss_start ldr r2, =_end mov r3, #0 clean: str r3, [r1] add r1, r1, #4 cmp r1, r2 ble clean bl main halt: b halt </syntaxhighlight> 将修改后的代码重新编译烧写在Nor Flash上,上电运行。 对本代码的启动情况进行分析:<br> [[File:chapter13_lesson5_001.jpg|700px]] 在生成的bin文件里,代码保存的位置是0x30000000。随后烧写到NOR Flash的0地址,但代码的结构没有变化。之后再重定位到SDRAM。 查看反汇编: <syntaxhighlight lang="c" > 3000005c: eb000106 bl 30000478 <sdram_init> 30000060: e3a01000 mov r1, #0 ; 0x0 30000064: e59f204c ldr r2, [pc, #76] ; 300000b8 <.text+0xb8> 30000068: e59f304c ldr r3, [pc, #76] ; 300000bc <.text+0xbc> 这里的bl 30000478不是跳转到30000478,这个时候sdram并未初始化; 为了验证,我们做另一个实验,修改连接脚本sdram.lds, 链接地址改为0x32000478,编译,查看反汇编: 3000005c: eb000106 bl 30000478 <sdram_init> 30000060: e3a01000 mov r1, #0 ; 0x0 30000064: e59f204c ldr r2, [pc, #76] ; 300000b8 <.text+0xb8> 30000068: e59f304c ldr r3, [pc, #76] ; 300000bc <.text+0xbc> </syntaxhighlight> 可以看到现在变成了bl 30000478,但两个的机器码eb000106都是一样的,机器码一样,执行的内容肯定都是一样的。 因此这里并不是跳转到显示的地址,而是跳转到: pc + offset,这个由链接器决定。 假设程序从0x30000000执行,当前指令地址:0x3000005c ,那么就是跳到0x30000478;如果程序从0运行,当前指令地址:0x5c 调到:0x00000478 跳转到某个地址并不是由bl指令所决定,而是由当前pc值决定。反汇编显示这个值只是为了方便读代码。 重点: 反汇编文件里, B或BL 某个值,只是起到方便查看的作用,并不是真的跳转。 '''怎么写位置无关码? ''' #使用相对跳转命令 b或bl; #重定位之前,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问); #重定位之后,使用ldr pc = xxx,跳转到/runtime地址; 写位置无关码,其实就是不使用绝对地址,判断有没有使用绝对地址,除了前面的几个规则,最根本的办法看反汇编。 因此,前面的例子程序使用bl命令相对跳转,程序仍在NOR/sram执行,要想让main函数在SDRAM执行,需要修改代码: //bl main /*bl相对跳转,程序仍在NOR/sram执行*/ ldr pc, =main/*绝对跳转,跳到SDRAM*/ =第006节_重定位_清除BSS段的C函数实现 = 在前面,我们使用汇编程序来实现了重定位和清bss段,本节我们将使用C语言,实现重定位和清除bss段。 1.打开start.S把原来的汇编代码删除改为调用C函数 <syntaxhighlight lang="c" > /* 重定位text, rodata, data段整个程序 */ mov r1, #0 ldr r2, =_start /* 第1条指令运行时的地址 */ ldr r3, =__bss_start /* bss段的起始地址 */ cpy: ldr r4, [r1] str r4, [r2] add r1, r1, #4 add r2, r2, #4 cmp r2, r3 ble cpy /* 清除BSS段 */ ldr r1, =__bss_start ldr r2, =_end mov r3, #0 clean: str r3, [r1] add r1, r1, #4 cmp r1, r2 ble clean </syntaxhighlight> 改为 <syntaxhighlight lang="c" > /* 重定位text, rodata, data段整个程序 */ mov r0, #0 ldr r1, =_start /* 第1条指令运行时的地址 */ ldr r2, =__bss_start /* bss段的起始地址 */ sub r2, r2, r1 /*长度*/ bl copy2sdram /* src, dest, len */ /* 清除BSS段 */ ldr r0, =__bss_start ldr r1, =_end bl clean_bss /* start, end */ </syntaxhighlight> 2. 在init.c 实现如上两个C函数 <syntaxhighlight lang="c" > void copy2sdram(volatile unsigned int *src, volatile unsigned int *dest, unsigned int len) /* src, dest, len */ { unsigned int i = 0; while (i < len) { *dest++ = *src++; i += 4; } } void clean_bss(volatile unsigned int *start, volatile unsigned int *end) /* start, end */ { while (start <= end) { *start++ = 0; } } </syntaxhighlight> 汇编中,为C语言传入的参数,依次就是R1、R2、R3。 编译,烧写运行没有问题。 我们假设不想汇编传入参数,而是C语言直接取参数。 1. 修改start.S 跳转到C函数不需要任何参数 <syntaxhighlight lang="c" > bl sdram_init //bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */ /* 重定位text, rodata, data段整个程序 */ bl copy2sdram /* 清除BSS段 */ bl clean_bss </syntaxhighlight> 2. 修改链接脚本,让__code_start 等于当前地址,也就是这里的0x30000000 <syntaxhighlight lang="c" > SECTIONS { . = 0x30000000; __code_start = .; //定义__code_start地址位当前地址 . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(.COMMON) } _end = .; } </syntaxhighlight> 3.修改init.c 用函数来获取参数 <syntaxhighlight lang="c" > void copy2sdram(void) { /* 要从lds文件中获得 __code_start, __bss_start * 然后从0地址把数据复制到__code_start */ extern int __code_start, __bss_start;//声明外部变量 volatile unsigned int *dest = (volatile unsigned int *)&__code_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_start; volatile unsigned int *src = (volatile unsigned int *)0; while (dest < end) { *dest++ = *src++; } } void clean_bss(void) { /* 要从lds文件中获得 __bss_start, _end */ extern int _end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&_end; while (start <= end) { *start++ = 0; } } </syntaxhighlight> 编译烧写运行 ,没有问题。 总结: C函数怎么使用lds文件总的变量abc? #在C函数中声明改变量为extern外部变量类型,比如:<code>extern int abc;</code> #使用时,要取址,比如:<code>int *p = &abc;//p的只即为lds文件中abc的值</code> 汇编文件中可以直接使用外部链接脚本中的变量,但C函数中要加上取址符号。 解释一下原因: C函数中,定义一个全局变量<code>int g_i;</code>,程序中必然有4字节的空间留出来给这个变量<code>g_i</code>。 假如我们的lds文件中有很多变量 lds{ a1 = ; a2 = ; a3 = ; ... } 如果我们C程序只用到几个变量,完全没必要全部存储lds里面的所有变量,C程序是不保存lds中的变量的。 对于万一要用到的变量,编译程序时,有一个symbol table符号表:<br> [[File:chapter13_lesson6_001.jpg|400px]] 如何使用symbol table符号表? #对于常规变量g_i,得到里面的值,使用&g_i得到addr; #为了保持代码的一致,对于lds中的a1,使用&a1得到里面的值; 这只是一个编译器的小技巧,不用深究。 结论: #C程序中不保存lds文件中的变量,lds再大也不影响; #借助symbol table保存lds的变量,使用时加上"&"得到它的值,链接脚本的变量要在C程序中声明为外部变量,任何类型都可以; ='''《《所有章节目录》》'''= <categorytree mode=all background*color:white;">ARM裸机加强版</categorytree> [[Category:ARM裸机加强版 ]]
返回至
第013课 代码重定位
。
导航
导航
WIKI首页
官方店铺
资料下载
交流社区
所有页面
所有产品
MPU-Linux开发板
MCU-单片机开发板
Linux开发系列视频
单片机开发系列视频
所有模块配件
Wiki工具
Wiki工具
特殊页面
页面工具
页面工具
用户页面工具
更多
链入页面
相关更改
页面信息
页面日志