【汇编逆向系列】九、函数传参之结构体 - 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;
}
相关推荐
会掉头发2 小时前
x86_64汇编
汇编
CHANG_THE_WORLD8 小时前
NEG指令说明
汇编·逆向·neg
南玖yy5 天前
Linux 桌面市场份额突破 5%:开源生态的里程碑与未来启示
linux·运维·服务器·汇编·科技·开源·gradle
GeekMax5 天前
(笔记)U-boot 2012.10 armv7启动汇编解析
汇编
南玖yy7 天前
Linux权限管理:从“Permission denied“到系统安全大师
linux·运维·汇编·后端·架构·系统安全·策略模式
Kira Skyler8 天前
c++,从汇编角度看lambda
汇编·c++
暗流者8 天前
学习pwn需要的基本汇编语言知识
汇编·学习·网络安全·pwn
单车少年ing11 天前
ARM64---C中调用汇编指令
汇编
无小道12 天前
函数返回值问题,以及返回值的使用问题(c/c++)
c语言·开发语言·汇编·c++
菜菜why14 天前
详细解析单片机启动汇编文件:以startup_stm32f407xx.s为例
汇编·单片机·嵌入式硬件·嵌入式软件