这个脚本中通过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
|