汇编语言002_课上笔记
文章目录
课后作业讲解
注: 在16位汇编中, 并使用sp作为寄存器寻址, 因此, 下面的代码并不能直接运行. 但在32位汇编中(将所有寄存器改成32位之后)是没有问题的.
int a=10;
int b=20;
int c=4;
a = (a << c) * (a>>c) / ((c ^ a) | (c ^b )) - (++c + --a)
部分示例:
mov ax,10;
mov bx,20;
mov cx,4;
用到的汇编指令:
shl, shr , idiv , xor , or , sub , inc, dec
优先级关系:
;1. a << c => 中间结果1
push ax ; 将ax的值保存到栈中
shl ax,cl ; ax <<= cl;
xchg ax,[sp] ; ax是运算结果,[sp]以前的值
;2. a >> c => 中间结果2
push ax ; 保存ax的值
shr ax , cl ; ax <<= cl
xchg ax , [sp]
;3. c ^ a => 中间结果3
push cx
xor cx , ax;
xchg cx,[sp]
;4. c ^ b => 中间结果4
push cx
xor cx , bx
xchg cx,[sp]
;5. (中间结果3 | 中间结果4) => 中间结果5
xchg ax,[sp+0] ; ax等于中间结果4, [sp+0]=旧ax的值
or ax ,[sp+2]
xchg ax,[sp+0]; sp+0 保存的是中间结果5, ax值被恢复了
;6. ++c
inc cx;
;7. --a
dec ax
;8. c+a => 中间结果6
push cx
add cx , ax;
xchg cx,[sp]
;9. 中间结果1 * 中间结果2 / 中间结果5 - 中间结果6
mov ax , [sp+8]
mul [sp+6] ; ax*= 中间结果2
idiv [sp+2]
sub ax,[sp+0]
; 恢复栈
add sp , 0ch
代码运行时的栈布局:
-
中间结果6
-
中间结果5; 第三次push和xchg的结果和ax交换了,然后又保存中间结果5
-
中间结果3 ; 第三次push和xchg
-
中间结果2 ; 第二次push,然后交换之后的值
-
中间结果1 ; 是第一次push,然后交换之后的值
寻址方式
-
立即数寻址
-
寄存器寻址
-
存储器寻址
-
直接寻址 :
mov ax, [ 01000h ];
直接在[]
内给出一个内存地址 -
寄存器间接寻址:
mov ax ,[si]
; 在[]
以寄存器的值给出内存地址. -
寄存器相对寻址:
mov ax,[si+0ch]
在[]
以寄存器的值和一个数相加之后作为内存地址.struct MyStruct{ int n1; char ch; int n2; }; MyStruct stc; stc.n1 = 0; stc.n2 = 10; //假设stc的内存首地址是0x1000 // 以汇编形式访问结构体字段: mov bx , 0x1000; // bx保存了结构体首地址 mov [bx+0] , 0; //stc.n1 mov [bx+8] , 10;//stc.n2
-
基址变址寻址:
mov ax,[si+bx]
使用两个寄存器相加之和作为内存地址char szBuff[10]; for(int i = 0; i<10;++i){ szBuff[i] = 0; } // 假设szBuff首地址是0x1000 mov si , 0x1000; xor bx,bx; for(int i =0;i<10;++i){ // szBuff[i] = 0; mov [si+bx] , 0; inc bx; }
-
相对基址变址寻址:
mov ax,[si+bx+0ch]
使用[]
内的表达式的相加之和作为内存地址. -
在16位汇编中, 要使用存储器寻址的时候, 如果希望用寄存器寻址, 那么只能使用
si,di,bx,bp
寄存器, 然后这些寄存器不能任意组合.
-
条件跳转指令
有有符号跳转和无符号跳转之分.
常见条件跳转指令:
- 有符号跳转:
-
jg
大于 -
jge
大于等于 -
jl
小于 -
jle
小于等于
-
- 无符号跳转
-
ja
大于, cf0 且 zf0则跳转 -
jae
大于等于 , cf ==0 则跳转 -
jb
小于, cf==1则跳转 -
jbe
小于等于, cf1 或 zf1则跳转
-
- 不区分符号跳转
-
je
zf==1 则跳转 -
jne
zf==0 则跳转
-
32位汇编
第一个汇编项目
.386 ;告诉汇编器, 使用32位汇编的语法来编译
.model flat , stdcall ;默认使用平坦模式,默认使用stdcall的调用约定
option casemap:none
.code ; 定义一个代码段
sldkfjlaskdjfmain:
ret
end sldkfjlaskdjfmain; 指定程序入口点
end ; 结束代码段
masm汇编器的语法
-
每个asm源码文件中都以下面的指令打头:
.386 ;告诉汇编器, 使用32位汇编的语法来编译 .model flat , stdcall ;默认使用平坦模式,默认使用stdcall的调用约定 option casemap:none
-
必须自己定义一个代码段, 定义代码段使用
.code
伪指令 , 汇编指令就写在.code
和end
之间 -
程序必须要有一个入口点.在C语言中, 就固定了是
main
函数. 在masm里面, 可以在代码段中使用end 标签
的方式来指定入口点. -
数据的定义必须放在数据段, 数据段使用
.data
指令来定义..data ; 定义数据段 .const ; 定义常量数据(不可修改的数据) .code ; 定义代码段
-
在数据段中定义数据, 可以使用
d
系列指令.data ch db 'a' ; 相当于 char ch='a'; buff db 0,0,0,0 ; 相当于 char buff[]={0,0,0,0}; str db "hello" , 0; 相当于 char str[]={'h','e','l','l','o' , 0 }; 也就是说,这里不会自动加上字符串结束符'\0' str2 db "hello\n" , 0; 在masm中没有转义字符. str2 db "hello", 0ah , 0 ; 0ah是'\n' ; 其它类型 var1 dw 100 ; word类型 var2 dd 100 ; dword类型 ; dup用于重复定义数据 , dup前是一个重复的次数, dup圆括号内是需要重复的初始化值. arr dd 100 dup(0) ; int arr[100]={0};
-
汇编版本的helloworld
.386 ;告诉汇编器, 使用32位汇编的语法来编译
.model flat , stdcall ;默认使用平坦模式,默认使用stdcall的调用约定
option casemap:none ; 不区分大小写
; 包含名为`msvcrt.inc`头文件(c语言的所有库函数)
include msvcrt.inc
; 包含库文件
includelib msvcrt.lib
;include windows.inc
;include user32.inc
;includelib user32.lib
.data
g_str db "hello world",0dh,0ah, 0 ; \r\n==0d0a
.code ; 定义一个代码段
main:
push offset g_str;
call crt_printf
add esp , 4
ret
end main; 说明程序入口点
end ; 结束代码段
汇编程序基础
三大程序结构
- 顺序结构
- 选择结构
- 在c中,
if else
,switch
- 在c中,
- 循环结构
模拟选择结构
模拟if-else
int n =0;
scanf("%d",&n);
if( n == 1){
printf("星期一\n");
}
else if(n==2){
printf("星期二\n");
}
else if(n==3){
printf("星期三\n");
}
汇编版本
.data
n dd 0 ; 定义一个全局变量, 名字为n
.code
_main:
cmp n , 1
je _FLAG1
cmp n , 2
je _FLAG2
cmp n , 3
je _FLAG3
_FLAG1:
printf("星期一\n");
jmp _ENDIF
_FLAG2:
printf("星期二\n");
jmp _ENDIF
_FLAG3:
printf("星期三\n");
_ENDIF:
end _main
end
模拟switch-case
switch( n )
{
case 1: printf("星期1\n");break;
case 2: printf("星期2\n");break;
case 3: printf("星期3\n");break;
}
汇编版本1(和if-else的一样)
汇编版本1 : 使用跳转表
.386 ;告诉汇编器, 使用32位汇编的语法来编译
.model flat , stdcall ;默认使用平坦模式,默认使用stdcall的调用约定
option casemap:none ; 不区分大小写
; 包含名为`msvcrt.inc`头文件(c语言的所有库函数)
include msvcrt.inc
; 包含库文件
includelib msvcrt.lib
;include windows.inc
;include user32.inc
;includelib user32.lib
.data
g_str db "hello world",0dh,0ah, 0 ; \r\n==0d0a
str1 db "星期一",0dh,0ah,0
str2 db "星期2",0dh,0ah,0
str3 db "星期3",0dh,0ah,0
.code ; 定义一个代码段
main:
.code
jmp being
jmptable dd _FLAG1,_FLAG2,_FLAG3 ; 在code段定义数据
being:
mov eax , 1 ; ;
dec eax;
jmp [jmptable+eax*4]; 根据eax的值,来跳转不同的位置.
_FLAG1:
invoke crt_printf, offset str1;
jmp _ENDIF
_FLAG2:
invoke crt_printf , offset str2;
jmp _ENDIF
_FLAG3:
invoke crt_printf ,offset str1;
_ENDIF:
end main; 说明程序入口点
end ; 结束代码段
模拟循环结构
使用条件跳转模拟循环
int i =0;
while (i < 10)
{
++i;
}
汇编版本1:
xor eax,eax ; 使用eax寄存器作为i
_WHILE:
cmp eax , 10 ;
jge _ENDWHILE
inc eax
jmp _WHILE
_ENDWHILE:
汇编版本2 : loop
循环, 该指令使用ecx作为默认寄存器, 保存着循环次数, loop指令执行之后, 会判断ecx的值是否等于0 , 如果等于了,就不会跳转, 如果没有等于, 就先将ecx递减1, 然后跳转
mov ecx , 10;
_WHILE:
loop _WHILE ;
函数结构
-堆栈平衡:函数调用完成后,要返回所有使用过的栈空间,也就是要退栈。
-函数调用约定:函数调用约定规定了函数参数的入栈方式和堆栈的平衡方式。
-
函数调用和函数返回语句
call,ret
-
call 目标地址
- 调用函数的指令- 会将call指令的下一条指令的地址push到栈中.
- 跳转到目标地址.
-
ret
返回指令- 其实就是
pop eip
,call指令将一个返回地址保存到栈中,ret
就默认把栈中的地址取出设置到eip
这样就能回调函数的调用点了. -
ret 字节数
- 返回时,顺便平衡指定字节栈空间.
- 其实就是
-
-
定义函数
- 通过
ret
指令来回到函数的调用点.
- 通过
-
调用函数
-
函数的传参是通过栈来完成的.
-
调用函数之前, 先将实参压入栈中.
-
进入函数之后, 就可以从栈中取出参数了.
-
在传参的时候, 是从右往左依次将参数入栈,还是从左往右,需要有一个函数调用约定
调用约定名 传参顺序 栈平衡者 _cdecl
- C调用约定从右往左 函数外部 _stdcall
- 标准调用约定从右往左 函数内部 _thiscall
- 对象调用从右往左,this指针保存到ecx寄存器 函数内部 fastcall
- 快速调用约定前两个参数通过 ecx
,edx
来传递, 后面的参数从右往左依次入栈传递函数内部
-
-
通过
call + 函数地址
完成调用
-
-
在函数内部定位栈中的参数
-
使用局部变量
-
栈空间布局
#lea指令和offset指令
lea 是机器指令,offset 是伪指令。
LEA BX, BUFFER ;在实际执行时才会将变量buffer的地址放入bx
–所谓伪指令是在编译阶段执行的,有编译器执行。
–机器指令是在运行阶段,由cpu执行的。
MOV BX, OFFSET BUFFER ;在编译时就已经计算出buffer的地址为4300(假设),然后将上句替换为: mov bx,4300
lea可以进行比较复杂的计算,比如lea eax,[esi+ebx4],把ebx的值4,加上esi的值,存入eax中。
mov就不行了。
OFFSET只能取得用"数据定义伪指令"定义的变量的有效地址,不能取得一般操作数的有效地址(摘自80x86汇编语言程序设计教程)
MOV BX,OFFSET [BX+200]这句是错误的 应该用LEA BX,[BX+200]
lea eax,[ebp]
说明: eax得到ebp指向的堆栈内容的偏移地址, 和寄存器ebp的值是相同的
上一篇: pytorch知识点总结基础篇
下一篇: Linux/Unix 简单快捷键小结