x86汇编语言(一)

从C语言到汇编

一个C语言程序从.c到可执行文件需要经过预处理(preprocessing),编译(compilation),汇编(assembly)和链接(linking)

我们观察一个最简单的例子:hello.c

#include 
int main(){
    printf("hello world!");
    return 0;
}

在linux中经过编译便可以得到汇编源代码hello.s

在terminal中输入gcc -S hello.c -o hello.s

hello.s

x86汇编语言(一)_第1张图片

 

上面看到的就是汇编代码。汇编代码在经过汇编程序编译生成Obj文件,然后链接最终生成可执行文件

下图是在windows下汇编得到的汇编代码,你会发现有些细微的不同

x86汇编语言(一)_第2张图片

一条汇编指令对应着一条机器码,汇编语言是直接对CPU编程的语言,所以针对不同的CPU架构有不同的汇编语言,这里主要讲解的是x86汇编语言

硬件基础

x86汇编语言(一)_第3张图片图1

上图表示的是CPU和内存

CPU是计算机核心部件,控制整个计算机的运作并进行运算,要想让它工作,就必须向它提供指令和数据。指令和数据就存放在内存中(也就是存储器)。

寄存器

寄存器是存储器和CPU之间数据交换的一个桥梁,存储器到CPU中的数据都要经过寄存器,存储器中不同地址中的内容的交换也要经过寄存器。了解寄存器是学习汇编语言中非常重要的

图2

上图是x86架构中的寄存器组

通用寄存器组

下图是通用寄存器组示意图

x86汇编语言(一)_第4张图片

• AX/EAX — Accumulator for operands and results data(操作数和结果的累加器)
• BX/EBX — Pointer to data in the DS segment(指向数据段中的数据的指针)
•CX/ECX — Counter for string and loop operations(用于字符串和循环操作的计数器)
• DX/EDX — I/O pointer(输入输出指针)
•SI/ESI — Pointer to data in the segment pointed to by the DS register; source pointer for string operations(指向由 DS 寄存器指向的段中的数据的指针;字符串操作的源指针。也叫做源变址)
• DI/EDI — Pointer to data (or destination) in the segment pointed to by the ES register; destination pointer for
string operations(指向由 ES 寄存器指向的段中的数据(或目标地址)的指针;字符串操作的目标指针。也叫做基变址)
• SP/ESP — Stack pointer (in the SS segment)(栈顶指针)
• BP/EBP — Pointer to data on the stack (in the SS segment)(指向栈上数据的指针,作用于函数调用的返回)

以上是这些通用寄存器常见的用途,但是最主要的作用就是通用寄存器。

段寄存器

The segment registers (CS, DS, SS, ES, FS, and GS) hold 16-bit segment selectors. A segment selector is a special
pointer that identifies a segment in memory. To access a particular segment in memory, the segment selector for
that segment must be present in the appropriate segment register.

这是官方对段寄存器的解释,段寄存器用于保存 16 位的段选择器。段选择器是一种特殊的指针,用于确定内存
中某个段的位置。

x86汇编语言(一)_第5张图片

x86汇编语言(一)_第6张图片段寄存器示意图

Each of the segment registers is associated with one of three types of storage: code, data, or stack.

the CS register contains the segment selector for the code segment, where the instructions being executed are stored.The processor fetches instructions from the code segment, using a logical address that consists of the segment selector in the CS register and the contents of the EIP register. The EIP register contains the offset within the code segment of the next instruction to be executed.The CS register cannot be loaded explicitly by an applicationprogram. Instead, it is loaded implicitly by instructions or internal processor operations that change program control (such as, procedure calls, interrupt handling, or task switching).

The DS, ES, FS, and GS registers point to four data segments.(图2上没有显示FS,GS是因为FS,GS是64位CPU新增的)The availability of four data segments permits efficient and secure access to different types of data structures.

The SS register contains the segment selector for the stack segment, where the procedure stack is stored for the program, task, or handler currently being executed. All stack operations use the SS register to find the stack segment. Unlike the CS register, the SS register can be loaded explicitly, which permits application programs to set up multiple stacks and switch among them.

标志寄存器EFLAGS

The 32-bit EFLAGS register contains a group of status flags, a control flag, and a group of system flags.(图2中那些后面不带数字的表示的就是标志寄存器)

x86汇编语言(一)_第7张图片

CF (bit 0) Carry flag — Set if an arithmetic operation generates a carry or a borrow out of the most significant bit of the result; cleared otherwise. This flag indicates an overflow condition for unsigned-integer arithmetic. It is also used in multiple-precision arithmetic.(进位标志,用于表示无符号数运算是否产生进位或者借位,如果产生了进位或借位则值为 1,否则值为 0。)
PF (bit 2) Parity flag — Set if the least-significant byte of the result contains an even number of 1 bits; cleared otherwise.(奇偶标志,用于表示运算结果中 1 的个数的奇偶性,偶数个1 时值为 1,奇数个 1 时值为 0。)
AF (bit 4) Auxiliary Carry flag — Set if an arithmetic operation generates a carry or a borrow out of bit 3 of the result; cleared otherwise. This flag is used in binary-coded decimal (BCD) arithmetic.(辅助进位标志,在字操作时标记低位字节(低 4 位)是否向高位字节(高 4 位)进位或借位。)
ZF (bit 6) Zero flag — Set if the result is zero; cleared otherwise.(零标志,用于表示运算结果是否为 0,结果为 0 时其值置1,否则置 0。)
SF (bit 7) Sign flag — Set equal to the most-significant bit of the result, which is the sign bit of a signed integer. (0 indicates a positive value and 1 indicates a negative value.)(符号标志,用来标记有符号数运算结果是否小于 0,小于 0时置 1,否则置 0。)
OF (bit 11) Overflow flag — Set if the integer result is too large a positive number or too small a negative number (excluding the sign-bit) to fit in the destination operand; cleared otherwise. This flag indicates an overflow condition for signed-integer (two’s complement) arithmetic.(溢出标志,用于表示有符号运算结果是否溢出,发生溢出时置 1,否则置 0。)

DF(bit 10)direction flag — The direction flag (DF, located in bit 10 of the EFLAGS register) controls string instructions (MOVS, CMPS, SCAS,LODS, and STOS). Setting the DF flag causes the string instructions to auto-decrement (to process strings from high addresses to low addresses). Clearing the DF flag causes the string instructions to auto-increment(process strings from low addresses to high addresses).(方向标志,决定字符串操作指令执行时指针寄存器的调整方向)

下面是系统标志和IOPL

The system flags and IOPL field in the EFLAGS register control operating-system or executive operations. They
should not be modified by application programs.(简单来说就是不允许通过程序修改)

TF (bit 8) Trap flag — Set to enable single-step mode for debugging; clear to disable single-step mode.(跟踪标志,用于程序调试,置 1 时 CPU 处于单步执行状态,置 0 时处于连续工作状态。)
IF (bit 9) Interrupt enable flag — Controls the response of the processor to maskable interrupt requests. Set to respond to maskable interrupts; cleared to inhibit maskable interrupts.(中断允许标志,决定 CPU 是否响应 CPU 外部的可屏蔽中断发出的中断请求,置 1 时可以响应中断,置 0 时不响应中断。)
IOPL (bits 12 and 13) I/O privilege level field — Indicates the I/O privilege level of the currently running program or task. The current privilege level (CPL) of the currently running program or task must be less than or equal to the I/O privilege level to access the I/O address space. The POPF and IRET instructions can modify this field only when operating at a CPL of 0.(I/O 特权标志,用于表示当前进程的 I/O 特权级别,只有当前进程的 CPL 小于或等于 IOPL 时才能访问 I/O 地址空间,只有CPL 为 0 时才能修改 IOPL 域。)
NT (bit 14) Nested task flag — Controls the chaining of interrupted and called tasks. Set when the current task is linked to the previously executed task; cleared when the current task is not linked to another task.(嵌套任务标志,置 1 时表示当前任务是在另一个任务中嵌
套执行,置 0 时表示非嵌套。)
RF (bit 16) Resume flag — Controls the processor’s response to debug exceptions.(恢复标志,用于表示是否响应指令断点,置 1 禁用指令断点,置 0 允许指令断点。)
VM (bit 17) Virtual-8086 mode flag — Set to enable virtual-8086 mode; clear to return to protected mode without virtual-8086 mode semantics.(虚拟 8086 模式标志,用于表示进程是运行在虚拟 8086模式还是保护模式,置 1 运行在虚拟 8086 模式,置 0 运行在保护模式。)
AC (bit 18) Alignment check (or access control) flag — If the AM bit is set in the CR0 register, alignment checking of user-mode data accesses is enabled if and only if this flag is 1. If the SMAP bit is set in the CR4 register, explicit supervisor-mode data accesses to user-mode pages are allowed if and only if this bit is 1. See Section 4.6, “Access Rights,” in the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A.(对齐检测标志,与 CR0 寄存器的 AM 标志联合使用,这两个标志位同时置 1 启用对内存引用的对齐检查,同时置 0 表示禁用对齐检查。对齐检查仅在用户态下进行,0 特权级下不做检查。)
VIF (bit 19) Virtual interrupt flag — Virtual image of the IF flag. Used in conjunction with the VIP flag. (To use this flag and the VIP flag the virtual mode extensions are enabled by setting the VME flag in control register CR4.)(虚拟中断标志,是 IF 标志的一个虚拟映像,与 VIP 标志一起使用,当控制寄存器 CR4 中的 VME 或者 PVI 标志位置 1 且 IOPL 小于 3 时,处理器只识别 VIF 标志。)
VIP (bit 20) Virtual interrupt pending flag — Set to indicate that an interrupt is pending; clear when no interrupt is pending. (Software sets and clears this flag; the processor only reads it.) Used in conjunction with the VIF flag.(虚拟中断等待标志,置 1 表示有一个等待处理的中断,置0 表示没有等待处理的中断。)
ID (bit 21) Identification flag — The ability of a program to set or clear this flag indicates support for the CPUID instruction.(识别标志,置 1 表示支持 CPUID 指令,置 0 表示不支持。)

IP/EIP

指令指针寄存器存储了当前代码段的偏移,指向了下一条要执行的指令,系统根据该寄存器从内存中取出指令,然后再译码执行。

存储器(内存)

存储器被划分成若干存储单元,每8位为一个存储单元,每个存储单元从0开始顺序编号。CPU要想和存储器进行数据的交换必须要完成下面的步骤

  1. 存储单元的地址
  2. 器件的选择,读或写的命令
  3. 读或写的数据 

它们所需要的硬件分别对应图1中的

  1. 地址总线
  2. 控制总线
  3. 数据总线

现在考虑要怎么传输物理地址。我们假设地址总线为20位,而一个AX只有16位,而且内存是会越来越大的,一个AX是无法完整的表示EA的。这时候就需要对内存进行分段,一般来说将64kb分为一段(也就是2^16),一个物理地址就由段首地址*16d+偏移地址构成,这就是实模式的寻址方式。还有一种是保护模式的寻址方式,这里先不做介绍。

到这里需要引入一个新的概念——内存地址空间,一台PC上会有各式各样的存储器芯片,这些存储器芯片在物理上是相互独立的,但是对于CPU来说,把它们总的看作一个由若干单元组成的逻辑存储器,这个就是内存地址空间。

x86汇编语言(一)_第8张图片

x86汇编语言(一)_第9张图片

不同的计算机系统内存地址空间的分配情况是不同的,下图是8086PC机内存地址空间的分配情况

x86汇编语言(一)_第10张图片

内存访问

寻址方式

先了解一下一条指令的格式,一条指令由操作码和操作数两部分组成,操作码说明计算机要组成哪种操作,操作数是操作的对象,比如mov ax,2000中mov是操作码,ax,2000是操作数

CPU要读写一个内存单元,必须先给出这个内存单元的地址,这个地址由段地址和偏移地址组成,DS寄存器通常用来存放要访问数据的段地址。所以偏移地址由我们给出。

寻址中用的的指令是mov指令,mov指令的格式是:mov dst,src

使用mov指令要注意几个不能:

*注意一个我自己编程犯的一个错误mov [2000],2000这样写是会报错的,因为没有指定数据的长度,所以要在[2000]加上byte ptr或者word ptr:mov byte ptr [2000],20/mov word ptr [2000],2000

  1. 不能同为段寄存器
  2. 数据类型不可以不同,就是数据的长度要一致
  3. CS不能作为目的操作数
  4. 立即数不能直接传给段寄存器
  5. 立即数不能作为目的操作数
  6. 两个操作数不能同为存储单元
  7. ip不能作为操作数

下面讲9种寻址方式:

1.立即数寻址 mov ax,2000h

2000直接当作数据被传入ax

x86汇编语言(一)_第11张图片

图中可以很清楚看到指令执行前和执行后ax的变化。

2.寄存器寻址 mov ax,bx

bx中的内容存入ax中

x86汇编语言(一)_第12张图片

讲解后几种寻址方式先说一下什么是有效地址(effective address),有效地址指的就是偏移地址

EA = 基址+比例因子*变址+位移量

基址:

16位中的bx,bp

32位中的eax,ebx,ecx,edx,esp,ebp,esi,edi

变址:

16位中si,di

32位除了esp以外的7个32位通用寄存器

比例因子:

只能32位寻址用,只能1,2,4,8,只能与变址寄存器联用

位移量:

存放在指令中的一个8位,16位,32位地址偏移部分,可正可负

3.直接寻址 mov ax,[2000h]

在debug中加[]代表的是地址,用masm中要加上段前缀ds:[2000h]

x86汇编语言(一)_第13张图片

4.寄存器间接寻址 mov ax,[bx]

这里的寄存器注意只能只用指定的基址和变址寄存器

5.寄存器相对寻址 mov ax,[bx+count]

基址/变址+位移量

count只是一个符号,可以代表任何数如1234h,0000h等

6.基址变址寻址 mov ax,[bx+bi]

基址+变址

7.相对基址变址寻址mov ax,[count+bx+bi]

基址+变址+位移量

下面两种是针对32位寻址的,加入了比例因子

8.基址比例变址寻址

基址+比例因子*变址

9.相对基址比例变址寻址

基址+比例因子*变址+位移量

(tips:间接代表就是先将地址放入寄存器,再从寄存器中取地址;相对表示加上位移量;基址变址是基址+变址)

上面讲的是对数据的寻址,但是内存中不仅有数据还有指令,下面是对指令的寻址方式

cpu读指令会从CS*16d+ip的地方开始读取,每次读一条指令ip就进行相应的变化。

x86汇编语言(一)_第14张图片

当不停的执行指令时,IP的值一直在增加

所以当我们要访问某一条具体的指令的时候就要修改cs和ip的值。

如果时段内寻址只需要修改ip,而段间寻址需要同时修改cs和ip

指令的修改不能用mov,所以我们介绍一条新的指令jmp

jmp 0234:1234这是jmp最简单的用法cs=0234h,ip=1234h

1.段内寻址

a.段内直接寻址

jmp short s(short代表短跳转,8位带符号数)

jmp near ptr progia(near ptr称为近跳转,16位带符号数)

ssume cs:codeseg

codeseg segment

s:		mov ax,2000
		jmp s

codeseg ends

end

这是一段汇编代码,我们看一下运行效果

x86汇编语言(一)_第15张图片

b.段内间接寻址

jmp bx

jmp word ptr [count+bp]

jmp dword ptr[ebp+table]

可以用除了立即数以外的任何一种寻址方式得到

2.段间寻址

a.段间直接寻址

jmp far ptr next

b.段间间接寻址

jmp dword ptr[bx]

jmp dword ptr[bx+mark]

jmp dword ptr[bx+si]

CPU中的栈和数据结构中所学的栈是一样的,都是FILO机制,我们在C语言使用函数调用时就用到了CPU的栈。在编写递归时递归层数过多经常会报错,这就是因为栈的空间大小事有限的,递归过多会导致栈溢出,从而占到其他的内存空间中,这是操作系统所不许发生的,所以会报错。

在CPU的寄存器中有两个和栈相关,分别是SS和SP,SS存放的是栈顶的段地址,SP存放的是偏移地址

我们直接通过程序来观察入栈和出栈的情况,先来看一下入栈

assume cs:codeseg

codeseg segment

	mov ax,3000h
	mov ss,ax
	mov sp,0010h
	push ax
	push ax
	push ax

codeseg ends

end

入栈前的存储单元

执行push ax后

x86汇编语言(一)_第16张图片

发现sp-2了,3000h入栈且放在3000e和3000f这两个存储单元,说明入栈时sp=sp-2然后再将数据入栈,此时SS:SP指向新栈顶

入栈分为两步

  1. SP=SP-2,栈顶指针上移
  2. 将ax中的内容送入SS:SP指向的内存单元处

出栈也分为两步

  1. 将SS:SP指向的内存单元处的数据送入ax中
  2. SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈栈顶下面的单元为新的栈顶

注意出栈时原来的数据将不会被覆盖

push和pop指令格式:

push/pop reg/seg/mem

第一个简单程序

assume cs:codeseg

codeseg segment
	
	mov ax,0123h
	mov bx,0456h
	add ax,bx
	add ax,ax

	mov ax,c400h
	int 21h

codeseg ends

end

上面是一个简单的汇编源代码

在汇编语言中有两类指令,一种是汇编指令,一种是伪指令。汇编指令有对应的机器码,伪指令没有对应的机器码,是由编译器来执行的指令。

上面程序中有3种伪指令

1.XXX segment......XXX ends

segment和ends是一对成对使用的伪指令,功能是定义一个段,segment说明是一个段的开始,ends说明一个段的结束

2.end

end是一个汇编程序的结束标记

3.assume

这条伪指令的含义为“假设”,它假设某一段寄存器和程序中的某一个用segment....ends定义的段相关联

汇编源程序中还有标号,比如上面程序中的codesg

一个程序是如何运行的呢?这里以DOS为基础来讨论一下。

一个程序P2要想运行,必须有一个正在运行的程序P1将P2的可执行文件加载如内存,将CPU的控制权交给P2,P2才能得以运行。P2运行后P1暂停运行。P2运行完后应该将CPU控制权还给P1,这个过程就是程序返回。mov ax,c400h和int 21h这两条指令作用就是程序返回

下面是将汇编代码转换成可执行代码的过程

一、编译

用masm汇编编译器来编译

在命令行中输入masm filename

然后一路按enter就好了

x86汇编语言(一)_第17张图片

完成后会生成.obj文件

二、链接

连接就是将obj文件转换成exe文件的过程

在命令行中输入link filename.obj

同样一路按回车

x86汇编语言(一)_第18张图片

这时就生成了一个可执行文件

前面说一个程序要执行必须要另一个正在运行的程序把它加载到内存当中,下面来详细讨论一下这个内容。

将程序加载到内存后,cx存放的是程序的长度,一共11个字节,CS:IP指向程序的入口

在DOS系统中可执行文件的加载过程如下:先找到一段足够容量的内存空闲区域,设其起始地址为SA:0000;在该区域前256个内存单元中创建PSP数据区(这个区主要是DOS系统用来和该程序进行通信);在这256个内存单元之后开始存入程序。

所以,各区域分配如下:

内存空闲区域:SA:0000~;

PSP区:SA:0000~SA:00FF;

程序装载区:(SA+10):0000~;

x86汇编语言(一)_第19张图片

你可能感兴趣的:(x86汇编)