【汇编逆向系列】九、函数传参之结构体 - 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;
}
相关推荐
liulilittle5 小时前
C/C++ inline-hook(x86)高级函数内联钩子
c语言·开发语言·汇编·c++·hook·底层·钩子
代码改变世界ctw7 小时前
ARM汇编编程(AArch64架构)课程 - 第8章:控制流与循环
汇编·arm开发
2401_861615281 天前
跨平台的ARM 和 x86 Docker 镜像:汇编语言实验环境搭建
linux·汇编·ubuntu·docker·容器
大P哥阿豪1 天前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
花小璇学linux1 天前
imx6ull-裸机学习实验1——汇编LED灯实验
linux·汇编·imx6ull·arm裸机开发
无小道2 天前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++
liulilittle3 天前
C++ i386/AMD64平台汇编指令对齐长度获取实现
c语言·开发语言·汇编·c++
工业互联网专业3 天前
汇编与接口技术:8259中断实验
汇编·单片机·嵌入式硬件·8259中断实验
small_wh1te_coder4 天前
硬件嵌入式学习路线大总结(一):C语言与linux。内功心法——从入门到精通,彻底打通你的任督二脉!
linux·c语言·汇编·嵌入式硬件·算法·c