Day3:函数与栈帧、数组与串指令、宏与结构体、x64汇编框架、内联汇编与混合编程

函数与栈帧、数组与串指令、宏与结构体、x64汇编框架、内联汇编与混合编程

函数与栈帧

  • “栈“是内存中的一部分,可以理解为连续的一块内存,它为程序运行提供了一个暂时的存放数据的地方,地址、数据等可以通过push指令从寄存器中被复制到栈中,进而寄存器可以进行其他操作,等到这些数据再次被需要时再从栈中被pop出来。栈遵循先进后出的规则,先push的在高地址,后push的在低地址

  • 在程序运行时,每个函数都会拥有独立的栈帧,即独立的栈上的一部分空间。有两个寄存器,esp,ebp(x86即32位环境下)负责维护函数的栈空间。ebp存放的是函数栈空间的栈基址,esp存放的是函数栈空间的栈顶。可以理解为ebp是指向函数栈空间最高位的一个指针(因为栈空间从高地址开始分配,因此指向的是最高位,但是是栈空间的起始地址),而esp是指向函数栈空间最低位的一个指针(指向的是最低位,但是会随着后续栈空间的扩充而变化)。

  • CALL指令:

    1
    2
    3
    ;call一个函数的时候,会发生以下内容
    push 返回地址(call所在指令的下一条)
    jmp 目标函数地址
  • RET指令:

    1
    2
    ;函数结束时,RET指令会发生以下内容
    pop eip ;从栈上弹出返回地址到eip(保存下一步指令地址的寄存器)
  • 栈帧:

    1
    2
    3
    4
    5
    6
    7
    ;跳转到每一个函数,会发生以下内容以开辟栈帧
    push ebp ;把上一个函数的ebp值放到栈上
    mov ebp,esp ;把esp值给ebp,开辟新函数的栈帧
    ;函数执行完毕后,会发生以下内容清理栈帧
    mov esp,ebp ;把ebp值给esp,清理函数栈帧
    pop ebp ;恢复ebp原来的值,即上一个函数的ebp
    ret

数组与串指令

  • 声明数组:

    1
    2
    3
    .data			;在数据段声明
    szHello db 'HelloWorld',0 ;声明一个字符串
    nNumber dd 20 dup(0) ;声明一个DWORD数组,20位,使用dup()初始化数组内容为0
  • 使用数组基址的几种方式:

    1
    2
    lea eax,szHello
    mov ebx,offset Nnumber
  • 访问数组成员:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ;[数组起始地址+索引寄存器*比例因子+偏移量](索引寄存器用来存放索引值,比例因子对应类型的字节大小,偏移量可用于结构体数组等场景)
    ;如:
    mov ebx,offset Array ;把数组基址给ebx
    mov esi,0 ;esi用来当索引寄存器
    mov ecx,10 ;ecx用来放索引上限(ecx常用作计数器)

    loop_start: ;创建一个循环
    mov eax,[ebx+esi*4] ;取出数组内容放到eax中
    add esi,1 ;索引递增
    cmp esi,ecx ;与索引上限比较,判断是否遍历完数组
    jl loop_start ;还没遍历完数组就循环操作,jump到loop_start再走一遍
  • 串指令:

    1
    2
    3
    4
    ;stos系列:stosb,stosw,stosd,stosq(分别对应byte,word,dword,qword,功能是把al/ax/eax/rax中的内容存到edi/rdi)
    ;lods系列:lodsb,lodsw,lodsd,lodsq(同样分别对应四个类型,功能是把esi/rsi的内容加载到al/ax/eax/rax中)
    ;movs系列:movsb,movsw,movsd,movsq(同样分别对应四个类型,功能是把esi的内容复制到edi中)
    ;cmps系列:cmpsb,cmpsw,cmpsd,cmpsq(同样分别对应四个类型,功能是比较esi和edi的内容并设置标志位)

宏与结构体

  • 宏:

    1
    2
    3
    4
    5
    6
    7
    8
    ;无参宏使用关键字EQU,如
    PI EQU 3 ;定义PI=3
    ;含参宏使用关键字MACRO,用endm结尾,如
    Myadd MACRO Number
    add eax,Number
    endm ;定义Myadd函数实现eax加上参数
    ;含参宏的调用:
    Myadd<参数>
  • 结构体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ;使用关键字struct和ends,如
    Point struct
    x word ?
    y word ?
    Point ends

    ;实例化结构体
    MyPoint Point<?> ;实例化出MyPoint

    ;访问结构体成员
    mov MyPoint.x,123

x64汇编框架(MASM)

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
;x64环境下的代码,传参的时候先使用寄存器(Windows:rcx,rdx,r8,r9  Linux:rdi,rsi,rdx,rcx,r8,r9)再使用栈
.code
;Add函数声明
Add proc
push rbp
mov rbp,rsp
lea rax,[rdi + rsi] ;从寄存器中取参数
pop rbp
ret
Add endp

main proc
mov rdi,2 ;把参数传入寄存器中
mov rsi,3
call Add
ret
main endp
end


;x86环境下的代码,传参使用栈
.code
;Add函数声明
Add proc
push ebp
mov ebp,esp
mov eax,[esp+8] ;从栈上取参数
add eax,[esp+12]
pop ebp
ret
Add endp

main proc
push 3 ;把参数压入栈中
push 2
call Add
add esp,8
main endp

内联汇编与混合编程

  • x86下,在Microsoft Visual C++ (MSVC)程序中使用汇编,可以通过_asm{}直接写在C++代码中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include<iostream>
    int main(){
    _asm{
    xor eax,eax
    mov eax,1
    add eax,1
    }
    return 0;
    }
  • x64下,在Microsoft Visual C++ (MSVC)程序中使用汇编,需要独立的asm文件,通过extern声明使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include<iostream>
    extern "C" long long Add(long long number1,long long number2);
    int main(){
    long long res=Add(1,2);
    std::cout<<res<<std::endl;
    return 0;
    }

    //asm文件
    .code
    Add proc
    push rbp
    mov rbp,rsp
    xor rax,rax
    add rax,rcx
    add rax,rdx
    pop rbp
    ret
    Add endp
    end

Day3:函数与栈帧、数组与串指令、宏与结构体、x64汇编框架、内联汇编与混合编程

https://sydzi.github.io/2025/07/08/Day3-函数与堆栈/

作者

SydzI

发布于

2025-07-08

更新于

2025-10-03

许可协议

评论