汇编语言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汇编器的语法

  1. 每个asm源码文件中都以下面的指令打头:

    .386 ;告诉汇编器, 使用32位汇编的语法来编译
    .model flat , stdcall ;默认使用平坦模式,默认使用stdcall的调用约定
    option casemap:none 
    
  2. 必须自己定义一个代码段, 定义代码段使用.code伪指令 , 汇编指令就写在.codeend之间

  3. 程序必须要有一个入口点.在C语言中, 就固定了是main函数. 在masm里面, 可以在代码段中使用end 标签的方式来指定入口点.

  4. 数据的定义必须放在数据段, 数据段使用.data指令来定义.

    .data ; 定义数据段
    .const ; 定义常量数据(不可修改的数据)
    .code ; 定义代码段
    
    1. 在数据段中定义数据, 可以使用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
  • 循环结构

模拟选择结构

模拟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的值是相同的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值