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

1. Linux下的二进制可执行文件。

    如果世界很简单,那么二进制可执行文件也应该很简单,只包括CPU要执行的指令就可以了。可惜,世界并不简单……。Linux下的二进制可执行文件(以下简称可执行文件),也并不是只包括了指令,还包括了很多其他的信息,比如,执行需要的数据,重定位信息,调试信息,动态链接信息,等等。 所有这些信息都按照一个预定的格式组织在一个可执行文件里面。Linux下叫ELF可执行文件。

    举一个最简单的例子,假设有下面这个程序:

    int main()

    {

         return 0;

    }

    这个连“Hello World”都不能打印的程序,自然是什么都做不了。当然,如果只是把这个文件保存为文本文件,是无论如何也执行不了得。还需要两个重要的步骤:编译和链接,才能把它转换为可执行的ELF格式。

    先来看看编译,也就是把C语言翻译成机器语言的过程。很简单,用下面的命令:

    gcc -c test.c -o test.o     <假设源文件名为test.c>

    -c 参数告诉gcc,我们只需要编译这个文件,不需要连接。这样就会生成一个test.o文件。这个文件包含了上面源程序翻译后的机器指令和其他一些信息。这个test.o也属于ELF格式。如何看test.o里面的内容,可以用objdump命令:

    objdump -x test.o

    会有类似下面的输出:

test.o:     file format elf32-i386
test.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000000a  00000000  00000000  00000034  2**
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000040  2**2
                  ALLOC
  3 .comment      0000002b  00000000  00000000  00000040  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  00000000  00000000  0000006b  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 test.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack
00000000 l    d  .comment       00000000 .comment
00000000 g     F .text  0000000a main

    test.o 主要包含了文件头和节。"节“是ELF文件的重要组成部分,一个节就是某一类型的数据。objdump的-x参数会打印出test.o中所有的节,也就是上面的"Sections". 其中.text节包含了可执行代码,.data节包含了已经初始化的数据,.bss节包含了未初始化数据。其他的节先忽略掉(其实是因为我也了解不多⋯⋯)


    如果要看看test.o是不是包含源文件的编译结果, 可以将其反汇编查看。使用objdump -d 命令。 默认情况下,该命令只返回目标文件的可执行部分,在这里就是.text节。 objdump -d test.o 得到的结果如下:

test.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   b8 00 00 00 00          mov    $0x0,%eax
   8:   5d                      pop    %ebp
   9:   c3                      ret

   可以看见这里就是一些栈的操作,没有做什么事情。当然,源码里面确实也没做什么事情。这个.o文件还不能执行,还需要经过链接。通常,我们可以用gcc一步完成编译链接过程,也就是我们最常用的:

    gcc test.c -o test


    如果再次用objdump -d 反编译生成的test文件:

    objdump -d test

    额……会发现多了一堆东西。这是因为,c程序通常都是链接到c运行库的。在main函数执行前,c运行库需要初始化一些东西。这也说明,main()并不是程序的真正入口点。真正的入口点可以用objdump -f 查看test的文件头:

test:     file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482e0

start address就是开始执行的入口点, 这个地址对应反汇编中的"_start"符号。

    那么可以让程序不链接到c运行库么?当然可以,可以用ld手工链接:

    ld test.o -e main -o test

   “-e main”告诉ld链接器用main函数作为入口点。这里也可以看出,一个程序的入口函数,不一定是main,可以是任意函数。再次反汇编刚生成的可执行文件,就会发现,已经没有c运行库的代码了。

   可是,如果试着执行刚刚生成的程序,竟然会得到一个段错误……这是因为,没有了c运行库,main函数返回之后,程序执行到不确定的地方。而如果通过c运行库调用main(),返回后会到c运行库里面,会调用相关函数来结束进程。


2. 裸机程序的实现

    所谓裸机程序,也就是没有操作系统支持,芯片上电后就可以开始执行的程序,就和单片机程序一样。不知道用”裸机程序“这个名称是否合适,不过也找不到其他的名字了。

    裸机程序与上面的ELF可执行文件有什么不同,首先很明显一点,ELF文件是需要有一个解析器,或者叫装载器的, 这个装载器负责解析文件头,将其中的节都映射到进程空间,如果有重定位,要先完成重定位,如果有动态链接库,还要加载动态链接库,完成种种初始化之后,才跳转到程序的入口点开始执行程序。而所有这些,都是由OS支持的。而对于一个ARM芯片来说,他可不知道什么ELF,重定位和动态链接。ARM只知道上电后,寄存器复位到初始值,PC寄存器为0x00000000,也就是从内存地址为0的地方开始取指令执行,其它的一概不知道,也不管。

    这么说来,要弄出一个裸机程序,其实也不难,只要我们编译上面的源代码,然后想办法把它加载到内存0开始的地方就可以了。事实,也确实是这样。只是有几个小问题要先解决掉:

    1.从0

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值