GCC链接时外部符号解析的问题——extern关键字

一、问题简述:
  这段时间在Linux下用GCC做ARM编程。有一段程序,是调用nand_read_ll()函数从NAND Flash读取程序本身,并拷贝到SDRAM中,其中用到了程序的连接地址__ro_start,这个符号是在head.S中定义的,代表程序的其实地址,在C程序中extern这个符号,编译、连接都通过,但载入目标机运行时却发生死机,无论如何修改代码都能解决,后来看了反汇编才明白,其实是对extern关键字理解的问题...

二、错误再现:
  编写一个汇编程序head.S,一个C程序linkss.c,总共两个文件。

head.S:
  1. /******************************************
  2. * A simple program for linking test.
  3. *    This program tests what the linker does,
  4. * how an external symbol is resolved, and 
  5. * what the keyword "extern" actually means.
  6. *******************************************/
  7. .extern main
  8. .global _start
  9. // export what we need in C file
  10. .global __ro_start
  11. .global __ro_end
  12. .text
  13. _start:
  14. __ro_start:
  15.     // disable watch-dog
  16.     mov r1, #0x53000000
  17.     mov r2, #0x0
  18.     str r2, [r1]
  19.     // initialize stack pointer
  20.     mov sp, #0x32000000
  21.     mov r0, #0
  22.     bl main
  23.     1:  b 1b  // halt
  24. __ro_end:


linkss.c:
  1. // import symbols in head.S
  2. extern void * __ro_start;
  3. extern void * __ro_end;
  4. int main()
  5. {
  6.     int size;
  7.     size = (char *)__ro_end - (char *)__ro_start;
  8.     // __ro_start is expected to be 0x30000000, and __ro_end to be 0x3000001c (0x30000000+ 7*4)
  9.     return size;
  10.     // return the size, just for test
  11. }


  编译: 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
  推荐使用方法二
--------------------------------

  (原创文章,转载时请注明本文网址)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值