动态内存管理练习题的反汇编代码分析(底层)

目录

1.练习题回顾

2.反汇编代码

3.分析

lea指令的作用

1.给普通指针赋值

反汇编显示

2.给结构体指针赋值

反汇编显示

[mov 指令的作用](#mov 指令的作用)

1.取普通指针指向地址的值(等价为C语言的*)

反汇编显示

2.取结构体指针指向地址里的值

反汇编显示

3.总结->的作用

4.回到本文分析

[细节分析mov ecx,0Ch](#细节分析mov ecx,0Ch)

解释

对rdi重新恢复为0的解释


1.练习题回顾

求下列代码的执行结果

cpp 复制代码
#include <stdio.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

int main()
{
	Test();
	return 0;
}

2.反汇编代码

VS2022+x64+debug

cpp 复制代码
#include <stdio.h>
char* GetMemory(void)
{
 push        rbp  
 push        rsi  
 push        rdi  
 sub         rsp,110h  
 lea         rbp,[rsp+20h]  
 lea         rdi,[rsp+20h]  
 mov         ecx,0Ch  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr [rdi]  
 mov         rax,qword ptr [__security_cookie (07FF68B18D000h)]  
 xor         rax,rbp  
 mov         qword ptr [rbp+0E8h],rax  
 lea         rcx,[__2D923C74_FileName@c (07FF68B192008h)]  
 call        __CheckForDebuggerJustMyCode (07FF68B181375h)  
 nop  
	char p[] = "hello world";
 lea         rax,[p]  
 lea         rcx,[string "hello world" (07FF68B18ACA8h)]  
 mov         rdi,rax  
 mov         rsi,rcx  
 mov         ecx,0Ch  
 rep movs    byte ptr [rdi],byte ptr [rsi]  
	return p;
 lea         rax,[p]  
}
 mov         rdi,rax  
 lea         rcx,[rbp-20h]  
 lea         rdx,[__xt_z+1E0h (07FF68B18AC80h)]  
 call        _RTC_CheckStackVars (07FF68B181311h)  
 mov         rax,rdi  
 mov         rcx,qword ptr [rbp+0E8h]  
 xor         rcx,rbp  
 call        __security_check_cookie (07FF68B1811B8h)  
 lea         rsp,[rbp+0F0h]  
 pop         rdi  
 pop         rsi  
 pop         rbp  
 ret  
............
void Test(void)
{
 push        rbp  
 push        rdi  
 sub         rsp,108h  
 lea         rbp,[rsp+20h]  
 lea         rcx,[__2D923C74_FileName@c (07FF68B192008h)]  
 call        __CheckForDebuggerJustMyCode (07FF68B181375h)  
 nop  
	char* str = NULL;
 mov         qword ptr [str],0  
	str = GetMemory();
 call        GetMemory (07FF68B18106Eh)  
 mov         qword ptr [str],rax  
	printf(str);
 mov         rcx,qword ptr [str]  
 call        printf (07FF68B18119Fh)  
 nop  
}
 lea         rsp,[rbp+0E8h]  
 pop         rdi  
 pop         rbp  
 ret  
............
int main()
{
 push        rbp  
 push        rdi  
 sub         rsp,0E8h  
 lea         rbp,[rsp+20h]  
 lea         rcx,[__2D923C74_FileName@c (07FF68B192008h)]  
 call        __CheckForDebuggerJustMyCode (07FF68B181375h)  
 nop  
	Test();
 call        Test (07FF68B18118Bh)  
 nop  
	return 0;
 xor         eax,eax  
}
 lea         rsp,[rbp+0C8h]  
 pop         rdi  
 pop         rbp  
 ret  

3.分析

在深入讲解之前,补充没有讲过的指令:lea,以及lea和mov指令的对比

8086的指令集是这样说的:

lea指令的全称:load effective address,加载有效地址(常用于C语言的&取地址)

mov指令的全称:move

lea指令的作用

1.给普通指针赋值

复制以下代码到VS2022以x86+debug环境调试

cpp 复制代码
int main()
{
    int a = 0;//映射(映射要加粗)到一个内存地址
    int* p = &a;
    return 0;
}

反汇编显示

cpp 复制代码
    int a = 0;
mov         dword ptr [a],0  
    int* p = &a;
lea         eax,[a]  
mov         dword ptr [p],eax

备注:称为普通指针的原因是因为和结构体指针做区别

这里的lea eax,[a]

[a]代表a的地址,lea的含义:将a的地址加载到eax寄存器中

2.给结构体指针赋值

复制以下代码到VS2022以x86+debug环境调试

cpp 复制代码
typedef struct INFO
{
    char c;
    int id;
    float f;
}INFO;

int main()
{
    INFO info = { 'A',100,6.6f };
    INFO* pInfo = &info;
    return 0;
}

反汇编显示

cpp 复制代码
    INFO* pInfo = &info;
 lea         eax,[info]  
 mov         dword ptr [pInfo],eax 

注意到lea eax,[info],为结构体指针赋值

mov 指令的作用

1.取普通指针指向地址的值(等价为C语言的*)

复制以下下代码到VS2022以x86+debug环境调试

cpp 复制代码
int main()
{
	int iNUM = 0;
	int* pNUM = &iNUM;
	int flag = *pNUM;
	return 0;
}

反汇编显示

cpp 复制代码
int iNUM = 0;
mov         dword ptr [iNUM],0  
    int* pNUM = &iNUM;
lea         eax,[iNUM]  
mov         dword ptr [pNUM],eax  
    int flag = *pNUM;
mov         eax,dword ptr [pNUM]  
mov         ecx,dword ptr [eax]  
mov         dword ptr [flag],ecx  

对dword ptr [...]的解释:mov dword ptr [iNUM],0 把0赋值到iNUM指向的地址中(这里的[pNUM]为普通指针,dword ptr [iNUM]就是普通指针指向地址的值)

注意到最后三个指令:

mov eax,dword ptr [pNUM]

mov ecx,dword ptr [eax]

mov dword ptr [flag],ecx

这里由ecx做中转寄存器(x86环境不支持mov dword ptr [flag],dword ptr [eax])

2.取结构体指针指向地址里的值

复制以下代码到VS2022以x86+debug环境调试

cpp 复制代码
typedef struct INFO
{
    char c;
    int id;
    float f;
}INFO;

int main()
{
    INFO info = { 'A',100,6.6f };
    INFO* pInfo = &info;
    char c = pInfo->c;
    return 0;
}

反汇编显示

cpp 复制代码
    INFO info = { 'A',100,6.6f };
mov         byte ptr [info],41h  
mov         dword ptr [ebp-10h],64h  
movss       xmm0,dword ptr [__real@40d33333 (0B77BCCh)]  
movss       dword ptr [ebp-0Ch],xmm0  
    INFO* pInfo = &info;
lea         eax,[info]  
mov         dword ptr [pInfo],eax  
    char c = pInfo->c;
mov         eax,dword ptr [pInfo]  
mov         cl,byte ptr [eax]  
mov         byte ptr [c],cl  

注意到最后三个指令均为mov指令
-> 为结构体的特有的符号,虽然不用*表示,但确有*的作用

备注:mov cl,byte ptr [eax] (byte对应cl寄存器)

在结尾处添加以下代码重新调试

cpp 复制代码
	char c = pInfo->c; 
	int id = pInfo->id;

对比这两行代码反汇编的不同之处

cpp 复制代码
    char c = pInfo->c;
mov         eax,dword ptr [pInfo]  
mov         cl,byte ptr [eax]  
mov         byte ptr [c],cl  
    int id = pInfo->id;
mov         eax,dword ptr [pInfo]  
mov         ecx,dword ptr [eax+4]  
mov         dword ptr [id],ecx  

注意到:

mov cl,byte ptr [eax+0 ]

mov ecx,dword ptr [eax+4 ]

发现eax加的偏移量不同(之前讲过结构体的内存对齐,见63.【C语言】再议结构体(上)文章)

对于char c = pInfo->c;结构体首元素从偏移量为0处开始存储

对于int id = pInfo->id;id的对齐数为4,VS的默认对齐数为8,4<8从偏移量为4的整数倍开始存储

备注:一个空格的存储空间为1个字节

3.总结->的作用

1.取结构体的首地址

2.首地址+成员变量的偏移(例如:eax+?)

3.mov取得到地址里面的值,刚好得到了成员变量的值


4.回到本文分析

由上方lea指令的讲解可知

lea rax,[p]

将p的地址加载到rax中

lea rcx,[string "hello world" (07FF68B18ACA8h)]

将已经写入内存的hello world字符串的首字符的地址加载至rcx中

mov rdi,rax

mov rsi,rcx

rax赋值给rdi,rcx赋值给rsi

mov ecx,0Ch

细节分析mov ecx,0Ch

如果调试的的话,会看到以下奇怪的现象

修改ecx会导致rcx整体都变,ecx为rcx的低32位,那rcx的高32位应该保持不变才对

解释

Intel规定:对ecx的写操作不仅修改了rcx的低32位,还会自动将rcx的高32位清零

详见开发手册,网站:http://x86asm.net/articles/x86-64-tour-of-intel-manuals/

下面截取的为关键信息


在ecx中设置计数次数为0Ch次"hello world\0"一共12个字符(不要忘记隐含的\0)

rep movs byte ptr [rdi],byte ptr [rsi]

[rdi]和[rsi]为指针,从[rsi]处重复复制(rep movs)字节(一次复制一个字节)至[rdi]处

重复复制rcx(0Ch)次,注意:每复制一次,rdi+1,rsi+1,rcx-1

注意:rep指令这里和8086CPU有所不同,VS中默认每复制一次指针+1(运行环境已经提前设置好了),但是8086CPU要设置方向(cld或std)

............

mov rdi,rax

............

pop rdi

虽然恢复了rdi指针的值使其指向了复制过后的字符串的首字符,但是最后出栈的值给了rdi,

rdi重新恢复为0,指针丢失,因此str为野指针

对rdi重新恢复为0的解释

x86+debug环境,VS打开调试模式

有push,就有pop

cpp 复制代码
;保存rbp,rsi,rdi的值
push        rbp  
push        rsi  
push        rdi  
-------------------------------------------------------------------------------------------
;恢复rbp,rsi,rdi原来的值
pop         rdi  
pop         rsi  
pop         rbp  

执行push rbp之前,查看寄存器

注意到rdi=0,因此在GetMemroy函数的最后pop rdi时,rdi的值恢复为0

相关推荐
Crossoads4 小时前
【汇编语言】call 和 ret 指令(一) —— 探讨汇编中的ret和retf指令以及call指令及其多种转移方式
android·开发语言·javascript·汇编·人工智能·数据挖掘·c#
码农飞飞6 小时前
详解Rust结构体struct用法
开发语言·数据结构·后端·rust·成员函数·方法·结构体
—Miss. Z—15 小时前
C语言中用指针输入字符串
c语言·字符串·指针
Crossoads1 天前
【汇编语言】转移指令的原理(三) —— 汇编跳转指南:jcxz、loop与位移的深度解读
android·汇编·人工智能·redis·单片机·深度学习·机器学习
花之亡灵2 天前
.net 7.0 解决“The keyword field is required”的问题
windows·visualstudio·c#·asp.net
zhuqiyua2 天前
深入解析Kernel32.dll与Msvcrt.dll
汇编·microsoft·windbg·二进制·dll
WXDWIN.2 天前
C++语言之模版与类型转换
c语言·开发语言·c++·visualstudio·visual studio code
Crossoads3 天前
【汇编语言】数据处理的两个基本问题(三) —— 汇编语言的艺术:从div,dd,dup到结构化数据的访问
android·linux·运维·服务器·汇编·机器学习·数据挖掘
Crossoads3 天前
【汇编语言】数据处理的两个基本问题(二) —— 解密汇编语言:数据长度与寻址方式的综合应用
android·java·开发语言·javascript·汇编·数据挖掘·c#
咩咩觉主4 天前
C# VS的常用功能(一) 视图篇
visualstudio·c#·vs是宇宙第一好用的编辑器