纯手工打造一个 a.out (不使用编译器,就硬写)
Posted Li-Yongjun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯手工打造一个 a.out (不使用编译器,就硬写)相关的知识,希望对你有一定的参考价值。
目标驱动型
目标驱动型,是人的性格的一种描述,它的核心是目标,先有目标然后驱使人们做出行动。
下面我就以 a.out 为例,研究一个 C 语言编译链接的问题,同时感受一下目标驱动型学习方法的魅力。
大概介绍下思路
- 先使用 16 进制手撸的形式造出一个可执行的 a.out
这一步,在目标驱动型学习步骤里面至为重要,就像,我们第一次拿到 51 单片机,先不管硬件工作原理是什么,也不管软件工作原理是什么,先烧进去一个跑马灯程序跑一跑,我滴个乖乖,太TM神奇了吧,我要好好研究它是什么原理,瞬间学习劲头就上来了,学习也就事半功倍,也不觉得累。有点类似于记叙文倒叙的手法,先看到目标,给你好奇心,给你动力去继续搞下去。 - 解释 16 进制的内容
- 如何使用汇编编译出 a.out
- 汇编程序解释
- 如何使用 C 语言编译出 a.out
- C 语言程序解释
- 编译器原理
- 连接器原理
- 程序运行原理
总之,这是一种倒着的学习路线,目标倒逼原理。
话不多说,开搞!
先上 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 语言编译出上述可执行内容
- 编译器的工作原理
- 链接器的工作原理
- 程序是怎么跑起来的(这个问题貌似都写成了一本书)
看吧,从一个小目标出发,我们也能够顺藤摸瓜式地学到整个路线中的众多内容,如果不是被这个小目标驱使着,如果是自上而下地研究的话,可能搞着搞着就放弃了,这就是自下而上学习的魅力,目标倒逼原理。
未完待续
后面有时间我们继续探索上述遗留问题。。。
以上是关于纯手工打造一个 a.out (不使用编译器,就硬写)的主要内容,如果未能解决你的问题,请参考以下文章