历史上的今天
今天是:2025年04月19日(星期六)
2020年04月19日 | s3c2440学习之路-011代码重定位
2020-04-19 来源:eefocus
1 基本原理
承接上1篇博客 s3c2440学习之路-010 sdram, sdram已经初始化完毕,现在可以正式的发挥SDRAM的价值了。(SDRAM 是可以随意读写的,后续的代码都会放到上面来运行)
1.1 程序段的划分
一个程序编译后,会有代码段、数据段、只读数据段、bss段和注释段

这里主要讲一下bss段为什么可以减小bin文件的大小。bbs主要记录未初始化和初始化为0的全局(static 修饰的局部)变量的位置,而不会记录其具体数据,因为这部分的数值会默认设置为0。
这里看2个小程序
test1.c
#include char a[1000]; int main(int argc, char *argv) { return 0; } test2.c #include char a[1000] = {1}; int main(int argc, char *argv) { return 0; } test1.c 定一个了1个char[1000]的数组,没有对其进行初始化,保存在bss段,最后编译出来的bin档大小为8756B test2.c同样定义了1个chart1000]的数组,但是对其进行了初始化,保存在data段,最后编译出来的bin档大小为9592B 因为test1.c的数据保存在bss段,而bss只会记录其位置,所以不需要很大的存储空间。而test2.c需要把整个数组的值全部保存在数据段,所以至少需要比test1.c的bin大1000Byte。 这里要注意,减小的只是bin文件的大小,代码实际运行起来时消耗的内存大小是一样的。 1.2 为什么要需要重定位代码 当使用nand启动时,因为2440的特性,只能运行前4K的代码(前4K会自动拷贝到内部的sram里),因此想运行1个100K+ 的uboot.bin是不可以能的,所以需要把代码重定位到外部的SDRAM里。 当使用nor启动时,jz2440开发板上nor为2MB,足够放一般的程序。但是nor 可以随意读无法随意写,这意味着全局变量是不可修改的。因为修改全局变量需要把数值重新写到nor 里面,而nor 是不可随意写的,所以写数值会失败。因此也需要把代码重定位到外部的SDRAM里。(局部变量是可以修改的,因为局部变量是放在栈里面,栈一般会设置在2440内部的sram) 1.3 如何实现重定位 实现重定位很简单,第1把代码全部拷贝上SDRAM上,第2把代码跳到SDRAM上运行,也就是修改PC的值。 要把代码拷贝到SDRAM上,就需要弄清楚从哪里开始拷,拷多大,有什么顺序或规则吗。 1)代码是放在nor /nand里面,所以是从nor/nand开始拷。而烧写代码时都是从nor/nand的0地址开始的,所以要从nor/nand的0地址开始拷。 2)程序分成了代码段、数据段、只读数据段、bss段和注释段,因此要把这些数据全部拷贝到SDRAM里面,但是具体的大小是多少,后面会简单的讲一下lsd链接脚本,通过这个脚本可以知道程序的大小。 3)顺序可以通过lds链接脚本来设置,不过一般的顺序为代码段->只读数据段->数据段->bss段。 1.4 lds链接脚本 因为对lsd链接脚本没有做深入的研究,所以只懂得简单的运用,详细的使用请看 http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.es.html 官方说明文档。这里只对自己使用到的lds链接脚本做注释。 test.lds SECTIONS { // .代表当前的位置, 把当前的位置设置为0x30000000, 0x30000000就是SDRAM的起始地址 . = 0x30000000 ; // 4字节对齐 . = ALIGN(4) ; //定义了一个变量 __copy_code_start = 当前的位置,也就是0x30000000 __copy_code_start = . ; //代码段 .text : { //先存放start.o的代码段,因为start.o是第1个运行的文件 start.o //*表示所有,所有文件的代码段 *(.text) } . = ALIGN(4) ; //所有文件的只读数据段 .rodata : { *(.rodata) } . = ALIGN(4) ; //所有文件的数据段 .data : { *(.data) } . = ALIGN(4) ; //定义变量__bss_start=当前位置,当前的位置等于0x30000000+所有文件的代码段+所有文件的只读数据段+所有文件的数据段 __bss_start = . ; //文件文件的bss段和注释段 .bss : { *(.bss) *(.COMMON) } //定义变量__end = 当前的位置,当前的位置等于__bss_start+所有文件的bss段和注释段 __end = . ; } 通过test.lds脚本可以知道,程序的存储顺序为:start.o的text->text->rodata->data->bss。总共定义了3个变量,__copy_code_start、__bss_start 、__end。 在编译的时候,使用-T就可以使用链接脚本了。因为链接脚本第1句“ . = 0x30000000 ;”,把当前的位置设置为0x30000000,所以反汇编看到dis文件的起始位置就是0x30000000。当你把生成的bin档烧录到nor/nand 里面时,还是会从nor/nand的0位置开始拷贝进去。复位后PC=0, 程序依旧是从0地址开始运行。因此dis文件中的0x30000000不会对代码产生任何影响。其中的__copy_code_start、__bss_start 、__end才是后面编程需要用到的。 Makefile test.dis 2 源码讲解 2.1 主要流程 start.s /* 初始化外部的sdram */ bl bank6_sdram_init /* 将代码从nor 拷贝到 sdram */ /* copy .data .rodata. .text to SDRAM */ bl copy2sdram /* 清空bss段 */ /* clear bss segment */ bl clear_bss /* 初始化串口 */ bl uart0_init /* 打印位置的数值 */ bl print_positon /* 跳到main去执行 */ ldr pc, =main /* abs jump to main */ start.s 前面会做一些其他的初始化,不过全部略去了,只看最重要的几个部分。SDRAM的初始化在上一篇博客讲了,这里就不做介绍了。 2.2 text、data、rodata段的拷贝 init.c void copy2sdram(void) { extern int __copy_code_start, __bss_start; volatile unsigned int *src = (unsigned int *)0; volatile unsigned int *dst = (unsigned int *)&__copy_code_start; volatile unsigned int *end = (unsigned int *)&__bss_start; while(dst < end) { *dst++ = *src++; } } __copy_code_start, __bss_start 是链接脚本里面的变量,引用外部变量,所以需要extern。现在的程序只支持nor 的重定位,而2440可以直接访问nor, 所以src=0 表示nor的0地址。&__copy_code_start表示取变量__copy_code_start的数值,也是0x30000000。这里的表达跟C语言不一样,C语言是取变量的地址,而__copy_code_start是链接脚本的变量,因此使用起来也不一样。可以把这个当做一种规则来记忆。同理,end = &__bss_start 也是取__bss_start的数值0x30001000。 这段代码的主要作用就是把nor 0地址的数据拷贝到0x30000000的地址去,长度为end - dst = &__bss_start - &__copy_code_start = 0x30001000 - 0x30000000 = 0x1000。这里只拷贝了text、data、rodata,bss段没有拷贝,接下来就处理bss段。(0x30000000就是SDRAM的起始地址,拷贝到这个地址就是拷贝到SDRAM里面,不理解的麻烦看看上一篇博客) 2.3 清空bss段 init.c void clear_bss(void) { extern int __bss_start, __end; volatile unsigned int *src = (unsigned int *)&__bss_start; volatile unsigned int *end = (unsigned int *)&__end; while(src <= end) { *src ++ = 0; } } 同理,引用 __bss_start, __end链接脚本变量需要使用extern。这里的&__bss_start 和 &__end也是获取其数值。src = &__bss_start = 0x30001000, end = &__end = 30001008。这段代码的主要作用就是把0x30001000~0x30001008上的数据清空。而拷贝代码时不要拷贝bss段,因此这段数据都是0,所以只需要记录bss的起始位置和终止位置,然后将其清空即可。这也照应了前面说的,bss段可以减小bin文件的大小。 2.4 打印__copy_code_start, __bss_start, __end init.c void print_positon(void) { extern int __copy_code_start, __bss_start, __end; volatile unsigned int *code_start = (unsigned int *)&__copy_code_start; volatile unsigned int *bss_start= (unsigned int *)&__bss_start; volatile unsigned int *end= (unsigned int *)&__end; printf("code_start:%xn", code_start); printf("bss_start:%xn", bss_start); printf("end:%xn", end); } print_positon 的作用就是打印__copy_code_start、__bss_start、__end的数值,因此需要先调用bl uart0_init来初始化串口。 2.5 程序跳转 start.s 最后执行 ldr pc, =main 将pc赋值成main的数值。通过dis文件可以看到, ldr pc, =main对应的语句为ldr pc, [pc, #16]。pc + 16 = 0x30000088, 0x30000088位置上的数值为0x30000af8,刚好main的数值就是0x30000af8。因此ldr pc, =main就等于pc=0x30000af8,此时程序就开始运行在SDRAM上了。 这里要特别说明,在执行ldr pc, =main之前,程序都是运行在nor上的,所以程序的地址不可能超过2M。虽然看到程序的起始地址是0x30000000,不过实际PC的值都需要减去0x30000000。虽然显示的地址为0x30000000+,不过程序是从nor的0地址开始烧录。复位后程序从nor 0地址开始取指令,也是对应的0x30000000。因此PC是从0开始往上增加,而不是从0x30000000开始往上增加,直到执行ldr pc, =main之后PC的值才真正的等于dis文件显示的。 2.6 测试 main.c #include "uart.h" //#include "key.h" //#include "led.h" #include "delay.h" #include "my_printf.h" #include "init.h" //2018.9.1 测试bss 段的初始数据是否是0 char _g_char1 = 'A'; char _g_char2 = 'a'; int _g_a; int _g_b = 0; const char _g_const_char = 'B'; int main(int argc, char *argv[]) { uart0_init(); printf("_g_a:%dn", _g_a); printf("_g_b:%dn", _g_b); while(1) { printf("%c", _g_char1); _g_char1 ++; printf("%c ", _g_char2); _g_char2 ++; delay(500000); } return 0 } 程序定义了_g_char1和 _g_char2 两个全局变量,如果程序可以正常的跑到main函数并且这个2个变量的数值可以正常的增加,说明重定位成功。(目前只能测试nor) 2.7 总结 看完后需要懂得以下几个问题才算是弄懂了代码重定位: 为什么需要代码重定位? bss为什么可以减小bin文件的大小 代码拷贝时为什么只拷贝text、rodata、data而不用拷贝bss段 为什么dis文件中显示程序的起始位置是0x30000000,而实际的PC值是从0开始。 为什么执行 ldr pc, =main 程序才真正的跳到SDRAM去执行了? 





下一篇:s3c2440之代码重定位
史海拾趣
|
发明器,让人们专门搞发明的设备小编记得有互联网投资皇帝之称的孙正义先生在年轻时就曾经应用了一个有趣的发明器来赞助自己搞创新,它的原理是可以将很多风马牛不相及的东西自由排列组合在一起,使你产生新的想法解决人们新的需求。成果依附这个发 ...… 查看全部问答> |
|
前提:“我们在家里安装了红外幕帘探头、门磁感应传感器、燃气泄漏报警器、烟雾报警器等各种设备,形成了一个移动视频智能系统。” 早上出门前,开启安防报警系 ...… 查看全部问答> |
|
ARM芯片上有一些管脚没有寄存器对其进行控制,在WinCE驱动程序中,请问这样的管脚应该如何操作呢?如对扩展总线进行读写控制的nROE、nRWE,还有扩展的地址线RADDR(GPA有些管脚是RADDR,可以操作,但其它的应如何赋值并操作呢?)等。 … 查看全部问答> |
|
首先,我是新手,没有接触过网卡驱动程序开发,仅看过windows上一个中间层的驱动代码。 我有些问题: ndis仅仅是windows上的东西吗? windows上开发一个驱动程序必须按照ndis吗? 网卡制造商如果仅有wi ...… 查看全部问答> |
|
评估板原先还好好的,就先前调触摸按键拆了充放电的电阻,有个脚的3.3M还没焊接回去。这段时间一直用板子的5V供电。刚刚想再看看这块板子,就出现这种情况,STVD的help里也找不到相关信息额。 STLINK是没问题的,因为我还可以用它debug。 评 ...… 查看全部问答> |
|
我板子用的是天运科技的CC2530板子。例程是sesor demo,这个例程在我原来用的板子上用的时候电脑是可以接受到数据的,但是吧这个例程下到天运的板子就是接受不到数据。协议栈我没有用他们资料里带的。还是以前用的版本,,我弄了好几天了不知道怎么 ...… 查看全部问答> |




