CPU即中央处理单元(Central Processing Unit ),有时也简称为处理器( processor ),其作用是从内存中读取指令,然后解码和执行。CPU架构就是CPU的内部设计和结构,也叫作微架构( Microarchitecture ),由一堆硬件电路组成,用于实现指令集所规定的操作或者运算。
指令集架构(Instruction Set Architecture,ISA)简称指令集,包含了一系列的操作码( opcode ),以及由特定CPU执行的基本命令。指令集在CPU中的实现称为微架构,要想设计CPU,首先决定
使用什么样的指令集,然后才是设计硬件电路。根据指令集的特征,通常可分为CICS和RISC两大阵营
由于指令集是一堆二进制数据,非常不利于阅读和理解,于是有人就发明了汇编语言( Assemblylanguage),用类似人类语言的方式对指令集进行描述,每条汇编指令都有对应的指令.再往后,C/C++等高级语言的诞生更加方便了程序的编写,推动了信息化和互联网的普及。
PC端最常见的架构——x86架构以及扩展的x64架构。汇编语言是人类与计算机交互过程中的底层,和汇编语言关系最密切的,莫过于计算机的中央处理器。
对于x86处理器而言,有三个最主要的操作模式:保护模式,实地址模式,系统管理模式。此外还有一个保护模式的子模式,称为虚拟8086模式。
对于x86-64处理器而言,还引入了一种名为IA-32e的操作模式。该模式包含两个子模式,分别为兼容模式和64位模式,在兼容模式下现有的32位和16位程序无须重新编译;在64位模式下,处理器将在64位的地址空间下运行程序。
寄存器:从8位处理器到16位处理器,再到32位以及64位处理器,寄存器的名称也有一些变化。
不同位数处理器寄存器名称:
在64位模式下,操作数的默认大小仍然为32位,且有8个通用寄存器;当给每条汇编指令增加REX(寄存器扩展)的前缀后,操作数变为64位,且增加了8个带有标号的通用寄存器(R8~R15 )。
此外,64位处理器还有两个不容忽视的特点:
整数常量:一个数字需要前缀后缀加以区分,十六进制下,前缀加0,后缀加h
浮点数常量:x86架构中有单独的浮点数寄存器和浮点数指令来处理相关浮点数常量。通常以十进制表示浮点数,而以十六进制编码浮点数。浮点数中至少包含一个整数和一个十进制的小数点,以下均为合法的浮点数:“1.”、“+2.3”、“-3.14159”、“26.E5”
字符串常量:字符串的存储往往时存储一个指针,指向字符串的地址
MOV指令的基本格式中:第一个参数为目的操作数,第二个参数为源操作数
MOV指令支持从寄存器到寄存器、从内存到寄存器、从寄存器到内存、从立即数到内存和从立即数到寄存器的数据传送,但不支持从内存到内存的直接传输,想要完成从内存到内存的数据传送,必须使用一个寄存器作为中转。
在编写汇编语言时,可能会出现将较小的操作数扩展为较大操作数的情况,这时就需要对操作数进行全零扩展或符号扩展。
最简单的算术运算指令是INC和 DEC,分别用于操作数加1和操作数减1。这两条指令的操作数既可以是寄存器,也可以是内存。
学习在介绍算术运算指令前,需要了解补码的知识。计算机底层的数据表示均是以补码表示的。两个机器数相加的补码可以先通过分别对两个机器数求补码,然后再相加得到。在采用补码形式表示时,进行加法运算可以把符号位和数值位一起进行运算(若符号位有进位则直接舍弃),结果为两数之和的补码形式。对于机器数的补码减法可以利用与其相反数的加法实现。
ADD指令将长度相同的操作数进行相加操作。
SUB指令为减法操作,将从目的操作数中减去源操作数。
NEG指令是把操作数转换为二进制补码,并将操作数的符号位取反。
在汇编语言中存在标志位寄存器,使用SUB、ADD等指令都可能会造成整数溢出、符号位等标
志位发生变化,因此进位标志位、零标志位、符号标志位、溢出标志位,辅助标志位,奇偶标志位都将根据存入的输入发生变化。
一般情况下,CPU是顺序加载并执行程序的。但是,指令集中会存在一些条件型指令,将根据CPU的标志位寄存器决定程序控制流的走向。
在x86汇编语言中,每一个条件指令都隐含着一个跳转指令。跳转指令有两种最基本的类型:
JMP指令是无条件跳转指令,在编写汇编语言时需要使用一个标号来标识,汇编器在编译时就会将该标号转换为相应的偏移量。一般情况下,该标号必须和JMP指令位于同一函数中,但使用全局标号则不受限制。
JMP指令也可以创建一个循环,也就是在循环结束时用JMP指令再跳回循环开始的位置。由于JMP是无条件跳转,所以除非使用其他方式退出,该循环将一直运算下去。
LOOP指令也可以创建一个循环代码块,ECX寄存器为循环的计数器(实地址模式中略有不同,CX寄存器是LOOP指令与LOOPW指令的默认循环计数器,ECX寄存器为LOOPD指令的循环计数器,64位的x86汇编语言LOOP指令使用RCX为默认循环计数器),每经过一次循环,ECX的值将减去1。
LOOP指令执行分为两步,第一步是ECX值减1;第二步将ECX与0进行比较,如果ECX不为0,则跳转到标号地址处;如果ECX为0,则不发生跳转,执行LOOP指令的下一条指令。在使用LOOP指令前,如果将ECX的值设为0,那么在执行LOOP指令时,ECX的值减去1后实际上为FFFFFFFFh,这将是一个非常大的循环,因此我们在编写x86汇编语言的过程中一般情况不需要显式地改变ECX寄存器的值,特别是存在循环嵌套的情况时。
栈是先进后出的数据结构
栈空间是计算机内存中一段确定的内存区域,也有着一些指针指向相应的内存地址,在x86架构中这个指针位于ESP寄存器,而在x86-64平台上为RSP寄存器。在计算机底层,栈主要的几个用是:
关于这一部分可以参考我的专栏:C与汇编
详细介绍函数调用机制,调用约定,常用的指令
section .data
msg db "Welcome_to_CTFshow_PWN", 0
section .text
global _start
_start:
; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1
; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx
; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax
; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax
; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax
; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax
; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用
; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用