基础汇编

8086汇编语言指令大致分为以下几类

  • 一、传送类指令
  • 二、算数运算类指令
  • 三、位操作指令
  • 四、串操作指令
  • 五、控制转移类指令
  • 六、处理器控制类指令
  • 七、汇编指令伪指令

指令简介

传送类

数据传送

1
2
MOV 目的操作数,源操作数
XCHG 目的操作数,源操作数 //把源和目的操作数交换数值

有效地址传送

1
2
3
4
LEA 目的操作数,源操作数
把源操作数的偏移地址送目的操作数,
源操作数必须是一个内存操作数,
目的操作数必须是16位通用寄存器

堆栈指针操作指令

堆栈是一块特殊的存储器区域,这块区域是以先进后出的方式工作,系统为此提供了特殊的指针SP,和段寄存器SS
主要特点是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(1)堆栈是以字方式操作的
(2)压入堆栈。
①先修改指针:SP=SP -2
②再存入一个字
(3)弹出堆栈。
①先弹出一个字
②再修改指针:SP=SP +2
(4)入栈指令PUSH
格式:PUSH 操作数
该指令为数据入栈指令
即将操作数指定的一个字节的内容传送至SP所指的栈顶
寻址方式有寄存器寻址(段寄存器SS除外)和存储器寻址,
不能用立即数作操作数。
该指令不影响标志位。
过程:
首先,堆栈SP=SP-2
然后,操作数的高位字节——>存入((SP)+1)单元中
操作数的低位字节——>送入(SP)
(5)出栈指令:POP
POP 操作数
将SP所指的栈顶内容传送至操作数指定的一个字。
寻址方式有:
寄存器寻址(段寄存器SS除外)和存储器寻址
不能用立即数作操作数。
该指令不影响标志位。
过程:
首先堆栈((SP)+1)单元字节——>操作数的高字节位,
(SP)单元字节——>操作数的低位字节。
然后SP=SP-2

输入输出指令

IN 输入指令

1
IN AL,63H  ; 将端口地址为63H的一个字节内容输入到AL

OUT 输出指令

1
OUT 66H,AL  ;将AL内容送端口地址66H的单元中

位操作指令

1
2
3
4
5
6
7
8
9
10
11
12
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.
以上八种移位指令,其移位次数可达255次.
移位一次时, 可直接用操作码. 如 SHL AX,1.
移位>1次时, 则由寄存器CL给出移位次数.

串操作指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DS:SI 源串段寄存器 :源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
Z标志 用来控制扫描或比较操作的结束.
MOVS 串传送.
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.
( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.
把AL或AX的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.
把源串中的元素(字或字节)逐一装入AL或AX中.
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串.
是LODS的逆过程.
REP 当CX/ECX<>0时重复.,也就是直到CX=0时结束
REPE/REPZ 当ZF=1或比较结果相等,且CX/ECX<>0时重复.
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX<>0时重复.
REPC 当CF=1且CX/ECX<>0时重复.
REPNC 当CF=0且CX/ECX<>0时重复.

控制转移类指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
JMP 无条件转移指令
CALL 过程调用
RET/RETF过程返回.
条件转移:
( 当且仅当(SF XOR OF)=1时,OP1<OP2 )
JA/JNBE 不小于或不等于时转移.
JAE/JNB 大于或等于转移.
JB/JNAE 小于转移.
JBE/JNA 小于或等于转移.
以上四条,测试无符号整数运算的结果(标志C和Z).
JG/JNLE 大于转移.
JGE/JNL 大于或等于转移.
JL/JNGE 小于转移.
JLE/JNG 小于或等于转移.
以上四条,测试带符号整数运算的结果(标志S,O和Z).
JE/JZ 等于转移.
JNE/JNZ 不等于时转移.
JC 有进位时转移.
JNC 无进位时转移.
JNO 不溢出时转移.
JNP/JPO 奇偶性为奇数时转移.
JNS 符号位为 "0" 时转移.
JO 溢出转移.
JP/JPE 奇偶性为偶数时转移.
JS 符号位为 "1" 时转移.
LOOP CX不为零时循环.
LOOPE/LOOPZ CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
JCXZ CX为零时转移.
JECXZ ECX为零时转移.
INT 中断指令
INTO 溢出中断
IRET 中断返回

处理器控制类指令

1
2
3
4
5
6
7
8
9
10
11
12
CLC(进位位置0指令)
CMC(进位位求反指令)
STC(进位位置为1指令)
CLD(方向标志置1指令)
STD(方向标志位置1指令)
CLI(中断标志置0指令)
STI(中断标志置1指令)
WAIT(等待)
ESC(交权)
LOCK(封锁总线)
NOP(无操作)
HLT(暂停)

汇编指令伪指令

1
2
3
4
5
6
7
DW 定义字(2字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.

寄存器与标志位


CF(Carry Flag)——进位标志位。

1
2
当执行一个加法(或减法)运算,使最高位产生进位(或借位)时
CF为1,则为0。

PF(Parity Flag)——奇偶标志位。

1
2
3
该标志位反映运算结果中1的个数是偶数还是奇数。
当指令执行结果的低8位中含有偶数个1时
PF=1;否则PF=0。

AF(Auxiliary carry Flag)——辅助进位标志位。

1
2
3
4
当执行一个加法(或减法)运算,使结果的低4位向高4位有进位(或借位)时,
AF=1;否则AF=0。
ZF(Zero Flag)——零标志位。
若当前的运算结果为零,ZF=1;否则ZF=0。

SF(Sign Flag)——符号标志位。

1
它和运算结果的最高位相同。

OF(Overflow Flag)——溢出标志位。

1
当补码运算有溢出时,OF=1;否则OF=0。

3个控制标志位用来控制CPU的操作,由指令进行置位和复位。

DF(Direction Flag)——方向标志位。

1
2
3
它用以指定字符串处理时的方向,
当该位置“1”时,字符串以递减顺序处理,即地址以从高到低顺序递减。
反之,则以递增顺序处理。

IF(Interrupt enable Flag)——中断允许标志位。

1
2
3
它用来控制8086是否允许接收外部中断请求。
若IF=1,8086能响应外部中断,反之则不响应外部中断。
注意:IF的状态不影响非屏蔽中断请求(NMI)和CPU内部中断请求

TF(Trap Flag)——跟踪标志位。

1
2
3
4
它是为调试程序而设定的陷阱控制位。
当该位置“1”时,8086 CPU处于单步状态,
此时CPU每执行完一条指令就自动产生一次内部中断。
当该位复位后,CPU恢复正常工作。

edi 相当于 nop,例如 mov edi edi,之所以用 edi 不用 nop 是因为执行两个 edi 比执行两个 nop 快很多


下面在具体的程序中分析汇编代码。

初始化变量

1
2
3
4
5
6
7
string a #字符串变量
int a # 整型变量
Boolean a #布尔型变量
char a #字符型变量
double a #双精度变量
float a #单精度变量
char array a #字符型数组

C代码:

1
2
3
4
5
6
7
string stringvar="Hello World";
int intvar=100;
bool boolvar=false;
char charvar='B';
double doublevar=3.1415;
float floatvar=3.14159265;
char carray[]={'a','b','c','d",'e'};

对应的初始化汇编代码:

变量分配:

C++字符串的启动:

初始化字符串需要调用内置函数进行

基本输出

这是一段C++的输出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
printf("Hello String Literal");
_asm{nop}
printf("%s",stringvar);
_asm{nop}
printf("%i",intvar);
_asm{nop}
printf("%c",charvar);
_asm{nop}
printf("%f",doublevar);
_asm{nop}
printf("%f",floatvar);
_asm{nop}
printf("%c",carrays[3]);

字符串文字的汇编代码:

1
2
3
push offset aHelloStringlit;"Hello String Literal"
call jprintf
add esp,4

字符串文字被压入堆栈作为printf函数的参数被调用
一个变量的输出:

1
2
3
4
5
mov eax,[ebp+intvar]
push eax
push offset aI_0 ;"%i"
call jprintf
add esp,8

首先将变量intvar移入EAX寄存器,然后将其用指示整数输出的字符串文字‘’%i’一起送入堆栈中。然后将这些变量从堆栈中取出,并作为在调用printf函数时的参数。

数学运算

包括:

  • 加法
  • 减法
  • 乘法
  • 除法
  • 按位与
  • 按位或
  • 按位异或
  • 按位非
  • 按位右移
  • 按位左移

用这段代码时进行数学运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void mathfunctions()
{//mathematical operations
intA=10;
intB=15;
int add=A+B;
int sub=A-B;
int mult=A*B;
int div=A/B;
int and=A&B;
int or=A|B;
int xor=AAB;
int not=~A;
int rshift=A>>B;
int 1shift=A<<B;

我们对每个函数分开分析。
首先我们将A表示为十六进制oA,表示十进制10,B表示为十六进制的oF,表示十进制的15。

1
2
mov [ebp+A],0Ah
mov [ebp+B],OFh

add加法汇编:

1
2
3
mov eax,[ebp+A]
add eax,[ebp+B]
mov [ebp+add], eax

sub减法汇编:

1
2
3
mov eax,[ebp+A]
sub eax,[ebp+B]
mov [ebp+sub],eax

imul乘法汇编:

1
2
3
mov eax,[ebp+A]
imul eax,[ebp+B]
mov [ebp+mult],eax

idiv除法汇编,使用’cdq’来加倍EAX的大小,以便我们可以适应除法运算的输出。:

1
2
3
4
mov eax,[ebp+A]
cdq
idiv [ebp+B]
mov [ebp+div],eax

and按位与运算:

1
2
3
mov eax,[ebp+A]
and eax,[ebp+B]
mov [ebp+and],eax

or按位或运算:

1
2
3
mov eax,[ebp+A]
or eax,[ebp+B]
mov [ebp+or],eax

xor按位异或运算:

1
2
3
mov eax,[ebp+A]
xor eax,[ebp+B]
mov [ebp+xor],eax

not按位非运算:

1
2
3
mov eax,[ebp+A]
not eax
mov [ebp+not],eax

shl进行按位左移:

1
2
3
4
mov eax,[ebp+A]
mov ecx,[ebp+B]
shl eax,cl
mov [ebp+lshift],eax

函数调用

3种不同类型的函数:

  • 基本的空函数
  • 返回整数的函数
  • 接受参数的函数

分别是下面这段代码对应的三个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
void newfunc(){
printf("Hello!I'm a new function!");
}
int newfuncret(){
int A=rand();
return A;
}
void funcparams(int iparam,string sparam,char cparam)
{// function with parameters
printf("%i \n",iparam);
printf("%s \n",sparam);
printf("%c\n",cparam);
}

基本的空函数:

1
2
3
void newfunc(){
printf("Hello!I'm a new function!");
}
1
call newfunc

看汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
newfunc_func proc near 
var_C0=byte ptr-ec0h
push ebp
mov ebp, esp
sub esp, oc0h
push ebx
push esi
push edi
1ea edi,[ ebp+var_Ce]
mov ecx,30h
mov eax, 0ccccccch
rep stosd
push offset aHelloIMANewFun;"Hello!I'm a new function!"
call j__printf
add esp,4
pop edi
pop esi
pop ebx
add esp, oc0h
cmp ebp, esp
call j__RTC_CheckEsp; compiler security check
mov esp, ebp
pop ebp
retn ; return to last position, not return with item.
newfunc_func endp

这个函数使用了retn操作码,但只是返回到了上一个位置。(这样程序能够在函数调用完成后继续。)

返回整数的函数

1
2
3
int newfuncret(){//new function that returns something 
int A=rand();
return A;
1
call newfuncret

汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
newfuncret_func proc near 
var_CC=byte ptr-0CCh
A=dword ptr-8
push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
1ea edi,[ebp+var_CC]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stosd
mov esi,esp
call ds:__imp__rand ; call rand()
cmp esi,esp
call j__RTC_CheckEsp;compiler security check
mov [ebp+A],eax ;set the A to result of rand()
mov eax,[ebp+A] ;set EAX to A to return later
pop edi
pop esi
pop ebx
add esp,0CCh
cmp ebp,esp
call j__RTC_CheckEsp ;compiler security check
mov esp,ebp
pop ebp
retn ;When retn is called,it returns with the value in EAX newfuncret func endp

首先为A变量分配空间。然后调用rand()函数,将其返回值放入EAX寄存器。接下来,将EAX变量移入A变量空间,将A做为rand()的结果。最后,将A变量移入EAX,以便函数可以将其用作返回值。

接受参数的函数

1
2
3
4
5
6
void funcparams(int iparam,string sparam,char cparam)
{// function with parameters
printf("%i \n",iparam);
printf("%s \n",sparam);
printf("%c\n",cparam);
}

调用申明:

1
2
3
4
5
6
7
8
9
10
11
movzx eax,[ebp+charvar]
push eax ;cparam
sub esp,1Ch
mov ecx,esp ;this
mov [ebp+var_14C],esp
1ea edx,[ebp+stringvar]
push edx ;_Right
call j??0$basic_stringeou?schar_traitsep@std@@V?$allocator@0@20@std@CQAE@ABve1@@Z
mov [ebp+var_160],eax
mov eax,[ebp+intvar]
call funcparams

先将变量放入寄存器中,然后将寄存器压入栈中,最后调用该函数。
函数汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
funcparams_func proc near 
var_C8=byte ptr-0c0h
iparam=dword ptr 8
sparam= std::basic_string<char,std::char_traitscchar>,std::allocator<char>>ptr
cparam= byte ptr 28h
mov ebp,esp
sub esp,8c8h
push ebx
push esi
push edi
lea edi,[ebprvar_ce]
mov ecx,30h
sov eax,eccccCCCCh
rep stosd
sov eax,[ebp+iparam]
push eax
push offset_Format;"%i\n"
call j_printf
add esp,8
sub esp,1Ch
mov ecx,7
1ea esi,[ebp+sparam]
mov edi,esp
rep sovsd
push offset a5 ;“%s\n”
call jprintf
add esp,20h
movsx eax,[ebp+cparam]
push eax
push offset ac ;“%c\n”
call j_printf
add esp,8
lea ecx,[ebp+sparam] ;this cal1 j?1isbasic_stringeou?schar_traitse0gstdgv?sallocatorgog2e0stde@QAEexz
pop edi
pop esi
pop ebx
add esp,ac8h
cmp ebp,esp
call jRTC_CheckEsp
mov esp,ebp
pop ebp
funcparams func endp

这个函数是接受一个字符串,一个整数和一个字符,并使用printf打印出来。首先在函数顶部分配3个变量,然后将这些变量作为printf函数的参数压入堆栈。

参考资料:https://www.kancloud.cn/wintry/python3/742485
https://www.kancloud.cn/wintry/python3/742484

-------------本文结束感谢您的阅读-------------
/*
*/