一、基础环境
VMware虚拟机
OD反编译工具
Ubuntu16
Ubuntu系统中下载:
qemu——调试C语言
nasm编辑器——sudo apt-get install nasm
bochs2.7(必须是2.7版本的,要与bochs配置文件相匹配,其他版本无效)——调试汇编、GDT、IDT、分页
二、CPU架构、指令集规范、汇编、硬编码
CPU架构决定了CPU的硬件设计,只要CPU架构相同,任何语言生成的硬编码(机器码)都是一样的。
指令集规范:指令集(ISA)是规范标准,往往用一本书或几张纸来记录描述,而CPU实现是基于指令集规范完成的源代码。比如我们可以基于x86/ARM/ RISC-V指令集,进行CPU微架构设计和实现形成源代码,并通过流片最终形成芯片产品。其中指令集规范与处理器实现的知识产权是独立的,不能混为一谈。"RISC-V是开源的"表示指令集规范是开源、开放和免费的(open and free),这与x86与ARM指令集有本质不同,但并不是指具体的CPU实现也都是开源免费的。通俗点说就是一个机器硬件的运行规范。指令集就是一个连接硬件和软件的命令的集合,为什么要有指令集呢?原因是为了保证同一款软件在不同的硬件上也能跑起来,因为每种硬件的参数条件都是不一样的,如果没有指令集,那么换一个硬件,就需要重新设计编写一次软件,不仅麻烦而且成本很大,并且升级硬件会变的非常麻烦,这就是指令集的意义。对于指令集的详细描述可见:百度安全验证
注:二进制码、硬编码、机器码都是同一个概念的不同表述。
三、MASM、NASM、ATT、ARM
汇编风格有MASM、NASM、ATT、ARM,可以理解编程风格。
CPU架构会影响汇编风格,但是操作系统不影响汇编风格,也就是说,汇编语言的风格是由硬件芯片来决定的,与软件操作系统无关。
CPU架构有两类:
1.CISC(复杂指令集)
intel芯片
2.RISC(精简指令集)
ARM芯片
Mac M1\M2芯片
汇编语言与高级语言、操作系统、硬件之间的层级关系如图所示:
java语言、Python语言 |
Java虚拟机、Python虚拟机、MySQL、Redis |
操作系统:Linux、Win |
C语言、C++ |
汇编 |
硬件:硬盘、网卡、显卡、中断控制芯片、CPU、内存条...... |
通过图示可以看出,汇编可以直接用来操作硬件设备,同时汇编也是C语言、操作系统、高级语言的底层技术。
不同操作系统下的可执行文件格式如下:
- PE(Portable Executable): 是目前Windows平台上主流的可执行文件格式,包括常见的可执行程序.EXE文件/动态链接库.DLL文件等等。
- ELF(Executable and Linking Format): 是一种用于二进制文件、可执行文件、目标代码、共享库和core转存格式文件。是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。
四、寄存器、内存、缓存之间的关系
寄存器:CPU寄存器是CPU内部的高速存储区域,用于临时存储数据和指令。寄存器是CPU的重要组成部分,用于提高程序的执行效率。它们比内存更快,可以使得CPU更有效地处理数据和执行指令。
内存:用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。内存是CPU直接与之沟通,并用其存储数据的部件,存放当前正在使用的(即执行中)的数据和程序,内存只能暂时存放程序和数据,一旦关闭电源或发生断电,程序和数据就会丢失。
CPU缓存:CPU缓存是位于CPU与内存之间的临时存储器,其容量远小于内存,但速度却可以接近处理器的频率。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先从缓存中调用,从而加快读取速度。CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存。当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。在CPU中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统了。缓存对CPU的性能影响很大,主要是因为CPU的数据交换顺序和CPU与缓存间的带宽引起的。
CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。
正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在缓存中,只有大约10%需要从内存读取。这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。
执行引擎:面向寄存器进行操作,它负责执行指令和处理数据。在执行指令时,执行引擎需要从寄存器中读取数据,或者把数据写入寄存器。当执行引擎执行时,会从寄存器中读取数据,寄存器会从CPU缓存中读取,或者从内存中读取。
寄存器种类比较多,一般cpu包含的寄存器如下,详细内容【操作系统】CPU寄存器详解-CSDN博客
通用寄存器 | EAX、EBX、ECX、EDX、AX、BX、CX、DX等 |
段寄存器 | ES、CS、SS、DS、FS、GS 、LDTR |
指令指针寄存器 | SP、BP、EBP、ESP |
状态寄存器 | SR、PSW |
控制寄存器 | CR0-CR4、CR3页表 |
调试寄存器 | DR0-DR7 |
描述符寄存器 | GDTR、LDTR、IDTR |
任务寄存器 | TR |
x64 CPU有新增寄存器,如型号专属寄存器 | ......... |
基于Intel CPU架构的寄存器:
目前64位的cpu共有16个通用寄存器,其中rax寄存器的结构是:
rax 64位 8B 只存在于64位CPU 函数返回值存储在这类寄存器中。
包含eax 32位 4B 存在于32位、64位CPU。
包含ax 16位 2B 存在于16位、32位、64位CPU。
包含ah 高8位 存在于8位、16位、32位、64位CPU。
包含al 低8位 存在于8位、16位、32位、64位CPU。
rax寄存器结构如下:
其中eax为32位寄存器,ax为eax的低16位的 “ 子寄存器 ”,al为eax(或说ax)低8位的 “ 子寄存器 ”,ah为eax(或说ax)高八位的“ 子寄存器 ”,其他寄存器同理。
只有rax、rbx、rcx、rdx有高8位、低8位寄存器。
rcx一般保存循环次数(256)、存放this指针(C++),比如lea [this],a
rsi、rdi用作数据拷贝,rsi存放源数据,rdi是拷贝的目的地
rbp、rsp存储栈操作的数据。
五、汇编语言例题
用汇编语言实现如下java代码的功能:
int a = 1
a = a + 2
1.只用一个寄存器实现
win系统使用OD工具尝试实现,可以看到eax最终存储的数值为3,操作eax之前,最好通过异或清零。
2.用多个寄存器实现
win系统使用OD工具尝试实现,使用了eax和ebx两个寄存器,可以看到ebx最终存储的数值为3。
在linux下,使用bochs进行实验,得到的结果也是一样的。但是要注意使用bochs的断点调试语句xchg bx,bx,当程序运行到这里,就会暂停调试。