0%

C与汇编的函数传参

C与汇编的函数传参

理论

汇编的分类

  1. 按指令集架构

    1. CISC(复杂指令集计算机),如x86(Intel/AMD)、VAX、Z80、Motorola 68k 等。
    2. RISC(精简指令集计算机),如ARM、MIPS、RISC-V、PowerPC、SPARC 等。
  2. 按处理器位数

    1. 32位汇编,如 x86(IA-32)、ARMv7、MIPS32
    2. 64位汇编,如 x86-64(AMD64)、ARMv8(AArch64)、MIPS64、RISC-V 64
  3. 按汇编风格

    1. Intel 语法,用于 x86/x86-64,指令操作数顺序为 dest, src

    2. AT&T 语法,用于 GNU 汇编(GAS),指令操作数顺序为 src, dest

参数调用规则

在 x86-64 架构下,C 语言和汇编的参数调用规则遵循特定的调用约定(calling convention),通常是 System V AMD64 ABI(在 Linux、macOS 等系统上)或 Microsoft x64 调用约定(在 Windows 上)。

这些调用约定规定了:

  1. 前 6 个参数(System V)或前 4 个参数(Microsoft)通过寄存器传递:
    • System V:RDI, RSI, RDX, RCX, R8, R9
    • Microsoft:RCX, RDX, R8, R9
  2. 超出寄存器范围的参数通过栈传递,从右到左的顺序压栈(这是逆序)。

实例

通过一个函数例子来分析。

汇编代码如下

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
loc_218DD:
sub [r15+9C68h], r13d
add r13d, edx
mov [rsp+6E8h+var_6C4], 0
mov [r15+9C64h], r13d
lea r8, [rsp+6E8h+var_5E8] ; char *
mov byte ptr [r15+r13+24h], 0
mov byte ptr [rcx+2], 0
mov rax, [r15+9C88h]
lea r12, [rsp+6E8h+var_6B8]
mov [rsp+6E8h+var_6D8], r10
sub rsp, 8
mov r9d, 0C8h ; unsigned int
mov [rsp+6F0h+var_6E8], r8
mov rdx, r12 ; char *
mov ecx, 0C8h ; unsigned int
lea rsi, [rax+2]
lea rax, [rsp+6F0h+var_6C4]
mov rdi, rbp ; char *
push rax ; unsigned int *
push 0C8h ; unsigned int
sub rsi, rbp ; unsigned int
lea r11, [rsp+700h+var_378]
push r11 ; char *
mov [rsp+708h+var_6E0], r11
push 0C8h ; unsigned int
lea r14, [rsp+710h+var_448]
push r14 ; char *
push 0C8h ; unsigned int
lea r13, [rsp+720h+var_518]
push r13 ; char *
call _Z22parseRTSPRequestStringPKcjPcjS1_jS1_jS1_jS1_jRj ; parseRTSPRequestString(char const*,uint,char *,uint,char *,uint, | char *,uint,char *,uint,char *,uint,uint &)

对应的C语言的函数如下

1
2
3
4
5
6
7
parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF+2 - fRequestBuffer,
cmdName, sizeof cmdName,
urlPreSuffix, sizeof urlPreSuffix,
urlSuffix, sizeof urlSuffix,
cseq, sizeof cseq,
sessionIdStr, sizeof sessionIdStr,
contentLength);

本文不关心具体的函数实现,主要关注函数传参的对应情况

分析

  1. 汇编代码中的寄存器没有%,说明是intel语法,源操作数和目的操作数的顺序是 dest, src
  2. 出现了 RDIRSI ,所以判断采用了System V AMD64 ABI 而不是Microsoft x64
    • 前者是前 6 个参数使用 RDI, RSI, RDX, RCX, R8, R9传参,剩余的用栈从右往左传参
    • 后者是前 4 个参数使用 RCX, RDX, R8, R9传参,剩余的从右到左的顺序压栈

参数\寄存器\栈

函数parseRTSPRequestString的前六个参数按照顺序使用 RDI, RSI, RDX, RCX, R8, R9传参

参数 寄存器 对应指令
fRequestBuffer RDI mov rdi, rbp
fLastCRLF+2 - fRequestBuffer RSI mov rax, [r15+9C88h]
lea rsi, [rax+2]
sub rsi, rbp
cmdName RDX mov rdx, r12
sizeof cmdName RCX mov ecx, 0C8h
urlPreSuffix R8 lea r8, [rsp+6E8h+var_5E8]
sizeof urlPreSuffix R9 mov r9d, 0C8h

函数parseRTSPRequestString的剩余参数按照逆序用栈传参

参数 对应指令
urlSuffix lea r13, [rsp+720h+var_518]
push r13
sizeof urlSuffix push 0C8h
cseq lea r14, [rsp+710h+var_448]
push r14
sizeof cseq push 0C8h
sessionIdStr lea r11, [rsp+700h+var_378]
push r11
sizeof sessionIdStr push 0C8h
contentLength lea rax, [rsp+6F0h+var_6C4]
push rax

为了便于阅读,将注释后的汇编代码补充如下

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
loc_218DD:
sub [r15+9C68h], r13d
add r13d, edx
mov [rsp+6E8h+var_6C4], 0
mov [r15+9C64h], r13d
lea r8, [rsp+6E8h+var_5E8] ; char * ; urlPreSuffix
mov byte ptr [r15+r13+24h], 0
mov byte ptr [rcx+2], 0
mov rax, [r15+9C88h]
lea r12, [rsp+6E8h+var_6B8]
mov [rsp+6E8h+var_6D8], r10
sub rsp, 8
mov r9d, 0C8h ; unsigned int ; sizeof urlPreSuffix 200 ; r9d是r9的低32位
mov [rsp+6F0h+var_6E8], r8
mov rdx, r12 ; char * ; cmdName
mov ecx, 0C8h ; unsigned int ; sizeof cmdName 200
lea rsi, [rax+2]
lea rax, [rsp+6F0h+var_6C4]
mov rdi, rbp ; char * ; fRequestBuffer
push rax ; unsigned int * ; contentLength
push 0C8h ; unsigned int ; sizeof sessionIdStr 200
sub rsi, rbp ; unsigned int ; fLastCRLF+2 - fRequestBuffer
lea r11, [rsp+700h+var_378]
push r11 ; char * ; sessionIdStr
mov [rsp+708h+var_6E0], r11
push 0C8h ; unsigned int ; sizeof cseq 200
lea r14, [rsp+710h+var_448]
push r14 ; char * ; cseq
push 0C8h ; unsigned int ; sizeof urlSuffix 200
lea r13, [rsp+720h+var_518]
push r13 ; char * ; urlSuffix
call _Z22parseRTSPRequestStringPKcjPcjS1_jS1_jS1_jS1_jRj ; parseRTSPRequestString(char const*,uint,char *,uint,char *,uint, | char *,uint,char *,uint,char *,uint,uint &)