我在研究这个C函数的汇编代码:
int stringlen(char *s1, char *s2) {
int result = 0;
int i = 0;
while (s1[i] != 0) {
result++;
i++;
}
int j = 0;
while (s2[j] != 0) {
result++;
j++;
}
return result;
}
int main(void) {
char s1[] = "this is ";
char s2[] = "a test";
int result = stringlen(s1, s2);
return 0;
}
基于汇编代码,在“LN3”我有,
$LN3:
push rbp
push rsi
push rdi
sub rsp, 336 ; 00000150H
lea rbp, QWORD PTR [rsp+32]
lea rdi, QWORD PTR [rsp+32]
mov ecx, 28
mov eax, -858993460 ; ccccccccH
rep stosd
mov rax, QWORD PTR __security_cookie
xor rax, rbp
mov QWORD PTR __$ArrayPad$[rbp], rax
lea rcx, OFFSET FLAT:__EFF96FC6_main@c
call __CheckForDebuggerJustMyCode
这与
main
功能有关。
第一题:
336
中减去rsp
?然后,我知道
lea
是用来保存地址的,以便于编码(因为使用指针可能容易出错),并且我知道 QWORD
表示四字,它只是表示 4 个字节(即 = 32 位)。
第二题:
rsp + 32
和rbp
中保存相同的地址(在rdi
)?最后2个问题:
我想我知道
ecx
寄存器通常用来保存数组或字符串的维数,但是
为什么我必须将
28
移动到ecx
?同样,
为什么我必须将
-858993460
移动到eax
?这两个数字总是一样的吗?或者它们会根据我创建的功能而改变吗?如果它们发生变化,我怎么知道这些寄存器中的哪个数字移动了?有没有办法计算它们?
您发布的程序集输出与您的代码无关:它似乎是 Microsoft 编译器生成的序言,作为其安全性增强的一部分。除非您对这个特定主题感兴趣,否则您可以忽略此代码并专注于为您的程序生成的实际汇编代码。
您可以使用 Godbolt 的在线编译器资源管理器非常有效地做到这一点
您可以测试不同的编译器和选项。
在链接页面上,您会注意到启用了优化的 clang 编译器只为
main
函数生成了 2 条指令,因为它确定函数调用没有副作用并且没有使用返回值。
在
stringlen
函数中,它生成一条指令 lea rax,[rax + 1]
来递增 rax
而不是 inc rax
或 add rax,1
以避免更新在以下指令中测试的标志 jne .LBB0_7
.
可以这样翻译。我也是装配新手。
顺便说一下,
QWORD
是8字节,DWORD
是4字节。
main :
push rbp
mov rbp, rsp
sub rsp, 32
movabs rax, 2338328219631577204 ; arbitrary value moved to rax
mov QWORD PTR[rbp - 13], rax ; first 8 char in s1
mov BYTE PTR[rbp - 5], 0 ; null byte terminator of s1
mov DWORD PTR[rbp - 20], 1702109281 ; first 4 byte of s2
mov DWORD PTR[rbp - 17], 7631717 ; last 3 byte of s2 including null byte
lea rdx, [rbp - 20] ; s1 decays to char pointer, rdx holds it s1[0] address
lea rax, [rbp - 13] ; s2 decays to char pointer, rax holds it s2[0] address
mov rsi, rdx ; move s1[0]'s address to rsi
mov rdi, rax ; move s2[0]'s address to rdi
call stringlen
mov DWORD PTR[rbp - 4], eax ; get return value from function, put it in int variable
mov eax, 0 ; put 0 to eax, eax will be return value from main()
stringlen:
push rbp
mov rbp, rsp
mov QWORD PTR[rbp - 24], rdi ; move s1's address to char pointer(8 byte)
mov QWORD PTR[rbp - 32], rsi ; move s2's address to char pointer(8 byte)
// no 7 byte differences between [rbp - 13] && [rbp - 20] like in main()
mov DWORD PTR[rbp - 4], 0 ; move 0 to int variable (int result)
mov DWORD PTR[rbp - 8], 0 ; move 0 to int variable (int i)
jmp.L2
.L3:
add DWORD PTR[rbp - 4], 1 ; increase result++
add DWORD PTR[rbp - 8], 1 ; increase i++
.L2 :
mov eax, DWORD PTR[rbp - 8] ; move (int i) to eax // firstLoop[int i = 0]
movsx rdx, eax ; move sign extended eax to rdi
// that integer(int i) can be used as negative too (that is why sign extend i guess)
mov rax, QWORD PTR[rbp - 24] ; move s1's address to rax
add rax, rdx ; (ptr + 0) // pointer aritmetics rax = s1[0]'s address
movzx eax, BYTE PTR[rax] ; get s[0] first char of s1[](char array) zero extend to eax
// zero extending says that i will be used as unsigned char
// that 1 byte will be used from 0 to 255 for ASCII Values
test al, al ; bitwise AND operation && s[i] && s[i] == 0 ZF set
// if ZF set je will be chosen else jne.L3 will be chosen
// loop begins
jne.L3
// when int i's value become 8 which s[8] == '\0' it will get out of the loop and continiues
mov DWORD PTR[rbp - 12], 0 ; move 0 to int variable (result)
jmp.L4
.L4 :
mov eax, DWORD PTR[rbp - 12]
movsx rdx, eax
mov rax, QWORD PTR[rbp - 32]
add rax, rdx
movzx eax, BYTE PTR[rax]
test al, al
jne.L5
mov eax, DWORD PTR[rbp - 4] ; return (int result) to main() inside eax;
.L5:
add DWORD PTR[rbp - 4], 1
add DWORD PTR[rbp - 12], 1