程序编译成二进制(指令集)
计算机是如何读懂二进制的
1.计算机外置存储
硬盘:
非易失性存储器,解决了大量内容需要长久存储的问题(你可以自备多块硬盘,存储各种内容)内存:
主要解决CPU无法存储大量信息,但又要处理大量信息(硬盘里的)而出现,主要是为了提升速度,此后在内存基础上又发展出了cache。一般来说,内存存储的是近期需要的内容。
注意: 以二进制形式存储(苏联有过三进制的计算机,即计算机选用二进制性价比最高,而不是非要使用二进制)
2.计算机运算能力的承担者
CPU组成:
寄存器、运算器、控制器
CPU处理二进制的依据:
指令集架构(ISA),即最小的操作集合(比如取指令),指令是有限的,规定了CPU能执行的功能。
CPU上跑的是啥:
首先,是程序,进一步,是进程、线程,再进一步是根据代码生成的指令集,这样一条条指令的跑着。
CPU如何工作:
指令 + 操作数(程序编译成的代码最终被翻译成了一条条指令)
3. 其他设备------------即输入、输出设备
输入:
传感器、键盘...
输出:
显示屏、音响...
总结:
- 整个过程信息都是以二进制存储的、CPU处理的也是二进制,CPU依据的指令集也是二进制的(不同二进制序列对应不同指令,指令有限)、而输入输出是把二进制信息与其他格式转换。(如音频转成声波播放)。而再电子计算机里,二进制的逻辑上用01表示,物理实现上为电容的有无等等。
- 整个过程可以看作信息流的处理。本质还是数据的输入、处理与输出。 图片、音频、代码等等都是二进制存储,如何显示在显示屏幕上、能播放出来音乐。如何让音频存储为二进制等等。所以,关键在于如何处理,放在计算机中就是CPU。
程序的实现------------主要源代码与数据文件
代码与数据资源:
从项目构建的角度来说,一开始只需要主要源代码,如果有更多功能,考虑使用第三方库,由此项目里必需有这些文件。然后还可以实现其他功能操作其他类型资源,所以有存储这些资源的文件夹。后续,为了程序自动化的实现会增添更多的文件夹,这里不追溯。
代码实现了这段逻辑,最后变成了指令集在CPU上运行,但要知道图片、音频以及其他文件(如文本文件、视频文件等)是数据,它们不会被编译为指令集,而是通过在程序写入它们的地址来操作。
本质上,运行软件,就是运行这些主要源代码编译成的指令集。数据文件有不在CPU上运行,而是通过代码操作。 所以,代码也规定了可以操作的文件范围。(如代码里没有对某个文件的操作,翻译成的指令也就不会操作它,即使有这个文件,也不会处理。)
项目的结构原因
本质是主要源代码、第三方库以及数据文件,然后代码编译成指令集,操作数据文件。那为何有那么多文件夹?
本质是为了分类:文件不分类,看起来麻烦也不方便管理。所以,把源代码和库放一个文件夹、把数据文件放一个文件夹,同时,因为技术的发展,比如版本管理(git),所以又划分了一个文件夹便于操作,如此下来,项目依赖了其他软件,也就要它对应的文件夹,这就是项目管理。
程序的分发
不分发源代码的原因
- 一般用户不需要也不会,所以这种方法效率极低(毕竟你不可能希望用户用你的软件时候还要下载编译器编译吧)
- 安全角度考虑,防止自己的技术被剽窃(开源则不同,为了分享)
用户得到的软件是啥?
- 首先,不是源代码
- 主要源代码和第三方库,主要源代码会直接在开发完成后编译成针对不同OS与CPU的可执行二进制文件指令集。(即不用用户编译,点开就能用),第三方库有两种策略,也编译好和主要源代码打包在一起或者动态编译,需要时再转化为指令集。
- 数据文件直接打包放在项目文件夹里
- 以及其他的文件(如项目管理的文件、或者自动构建的配置文件)
所以,如果没有开源代码,我们得到的就是这样的一个已经处理好的项目文件。其中主要源代码已经被编译汇编完了。面对这样的东西,我们就要尝试把它(主要是由源代码生成的二进制文件,不要被无关内容干扰)从二进制文件恢复到高级语言源码。这就是软件逆向的过程。
总结
项目从开发者到用户:
项目文件(源代码、数据文件以及其他文件) ------------------>
编译 + 汇编
------------------> 项目文件(其中源代码由高级语言变为汇编语言变为二进制指令集)
反编译与反汇编
正向开发与逆向工程
1. 正向开发过程
先编译:
高级语言 ------> 汇编语言再汇编:
汇编语言 ------> 二进制文件
2. 逆向工程
先反汇编:
二进制文件 ------> 汇编语言再反编译:
汇编语言 ------> 高级语言
逆向工程两步的难度
1. 反汇编难度
机器码(如0x89 0xD8)可直接翻译回汇编指令(如MOV EAX, EBX),而汇编指令能反映基础逻辑。
所以二进制到汇编这一步当然没问题,关键是如何从汇编到源码
2. 反编译难度
编译的不可逆性导致 反编译的难度最大
原因:
- 信息丢失:编译过程中,变量名、函数名、注释等高级语言信息通常会被丢弃,反编译工具需要通过分析来推测这些信息,但结果可能不准确。
- 代码优化:编译器的优化操作(如循环展开、内联展开)会改变代码的结构和逻辑,使得反编译后的代码难以还原为原始的高级语言代码。
- 复杂特性还原:对于C++等语言,反编译需要还原类、继承、多态等复杂特性,这增加了还原的复杂性。
- 代码混淆与保护:开发者可以通过代码混淆、加密等手段增加反编译的难度。
- 工具限制:高质量的反编译工具较少,且无法保证100%的正确性
总结
反汇编简单反编译难,反汇编(Assembly)是确定的、可逆的,而反编译(Decompilation)是近似的、依赖推理的,本质上是一种"有根据的猜测"
特性 反汇编(Disassembly) 反编译(Decompilation) 输入/输出 二进制机器码 → 汇编指令 汇编指令 → 高级语言伪代码(如C) 确定性 完全可逆(1:1映射) 部分可逆(多对一关系,信息已丢失) 工具依赖 工具结果基本一致(如objdump、IDA) 工具差异大(Ghidra/Hex-Rays各有优劣) 人工干预 几乎不需要 重度依赖人工修正命名、逻辑结构