当前位置 博文首页 > lyndon:纯手工打造一个 a.out (不使用编译器,就硬写)

    lyndon:纯手工打造一个 a.out (不使用编译器,就硬写)

    作者:[db:作者] 时间:2021-08-23 16:21

    目标驱动型

    目标驱动型,是人的性格的一种描述,它的核心是目标,先有目标然后驱使人们做出行动。
    下面我就以 a.out 为例,研究一个 C 语言编译链接的问题,同时感受一下目标驱动型学习方法的魅力。
    大概介绍下思路

    1. 先使用 16 进制手撸的形式造出一个可执行的 a.out
      这一步,在目标驱动型学习步骤里面至为重要,就像,我们第一次拿到 51 单片机,先不管硬件工作原理是什么,也不管软件工作原理是什么,先烧进去一个跑马灯程序跑一跑,我滴个乖乖,太TM神奇了吧,我要好好研究它是什么原理,瞬间学习劲头就上来了,学习也就事半功倍,也不觉得累。有点类似于记叙文倒叙的手法,先看到目标,给你好奇心,给你动力去继续搞下去。
    2. 解释 16 进制的内容
    3. 如何使用汇编编译出 a.out
    4. 汇编程序解释
    5. 如何使用 C 语言编译出 a.out
    6. C 语言程序解释
    7. 编译器原理
    8. 连接器原理
    9. 程序运行原理

    总之,这是一种倒着的学习路线,目标倒逼原理。

    话不多说,开搞!

    先上 16 进制内容

    7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 02 00 3e 00 01 00 00 00 78 00 40 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 38 00 01 00 00 00 00 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 a9 00 00 00 00 00 00 00 a9 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 b8 01 00 00 00 bf 01 00 00 00 48 be 9d 00 40 00 00 00 00 00 ba 0d 00 00 00 0f 05 b8 3c 00 00 00 48 31 ff 0f 05 68 65 6c 6c 6f 20 77 6f 72 6c 64 0a
    

    这就是 a.out 的 16 进制全部内容了,总共 169 字节,我们把以上内容存到 a.out 中,看看能不能运行。

    bless 工具

    我使用的环境是 ubuntu,我们需要一个可以方便编辑 16 进制文件的软件,这里我使用的是 bless
    在这里插入图片描述
    安装命令

    sudo apt install bless
    

    制作 a.out

    我们把上面的 16 进制内容复制到 bless 编辑器中。(可能是 markdown 格式的问题,直接从博文中拷贝粘贴,可能会出现被 bless 识别成字符的形式,其实我们这里是 16 进制形式。因此建议先将内容拷贝到 Notepad++ 中,去掉最后面的换行,再粘贴到 bless 中。)
    在这里插入图片描述
    另存为 a.out
    在这里插入图片描述

    运行

    增加可执行权限

    $ chmod +x a.out
    

    运行

    $ ./a.out 
    hello world
    

    👏👏👏 太神奇了,可以运行哎!到底什么原理呢?

    16 进制内容解释

    前 64 字节为 ELF 头,接着是 56 字节的程序头,再接着是 37 字节的代码,最后是 12 字节的字符串数据 “hello world”。

    # ELF Header
    00:   7f 45 4c 46 02 01 01 00 # e_ident
    08:   00 00 00 00 00 00 00 00 # reserved
    10:   02 00 # e_type
    12:   3e 00 # e_machine
    14:   01 00 00 00 # e_version
    18:   78 00 40 00 00 00 00 00 # e_entry
    20:   40 00 00 00 00 00 00 00 # e_phoff
    28:   00 00 00 00 00 00 00 00 # e_shoff
    30:   00 00 00 00 # e_flags
    34:   40 00 # e_ehsize
    36:   38 00 # e_phentsize
    38:   01 00 # e_phnum
    3a:   00 00 # e_shentsize
    3c:   00 00 # e_shnum
    3e:   00 00 # e_shstrndx
    
    # Program Header
    40:   01 00 00 00 # p_type
    44:   05 00 00 00 # p_flags
    48:   00 00 00 00 00 00 00 00 # p_offset
    50:   00 00 40 00 00 00 00 00 # p_vaddr
    58:   00 00 40 00 00 00 00 00 # p_paddr
    60:   aa 00 00 00 00 00 00 00 # p_filesz
    68:   aa 00 00 00 00 00 00 00 # p_memsz
    70:   00 10 00 00 00 00 00 00 # p_align
    
    # Code
    78:   b8 01 00 00 00          # mov    $0x1,%eax
    7d:   bf 01 00 00 00          # mov    $0x1,%edi
    82:   48 be 9d 00 40 00 00 00 00 00    # movabs $0x40009d,%rsi
    8c:   ba 0d 00 00 00          # mov    $0xd,%edx
    91:   0f 05                   # syscall
    93:   b8 3c 00 00 00          # mov    $0x3c,%eax
    98:   48 31 ff                # xor    %rdi,%rdi
    9b:   0f 05                   # syscall
    9d:   68 65 6c 6c 6f 20 77 6f 72 6c 64 0a # "hello world\n"
    

    初见眉目

    到这里,我们已然已经看清了一个二进制可执行文件(ELF)里面到底都有什么内容,这就是纯手工打造 a.out 带来的好处,接下来,留给我们继续探索的内容还有:

    • 如何使用汇编编译出上述可执行内容
    • 如何使用 C 语言编译出上述可执行内容
    • 编译器的工作原理
    • 链接器的工作原理
    • 程序是怎么跑起来的(这个问题貌似都写成了一本书)
      看吧,从一个小目标出发,我们也能够顺藤摸瓜式地学到整个路线中的众多内容,如果不是被这个小目标驱使着,如果是自上而下地研究的话,可能搞着搞着就放弃了,这就是自下而上学习的魅力,目标倒逼原理。

    未完待续

    后面有时间我们继续探索上述遗留问题。。。

    cs