首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

ARM裸机程序研究 - 编译和链接(3)

ARM裸机程序研究 - 编译和链接(3)

这个脚本中通过SECTIONS命令定义了一个节,节的名字为.text,而节的内容,就是冒号后面的,*(.init)表示所有输入文件中的.init节,*(.text)表示所有输入文件的.text节。在这里我们只有一个.init节和一个.text节,这样就可以保证,在输出的.text节中,包含有输入文件的.init节和.text节,而且.init节在最前面。    将这个文件保存为ld.ld,然后就可以调用链接器来链接所有的文件:
    arm-linux-ld -T ld.ld init.o led.o -o led
    生成的led ELF文件,其中就包含有我们需要的.text节,可以通过arm-linux-objdump查看。但是,对于ARM芯片来说,它不认识ELF文件,我们还要想办法将这个led文件的.text节抠出来。这时候需要用到另一个命令:
    arm-linux-objcopy -j ".text" -O binary led led.bin
    该命令可以将led中的.text文件copy出来,生成led.bin文件。我生成的led.bin大小为148字节,不同的编译器可能产生的大小有点不一样。这个led.bin就是我们最终需要的,能够下载到内存0开始地方的代码。如果想反汇编这个led.bin文件,还是可以用arm-linux-objdump。但是因为led.bin已经不是ELF文件,arm-linux-objdump没法知道这个文件中哪里开始是代码,是什么类型的代码。需要通过命令行来告诉它:
    arm-linux-objdump -b binary -m arm -D led.bin
    上面的命令行参数就是告诉arm-linux-objdmp, led.bin是一个二进制文件,包含的是arm代码,请反汇编所有的内容。
    接下来,通过openocd,用jtag连接上开发板,就可以准备运行代码了。下面是在openocd中执行命令的过程:
   Open On-Chip Debugger
> reset halt
JTAG tap: s3c2440.cpu tap/device found: 0x0032409d (mfg: 0x04e, part: 0x0324, ver: 0x0)
target state: halted
target halted in ARM state due to debug-request, current mode: Supervisor
cpsr: 0x200000d3 pc: 0x00000000
MMU: disabled, D-Cache: disabled, I-Cache: disabled
NOTE! DCC downloads have not been enabled, defaulting to slow memory writes. Type 'help dcc'.
NOTE! Severe performance degradation without fast memory access enabled. Type 'help fast'.
> load_image led.bin 0 bin
148 bytes written at address 0x00000000
downloaded 148 bytes in 0.014759s (9.793 KiB/s)
> resume
>

    可以看到,在输入resume命令后,开发板上的LED就点亮了。如果感兴趣,还可以在下载代码后,通过step命令单步执行,观察这个小程序的执行过程。更多的命令可以查看openocd的使用手册。

    虽然上面这个小程序已经可以运行了,但是还有一个问题忽略了。在这段程序中,目前还只能定义局部变量,不能定义全局变量。因为局部变量是通过调整栈指针,在栈上面分配的。我们已经通过一小段汇编设置好了栈指针,因此局部变量是没有问题。但是全局变量呢,编译器怎么知道全局变量放在哪,怎么可以访问到呢?
    全局变量有两种,初始化的和未初始化的。对于编译和链接过程来说,如果是初始化的全局变量,那么在生成的可执行文件中,一定要有该变量的值。这样当可执行文件被加载到内存时,这些值在内存中能被访问到。而未初始化的变量,则不需要在可执行文件中未其分配空间,因为本来就没有值可以保存。但是在运行时,要为这些变量分配空间,使代码能够访问他们。还有一种局部静态变量,其实在内存分配上,它和全局变量是一样的,只是在语法上,它的作用域和局部变量一样。代码经过编译后,在汇编代码的层面上,它就和全局变量没有任何区别了。
   还记得上面在反汇编一个目标文件的时候,看到了.data节和.bss节。其中.data节就是存放初始化了的全局变量的,而.bss节存放的是未初始化的全局变量。比如下面这个例子:
[plain] view plaincopy

  • int a=1;  
  • int b=2;  
  • int c;  
  • int main()  
  • {  
  •     c = 3;  
  •     return a+b+c;  
  • }  


    保存为test.c并编译:
    arm-linux-gcc -g -c test.c -o test.o
   加上-g目的是使得输出文件中包含调试信息,便于反汇编时查看。首先看看test.o中节的信息:
    arm-linux-objdump -x test.o
    得到的结果比较长,下面只是一部分:
[plain] view plaincopy

  • Sections:  
  • Idx Name          Size      VMA       LMA       File off  Algn  
  •   0 .text         00000050  00000000  00000000  00000034  2**2  
  •                   CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE  
  •   1 .data         00000008  00000000  00000000  00000084  2**2  
  •                   CONTENTS, ALLOC, LOAD, DATA  
  •   2 .bss          00000000  00000000  00000000  0000008c  2**0  
  •                   ALLOC  
  •   3 .debug_abbrev 00000045  00000000  00000000  0000008c  2**0  
  •                   CONTENTS, READONLY, DEBUGGING  


结果基本还是比较符合预期的。.data节大小为8字节,因为两个初始化的全局变量。.bss节大小为0。进一步反汇编这段代码,可以看看这些变量是如何被访问的:
    arm-linux-objdump -S -d test.o
[plain] view plaincopy

  • Disassembly of section .text:  

  • 00000000 <main>:  
  • int a = 1;  
  • int b = 2;  
  • int c;  
  • int main()  
  • {  
  •    0:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
  •    4:   e28db000    add fp, sp, #0  
  •     c = 3;  
  •    8:   e59f3034    ldr r3, [pc, #52]   ; 44 <main+0x44>  
  •    c:   e3a02003    mov r2, #3  
  •   10:   e5832000    str r2, [r3]  
  •     return a+b+c;  
  •   14:   e59f302c    ldr r3, [pc, #44]   ; 48 <main+0x48>  
  •   18:   e5932000    ldr r2, [r3]  
  •   1c:   e59f3028    ldr r3, [pc, #40]   ; 4c <main+0x4c>  
  •   20:   e5933000    ldr r3, [r3]  
  •   24:   e0822003    add r2, r2, r3  
  •   28:   e59f3014    ldr r3, [pc, #20]   ; 44 <main+0x44>  
  •   2c:   e5933000    ldr r3, [r3]  
  •   30:   e0823003    add r3, r2, r3  
  • }  
  •   34:   e1a00003    mov r0, r3  
  •   38:   e28bd000    add sp, fp, #0  
  •   3c:   e8bd0800    pop {fp}  
  •   40:   e12fff1e    bx  lr  


    这里有些比较有趣的地方,这些变量都是通过间接访问得到的。比如在 “c = 3"的过程中,汇编显示从<main+0x44>这个地方读入一个值,然后把3存入以这个值为地址的变量。也就是说<main+0x44>这个地方保存的还不是变量c,而是c的地址。同样,后面的<main+0x48> <main+0x4c>保存的是a和 b的地址。那么这些地址到底是多少,在上面的反汇编中并没有显示出来,因为他们不是代码,不能反汇编。但是也并不难找到。从section的信息来看,.text节大小为0x50(上面的反汇编代码只有0x44个字节大小,所以后面的三个地址也是属于.text的节的)。起始于文件偏移0x34的地方,而main在.text节中偏移为0的地方,所以main在文件中的偏移也是0x34了,那么<main+0x44>在文件中的偏移就是0x78了,而另两个则是0x7c和0x80。知道了位置,则可以用用下面的命令以16进制方式查看文件:
    hexdump -s 0x78 -n 12 -Cv test.o

出来的结果居然都是0……a, b, c的地址都是0?这里,还有一点点关于重定位的知识。


在编译阶段,所有节的位置都还是不确定的,可以看到节的VMA都是0。编译器还不知道节的位置,也就不知道那些变量的位置,自然也无法生成准确的代码来引用那些变量。这些都要等到链接器来决定。链接器会安排好所有节的位置,然后修改上面的这些0,用真实的地址来替换。在刚生成的test.o中,包含有重定位信息,连接器就是根据这些信息来完成重定位。

   下面就是arm-linux-objdump -x输出中的重定位信息,这里只包含了.text节


[plain] view plaincopy

  • RELOCATION RECORDS FOR [.text]:  
  • OFFSET   TYPE              VALUE   
  • 00000044 R_ARM_ABS32       c  
  • 00000048 R_ARM_ABS32       a  
  • 0000004c R_ARM_ABS32       b  


   可以清楚的看到,在.text节偏移为0x44, 0x48, 0x4c的地方,分别保存有变量c, a, b的地址(重定位类型为R_ARM_ABS32)。

可以尝试链接刚才的程序,看看链接后是什么样子:

   arm-linux-ld test.o -e main -o test
   arm-linux-objdump -x test
[plain] view plaincopy

  • Sections:  
  • Idx Name          Size      VMA       LMA       File off  Algn  
  •   0 .text         00000050  00008094  00008094  00000094  2**2  
  •                   CONTENTS, ALLOC, LOAD, READONLY, CODE  
  •   1 .data         00000008  000100e4  000100e4  000000e4  2**2  
  •                   CONTENTS, ALLOC, LOAD, DATA  
  •   2 .bss          00000004  000100ec  000100ec  000000ec  2**2  
  •                   ALLOC  
  •   3 .comment      00000011  00000000  00000000  000000ec  2**0  
  •                   CONTENTS, READONLY  
继承事业,薪火相传
返回列表