这段时间在Linux下用GCC做ARM编程。有一段程序,是调用nand_read_ll()函数从NAND Flash读取程序本身,并拷贝到SDRAM中,其中用到了程序的连接地址__ro_start,这个符号是在head.S中定义的,代表程序的其实地址,在C程序中extern这个符号,编译、连接都通过,但载入目标机运行时却发生死机,无论如何修改代码都能解决,后来看了反汇编才明白,其实是对extern关键字理解的问题...
二、错误再现:
编写一个汇编程序head.S,一个C程序linkss.c,总共两个文件。
head.S:
- /******************************************
- * A simple program for linking test.
- * This program tests what the linker does,
- * how an external symbol is resolved, and
- * what the keyword "extern" actually means.
- *******************************************/
- .extern main
- .global _start
- // export what we need in C file
- .global __ro_start
- .global __ro_end
- .text
- _start:
- __ro_start:
- // disable watch-dog
- mov r1, #0x53000000
- mov r2, #0x0
- str r2, [r1]
- // initialize stack pointer
- mov sp, #0x32000000
- mov r0, #0
- bl main
- 1: b 1b // halt
- __ro_end:
linkss.c:
- // import symbols in head.S
- extern void * __ro_start;
- extern void * __ro_end;
- int main()
- {
- int size;
- size = (char *)__ro_end - (char *)__ro_start;
- // __ro_start is expected to be 0x30000000, and __ro_end to be 0x3000001c (0x30000000+ 7*4)
- return size;
- // return the size, just for test
- }
编译: arm-linux-gcc -c head.S linkss.c
链接,入口地址0x30000000:
arm-linux-ld -Ttext 0x30000000 head.o linkss.o -o linkss
反汇编看结果,输出到linkss.txt: arm-linux-objdump -D -m arm linkss>linkss.txt
反汇编代码如下:
linkss: ???? elf32-littlearm
??? .text ?:
30000000 <__ro_start>:
30000000: e3a01453 mov r1, #1392508928 ; 0x53000000
30000004: e3a02000 mov r2, #0 ; 0x0
30000008: e5812000 str r2, [r1]
3000000c: e3a0d432 mov sp, #838860800 ; 0x32000000
30000010: e3a00000 mov r0, #0 ; 0x0
30000014: eb000000 bl 3000001c <main>
30000018: eafffffe b 30000018 <__ro_start+0x18>
3000001c <main>:
3000001c: e1a0c00d mov ip, sp
30000020: e92dd800 stmdb sp!, {fp, ip, lr, pc}
30000024: e24cb004 sub fp, ip, #4 ; 0x4
30000028: e24dd004 sub sp, sp, #4 ; 0x4
3000002c: e59f301c ldr r3, [pc, #1c] ; 30000050 <main+0x34>
30000030: e59f201c ldr r2, [pc, #1c] ; 30000054 <main+0x38>
30000034: e5933000 ldr r3, [r3]
30000038: e5922000 ldr r2, [r2]
3000003c: e0623003 rsb r3, r2, r3
30000040: e50b3010 str r3, [fp, -#16]
30000044: e51b3010 ldr r3, [fp, -#16]
30000048: e1a00003 mov r0, r3
3000004c: ea000001 b 30000058 <main+0x3c>
30000050: 3000001c andcc r0, r0, ip, lsl r0
30000054: 30000000 andcc r0, r0, r0
30000058: e91ba800 ldmdb fp, {fp, sp, pc}
??? .data ?:
三、问题分析:
仔细看反汇编码,0x30000030位置的 ldr r2, [pc, #1c] 从0x30000054取值,这个值是0x30000000,恰好是__ro_start所代表的地址,但再看0x30000038位置的 ldr r2, [r2] ,程序又利用这个值,从这个值所指的地址取回了一个数——问题就在这里!! 我们要的是0x30000000这个地址,然而程序却取回了这个地址处的指令(即程序的第一条指令)! 起初我还怀疑编译器有bug,不过仔细想想,发现是我对extern关键字的理解有误。
C程序中的 extern void * __ro_start ,这里看似是声明了一个表示地址的外部符号(即指针),但本质上还是变量,跟 unsigned int 类型的存储是一样的,还是32位无符号整型变量,而变量必须依赖一个内存地址来存放,因此编译器从__ro_start符号所代表的地址取回了这个变量。
总结:
编译器认为extern的符号代表变量的内存地址,所以获取该变量的值需要从这个地址取数。当然,针对上面的程序解决办法还是有的。
方法一: 在C程序里使用 & 符号获取所需的地址,即 &__ro_start 和 &__ro_end;
方法二: 在汇编程序的地址里挖一块地儿,把这两个值存进去,然后在C语言里使用这两个值所在地址的标识符号来取这两个值,即:
head.S最后加上:
__ro_start_addr: .word __ro_start
__ro_end_addr: .word __ro_end
然后C程序里将__ro_start和__ro_end分别改为__ro_start_addr和__ro_end_addr
推荐使用方法二
--------------------------------
(原创文章,转载时请注明本文网址)