怎样创建真正很小的Linux下的ELF可执行文件————X86-64 Ubuntu实践

本文介绍了如何在X86-64的Ubuntu环境下创建极小的ELF可执行文件。作者通过readelf、nm、objdump、ar和objcopy等工具,详细讲解了程序的编译过程和 ELF 文件结构的理解。参考自http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html。
摘要由CSDN通过智能技术生成

/***

***by hjs.hust

***hjs.hust@gmail.com

***2012-12-17

***/



参考:http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html 

相关软件工具:
    readelf:readelf显示一个或多个elf格式的目标文件信息,还可以反汇编(具体看手册),可通过各种参数选项来控制显示目标文件的特定信息
    nm:主要功能是列出目标文件中的符号,这样可以帮助程序员定位和分析程序和目标文件中的符号信息
    objdump:显示文件的特定信息
    ar:用于创建、修改、提取归档文件
    objcopy:转换文件格式(只能是BFD库中所描述的文件格式)    
elf定义:/usr/include/elf.h中有elf的更详细的结构定义,或者man elf
程序:HelloWorld.c
开始:
        $ readelf -a hw
    只看头部信息:        
        $ readelf -h hw

开始:

  //tiny.c

    int main(void)
    {
            return 42;
    }
执行一下:
    $ gcc -Wall tiny.c
    $ ./a.out ; echo $?    #print return result   
看下文件多大:
    $ wc -c a.out
    8655 a.out
 strip可执行文件,编译到汇编语言,不进行汇编和链接
    $ gcc -Wall -S tiny.c
    $ wc -c a.out
    6296 a.out
进行优化
    $ gcc -Wall -S -O3 tiny.c
    $ wc -c a.out
    6296 a.out        //没有变化,程序中几乎没有什么可以优化的

下面用汇编了:
    ; tiny.asm
    BITS 64
    GLOBAL main
    SECTION .text
    main:
    mov eax, 42
    ret
编译:
    $ nasm -f elf64 tiny.asm
    $ gcc -Wall -s tiny.o
    $ wc -c a.out
    6296 a.out    //天哪,有没变,难道C较之汇编得没有额外开销吗?
问题是,通过使用main()接口我们仍然引进了很大开销。链接器仍然为我们添加一个到OS的接口,真正调用main()的是那个接口。那如果我们不需要它的话该怎么做?链接器真正使用的入口点缺省是名字为_start的符号。当我们使用gcc链接时,它自动包含一个_start例程,该例程将设置argc和argv,以及其他事情,然后调用main()。那么,看看我们能否绕过这一点。定义我们自己的_start例程:
    ; tiny.asm
    BITS 64
    GLOBAL _start
    SECTION .text
    _start:
    mov eax, 42
    ret
编译:
    $ nasm -f elf64 tiny.asm
    $ gcc -Wall -s -nostartfiles  tiny.o         //注意添加这个参数
但是:
    $ ./a.out ; echo $?
    段错误 (核心已转储)
    139
错在哪里?错误在于我们把_start当作它好像是一个C函数,并且试图从它返回。实际上,它根本不是一个函数。它只是目标文件中链接器用来定位程序入口点的一个符号。当我们的程序被激活时,它被直接激活。如果我们去查看一下,将会发现栈顶上是数1,这显然不像是一个地址。事实上,栈顶上是我们程序的argc值。在这之后是argv数组的元素,包括结束时的NULL元素,接着是envp的元素。这就是全部。在栈顶上没有返回地址。那么,_start是如何退出的?它调用了exit()函数!毕竟,这就是它出现的作用。实际上,我说谎了。它真正做的是调用_exit()函数。[标准启动中_start还是调用exit()。]exit()要为进程进行某些任务的结束处理,但是这些任务将不会被启动,因为我们绕过了库的启动代码。所以我们也需要绕过库的结束代码,直接到达操作系统的结束处理。好,让我们再试一下。我们将要调用_exit(),这是一个需要一个整数参数的函数。所以我们需要做的就是把那个数压到栈上并调用该函数。(我们还需要声明_exit()为外
部)下面是我们的汇编:
    ; tiny.asm
    BITS 32
    EXTERN _exit
    GLOBAL _start
    SECTION .text
    _start:
    push dword 42
    call _exit
好吧,这在32位机里面是正确的,但到了64咋就不行了呢(返回232),不过不要紧,下面还有。
gcc还有什么别的有意思的选项吗?在文档中紧接着-nostartfiles的,很是显眼:-nostdlib
    Don't use the standard system libraries and startup files whenlinking. Only the files you specify will be passed to the linker.
    $ gcc -Wall -s -nostdlib tiny.o
    tiny.o:在函数‘_start’中:
    tiny.asm:(.text+0x3):对‘_exit’未定义的引用
    collect2: 错误: ld 返回 1
是的..._exit()毕竟是一个库函数。它必须要被填充。好吧。但是肯定,我们并不需要libc的帮助来结束一个程序,不是吗?是的,我们不需要。如果我们愿意抛弃所有可移植性要求,我们可以退出程序而不需要和任何其他东西链接。然而,首先我们需要了解如何在Linux下进行系统调用。Linux,像大多数操作系统一样,通过系统调用对它支持的程序提供基本的必需功能。这包括打开文件,读写文件句柄——当然,也包括结束一个进程。Linux系统调用接口是一条指令:int 0x80。所有的系统调用都通过这个中断进行。要进行一个系统调用,eax应当包含一个数来指明那个系统调用被调用,并且其他寄存器用于传递参数,如果有的话。如果系统调用需要一个参数,它将在ebx里;两个参数的系统调将使用ebx和ecx。类似的,edx,esi,和edi将分别
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值