设为首页 加入收藏

TOP

5.10 汇编语言:汇编过程与结构(一)
2023-08-26 21:10:31 】 浏览:191
Tags:5.10 程与结

过程的实现离不开堆栈的应用,堆栈是一种后进先出(LIFO)的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场。

栈是由CPU管理的线性内存数组,它使用两个寄存器(SS和ESP)来保存栈的状态,SS寄存器存放段选择符,而ESP寄存器的值通常是指向特定位置的一个32位偏移值,我们很少需要直接操作ESP寄存器,相反的ESP寄存器总是由CALL,RET,PUSH,POP等这类指令间接性的修改。

CPU提供了两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  • ESP 栈指针寄存器:栈指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
  • EBP 基址指针寄存器:基址指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

在通常情况下ESP是可变的,随着栈的生成而逐渐变小,而EBP寄存器是固定的,只有当函数的调用后,发生入栈操作而改变。

  • 执行PUSH压栈时,堆栈指针自动减4,再将压栈的值复制到堆栈指针所指向的内存地址。
  • 执行POP出栈时,从栈顶移走一个值并将其复制给内存或寄存器,然后再将堆栈指针自动加4。
  • 执行CALL调用时,CPU会用堆栈保存当前被调用过程的返回地址,直到遇到RET指令再将其弹出。

10.1 PUSH/POP

PUSH和POP是汇编语言中用于堆栈操作的指令,它们通常用于保存和恢复寄存器的值,参数传递和函数调用等。

PUSH指令用于将操作数压入堆栈中,它执行的操作包括将操作数复制到堆栈的栈顶,并将堆栈指针(ESP)减去相应的字节数。指令格式如下:

PUSH operand

其中,operand可以是8位,16位或32位的寄存器,立即数,以及内存中的某个值。例如,要将寄存器EAX的值压入堆栈中,可以使用以下指令:

PUSH EAX

从汇编代码的角度来看,PUSH指令将操作数存储到堆栈中,它实际上是一个入栈操作。

POP指令用于将堆栈中栈顶的值弹出到指定的目的操作数中,它执行的操作包括将堆栈顶部的值移动到指定的操作数,并将堆栈指针增加相应的字节数。指令格式如下:

POP operand

其中,operand可以是8位,16位或32位的寄存器,立即数,以及内存中的某个位置。例如,要将从堆栈中弹出的值存储到BX寄存器中,可以使用以下指令:

POP EBX

从汇编代码的角度来看,POP指令将从堆栈中取出一个值,并将其存储到目的操作数中,它是一个出栈操作。

在函数调用时,PUSH指令被用于向堆栈中推送函数的参数,这些参数可以是寄存器、立即数或者内存中的某个值。在函数返回之前,POP指令被用于将堆栈顶部的值弹出,并将其存储到寄存器或者内存中。

读者需要特别注意,在使用PUSHPOP指令时需要保证堆栈的平衡,也就是说,每个PUSH指令必须有对应的POP指令,否则堆栈会失去平衡,最终导致程序出现错误。

在读者了解了这两条指令时则可以执行一些特殊的操作,如下代码我们以数组入栈与出栈为例,执行PUSH指令时,首先减小ESP的值,然后把源操作数复制到堆栈上,执行POP指令则是先将数据弹出到目的操作数中,然后再执行ESP值增加4,并以此分别将数组中的元素压入栈,最终再通过POP将元素反弹出来。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib

.data
  Array DWORD 1,2,3,4,5,6,7,8,9,10
  szFmt BYTE '%d ',0dh,0ah,0
.code
  main PROC
    ; 使用Push指令将数组正向入栈
    mov eax,0
    mov ecx,10
  S1:
    push dword ptr ds:[Array + eax * 4]
    inc eax
    loop S1
    
    ; 使用pop指令将数组反向弹出
    mov ecx,10
  S2:
    push ecx                         ; 保护ecx
    pop ebx                          ; 将Array数组元素弹出到ebx
    invoke crt_printf,addr szFmt,ebx
    pop ecx                          ; 弹出ecx
    loop S2
    
    int 3
  main ENDP
END main

至此当读者理解了这两个指令之后,那么利用堆栈的先进后出特定,我们就可以实现将特殊的字符串反转后输出的效果,首先我们循环将字符串压入堆栈,然后再从堆栈中反向弹出来,这样就可以实现字符串的反转操作,这段代码的实现也相对较为容易;

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib

.data
  MyString BYTE "hello lyshark",0
  NameSize DWORD ($ - MyString) - 1
  szFmt BYTE '%s',0dh,0ah,0
.code
  main PROC
    ; 正向压入字符串
    mov ecx,dword ptr ds:[NameSize]
    mov esi,0
  S1: movzx eax,byte ptr ds:[MyString + esi]
    push eax
    inc esi
    loop S1

    ; 反向弹出字符串
    mov ecx,dword ptr ds:[NameSize]
    mov esi,0
  S2: pop eax
    mov byte ptr ds:[MyString + esi],al
    inc esi
    loop S2
    
    invoke crt_printf,addr szFmt,addr MyString
    int 3
  main ENDP
END main

10.2 PROC/ENDP

PROC/ENDP 伪指令是用于定义过程(函数)的伪指令,这两个伪指令可分别定义过程的开始和结束位置。此处读者需要注意,这两条伪指令并非是汇编语言中所兼容的,而是MASM编译器为我们提供的一个宏,是MASM的一部分,它允许程序员使用汇编语言定义过程(函数)可以像标准汇编指令一样使用。

对于不使用宏定义来创建函数时我们通常会自己管理函数栈参数,而有了宏定义这些功能都可交给编译器去管理,下面的一个案例中,我们通过使用过程创建ArraySum函数,实现对整数数组求和操作,函数默认将返回值存储在EAX中,并打印输出求和后的参数。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib

.data
  MyArray  DWORD 1,2,3,4,5,6,7,8,9,10
  Sum      DWORD ?
  szFmt    BYTE '%d',0dh,0ah,0
.code
  ; 数组求和过程
  ArraySum PROC
    push e
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇两数相加 下一篇ImGui界面优化:使用图标字体、隐..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目