【汇编逆向系列】九、函数传参之结构体 - SHL、SHR指令,小型结构体参数和返回值

目录

[1. 汇编代码](#1. 汇编代码)

[1.1 debug编译](#1.1 debug编译)

[1.2 release编译](#1.2 release编译)

[2. 汇编分析](#2. 汇编分析)

[2.1 结构体的头文件](#2.1 结构体的头文件)

[2.2 结构体传参](#2.2 结构体传参)

[2.3 计算](#2.3 计算)

2.4返回结构体

[2.5 汇编优化 - SHR指令](#2.5 汇编优化 - SHR指令)

[3. 汇编转化](#3. 汇编转化)

[3.1 debug编译](#3.1 debug编译)

[3.2 Release编译](#3.2 Release编译)

[3.3 C语言转化](#3.3 C语言转化)


1. 汇编代码

前面章节描述了参数传参的汇编写法,但是一般情况下如果参数非常多,也不会使用很多参数而是使用结构体,那么参数是一个结构体在汇编上是如何体现的呢?先来看一组汇编代码。

1.1 debug编译

复制代码
struct_by_value_param:
  0000000000000A90: 48 89 4C 24 08     mov         qword ptr [rsp+8],rcx
  0000000000000A95: 57                 push        rdi
  0000000000000A96: 48 83 EC 40        sub         rsp,40h
  0000000000000A9A: 48 8B FC           mov         rdi,rsp
  0000000000000A9D: B9 10 00 00 00     mov         ecx,10h
  0000000000000AA2: B8 CC CC CC CC     mov         eax,0CCCCCCCCh
  0000000000000AA7: F3 AB              rep stos    dword ptr [rdi]
  0000000000000AA9: 48 8B 4C 24 50     mov         rcx,qword ptr [rsp+50h]
  0000000000000AAE: 8B 44 24 50        mov         eax,dword ptr [rsp+50h]
  0000000000000AB2: D1 E0              shl         eax,1
  0000000000000AB4: 89 44 24 28        mov         dword ptr [rsp+28h],eax
  0000000000000AB8: 8B 44 24 54        mov         eax,dword ptr [rsp+54h]
  0000000000000ABC: D1 E0              shl         eax,1
  0000000000000ABE: 89 44 24 2C        mov         dword ptr [rsp+2Ch],eax
  0000000000000AC2: 48 8B 44 24 28     mov         rax,qword ptr [rsp+28h]
  0000000000000AC7: 48 8B F8           mov         rdi,rax
  0000000000000ACA: 48 8B CC           mov         rcx,rsp
  0000000000000ACD: 48 8D 15 00 00 00  lea         rdx,[struct_by_value_param$rtcFrameData]
                    00
  0000000000000AD4: E8 00 00 00 00     call        _RTC_CheckStackVars
  0000000000000AD9: 48 8B C7           mov         rax,rdi
  0000000000000ADC: 48 83 C4 40        add         rsp,40h
  0000000000000AE0: 5F                 pop         rdi
  0000000000000AE1: C3                 ret

1.2 release编译

复制代码
struct_by_value_param:
  0000000000000000: 8D 04 09           lea         eax,[rcx+rcx]
  0000000000000003: 48 C1 E9 20        shr         rcx,20h
  0000000000000007: 03 C9              add         ecx,ecx
  0000000000000009: 89 44 24 08        mov         dword ptr [rsp+8],eax
  000000000000000D: 89 4C 24 0C        mov         dword ptr [rsp+0Ch],ecx
  0000000000000011: 48 8B 44 24 08     mov         rax,qword ptr [rsp+8]
  0000000000000016: C3                 ret

2. 汇编分析

2.1 结构体的头文件

在此类汇编中,汇编语言是操作地址,编译器会将结构体的每个参数都转化为地址,所以一定需要头文件开看结构体的声明才能了解汇编做了什么,所以先找到该结构体的声明

复制代码
typedef struct {
    int x;
    int y;
} Point;

可以看到该结构体只是非常简单两个4字节的有符号整型参数

2.2 结构体传参

在 x64 调用约定中,小型结构体(如 8 字节的 Point)通过寄存器 RCX 传递(Windows)或拆分到多个寄存器(Linux)。

此处通过 mov qword ptr [rsp+8], rcx 保存到栈的 [rsp+8]位置

push rdi和sub rsp,40h两个指令将rsp的地址减了0x48h,所以[rsp+8]地址变为[rsp+50h]

后续通过 [rsp+50h] 访问:

  • [rsp+50h]p.x(低 4 字节)
  • [rsp+54h]p.y(高 4 字节)

2.3 计算

复制代码
mov eax, dword ptr [rsp+50h]  ; 加载 p.x
shl eax, 1                    ; eax = p.x * 2
mov dword ptr [rsp+28h], eax   ; 存储到局部变量 result.x
mov         eax,dword ptr [rsp+54h]; 加载p.y
shl         eax,1             ; eax= p.yu *2
mov         dword ptr [rsp+2Ch],eax  ; 存储局部变量 result.y

可以看到这一步操作将p.x和p.y 都乘以2

SHL为左移指令,左移一位代表乘以2

将两个临时变量存储到了[rsp+0x28h]和[rsp+0x2ch]

2.4返回结构体

小型结构体通过 RAX 寄存器返回:

复制代码
mov rax, qword ptr [rsp+28h]  ; 将 result.x 和 result.y 作为 8 字节值加载到 RAX
mov rdi, rax                  ; 临时保存返回值
call _RTC_CheckStackVars      ; 栈检查(调试用)
mov rax, rdi                  ; 恢复返回值到 RAX
ret                           ; 返回 RAX 中的 Point 结构体

2.5 汇编优化 - SHR指令

复制代码
lea         eax, [rcx+rcx]  // LEA指令计算 x*2:取RCX低32位(x)与自身相加,结果存入EAX(相当于x*2)
shr         rcx, 20h        // 将RCX右移32位(0x20),使原高32位(y)移动到低32位
add         ecx, ecx         // 将ECX(原y值)乘以2:ecx = ecx + ecx

汇编优化操作,几个指令后,EAX存放X*2, ECX存放Y*2

3. 汇编转化

可以看到这个函数做了一个小型结构体的转化示例,但是大型结构体的操作是不一样的下一节,我们单独介绍大型结构体如何传参

3.1 debug编译

复制代码
struct_by_value_param:
  ; 函数入口:保存参数寄存器RCX(结构体指针)到栈上[rsp+8]
  0000000000000A90: 48 89 4C 24 08     mov  qword ptr [rsp+8], rcx  ; 保存RCX(结构体参数)到调用者栈帧

  ; 函数序言:保存非易失寄存器并分配栈空间
  0000000000000A95: 57                 push rdi                     ; 保存RDI(非易失寄存器)
  0000000000000A96: 48 83 EC 40        sub  rsp, 40h                ; 分配64字节栈空间(含影子空间+局部变量)

  ; 调试模式栈初始化:用0xCC填充栈空间(调试器未初始化标记)
  0000000000000A9A: 48 8B FC           mov  rdi, rsp                ; RDI = 栈顶地址
  0000000000000A9D: B9 10 00 00 00     mov  ecx, 10h                ; 循环次数=16(64字节/4)
  0000000000000AA2: B8 CC CC CC CC     mov  eax, 0CCCCCCCCh         ; 填充值=0xCCCCCCCC
  0000000000000AA7: F3 AB              rep stos dword ptr [rdi]      ; 重复填充栈空间

  ; 加载结构体参数并处理第一个字段(x)
  0000000000000AA9: 48 8B 4C 24 50     mov  rcx, qword ptr [rsp+50h] ; RCX = 原始结构体指针(参数区)
  0000000000000AAE: 8B 44 24 50        mov  eax, dword ptr [rsp+50h] ; EAX = 结构体第一个成员(x)
  0000000000000AB2: D1 E0              shl  eax, 1                  ; EAX = x * 2
  0000000000000AB4: 89 44 24 28        mov  dword ptr [rsp+28h], eax ; 结果存局部变量1(x*2)

  ; 处理第二个字段(y)
  0000000000000AB8: 8B 44 24 54        mov  eax, dword ptr [rsp+54h] ; EAX = 结构体第二个成员(y)
  0000000000000ABC: D1 E0              shl  eax, 1                  ; EAX = y * 2
  0000000000000ABE: 89 44 24 2C        mov  dword ptr [rsp+2Ch], eax ; 结果存局部变量2(y*2)

  ; 准备返回值(新结构体)
  0000000000000AC2: 48 8B 44 24 28     mov  rax, qword ptr [rsp+28h] ; RAX = 合并的x*2和y*2(8字节)
  0000000000000AC7: 48 8B F8           mov  rdi, rax                ; RDI临时保存返回值(RAX是返回寄存器)

  ; 调试检查:验证栈变量是否被破坏
  0000000000000ACA: 48 8B CC           mov  rcx, rsp                ; RCX = 栈帧基址
  0000000000000ACD: 48 8D 15 00 00 00  lea  rdx, [struct_by_value_param$rtcFrameData] ; 调试信息表
                    00
  0000000000000AD4: E8 00 00 00 00     call _RTC_CheckStackVars      ; 调用运行时栈检查(调试模式)

  ; 函数收尾:设置返回值并恢复栈
  0000000000000AD9: 48 8B C7           mov  rax, rdi                ; RAX = 返回值(新结构体)
  0000000000000ADC: 48 83 C4 40        add  rsp, 40h                ; 释放栈空间(64字节)
  0000000000000AE0: 5F                 pop  rdi                     ; 恢复RDI寄存器
  0000000000000AE1: C3                 ret                          ; 函数返回

3.2 Release编译

复制代码
// 函数:struct_by_value_param
// 输入:RCX = 64位参数(低32位为x,高32位为y)
// 输出:RAX = 64位返回值(低32位为x*2,高32位为y*2)

0000000000000000: 8D 04 09           lea         eax, [rcx+rcx]  
    // LEA指令计算 x*2:取RCX低32位(x)与自身相加,结果存入EAX(相当于x*2)

0000000000000003: 48 C1 E9 20        shr         rcx, 20h        
    // 将RCX右移32位(0x20),使原高32位(y)移动到低32位

0000000000000007: 03 C9              add         ecx, ecx         
    // 将ECX(原y值)乘以2:ecx = ecx + ecx

0000000000000009: 89 44 24 08        mov         dword ptr [rsp+8], eax  
    // 将x*2的结果存入栈[rsp+8](低32位)

000000000000000D: 89 4C 24 0C        mov         dword ptr [rsp+0Ch], ecx  
    // 将y*2的结果存入栈[rsp+0Ch](高32位)

0000000000000011: 48 8B 44 24 08     mov         rax, qword ptr [rsp+8]  
    // 从栈中加载8字节数据(x*2和y*2)到RAX作为返回值

0000000000000016: C3                 ret                         
    // 函数返回,RAX包含新结构体

3.3 C语言转化

复制代码
Point struct_by_value_param(Point p) {
    Point result = {p.x * 2, p.y * 2};
    return result;
}
相关推荐
我在人间贩卖青春6 天前
汇编之伪指令
汇编·伪指令
我在人间贩卖青春7 天前
汇编之伪操作
汇编·伪操作
济6177 天前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
myloveasuka7 天前
汇编TEST指令
汇编
我在人间贩卖青春7 天前
汇编编程驱动LED
汇编·点亮led
我在人间贩卖青春7 天前
汇编和C编程相互调用
汇编·混合编程
myloveasuka8 天前
寻址方式笔记
汇编·笔记·计算机组成原理
请输入蚊子8 天前
《操作系统真象还原》 第六章 完善内核
linux·汇编·操作系统·bochs·操作系统真像还原
myloveasuka8 天前
指令格式举例
汇编·笔记·计算机组成原理
我在人间贩卖青春8 天前
汇编之分支跳转指令
汇编·arm·分支跳转