kr 第三阶段(九)64 位逆向

X64 汇编程序

64 位与 32 位的区别

更大的内存

  • 64 位 CPU 与 32 位 CPU 的区别
    • 引脚根数:
      • x86 程序:20 根
      • x64 程序:52 根,实际寻址的有 48 根,所以最大内存是 0~256T
    • 寻址区间:
      • x86 程序:0x00000000 ~ 0xFFFFFFFF
      • x64程序:0xXXXX0000`00000000 ~ 0xXXXXFFFF`FFFFFFFF ,intel 高16 位必须是第 48 位符号扩展,所以实际如下:
        • R3 的逻辑地址范围:0x00000000`00000000 ~ 0xFFFFFFFF`FFFFFFFF
        • R0 的逻辑地址范围:0xFFFF8000`00000000 ~ 0xFFFFFFFF`FFFFFFFF
  • 64 位 CPU 的三种运行模式
    • 实模式:在此模式下,可以跑16 位程序,微软的操作系统禁用此模式。
    • 64 位模式:只能运行 64 位的程序。
    • 兼容模式:能运行 x32 位和 x64 位程序。模拟执行 32 位程序。

更快的运行速度

x64 程序最先是 AMD 提出来的,随后 inter 跟着推出了 IA(安腾处理器)但是由于其不兼容 x86 指令集,所以并没有得到太大的适用性。随后 AMD64 的问世,由于指令集并没有变动太多,且还能兼容运行 x86 的程序,所以得到了大量的普适性,Intel 也推出了 AMD64 标准的指令集 IA32e 。

寄存器的扩展:

  • 从原来的 32 位寄存器扩展到了 64 位寄存器。
  • 通用寄存器的数量得到了大量的提升(可以寄存器传参,局部变量寄存器存储)。

x64 汇编基础知识

寄存器

在这里插入图片描述

  • 通用寄存器

    • eaxebxecxedxebpesiediesp 全部又原先的 32 位扩展至 64 位,前缀由 e 改为 r
    • 增加了 r8 ~ r9 8 个 64 位通用寄存器。
  • 多媒体相关寄存器:

    • x87 浮点寄存器(MMX 寄存器)无变化
    • SSE(AVX)寄存器数量由原理的 8 个增加至 16 个(YMM/XMM8 ~ YMM/XMM15
  • 标志寄存器:
    在这里插入图片描述

    • 扩展为 64 位,但高 32 位保留不使用(填充 0)。
  • 段寄存器:
    在这里插入图片描述

    • 64 位模式下忽略了 dsesss 寄存器。
  • eip 寄存器扩展为 rip 寄存器。64 位模式下只能使用 rip 寄存器。

x64 寄存器后缀访问表示访问寄存器大小:b(低8位)、w(低16位)、d(低32位),例: r8br9wr10dr11
在这里插入图片描述
注意: 操作数为低 8 位、16 位,传送不影响高位;操作数为低 32 位,高位被扩展(零扩展,填充 0);

模式

在这里插入图片描述
64 位操作系统在只有 64 位模式下可以使用扩展寄存器,兼容模式可以运行 32 位和 64 位程序,但不能使用扩展寄存器。具体兼容模式和 64 为模式能够使用的寄存器种类如下图所示:
在这里插入图片描述

指令集

32 位原有指令集就能够满足常规使用。

x64 调用约定

64 位汇编与 32 位汇编的最大区别就是函数调用方式的变化。x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall__cdecl__fastcall_thiscall 等)的混乱,只保留了 4 寄存器 fastcall 一种调用约定。

  • 整数传参:
    • 前 4 个参数依序传入 RCXRDXR8R9
    • 前 4 个以外的整数参数将使用栈传参。
  • 浮点参数:
    • 4 个参数依序传入 XMM0XMM1XMM2XMM3,后续的浮点参数将放置到线程堆栈上。
  • 混合传参:
    • 如果整型和浮点型参数混合会放到对应的寄存器中。例如 fun(int, float, int, float) 函数对应参数会依次放到 ecxxmm1r8dxmm3 寄存器中。
  • 返回值:整型放 rax,浮点型放 xmm0

x64 汇编程序设计

开发环境

  • 64 位汇编需要使用 VS 的 x64 编译器 ml64.exe.asm 文件编译为 .obj 文件。该编译器的路径为 Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\ml64.exe
  • 选用 VS 的配套链接器 link.exe ,可以链接x64位的程序,link.exeml64.exe 在同一目录下。
  • 由于编译链接需要很多外部库,为了避免配置环境变量等出错,这里直接在 VS 自带的 x64 Native Tools Command Prompt for VS 中进行编译链接。编译链接命令如下:
    ml64 /c Hello.asm
    link /subsystem:windows /entry:Entry Hello.obj
    

第一个 x64 汇编程序

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc
extern ExitProcess:proc
extern GetCurrentProcessId:proc
extern wsprintfA:proc

.const 
MSG1    db "Hello x64! pid:%d %d %d %d", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code
Entry proc
  push  rbx
  push  rcx
  sub  rsp, 38h     ;保证栈地址模16   必须申请32个字节栈预留空间

  call GetCurrentProcessId
  mov rcx, offset BUFFER
  mov rdx, offset MSG1
  mov r8d, eax
  mov r9d, 5
  mov dword ptr [rsp + 20h], 6
  mov dword ptr [rsp + 28h], 7
  call wsprintfA

  mov  rcx, 0
  mov  rdx, offset BUFFER
  mov  r8, offset TITLE1
  mov  r9d, 0
  call MessageBoxA

  mov   ecx, 0
  call  ExitProcess

  sub  rsp, 38h
  pop  rcx
  pop  rbx
  ret
Entry endp

end 

使用运行时库

如果想使用运行时库函数 sprintf_s 来代替 wsprintfA 需要注意程序入口点应当指定为 WinMainCRTStartup 并且程序从 main 函数开始。因为运行时库函数使用前需要事先初始化相关变量。

编译链接脚本如下:

ml64 /c Hello.asm
link /entry:WinMainCRTStartup /subsystem:windows Hello.obj libcmt.lib msvcrt.lib

示例代码如下:

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc 
extern ExitProcess:proc 
extern GetCurrentProcessId:proc
extern sprintf_s:proc

.const 
MSG1    db "Hello x64! pid:%d %d %d", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code

WinMain proc
  push  rbx
  push  rcx
  sub  rsp, 38h     ;保证栈地址模16   必须申请32个字节栈预留空间

  call GetCurrentProcessId
  mov rcx, offset BUFFER
  mov rdx, sizeof BUFFER
  mov r8, offset MSG1
  mov r9d, eax
  mov dword ptr [rsp + 20h], 6
  mov dword ptr [rsp + 28h], 7
  call sprintf_s

  mov  rcx, 0
  mov  rdx, offset BUFFER
  mov  r8, offset TITLE1
  mov  r9d, 0
  call MessageBoxA

  mov   ecx, 0
  call  ExitProcess

  sub  rsp, 38h
  pop  rcx
  pop  rbx
  ret
WinMain endp

end 

使用资源

以对话框为例,x64 汇编程序使用资源的示例代码如下:

includelib kernel32.lib
includelib user32.lib

extern MessageBoxA:proc 
extern DialogBoxParamA:proc 
extern EndDialog:proc 
extern ExitProcess:proc 

IDD_DIALOG1 equ 101
IDC_BUTTON1 equ 1001
IDC_BUTTON2 equ 1002
WM_CLOSE    equ 10h
WM_COMMAND  equ 111h

.const 
MSG1    db "WM_COMMAND", 0
TITLE1  db "sky123", 0

.data
BUFFER db 260 dup(0)

.code

DialogProc proc
  mov [rsp + 8], rcx
  mov [rsp + 10h], rdx
  mov [rsp + 18h], r8
  mov [rsp + 20h], r9

  sub rsp, 28h
  xor eax, eax
  cmp edx, WM_CLOSE
  jz HANDLE_CLOSE
  cmp edx, WM_COMMAND
  jz HANDLE_COMMAND
  jmp HANDLE_DEFAULT
HANDLE_COMMAND:
  xor ecx, ecx
  mov rdx, offset MSG1
  mov r8, offset TITLE1
  xor r9d, r9d
  call  MessageBoxA
  inc eax
  jmp HANDLE_DEFAULT
HANDLE_CLOSE:
  mov rcx, [rsp + 30h]
  xor edx, edx
  call EndDialog
  inc eax
HANDLE_DEFAULT:
  add rsp, 28h
  ret
DialogProc endp

WinMain proc
  sub  rsp, 38h
  
  mov rdx, IDD_DIALOG1
  xor r8d, r8d
  mov r9, offset DialogProc
  mov qword ptr [rsp + 20h], 0
  call DialogBoxParamA

  xor ecx, ecx
  call  ExitProcess

  sub  rsp, 38h
  ret
WinMain endp

end 

链接时需要将资源也链接进去,资源可以通过 VS 工程创建并编译。

ml64 /c Hello.asm
link /entry:WinMainCRTStartup /subsystem:windows Hello.obj res.res msvcrt.lib

如何寻找 main 函数

从入口函数开始找,第一个 exit 的调用,离该函数最近且有三个参数的函数为 main 函数。

制作 IDA 签名文件

在 VS 的 项目属性 → 配置属性 → VC++目录 → 常规 → 库目录 中包含了 VC 编译时使用的 lib 库。我们可以将这些库制作成签名文件方便逆向分析。

这次制作签名文件我们使用高版本的 Flair ,高本版的 Flair 可以自动将多个 lib 库制作为一个签名文件,不需要手动写脚本实现自动化。

将需要制作签名的 lib 库拷贝到同一目录下,然后运行命令 pcf.exe *.lib vs2022.pat 提取特征。之后运行 sigmake.exe .\vs2022.pat .\vs2022.sig 制作签名文件。

正常情况下制作签名文件时会出现冲突的情况,并且会生成一个 vs2022.exc 文件记录具体冲突的函数。

> sigmake.exe .\vs2022.pat .\vs2022.sig
.\vs2022.sig: modules/leaves: 16108/35926, COLLISIONS: 1996
See the documentation to learn how to resolve collisions.

vs2022.exc 文件中的内容如下:

;--------- (delete these lines to allow sigmake to read this file)
; add '+' at the start of a line to select a module
; add '-' if you are not sure about the selection
; do nothing if you want to exclude all modules

??0CppInlineNamespaceAttribute@?A0x020fbda2@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
??0CppInlineNamespaceAttribute@?A0x0f11cde5@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
??0CppInlineNamespaceAttribute@?A0x9646ae10@vc.cppcli.attributes@@$$FQE$AAM@PE$AAVString@System@@@Z	00 0000 0330010007000000000000000228........2A..........................
...

我们有两种方式解决冲突:

  • 删掉签名的 4 行注释,这样 IDA 会使用 unknown_libname 来替代。
  • 删除冲突的函数中选择使用的函数名。

解决冲突后再次运行 sigmake.exe .\vs2022.pat .\vs2022.sig 命令就可以制作出签名文件。

将制作的签名文件放置在 IDA 安装路径下的 sig\pc 目录下就可以 Shift + F5 打开签名窗口然后 Insert 加载签名文件。

最终可以成功识别出 mian 函数的位置。

  Code = invoke_main();
  if ( !(unsigned __int8)j_unknown_libname_2() )
    j_exit(Code);
  if ( !v2 )
    j__cexit();

表达式

加法、减法、乘法

在 x64 位汇编对应的加法、减法、乘法指令的速度相对于 32 位汇编有了很大的提升,因此多数情况都是直接使用对应的指令而不作优化。少部分可以情况可以适用移位和比例因子进行优化。

除法

  • 除数为变量时无法优化,根据除数类型选择 dividiv
  • 除数为常量时可以优化。

被除数无符号,除数为 2 的整数次幂

直接 shr 移位即可。

被除数有符号,除数为 2 的整数次幂

如果是正数直接 sar 移位即可。如果是负数由于算术移位是向下取整,需要改为向上取整,因此 [ − a 2 n ] = ⌊ − a + 2 n − 1 2 n ⌋ = ⌈ − a 2 n ⌉ \left [ \frac{-a}{2^n} \right ] =\left \lfloor \frac{-a+2^n-1}{2^n} \right \rfloor =\left \lceil \frac{-a}{2^n}\right \rceil [2na]=2na+2n1=2na

例如 ⌊ x 8 ⌋ \left \lfloor \frac{x}{8} \right \rfloor 8x 对应的汇编代码如下:

.text:0000000140001000 mov     rax, rcx		; rcx = x
.text:0000000140001003 cqo					; rax 符号扩展至 rdx
.text:0000000140001005 and     edx, 7		; rdx = x < 0 ? 7 : 0
.text:0000000140001008 add     rax, rdx
.text:000000014000100B sar     rax, 3		; rax = (x + (x < 0 ? 7 : 0)) >> 3

注意:如果除数为 2 的整数次幂且无符号则无论被除数正负均向下取整。

被除数无符号,除数为非 2 的整数次幂

对于被除数无符号除数为非 2 的整数次幂的情况,为了避免使用除法指令,可以做如下转换:
⌊ a b ⌋ = ⌊ a × ⌈ 2 n b ⌉ 2 n ⌋   ( n ≥ ⌈ log ⁡ 2 a ⌉ ) \left \lfloor \frac{a}{b} \right \rfloor =\left \lfloor \frac{a\times \left \lceil \frac{2^n}{b} \right \rceil }{2^n} \right \rfloor\ (n\ge \left \lceil \log_2 a \right \rceil ) ba=2na×b2n (nlog2a)
例如 ⌊ a 35 ⌋ \left \lfloor \frac{a}{35} \right \rfloor 35a 对应的汇编代码如下:

.text:0000000140001000 mov     rax, 0EA0EA0EA0EA0EA0Fh
.text:000000014000100A mul     rcx		; a
.text:000000014000100D shr     rdx, 5
.text:0000000140001011 mov     eax, edx

其中 0EA0EA0EA0EA0EA0Fh ⌈ 2 69 35 ⌉ \left \lceil\frac{2^{69}}{35} \right \rceil 35269 ,因此整段汇编计算的是 ⌊ a × ⌈ 2 69 35 ⌉ 2 69 ⌋ \left \lfloor \frac{a\times \left \lceil \frac{2^{69}}{35} \right \rceil }{2^{69}} \right \rfloor 269a×35269

当然如果除数过小可能会出现 MagicNumber 超过 2 64 2^{64} 264 的情况,例如 ⌊ a 7 ⌋ \left \lfloor \frac{a}{7} \right \rfloor 7a 对应的 MagicNumber ⌈ 2 67 7 ⌉ \left \lceil\frac{2^{67}}{7} \right \rceil 726712492492492492493h 超过了 2 64 2^{64} 264 ,需要通过进行多次移位在不超过 2 64 2^{64} 264 的常量参与运算的前提下实现同等的效果。因此实际上 ⌊ a 7 ⌋ \left \lfloor \frac{a}{7} \right \rfloor 7a 对应的汇编代码如下:

.text:0000000140001000 mov     rax, 2492492492492493h
.text:000000014000100A mul     rcx
.text:000000014000100D sub     rcx, rdx
.text:0000000140001010 shr     rcx, 1
.text:0000000140001013 lea     rax, [rdx+rcx]
.text:0000000140001017 shr     rax, 2

这段汇编代码实现了如下计算:
⌊ a × ⌈ 2 67 7 ⌉ 2 67 ⌋ = ⌊ a − ⌊ a × ( ⌈ 2 67 7 ⌉ − 2 64 ) 2 64 ⌋ 2 + ⌊ a × ( ⌈ 2 67 7 ⌉ − 2 32 ) 2 64 ⌋ 2 2 ⌋ \left \lfloor \frac{a\times \left \lceil \frac{2^{67}}{7} \right \rceil }{2^{67}} \right \rfloor=\left \lfloor \frac{\frac{a-\left \lfloor \frac{a\times (\left \lceil \frac{2^{67}}{7} \right \rceil-2^{64} )}{2^{64}} \right \rfloor }{2}+\left \lfloor \frac{a\times (\left \lceil \frac{2^{67}}{7} \right \rceil-2^{32} )}{2^{64}} \right \rfloor }{2^2} \right \rfloor 267a×7267 = 222a264a×(7267264)+264a×(7267232)
其中 ⌈ 2 67 7 ⌉ − 2 64 < 2 64 \left \lceil \frac{2^{67}}{7} \right \rceil-2^{64}<2^{64} 7267264<264 ,满足条件。

被除数有符号,除数为非 2 的整数次幂

对于被除数为有符号数的情况,汇编代码如下:

.text:0000000140001000 mov     rax, 0EA0EA0EA0EA0EA1h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 63						; 取 (MagicNumber * a) >> 63 的符号位
.text:0000000140001017 add     rax, rdx						; 如果结果为负数则需要将结果加上 1

64 位汇编的优化与 32 位汇编相同,在被除数为负数时会在结果上加 1 实现向上取整的效果。编译器会选择合适的 2 n 2^n 2n 来确保 MagicNumber * a 不会被 2 n 2^n 2n 整除。

如果输出为负数则将符号转移至 MagicNumber 。例如 ⌊ a − 7 ⌋ \left \lfloor \frac{a}{-7} \right \rfloor 7a 汇编代码如下,其中 4924924924924925h ⌈ 2 65 7 ⌉ \left \lceil \frac{2^{65}}{7} \right \rceil 7265

.text:0000000140001000 mov     rax, -4924924924924925h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 3Fh
.text:0000000140001017 add     rax, rdx

取模

  • 模(除)数为变量时无法优化,根据模(除)数类型选择 dividiv
  • 模(除)数为常量时可以优化。

被除数无符号,模(除)数为 2 的整数次幂

如果被除数为无符号数,则 a m o d    2 n a\mod 2^n amod2n 相当于 a & ( 2 n − 1 ) a\&(2^n-1) a&(2n1) 。如果模数比较小会使用 32 位寄存器减短指令长度。

被除数有符号,模(除)数为 2 的整数次幂

64 位汇编的取模优化策略与 32 位不同,这里采用了下面的公式进行优化:
a   m o d   2 n = { a & ( 2 n − 1 ) ( a ≥ 0 ) ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) ( a < 0 ) a \bmod 2^n = \left\{\begin{aligned} &a\&(2^n-1) \quad &&(a \geq 0)\\ &(a+2^n-1)\&(2^n-1)-(2^n-1) \quad &&(a < 0) \end{aligned}\right. amod2n={a&(2n1)(a+2n1)&(2n1)(2n1)(a0)(a<0)
证明如下:

  • 对于 a ≥ 0 a\ge0 a0 的情况显然成立。

  • 对于 a < 0 a<0 a<0 的情况,首先 ( a + 2 n − 1 ) & ( 2 n − 1 ) (a+2^n-1)\&(2^n-1) (a+2n1)&(2n1) 可以看做为:
    ( a + 2 n − 1 ) & ( 2 n − 1 ) = { 2 n − 1 ( a   m o d   2 n = 0 ) ( ∣ a ∣   m o d   2 n ) − 1 ( a   m o d   2 n ≠ 0 ) (a+2^n-1)\&(2^n-1)=\left\{\begin{aligned} &2^n-1 &&(a \bmod 2^n= 0)\\ &(\left | a \right | \bmod 2^n)-1 \quad &&(a \bmod 2^n \neq 0) \end{aligned}\right. (a+2n1)&(2n1)={2n1(amod2n)1(amod2n=0)(amod2n=0)

    • 对于 a   m o d   2 n = 0 a \bmod 2^n= 0 amod2n=0 的情况 ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) = 0 (a+2^n-1)\&(2^n-1)-(2^n-1)=0 (a+2n1)&(2n1)(2n1)=0 符合题意。
    • 对于 a   m o d   2 n ≠ 0 a \bmod 2^n \neq 0 amod2n=0 的情况, ( a + 2 n − 1 ) & ( 2 n − 1 ) − ( 2 n − 1 ) = ( ∣ a ∣   m o d   2 n ) − 1 − ( 2 n − 1 ) = ( ∣ a ∣   m o d   2 n ) − 2 n = a   m o d   2 n (a+2^n-1)\&(2^n-1)-(2^n-1)=(\left | a \right | \bmod 2^n)-1-(2^n-1)=(\left | a \right | \bmod 2^n)-2^n=a\bmod 2^n (a+2n1)&(2n1)(2n1)=(amod2n)1(2n1)=(amod2n)2n=amod2n ,同样符合题意。

综上原命题得证。

a   m o d   8 a\bmod 8 amod8 为例,对应的汇编代码如下:

.text:0000000140001000 mov     rax, rcx		; rcx = a
.text:0000000140001003 cqo					; rdx = a >= 0 ? 0 : -1
.text:0000000140001005 and     edx, 7		; rdx = a >= 0 ? 0 : 7
.text:0000000140001008 add     rax, rdx
.text:000000014000100B and     eax, 7
.text:000000014000100E sub     rax, rdx

高版本的 clang 编译器采用了另一种优化方式:
a   m o d   2 n = { a − ( a & ∼ ( 2 n − 1 ) ) ( a ≥ 0 ) a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) ( a < 0 ) a \bmod 2^n=\left\{\begin{aligned} &a - (a\& \sim (2^n-1))\quad&&(a \ge 0)\\ &a - ((a+2^n-1)\& \sim (2^n-1)) \quad &&(a < 0) \end{aligned}\right. amod2n={a(a&(2n1))a((a+2n1)&(2n1))(a0)(a<0)
证明如下:

  • 对于 a ≥ 0 a\ge0 a0 的情况显然成立。
  • 对于 a < 0 a<0 a<0 的情况:
    • a   m o d   2 n = 0 a \bmod 2^n= 0 amod2n=0 时, ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) = a & ∼ ( 2 n − 1 ) = a (a+2^n-1)\& \sim (2^n-1)=a\& \sim (2^n-1)=a (a+2n1)&(2n1)=a&(2n1)=a ,所以 a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) = 0 a - ((a+2^n-1)\& \sim (2^n-1)) =0 a((a+2n1)&(2n1))=0 显然成立。
    • a   m o d   2 n ≠ 0 a \bmod 2^n\not = 0 amod2n=0 时, ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) = ( a & ∼ ( 2 n − 1 ) ) + 2 n (a+2^n-1)\& \sim (2^n-1)=(a\& \sim (2^n-1))+2^n (a+2n1)&(2n1)=(a&(2n1))+2n,因此 a − ( ( a + 2 n − 1 ) & ∼ ( 2 n − 1 ) ) = ( a − ( a & ∼ ( 2 n − 1 ) ) ) − 2 n = ( ∣ a ∣   m o d   2 n ) − 2 n = a   m o d   2 n a - ((a+2^n-1)\& \sim (2^n-1))=(a-(a\& \sim (2^n-1)))-2^n=(\left | a \right | \bmod 2^n)-2^n=a\bmod 2^n a((a+2n1)&(2n1))=(a(a&(2n1)))2n=(amod2n)2n=amod2n ,同样成立。

综上,原命题得证。

a   m o d   8 a\bmod 8 amod8 为例,对应的汇编代码如下:

.text:0000000000401140 mov     rax, [rsp]					; rax = a
.text:0000000000401144 lea     rcx, [rax+7]					; rcx = a + 7
.text:0000000000401148 test    rax, rax
.text:000000000040114B cmovns  rcx, rax						; rcx = a >= 0 ? a : a + 7
.text:000000000040114F and     rcx, 0FFFFFFFFFFFFFFF8h		; rcx = rcx & ~((1 << 3) - 1)
.text:0000000000401153 sub     rax, rcx						; rax = a - rcx

被除数无符号,模数不为 2 的整数次幂

对于被除数无符号,模数不为 2 的整数次幂的情况, a   m o d   b a \bmod b amodb 会被编译器优化为 a − ⌊ a b ⌋ × b a-\left \lfloor \frac{a}{b} \right \rfloor \times b aba×b,其中 ⌊ a b ⌋ \left \lfloor \frac{a}{b} \right \rfloor ba 会按照被除数无符号,除数为非 2 的整数次幂的除法优化。

x % 7 为例,汇编代码如下:

.text:0000000140001000 mov     rax, 2492492492492493h
.text:000000014000100A mul     rcx						; rcx = x
.text:000000014000100D mov     rax, rcx
.text:0000000140001010 sub     rax, rdx
.text:0000000140001013 shr     rax, 1
.text:0000000140001016 add     rax, rdx
.text:0000000140001019 shr     rax, 2
.text:000000014000101D imul    eax, 7					; eax = x / 7 * 7
.text:0000000140001020 sub     ecx, eax					; ecx = x - x / 7 * 7
.text:0000000140001022 mov     eax, ecx

被除数有符号,模数不为 2 的整数次幂

如果模数为正数,则 a   m o d   b a \bmod b amodb 会同样按照 a − ⌊ a b ⌋ × b a-\left \lfloor \frac{a}{b} \right \rfloor \times b aba×b 来进行优化,只不过这里的除法按照被除数有符号,除数为非 2 的整数次幂的情况进行优化。

.text:0000000140001000 mov     rax, 4924924924924925h
.text:000000014000100A imul    rcx
.text:000000014000100D sar     rdx, 1
.text:0000000140001010 mov     rax, rdx
.text:0000000140001013 shr     rax, 3Fh
.text:0000000140001017 add     rdx, rax
.text:000000014000101A imul    eax, edx, 7				; eax = x / 7 * 7
.text:000000014000101D sub     ecx, eax					; ecx = x - x / 7 * 7
.text:000000014000101F mov     eax, ecx

如果模数为负数,结果与模数为正数相同,即 a   m o d   b = a   m o d   ∣ b ∣ a \bmod b = a \bmod \left | b \right | amodb=amodb

控制流程

32 位程序和 64 位程序的控制流程基本不变,这里只介绍有变化的部分。

三目运算符

相较于 32 位汇编,64 位汇编普便采用 cmovxx 指令,而不会针对每种类型的三目运算进行无分支优化。

  • 如果三目运算符中的表达式不是特别复杂会采用 cmovxx 指令。
  • 如果三目运算符中的表达式比较复杂会转换为 if-else 形式。
  • 特别的,如果是 x == a ? b : b + 1 形式的三目运算会使用 setnz 进行优化。

switch 语句

分支较多但比较连续

为了节省空间 64 位的跳转表并没有像 32 位那样存放分支的地址,而是存放分支地址相对于某一基址的偏移,而这一基址即 PE 文件的 ImageBase(PE 文件的实际加载基址,PE 文件加载时会通过重定位表进行修正) ,跳转表中元素的大小则为 4 字节。

.text:0000000140001000 sub     rsp, 28h
.text:0000000140001004 dec     ecx                             ; switch 6 cases
.text:0000000140001006 cmp     ecx, 5
.text:0000000140001009 ja      def_140001023                   ; jumptable 0000000140001023 default case

.text:000000014000100F movsxd  rax, ecx
.text:0000000140001012 lea     rdx, cs:140000000h			   ; ImageBase
.text:0000000140001019 mov     ecx, ds:(jpt_140001023 - 140000000h)[rdx+rax*4]	; mov     ecx, [rdx+rax*4+10B4h]
.text:0000000140001020 add     rcx, rdx						   ; 跳转表中的偏移还要加上 ImageBase 才是分支的地址
.text:0000000140001023 jmp     rcx                             ; switch jump

.text:0000000140001025 $LN4:
.text:0000000140001025 lea     rcx, string1                    ; jumptable 0000000140001023 case 1
.text:000000014000102C call    cs:__imp_puts
.text:0000000140001032 xor     eax, eax
.text:0000000140001034 add     rsp, 28h
.text:0000000140001038 retn

.text:0000000140001039 $LN5:
.text:0000000140001039 lea     rcx, string2                    ; jumptable 0000000140001023 case 2
.text:0000000140001040 call    cs:__imp_puts
.text:0000000140001046 xor     eax, eax
.text:0000000140001048 add     rsp, 28h
.text:000000014000104C retn

.text:000000014000104D $LN6:
.text:000000014000104D lea     rcx, string3                    ; jumptable 0000000140001023 case 3
.text:0000000140001054 call    cs:__imp_puts
.text:000000014000105A xor     eax, eax
.text:000000014000105C add     rsp, 28h
.text:0000000140001060 retn

.text:0000000140001061 $LN7:
.text:0000000140001061 lea     rcx, string4                    ; jumptable 0000000140001023 case 4
.text:0000000140001068 call    cs:__imp_puts
.text:000000014000106E xor     eax, eax
.text:0000000140001070 add     rsp, 28h
.text:0000000140001074 retn

.text:0000000140001075 $LN8:
.text:0000000140001075 lea     rcx, string5                    ; jumptable 0000000140001023 case 5
.text:000000014000107C call    cs:__imp_puts
.text:0000000140001082 xor     eax, eax
.text:0000000140001084 add     rsp, 28h
.text:0000000140001088 retn

.text:0000000140001089 $LN9:
.text:0000000140001089 lea     rcx, string6                    ; jumptable 0000000140001023 case 6
.text:0000000140001090 call    cs:__imp_puts
.text:0000000140001096 xor     eax, eax
.text:0000000140001098 add     rsp, 28h
.text:000000014000109C retn

.text:000000014000109D def_140001023:
.text:000000014000109D lea     rcx, string7                    ; jumptable 0000000140001023 default case
.text:00000001400010A4 call    cs:__imp_puts
.text:00000001400010AA xor     eax, eax
.text:00000001400010AC add     rsp, 28h
.text:00000001400010B0 retn

.text:00000001400010B4 jpt_140001023 dd offset $LN4 - 140000000h
.text:00000001400010B4 dd offset $LN5 - 140000000h             ; jump table for switch statement
.text:00000001400010B4 dd offset $LN6 - 140000000h
.text:00000001400010B4 dd offset $LN7 - 140000000h
.text:00000001400010B4 dd offset $LN8 - 140000000h
.text:00000001400010B4 dd offset $LN9 - 140000000h
.text:00000001400010B4 main endp

分支较多且比较不连续

.text:0000000140001000 sub     rsp, 28h
.text:0000000140001004 dec     ecx                             ; switch 123 cases
.text:0000000140001006 cmp     ecx, 7Ah
.text:0000000140001009 ja      def_14000102B                   ; jumptable 000000014000102B default case, cases 6-122

.text:000000014000100F movsxd  rax, ecx
.text:0000000140001012 lea     rdx, cs:140000000h
.text:0000000140001019 movzx   eax, ds:(byte_1400010D8 - 140000000h)[rdx+rax]
.text:0000000140001021 mov     ecx, ds:(jpt_14000102B - 140000000h)[rdx+rax*4]
.text:0000000140001028 add     rcx, rdx
.text:000000014000102B jmp     rcx                             ; switch jump

.text:000000014000102D $LN4:
.text:000000014000102D lea     rcx, string1                    ; jumptable 000000014000102B case 1
.text:0000000140001034 call    cs:__imp_puts
.text:000000014000103A xor     eax, eax
.text:000000014000103C add     rsp, 28h
.text:0000000140001040 retn

.text:0000000140001041 $LN5:
.text:0000000140001041 lea     rcx, string2                    ; jumptable 000000014000102B case 2
.text:0000000140001048 call    cs:__imp_puts
.text:000000014000104E xor     eax, eax
.text:0000000140001050 add     rsp, 28h
.text:0000000140001054 retn

.text:0000000140001055 $LN6:
.text:0000000140001055 lea     rcx, string3                    ; jumptable 000000014000102B case 3
.text:000000014000105C call    cs:__imp_puts
.text:0000000140001062 xor     eax, eax
.text:0000000140001064 add     rsp, 28h
.text:0000000140001068 retn

.text:0000000140001069 $LN7:
.text:0000000140001069 lea     rcx, string4                    ; jumptable 000000014000102B case 4
.text:0000000140001070 call    cs:__imp_puts
.text:0000000140001076 xor     eax, eax
.text:0000000140001078 add     rsp, 28h
.text:000000014000107C retn

.text:000000014000107D $LN8:
.text:000000014000107D lea     rcx, string5                    ; jumptable 000000014000102B case 5
.text:0000000140001084 call    cs:__imp_puts
.text:000000014000108A xor     eax, eax
.text:000000014000108C add     rsp, 28h
.text:0000000140001090 retn

.text:0000000140001091 $LN9:
.text:0000000140001091 lea     rcx, string6                    ; jumptable 000000014000102B case 123
.text:0000000140001098 call    cs:__imp_puts
.text:000000014000109E xor     eax, eax
.text:00000001400010A0 add     rsp, 28h
.text:00000001400010A4 retn

.text:00000001400010A5 def_14000102B:
.text:00000001400010A5 lea     rcx, string7                    ; jumptable 000000014000102B default case, cases 6-122
.text:00000001400010AC call    cs:__imp_puts
.text:00000001400010B2 xor     eax, eax
.text:00000001400010B4 add     rsp, 28h
.text:00000001400010B8 retn

.text:00000001400010BC jpt_14000102B dd offset $LN4 - 140000000h
.text:00000001400010BC dd offset $LN5 - 140000000h             ; jump table for switch statement
.text:00000001400010BC dd offset $LN6 - 140000000h
.text:00000001400010BC dd offset $LN7 - 140000000h
.text:00000001400010BC dd offset $LN8 - 140000000h
.text:00000001400010BC dd offset $LN9 - 140000000h
.text:00000001400010BC dd offset def_14000102B - 140000000h

.text:00000001400010D8 byte_1400010D8 db      0,     1,     2,     3
.text:00000001400010D8 db      4,     6,     6,     6          ; indirect table for switch statement
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     6,     6
.text:00000001400010D8 db      6,     6,     5

函数

x64 调用约定

默认情况下,x64 应用程序二进制接口 (ABI) 使用四寄存器 fast-call 调用约定。 系统在调用堆栈上分配空间作为影子存储,供被调用方保存这些寄存器。

函数调用的参数与用于这些参数的寄存器之间有着严格的一一对应关系。 任何无法放入 8 字节或者不是 1、2、4 或 8 字节的参数都必须按引用传递。 单个参数永远不会分布在多个寄存器中。

整数参数在寄存器 RCXRDXR8R9 中传递。 浮点数参数在 XMM0LXMM1LXMM2LXMM3L 中传递。 16 字节参数按引用传递。

参数传递

前 4 个整数参数从左到右分别在 RCXRDXR8R9 中传递。 如前所述,第 5 个和更高位置的参数在堆栈上传递。 寄存器中的所有整型参数都是向右对齐的,因此被调用方可忽略寄存器的高位,只访问所需的寄存器部分。

.text:0000000140001000 ; __int64 __fastcall func1(int, int, int, int, int e, int f)
.text:0000000140001000 func1 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 lea     eax, [rcx+rdx]				  ; eax = 参数1 + 参数2
.text:0000000140001003 add     eax, r8d						  ; eax = eax + 参数3
.text:0000000140001006 add     eax, r9d						  ; eax = eax + 参数4
.text:0000000140001009 add     eax, [rsp+e]					  ; eax = eax + 参数5
.text:000000014000100D add     eax, [rsp+f]					  ; eax = eax + 参数6
.text:0000000140001011 retn
.text:0000000140001011
.text:0000000140001011 func1 endp

.text:0000000140001020 sub     rsp, 38h
.text:0000000140001024 mov     edx, 2                          ; int 参数2
.text:0000000140001029 mov     dword ptr [rsp+28h], 6          ; int 参数6
.text:0000000140001031 mov     dword ptr [rsp+20h], 5          ; int 参数5
.text:0000000140001039 lea     r9d, [rdx+2]                    ; int 参数4
.text:000000014000103D lea     r8d, [rdx+1]                    ; int 参数3
.text:0000000140001041 lea     ecx, [rdx-1]                    ; int 参数1
.text:0000000140001044 call    func1
.text:0000000140001044
.text:0000000140001049 add     rsp, 38h
.text:000000014000104D retn

前 4 个参数中的所有浮点和双精度参数都在 XMM0 - XMM3(具体视位置而定)中传递。

.text:0000000140001000 ; float __fastcall func2(float, long double, float, long double, float, float)
.text:0000000140001000 func2 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 xorps   xmm4, xmm4
.text:0000000140001003 cvtss2sd xmm4, xmm0			; xmm4 = (double)参数1
.text:0000000140001007 movss   xmm0, [rsp+e]		; xmm0 = 参数5
.text:000000014000100D addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数2 (参数1 + 参数2)
.text:0000000140001011 xorps   xmm1, xmm1
.text:0000000140001014 cvtss2sd xmm1, xmm2			; xmm1 = (double)参数3
.text:0000000140001018 cvtps2pd xmm0, xmm0			; xmm0 = (double)参数5
.text:000000014000101B addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数3 (参数1 + 参数2 + 参数3)
.text:000000014000101F movss   xmm1, [rsp+f]
.text:0000000140001025 cvtps2pd xmm1, xmm1			; xmm1 = (double)参数6
.text:0000000140001028 addsd   xmm4, xmm3			; xmm4 = xmm4 + 参数4 (参数1 + 参数2 + 参数3 + 参数4)
.text:000000014000102C addsd   xmm4, xmm0			; xmm4 = xmm4 + 参数5 (参数1 + 参数2 + 参数3 + 参数4 + 参数5)
.text:0000000140001030 addsd   xmm4, xmm1			; xmm4 = xmm4 + 参数6 (参数1 + 参数2 + 参数3 + 参数4 + 参数5 + 参数6)
.text:0000000140001034 cvtpd2ps xmm0, xmm4			; xmm0 = (float) 参数4
.text:0000000140001038 retn
.text:0000000140001038
.text:0000000140001038 func2 endp

.text:0000000140001040 sub     rsp, 38h
.text:0000000140001044 movss   xmm1, cs:__real@40a00000
.text:000000014000104C movss   xmm0, cs:__real@40c00000
.text:0000000140001054 movsd   xmm3, cs:__real@4010000000000000 ; long double 参数4
.text:000000014000105C movss   xmm2, cs:__real@40400000        ; float 参数3
.text:0000000140001064 movss   dword ptr [rsp+28h], xmm0       ; float 参数6
.text:000000014000106A movss   xmm0, cs:__real@3f800000        ; float 参数1
.text:0000000140001072 movss   dword ptr [rsp+20h], xmm1       ; float 参数5
.text:0000000140001078 movsd   xmm1, cs:__real@4000000000000000 ; long double 参数2
.text:0000000140001080 call    func2
.text:0000000140001080
.text:0000000140001085 cvttss2si eax, xmm0
.text:0000000140001089 add     rsp, 38h
.text:000000014000108D retn

浮点数和整数混合传参,参数会放到对应的整数或浮点寄存器中。例如 fun(int, float, int, float) 函数对应参数会依次放到 ecxxmm1r8dxmm3 寄存器中。

.text:0000000140001000 ; float __fastcall func3(int, long double, int, float, int, float)
.text:0000000140001000 func3 proc near
.text:0000000140001000
.text:0000000140001000 e= dword ptr  28h
.text:0000000140001000 f= dword ptr  30h
.text:0000000140001000
.text:0000000140001000 xorps   xmm2, xmm2
.text:0000000140001003 xorps   xmm0, xmm0
.text:0000000140001006 cvtsi2sd xmm2, ecx			; xmm2 = (double)参数1
.text:000000014000100A cvtsi2sd xmm0, r8d			; xmm0 = (double)参数3
.text:000000014000100F addsd   xmm2, xmm1			; xmm2 = xmm2 + 参数1 (参数1 + 参数2)
.text:0000000140001013 xorps   xmm1, xmm1
.text:0000000140001016 cvtss2sd xmm1, xmm3			; xmm1 = (double) 参数4
.text:000000014000101A addsd   xmm2, xmm0			; xmm2 = xmm2 + 参数3 (参数1 + 参数2 + 参数3)
.text:000000014000101E xorps   xmm0, xmm0
.text:0000000140001021 cvtsi2sd xmm0, [rsp+e]		; xmm0 = (double)参数5
.text:0000000140001027 addsd   xmm2, xmm1			; xmm2 = xmm2 + 参数4 (参数1 + 参数2 + 参数3 + 参数4)
.text:000000014000102B movss   xmm1, [rsp+f]		; xmm1 = 参数6
.text:0000000140001031 cvtps2pd xmm1, xmm1
.text:0000000140001034 addsd   xmm2, xmm0			; xmm2 = xmm2 + xmm0 (参数1 + 参数2 + 参数3 + 参数4 + 参数5)
.text:0000000140001038 addsd   xmm2, xmm1			; xmm2 = xmm2 + xmm1 (参数1 + 参数2 + 参数3 + 参数4 + 参数5 + 参数6)
.text:000000014000103C cvtpd2ps xmm0, xmm2			; xmm0 = (float)xmm2
.text:0000000140001040 retn
.text:0000000140001040
.text:0000000140001040 func3 endp

.text:0000000140001050 sub     rsp, 38h
.text:0000000140001054 movss   xmm0, cs:__real@40c00000
.text:000000014000105C mov     r8d, 3                          ; int 参数3
.text:0000000140001062 movss   xmm3, cs:__real@40800000        ; float 参数4
.text:000000014000106A movsd   xmm1, cs:__real@4000000000000000 ; long double 参数2
.text:0000000140001072 movss   dword ptr [rsp+28h], xmm0       ; float 参数6
.text:0000000140001078 lea     ecx, [r8-2]                     ; int 参数1
.text:000000014000107C mov     dword ptr [rsp+20h], 5          ; int 参数5
.text:0000000140001084 call    func3
.text:0000000140001084
.text:0000000140001089 cvttss2si eax, xmm0
.text:000000014000108D add     rsp, 38h
.text:0000000140001091 retn

__m128 类型、数组和字符串从不通过即时值传递。 而是将分配的内存的指针传递给被调用方。 大小为 8、16、32 或 64 位的结构和联合以及 __m64 类型作为相同大小的整数传递。 其他大小的结构或联合作为指针传递给调用方分配的内存。 对于这些作为指针传递的聚合类型(包括 __m128),调用方分配的临时内存必须是 16 字节对齐的。

例如下面这段代码:

#include<xmmintrin.h>

struct c {
    float a;
    float b;
};

double func4(__m64 a, __m128 b, struct c c, float d, __m128 e, __m128 f){
    return a.m64_f32[0] + b.m128_f32[0] + c.b + d + e.m128_i16[2] + f.m128_i8[4];
}

int main() {
    return func4((__m64){1}, (__m128){2}, (struct c){3}, 4, (__m128){5}, (__m128){6});
}

根据 IDA 反编译代码可知 __m128 类型传的是地址,其余传的是值。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int128 v4; // [rsp+40h] [rbp-48h] BYREF
  __int128 v5; // [rsp+50h] [rbp-38h] BYREF
  __int128 v6; // [rsp+60h] [rbp-28h] BYREF

  v4 = _xmm;
  v5 = _xmm;
  v6 = _xmm;
  return (int)func4((__m64)1i64, (__m128 *)&v6, (c)0x40400000i64, 4.0, (__m128 *)&v5, (__m128 *)&v4);
}

main 函数汇编如下:

.text:0000000140001050 mov     r11, rsp
.text:0000000140001053 sub     rsp, 88h
.text:000000014000105A mov     rax, cs:__security_cookie
.text:0000000140001061 xor     rax, rsp
.text:0000000140001064 mov     [rsp+70h], rax
.text:0000000140001069 movaps  xmm2, cs:__xmm@00000000000000000000000040c00000
.text:0000000140001070 lea     rax, [r11-48h]
.text:0000000140001074 movaps  xmm1, cs:__xmm@00000000000000000000000040a00000
.text:000000014000107B lea     rdx, [r11-28h]										; 参数2 [rsp+0x60] 地址
.text:000000014000107F movaps  xmm0, cs:__xmm@00000000000000000000000040000000
.text:0000000140001086 mov     ecx, 1												; 参数1
.text:000000014000108B movss   xmm3, cs:__real@40800000								; 参数4
.text:0000000140001093 mov     [r11-60h], rax										; 参数6 将地址 [rsp+0x40] 存入 [rsp+0x28]
.text:0000000140001097 lea     rax, [r11-38h]
.text:000000014000109B mov     qword ptr [r11-58h], 40400000h
.text:00000001400010A3 mov     r8, [r11-58h]										; 参数3
.text:00000001400010A7 mov     [r11-68h], rax										; 参数5 将地址 [rsp+0x50] 存入 [rsp+0x20]
.text:00000001400010AB movaps  xmmword ptr [rsp+40h], xmm2							; 初始化参数6
.text:00000001400010B0 movaps  xmmword ptr [rsp+50h], xmm1							; 初始化参数5
.text:00000001400010B5 movaps  xmmword ptr [rsp+60h], xmm0							; 初始化参数2
.text:00000001400010BA call    func4
.text:00000001400010BA
.text:00000001400010BF cvttsd2si eax, xmm0
.text:00000001400010C3 mov     rcx, [rsp+88h+var_18]
.text:00000001400010C8 xor     rcx, rsp                        ; StackCookie
.text:00000001400010CB call    __security_check_cookie
.text:00000001400010CB
.text:00000001400010D0 add     rsp, 88h
.text:00000001400010D7 retn

返回值

可以适应 64 位的标量返回值(包括 __m64 类型)是通过 RAX 返回的。

; __int64 func()
func proc near
mov     eax, 114514h
retn
func endp

非标量类型(包括浮点类型、双精度类型和向量类型,例如 __m128__m128i__m128d)以 XMM0 的形式返回。

; float func(...)
func proc near
movss   xmm0, cs:__real@47dfa900
retn
func endp

用户定义的结构体如果不超过 64 位则放入 RAX 寄存器返回,调用方传入局部结构体变量的地址作为第一个参数,返回值也是该结构体地址。例如下面这段代码:

struct Struct {
    int j, k, l; // Struct1 exceeds 64 bits.
};

struct Struct func(int a,int b,int c) {
    return (struct Struct){a, b, c};
}

int main() {
    return func(1,2,3).j;
}

反编译代码如下,可以看到 func 函数的第一个参数为结构体地址,而这个结构体是 main 函数的一个局部变量。

Struct *__fastcall func(Struct *s, int a, int b, int c)
{
  Struct *ret; // rax

  s->j = a;
  ret = s;
  s->k = b;
  s->l = c;
  return ret;
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  Struct s; // [rsp+20h] [rbp-18h] BYREF

  return func(&s, 1, 2, 3)->j;
}

具体汇编代码如下:

.text:0000000140001000 ; Struct *__fastcall func(Struct *s, int a, int b, int c)
.text:0000000140001000 func proc near
.text:0000000140001000 mov     [rcx], edx
.text:0000000140001002 mov     rax, rcx
.text:0000000140001005 mov     [rcx+4], r8d
.text:0000000140001009 mov     [rcx+8], r9d
.text:000000014000100D retn
.text:000000014000100D
.text:000000014000100D func endp

.text:0000000140001010 sub     rsp, 38h
.text:0000000140001014 mov     edx, 1                          ; int
.text:0000000140001019 lea     rcx, [rsp+20h]                  ; result
.text:000000014000101E lea     r9d, [rdx+2]                    ; int
.text:0000000140001022 lea     r8d, [rdx+1]                    ; int
.text:0000000140001026 call    func
.text:0000000140001026
.text:000000014000102B mov     eax, [rax]
.text:000000014000102D add     rsp, 38h
.text:0000000140001031 retn

变量

局部变量

  • x64 程度的函数中的局部变量大概率会被优化掉或者使用寄存器存储。
  • 如果局部变量存储在栈上,为了保留函数的影子空间,一般会存放在 [rsp+20h] 以上的位置。

全局变量

示例代码如下(为了便于观察去掉了全局变量的C++名称修饰):

#include<random>

extern "C" {
    int x;
    int y = 1;
    int z = (srand(time(NULL)), rand());
}
int main() {
    printf("%d %d %d\n", x, y, z);
    return 0;
}

我们可以看到全局变量 xyz 全部定义在了 .data 段:

.data:0000000140021000 y dd 1

.data:0000000140021AC0 x dd 0 
.data:0000000140021AC4 z dd 0 

.text:0000000140001090 sub     rsp, 28h
.text:0000000140001094 mov     r9d, cs:z
.text:000000014000109B lea     rcx, _Format                    ; "%d %d %d\n"
.text:00000001400010A2 mov     r8d, cs:y
.text:00000001400010A9 mov     edx, cs:x
.text:00000001400010AF call    printf
.text:00000001400010AF
.text:00000001400010B4 xor     eax, eax
.text:00000001400010B6 add     rsp, 28h
.text:00000001400010BA retn

其中 z 变量被 dynamic_initializer_for__z__ 函数初始化:

int dynamic_initializer_for__z__()
{
  unsigned int v0; // eax
  int result; // eax

  v0 = time(0i64);
  srand(v0);
  result = rand();
  z = result;
  return result;
}

在调用 main 函数之前 _scrt_common_main_seh 函数调用了 C++ 的初始化函数 initterm,该函数会依次调用 _xc_a_xc_z 之间的函数指针指向的函数,其中就包括 dynamic_initializer_for__z__ 函数。

另外 C 的初始化函数是 initterm_e(_xi_a, _xi_z),该函数先于 initterm 函数调用。dynamic_initializer_for__z__ 函数指针没有放到 _xi_a_xi_z 之间也说明了 C 语言不支持全局变量 z 这种初始化方式。

// Calls each function in [first, last).  [first, last) must be a valid range of
// function pointers.  Each function is called, in order.
extern "C" void __cdecl _initterm(_PVFV* const first, _PVFV* const last)
{
    for (_PVFV* it = first; it != last; ++it)
    {
        if (*it == nullptr)
            continue;

        (**it)();
    }
}

// Calls each function in [first, last).  [first, last) must be a valid range of
// function pointers.  Each function must return zero on success, nonzero on
// failure.  If any function returns nonzero, iteration stops immediately and
// the nonzero value is returned.  Otherwise all functions are called and zero
// is returned.
//
// If a nonzero value is returned, it is expected to be one of the runtime error
// values (_RT_{NAME}, defined in the internal header files).
extern "C" int __cdecl _initterm_e(_PIFV* const first, _PIFV* const last)
{
    for (_PIFV* it = first; it != last; ++it)
    {
        if (*it == nullptr)
            continue;

        int const result = (**it)();
        if (result != 0)
            return result;
    }

    return 0;
}


    if ( initterm_e(_xi_a, _xi_z) )
      return 0xFFi64;
    initterm(_xc_a, _xc_z);

静态局部变量

首先如果静态全局变量的初始值为常量则等价为已初始化的全局变量。

如果初值不为常量会设置标志位来确保静态局部变量只被初始化一次。

实例代码:

int main(int argc) {
    static int x = argc;
    static int y = argc;
    return (int) &x + (int) &y;
}

反编译代码:

.data:000000014001A000 _Init_global_epoch dd 80000000h

.data:000000014001AA80 x dd 0
.data:000000014001AA84 $TSS0 dd 0
.data:000000014001AA88 y dd 0
.data:000000014001AA8C $TSS1 dd 0   

void __fastcall Init_thread_header(int *pOnce)
{
  AcquireSRWLockExclusive(&g_tss_srw);
  while ( 1 )
  {
    if ( !*pOnce )
    {
      *pOnce = -1;
      goto LABEL_7;
    }
    if ( *pOnce != -1 )
      break;
    SleepConditionVariableSRW(&g_tss_cv, &g_tss_srw, 0xFFFFFFFF, 0);
  }
  *(_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + tls_index) + 4i64) = Init_global_epoch;
LABEL_7:
  ReleaseSRWLockExclusive(&g_tss_srw);
}

void __fastcall Init_thread_footer(int *pOnce)
{
  __int64 v2; // rdx

  AcquireSRWLockExclusive(&g_tss_srw);
  v2 = tls_index;
  *pOnce = ++Init_global_epoch;
  *(_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + v2) + 4i64) = Init_global_epoch;
  ReleaseSRWLockExclusive(&g_tss_srw);
  WakeAllConditionVariable(&g_tss_cv);
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _DWORD *v4; // rdi

  v4 = (_DWORD *)(*((_QWORD *)NtCurrentTeb()->Reserved1[11] + tls_index) + 4i64);
  if ( _TSS0 > *v4 )
  {
    Init_thread_header(&_TSS0);
    if ( _TSS0 == -1 )
    {
      x = argc;
      Init_thread_footer(&_TSS0);
    }
  }
  if ( _TSS1 > *v4 )
  {
    Init_thread_header(&_TSS1);
    if ( _TSS1 == -1 )
    {
      y = argc;
      Init_thread_footer(&_TSS1);
    }
  }
  return (unsigned int)&x + (unsigned int)&y;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137448.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python实现一个简单的桌面倒计时小程序

本章内容主要是利用python制作一个简单的桌面倒计时程序&#xff0c;包含开始、重置 、设置功能。 目录 一、效果演示 二、程序代码 一、效果演示 二、程序代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ author: Roc-xb """import tkin…

汽车ECU的虚拟化技术初探(二)

目录 1.概述 2.U2A虚拟化方案概述 3.U2A的虚拟化功能概述 4.虚拟化辅助功能的使能 5.留坑 1.概述 在汽车ECU的虚拟化技术初探(一)-CSDN博客里&#xff0c;我们聊到虚拟化技术比较关键的就是vECU的虚拟地址翻译问题&#xff0c;例如Cortex-A77就使用MMU来进行虚实地址的转换…

阿里云国际站:专有宿主机

文章目录 一、专有宿主机的概念 二、专有宿主机的优势 三、专有宿主机的应用场景 一、专有宿主机的概念 专有宿主机&#xff08;Dedicated Host&#xff0c;简称DDH&#xff09;是阿里云专为企业用户定制优化的解决方案。具有物理资源独享、部署更灵活、配置更丰富、性价比…

遇到问题,我该如何提问?

作为IT行业的从业者&#xff0c;我们深知程序员在保障系统安全、数据防护以及网络稳定方面所起到的重要作用。他们是现代社会的护城河&#xff0c;用代码构筑着我们的未来。那程序员的护城河又是什么呢&#xff1f;是技术能力的深度&#xff1f;是对创新的追求&#xff1f;还是…

Linux yum,vim详解

yum是什么 yum是一个Linux系统预装的指令&#xff0c;yum的功能是可以对app进行搜索&#xff0c;下载&#xff0c;相当于Linux下的应用商店。 yum是读取Linux中镜像文件中的网页地址&#xff0c;下载用户所输入的命令。 如何使用yum下载软件 yum install -y(所有选项都yes) …

换根dp学习笔记

最近模拟赛经常做到&#xff0c;于是我就学习了一下。 算法原理 换根 d p dp dp的题一般都会给出一个无根树&#xff0c;因为以不同的点为根时&#xff0c;问题的答案不一样&#xff0c;所以它会让你输出答案的最大或最小值。 暴力去做这种题&#xff0c;就是以每个点为根然…

为什么要用“交叉熵”做损失函数

大家好啊&#xff0c;我是董董灿。 今天看一个在深度学习中很枯燥但很重要的概念——交叉熵损失函数。 作为一种损失函数&#xff0c;它的重要作用便是可以将“预测值”和“真实值(标签)”进行对比&#xff0c;从而输出 loss 值&#xff0c;直到 loss 值收敛&#xff0c;可以…

springboot项目使用Swagger3

一、Swagger介绍 号称世界上最流行的Api框架&#xff1b;Restful Api 文档在线自动生成工具>Api文档与API定义同步更新直接运行&#xff0c;可以在在线测试API 接口支持多种语言&#xff1a;&#xff08;java&#xff0c;Php…&#xff09; 二、Swagger3 准备工作 1、在p…

学习c#的第七天

目录 C# 封装 概念 Public 访问修饰符 Private 访问修饰符 Protected 访问修饰符 Internal 访问修饰符 Protected Internal 访问修饰符 总结 C# 封装 概念 在面向对象程序设计中&#xff0c;封装是一种将数据和方法包含在一个单元中&#xff0c;并控制对这些数据和方…

海康Visionmaster-Qt+VS 二次开发环境如何配置?

1 新建 Qt 工程&#xff0c;添加 Qt 模块 Core、GUI、Active Qt 和 Container Widgets 2 拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0\ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 3 第一…

AOMedia发布免版税沉浸音频规范IAMF

11月10日&#xff0c;开放媒体联盟&#xff08;AOMedia&#xff09;发布了旗下首个沉浸式音频规范IAMF&#xff08;https://aomediacodec.github.io/iamf/&#xff09;&#xff0c;IAMF是一种编解码器无关的容器规范&#xff0c;可以携带回放时间渲染算法和音频混音的信息&…

Spring Data JPA 实现集成实体对象数据库的创建、修改时间字段自动更新

JPA提供了一种事件监听器的机制&#xff0c;用于SQL审计&#xff0c;通过监听器我们可以很快速地去自动更新创建时间、修改时间&#xff0c;主要步骤如下&#xff1a; 一、创建基础实体&#xff0c;包含了创建和修改时间&#xff0c;然后让其他真正的实体继承该实体&#xff0…

59基于matlab的爬行动物搜索算法(Reptile search algorithm, RSA)

基于matlab的爬行动物搜索算法&#xff08;Reptile search algorithm, RSA&#xff09;一种新型智能优化算法。该算法主要模拟鳄鱼的捕食行为&#xff0c;来实现寻优求解&#xff0c;具有收敛速度快&#xff0c;寻优能力强的特点。程序已调通&#xff0c;可直接运行。 59matlab…

3分钟带你了解前端缓存-HTTP缓存

前情提要 前端缓存分为下面三大类&#xff0c;本文主要讲解HTTP缓存~ 1. HTTP缓存 强缓存协商缓存 2. 浏览器缓存 本地小容量缓存本地大容量缓存 3. 应用程序缓存 HTML5应用程序缓存 缓存作用 减少了冗余的数据传输减少服务器的负担提高了网站的性能加快加载网页速度 …

JPA Buddy快速创建update、find、count、delete、exists方法

JPA Buddy快速创建update、find、count、delete、exists方法&#xff0c;JPA默认提供的CrudRepository\JpaRepository提供的方法比较少&#xff0c;一般我们会手写一些方法&#xff0c;这里我们选择通过JPA Buddy快速生成&#xff0c;之前文章中讲到了JPA Buddy原本是IDEA收费插…

未来的拥塞控制与 Linux EEVDF 调度器

有破要有立。 前面提到 经典端到端拥塞控制将越来越失效&#xff0c;未来该如何&#xff0c;谈谈我的看法。 端到端拥塞控制的难点根本上是要解决公平性问题&#xff0c;顺带着提高资源利用率。我们很容易理解&#xff0c;在共享资源场景下&#xff0c;不公平一定是低效的&am…

Redis的特性以及使用场景

分布式发展历程参考 陈佬 http://t.csdnimg.cn/yYtWK 介绍redis Redis&#xff08;Remote Dictionary Server&#xff09;是一个基于客户端-服务器架构的在内存中存储数据的中间件&#xff0c;属于NoSQL的一种。它可以用作数据库、缓存/会话存储以及消息队列。 作为一种内存数…

【Pytorch和深度学习】栏目导读

一、栏目说明 本栏目《pytorch实践》是为初学者入门深度学习准备的。本文是该栏目的导读部分&#xff0c;因为计划本栏目在明年完成&#xff0c;因此&#xff0c;导读部分&#xff0c;即本文也在持续更新中。 本栏目设计目标是将深度学习全面用pytorch实践一遍&#xff0c;由浅…

原型模式 rust和java的实现

文章目录 原型模式介绍优点缺点使用场景 实现java 实现rust 实现 rust代码仓库 原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当…

mysql主从复制-使用心得

文章目录 前言环境配置主库从库 STATEMENTbinloggtidlog-errorDistSQL总结 前言 mysql 主从复制使用感受&#xff0c;遇到一些问题的整理&#xff0c;也总结了一些排查问题技巧。 环境 mysql5.7 配置 附&#xff1a;千万级数据快速插入配置可以参考&#xff1a;mysql千万数…