驱动下怎么调用ZwReadVirtualMemory函数

驱动下怎么调用ZwReadVirtualMemory函数,第1张

获取DLL中的函数地址 

lpFunctionName        函数名称

pDllName                DLL文件名称

/

/

        原理:  R0下没有LoadLibrary函数

                        利用ZwCreateFile打开文件

                        利用ZwCreateSection创建区段

                        利用ZwMapViewOfSection映射区段到当前进程的虚拟内存

                        定位PE Header地址

                        定位第一个数据目录

                        到EAT导出表

                        搜索函数名,定位函数地址

/

DWORD GetDllFunctionId(char lpFunctionName, PUNICODE_STRING pDllName) 

        HANDLE  hSection, hFile, hMod; 

        //        SECTION_IMAGE_INFORMATION sii; 

        IMAGE_DOS_HEADER dosheader; 

        IMAGE_OPTIONAL_HEADER opthdr; 

        IMAGE_EXPORT_DIRECTORY pExportTable; 

        DWORD arrayOfFunctionAddresses; 

        DWORD arrayOfFunctionNames; 

        WORD arrayOfFunctionOrdinals; 

        DWORD functionOrdinal; 

        DWORD Base, x, functionAddress; 

        char functionName; 

        STRING  ntFunctionName, ntFunctionNameSearch; 

        PVOID BaseAddress = NULL; 

        SIZE_T size=0; 

        

        OBJECT_ATTRIBUTES oa = {sizeof oa, 0, pDllName, OBJ_CASE_INSENSITIVE}; 

        IO_STATUS_BLOCK iosb; 

        NTSTATUS status = STATUS_UNSUCCESSFUL;

        

        ZwOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); 

        

        oaObjectName = 0; 

        //小红伞杀ZwCreateSection +ZwMapViewOfSection

        ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0,PAGE_EXECUTE, SEC_IMAGE, hFile); 

        

        ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 1000, 0, &size, (SECTION_INHERIT)1, 

                                                         MEM_TOP_DOWN, PAGE_READWRITE); 

        ZwClose(hFile); 

        

        hMod = BaseAddress; 

                                

        dosheader = (IMAGE_DOS_HEADER )hMod; 

                                

        opthdr =(IMAGE_OPTIONAL_HEADER ) ((BYTE)hMod+dosheader->e_lfanew+24); 

                                

        pExportTable =(IMAGE_EXPORT_DIRECTORY)((BYTE) hMod + opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT] 

                

                VirtualAddress); 

                                

        arrayOfFunctionAddresses = (DWORD)( (BYTE)hMod + pExportTable->AddressOfFunctions); 

                                

        arrayOfFunctionNames = (DWORD)( (BYTE)hMod + pExportTable->AddressOfNames); 

                                

        arrayOfFunctionOrdinals = (WORD)( (BYTE)hMod + pExportTable->AddressOfNameOrdinals); 

                                

        Base = pExportTable->Base; 

                                

        RtlInitString(&ntFunctionNameSearch, lpFunctionName); 

                                

        for(x = 0; x < pExportTable->NumberOfFunctions; x++) 

                                { 

                functionName = (char)( (BYTE)hMod + arrayOfFunctionNames[x]); 

                

                RtlInitString(&ntFunctionName, functionName); 

                

                functionOrdinal = arrayOfFunctionOrdinals[x] + Base - 1; 

                functionAddress = (DWORD)( (BYTE)hMod + arrayOfFunctionAddresses[functionOrdinal]); 

                if (RtlCompareString(&ntFunctionName, &ntFunctionNameSearch, TRUE) == 0) 

                { 

                        ZwClose(hSection); 

                        return functionAddress; 

                } 

        } 

                                

        ZwClose(hSection); 

        return 0; 

根据DLL中函数的地址 找到在SSDT中索引号

lpFunctionName        函数名称

/

PVOID  GetSSDTFunctionAddress(char lpFunctionName){

        DWORD FunctionId = 0;

        ULONG Pread = 0;

        UNICODE_STRING DllName;

        RtlInitUnicodeString(&DllName,L"\\Device\\HarddiskVolume1\\Windows\\System32\\ntdlldll");

        FunctionId=GetDllFunctionId(lpFunctionName,&DllName);

        Pread=((WORD )(FunctionId+1));

        

        return (KeServiceDescriptorTable->ServiceTable[Pread]);

}

以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很罕见的。  按照惯例,这个列表以man pages第2节,即系统调用节为蓝本。按照笔者的理解,对其作了大致的分类,同时也作了一些小小的修改,删去了几个仅供内核使用,不允许用户调用的系统调用,对个别本人稍觉不妥的地方作了一些小的修改,并对所有列出的系统调用附上简要注释。    其中有一些函数的作用完全相同,只是参数不同。(可能很多熟悉C++朋友马上就能联想起函数重载,但是别忘了Linux核心是用C语言写的,所以只能取成不同的函数名)。还有一些函数已经过时,被新的更好的函数所代替了(gcc在链接这些函数时会发出警告),但因为兼容的原因还保留着,这些函数我会在前面标上“”号以示区别。一、进程控制:fork 创建一个新进程 clone 按指定条件创建子进程 execve 运行可执行文件 exit 中止进程 _exit 立即中止当前进程 getdtablesize 进程所能打开的最大文件数 getpgid 获取指定进程组标识号 setpgid 设置指定进程组标志号 getpgrp 获取当前进程组标识号 setpgrp 设置当前进程组标志号 getpid 获取进程标识号 getppid 获取父进程标识号 getpriority 获取调度优先级 setpriority 设置调度优先级 modify_ldt 读写进程的本地描述表 nanosleep 使进程睡眠指定的时间 nice 改变分时进程的优先级 pause 挂起进程,等待信号 personality 设置进程运行域 prctl 对进程进行特定 *** 作 ptrace 进程跟踪 sched_get_priority_max 取得静态优先级的上限 sched_get_priority_min 取得静态优先级的下限 sched_getparam 取得进程的调度参数 sched_getscheduler 取得指定进程的调度策略 sched_rr_get_interval 取得按RR算法调度的实时进程的时间片长度 sched_setparam 设置进程的调度参数 sched_setscheduler 设置指定进程的调度策略和参数 sched_yield 进程主动让出处理器,并将自己等候调度队列队尾 vfork 创建一个子进程,以供执行新程序,常与execve等同时使用 wait 等待子进程终止 wait3 参见wait waitpid 等待指定子进程终止 wait4 参见waitpid capget 获取进程权限 capset 设置进程权限 getsid 获取会晤标识号 setsid 设置会晤标识号 二、文件系统控制1、文件读写 *** 作fcntl 文件控制 open 打开文件 creat 创建新文件 close 关闭文件描述字 read 读文件 write 写文件 readv 从文件读入数据到缓冲数组中 writev 将缓冲数组里的数据写入文件 pread 对文件随机读 pwrite 对文件随机写 lseek 移动文件指针 _llseek 在64位地址空间里移动文件指针 dup 复制已打开的文件描述字 dup2 按指定条件复制文件描述字 flock 文件加/解锁 poll I/O多路转换 truncate 截断文件 ftruncate 参见truncate umask 设置文件权限掩码 fsync 把文件在内存中的部分写回磁盘 2、文件系统 *** 作access 确定文件的可存取性 chdir 改变当前工作目录 fchdir 参见chdir chmod 改变文件方式 fchmod 参见chmod chown 改变文件的属主或用户组 fchown 参见chown lchown 参见chown chroot 改变根目录 stat 取文件状态信息 lstat 参见stat fstat 参见stat statfs 取文件系统信息 fstatfs 参见statfs readdir 读取目录项 getdents 读取目录项 mkdir 创建目录 mknod 创建索引节点 rmdir 删除目录 rename 文件改名 link 创建链接 symlink 创建符号链接 unlink 删除链接 readlink 读符号链接的值 mount 安装文件系统 umount 卸下文件系统 ustat 取文件系统信息 utime 改变文件的访问修改时间 utimes 参见utime quotactl 控制磁盘配额 三、系统控制ioctl I/O总控制函数 _sysctl 读/写系统参数 acct 启用或禁止进程记账 getrlimit 获取系统资源上限 setrlimit 设置系统资源上限 getrusage 获取系统资源使用情况 uselib 选择要使用的二进制函数库 ioperm 设置端口I/O权限 iopl 改变进程I/O权限级别 outb 低级端口 *** 作 reboot 重新启动 swapon 打开交换文件和设备 swapoff 关闭交换文件和设备 bdflush 控制bdflush守护进程 sysfs 取核心支持的文件系统类型 sysinfo 取得系统信息 adjtimex 调整系统时钟 alarm 设置进程的闹钟 getitimer 获取计时器值 setitimer 设置计时器值 gettimeofday 取时间和时区 settimeofday 设置时间和时区 stime 设置系统日期和时间 time 取得系统时间 times 取进程运行时间 uname 获取当前UNIX系统的名称、版本和主机等信息 vhangup 挂起当前终端 nfsservctl 对NFS守护进程进行控制 vm86 进入模拟8086模式 create_module 创建可装载的模块项 delete_module 删除可装载的模块项 init_module 初始化模块 query_module 查询模块信息 get_kernel_syms 取得核心符号,已被query_module代替 四、内存管理brk 改变数据段空间的分配 sbrk 参见brk mlock 内存页面加锁 munlock 内存页面解锁 mlockall 调用进程所有内存页面加锁 munlockall 调用进程所有内存页面解锁 mmap 映射虚拟内存页 munmap 去除内存页映射 mremap 重新映射虚拟内存地址 msync 将映射内存中的数据写回磁盘 mprotect 设置内存映像保护 getpagesize 获取页面大小 sync 将内存缓冲区数据写回硬盘 cacheflush 将指定缓冲区中的内容写回磁盘 五、网络管理getdomainname 取域名 setdomainname 设置域名 gethostid 获取主机标识号 sethostid 设置主机标识号 gethostname 获取本主机名称 sethostname 设置主机名称 六、socket控制socketcall socket系统调用 socket 建立socket bind 绑定socket到端口 connect 连接远程主机 accept 响应socket连接请求 send 通过socket发送信息 sendto 发送UDP信息 sendmsg 参见send recv 通过socket接收信息 recvfrom 接收UDP信息 recvmsg 参见recv listen 监听socket端口 select 对多路同步I/O进行轮询 shutdown 关闭socket上的连接 getsockname 取得本地socket名字 getpeername 获取通信对方的socket名字 getsockopt 取端口设置 setsockopt 设置端口参数 sendfile 在文件或端口间传输数据 socketpair 创建一对已联接的无名socket 七、用户管理getuid 获取用户标识号 setuid 设置用户标志号 getgid 获取组标识号 setgid 设置组标志号 getegid 获取有效组标识号 setegid 设置有效组标识号 geteuid 获取有效用户标识号 seteuid 设置有效用户标识号 setregid 分别设置真实和有效的的组标识号 setreuid 分别设置真实和有效的用户标识号 getresgid 分别获取真实的,有效的和保存过的组标识号 setresgid 分别设置真实的,有效的和保存过的组标识号 getresuid 分别获取真实的,有效的和保存过的用户标识号 setresuid 分别设置真实的,有效的和保存过的用户标识号 setfsgid 设置文件系统检查时使用的组标识号 setfsuid 设置文件系统检查时使用的用户标识号 getgroups 获取后补组标志清单 setgroups 设置后补组标志清单 八、进程间通信ipc 进程间通信总控制调用 1、信号sigaction 设置对指定信号的处理方法 sigprocmask 根据参数对信号集中的信号执行阻塞/解除阻塞等 *** 作 sigpending 为指定的被阻塞信号设置队列 sigsuspend 挂起进程等待特定信号 signal 参见signal kill 向进程或进程组发信号 sigblock 向被阻塞信号掩码中添加信号,已被sigprocmask代替 siggetmask 取得现有阻塞信号掩码,已被sigprocmask代替 sigsetmask 用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替 sigmask 将给定的信号转化为掩码,已被sigprocmask代替 sigpause 作用同sigsuspend,已被sigsuspend代替 sigvec 为兼容BSD而设的信号处理函数,作用类似sigaction ssetmask ANSI C的信号处理函数,作用类似sigaction 2、消息msgctl 消息控制 *** 作 msgget 获取消息队列 msgsnd 发消息 msgrcv 取消息 3、管道pipe 创建管道 4、信号量semctl 信号量控制 semget 获取一组信号量 semop 信号量 *** 作 5、共享内存shmctl 控制共享内存 shmget 获取共享内存 shmat 连接共享内存 shmdt 拆卸共享内存

在实现过程中,也有点拿不稳,特别是用队列或栈来存储树的结点(也是指针)时,为了确保没问题,特别是内存的分配,我搜索并安装了Virtual Leak Detector,一个开源的内存泄漏检测工具。

初识Visual Leak Detector

灵活自由是C/C++语言的一大特色,而这也为C/C++程 序员出了一个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严 重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现。然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是 惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行。另外内存问题的一个共同特点是,内存问题本身并不会有很明显的现象,当有异常现象出现时 已时过境迁,其现场已非出现问题时的现场了,这给调试内存问题带来了很大的难度。< xmlnamespace prefix ="o" />

Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。可以在http://wwwcodeprojectcom/tools/visualleakdetectorasp 下载到。相比较其它的内存泄露检测工具,它在检测到内存泄漏的同时,还具有如下特点:

1、 可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在文件及行号;

2、 可以得到泄露内存的完整数据;

3、 可以设置内存泄露报告的级别;

4、 它是一个已经打包的lib,使用时无须编译它的源代码。而对于使用者自己的代码,也只需要做很小的改动;

5、 他的源代码使用GNU许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理的读者,是一个不错的选择。

可见,从使用角度来讲,Visual Leak Detector简单易用,对于使用者自己的代码,唯一的修改是#include Visual Leak Detector的头文件后正常运行自己的程序,就可以发现内存问题。从研究的角度来讲,如果深入Visual Leak Detector源代码,可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存 *** 作的常用技巧等。

本文首先将介绍Visual Leak Detector的使用方法与步骤,然后再和读者一起初步的研究Visual Leak Detector的源代码,去了解Visual Leak Detector的工作原理。

< xmlnamespace prefix ="v" />

使用Visual Leak Detector(10)

下面让我们来介绍如何使用这个小巧的工具。

首先从网站上下载zip包,解压之后得到vldh, vldapih, vldlib, vldmtlib, vldmtdlllib, dbghelpdll等文件。将h文件拷贝到Visual C++的默认include目录下,将lib文件拷贝到Visual C++的默认lib目录下,便安装完成了。因为版本问题,如果使用windows 2000或者以前的版本,需要将dbghelpdll拷贝到你的程序的运行目录下,或其他可以引用到的目录。

注:我下载的是较新版19,直接安装到系统中。因此使用时必须先在VC中设置一下目录。

接下来需要将其加入到自己的代码中。方法很简单,只要在包含入口函数的cpp文件中包含vldh就可以。如果这个cpp文件包含了stdafxh,则将包含vldh的语句放在stdafxh的包含语句之后,否则放在最前面。如下是一个示例程序:

#include <vldh>

void main()

{

}

接下来让我们来演示如何使用Visual Leak Detector检测内存泄漏。下面是一个简单的程序,用new分配了一个int大小的堆内存,并没有释放。其申请的内存地址用printf输出到屏幕上。

编译运行后,在标准输出窗口得到:

p=003a89c0

在Visual C++的Output窗口得到:

WARNING: Visual Leak Detector detected memory leaks!

---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57号块0x003A89C0地址泄漏了4个字节

Call Stack: --下面是调用堆栈

d:/test/testvldconsole/testvldconsole/maincpp (7): f --表示在maincpp第7行的f()函数

d:/test/testvldconsole/testvldconsole/maincpp (14): main –双击以引导至对应代码处

f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexec (586): __tmainCRTStartup

f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexec (403): mainCRTStartup

0x7C816D4F (File and line number not available): RegisterWaitForInputIdle

Data: --这是泄漏内存的内容,0x12345678

78 56 34 12 xV4

Visual Leak Detector detected 1 memory leak

第二行表示57号块有4字节的内存泄漏,地址为0x003A89C0,根据程序控制台的输出,可以知道,该地址为指针p。程序的第7行,f()函数里,在该地址处分配了4字节的堆内存空间,并赋值为0x12345678,这样在报告中,我们看到了这4字节同样的内容。

可以看出,对于每一个内存泄漏,这个报告列出了它的泄漏点、长度、分配该内存时的调用堆栈、和泄露内存的内容(分别以16进制和文本格式列出)。双击该堆栈报告的某一行,会自动在代码编辑器中跳到其所指文件的对应行。这些信息对于我们查找内存泄露将有很大的帮助。

这是一个很方便易用的工具,安装后每次使用时,仅仅需要将它头文件包含进来重新build就可以。而且,该工具仅在build Debug版的时候会连接到你的程序中,如果build Release版,该工具不会对你的程序产生任何性能等方面影响。所以尽可以将其头文件一直包含在你的源代码中。

Visual Leak Detector工作原理

下面让我们来看一下该工具的工作原理。

在这之前,我们先来看一下Visual C++内置的内存泄漏检测工具是如何工作的。Visual C++内置的工具CRT Debug Heap工作原来很简单。在使用Debug版的malloc分配内存时,malloc会在内存块的头中记录分配该内存的文件名及行号。当程序退出时CRT会在main()函数返回之后做一些清理工作,这个时候来检查调试堆内存,如果仍然有内存没有被释放,则一定是存在内存泄漏。从这些没有被释放的内存块的头中,就可以获得文件名及行号。

这种静态的方法可以检测出内存泄漏及其泄漏点的文件名和行号,但是并不知道泄漏究竟是如何发生的,并不知道该内存分配语句是如何被执行到的。要想了解这些,就必须要对程序的内存分配过程进行动态跟踪。Visual Leak Detector就是这样做的。它在每次内存分配时将其上下文记录下来,当程序退出时,对于检测到的内存泄漏,查找其记录下来的上下文信息,并将其转换成报告输出。

初始化

Visual Leak Detector要记录每一次的内存分配,而它是如何监视内存分配的呢?Windows提供了分配钩子(allocation hooks)来监视调试堆内存的分配。它是一个用户定义的回调函数,在每次从调试堆分配内存之前被调用。在初始化时,Visual Leak Detector使用_CrtSetAllocHook注册这个钩子函数,这样就可以监视从此之后所有的堆内存分配了。

如何保证在Visual Leak Detector初始化之前没有堆内存分配呢?全局变量是在程序启动时就初始化的,如果将Visual Leak Detector作为一个全局变量,就可以随程序一起启动。但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分配,则可能无法检测到。Visual Leak Detector使用了C/C++提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始化的概率。根据#pragma init_seg的定义,全局变量的初始化分三个阶段:首先是compiler段,一般c语言的运行时库在这个时候初始化;然后是lib段,一般用于第三方的类库的初始化等;最后是user段,大部分的初始化都在这个阶段进行。Visual Leak Detector将其初始化设置在compiler段,从而使得它在绝大多数全局变量和几乎所有的用户定义的全局变量之前初始化。

记录内存分配

一个分配钩子函数需要具有如下的形式:

int YourAllocHook( int allocType, void userData, size_t size, int blockType, long requestNumber, const unsignedchar filename, int lineNumber);

就像前面说的,它在Visual Leak Detector初始化时被注册,每次从调试堆分配内存之前被调用。这个函数需要处理的事情是记录下此时的调用堆栈和此次堆内存分配的唯一标识——requestNumber。

得到当前的堆栈的二进制表示并不是一件很复杂的事情,但是因为不同体系结构、不同编译器、不同的函数调用约定所产生的堆栈内容略有不同,要解释堆栈并得到整个函数调用过程略显复杂。不过windows提供一个StackWalk64函数,可以获得堆栈的内容。StackWalk64的声明如下:

BOOL StackWalk64(

DWORD MachineType,

HANDLE hProcess,

HANDLE hThread,

LPSTACKFRAME64 StackFrame,

PVOID ContextRecord,

PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,

PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,

PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,

PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress

);

STACKFRAME64结构表示了堆栈中的一个frame。给出初始的STACKFRAME64,反复调用该函数,便可以得到内存分配点的调用堆栈了。

// Walk the stack

while (count < _VLD_maxtraceframes) {

count++;

if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context,

NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {

// Couldn't trace back through any more frames

break;

}

if (frameAddrFrameOffset == 0) {

// End of stack

break;

}

// Push this frame's program counter onto the provided CallStack

callstack->push_back((DWORD_PTR)frameAddrPCOffset);

}

那么,如何得到初始的STACKFRAME64结构呢?在STACKFRAME64结构中,其他的信息都比较容易获得,而当前的程序计数器(EIP)在x86体系结构中无法通过软件的方法直接读取。Visual Leak Detector使用了一种方法来获得当前的程序计数器。首先,它调用一个函数,则这个函数的返回地址就是当前的程序计数器,而函数的返回地址可以很容易的从堆栈中拿到。下面是Visual Leak Detector获得当前程序计数器的程序:

#if defined(_M_IX86) || defined(_M_X64)

#pragma auto_inline(off)

DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 ()

{

DWORD_PTR programcounter;

__asm mov AXREG, [BPREG + SIZEOFPTR] // Get the return address out of the current stack frame

__asm mov [programcounter], AXREG // Put the return address into the variable we'll return

return programcounter;

}

#pragma auto_inline(on)

#endif // defined(_M_IX86) || defined(_M_X64)

得到了调用堆栈,自然要记录下来。Visual Leak Detector使用一个类似map的数据结构来记录该信息。这样可以方便的从requestNumber查找到其调用堆栈。分配钩子函数的allocType参数表示此次堆内存分配的类型,包括_HOOK_ALLOC, _HOOK_REALLOC, 和 _HOOK_FREE,下面代码是Visual Leak Detector对各种情况的处理。

switch (type) {

case _HOOK_ALLOC:

visualleakdetectorhookmalloc(request);

break;

case _HOOK_FREE:

visualleakdetectorhookfree(pdata);

break;

case _HOOK_REALLOC:

visualleakdetectorhookrealloc(pdata, request);

break;

default:

visualleakdetectorreport("WARNING: Visual Leak Detector: in allochook(): Unhandled allocation type (%d)/n", type);

break;

}

这里,hookmalloc()函数得到当前堆栈,并将当前堆栈与requestNumber加入到类似map的数据结构中。hookfree()函数从类似map的数据结构中删除该信息。hookrealloc()函数依次调用了hookfree()和hookmalloc()。

检测内存泄露

前面提到了Visual C++内置的内存泄漏检测工具的工作原理。与该原理相同,因为全局变量以构造的相反顺序析构,在Visual Leak Detector析构时,几乎所有的其他变量都已经析构,此时如果仍然有未释放之堆内存,则必为内存泄漏。

分配的堆内存是通过一个链表来组织的,检查内存泄漏则是检查此链表。但是windows没有提供方法来访问这个链表。Visual Leak Detector使用了一个小技巧来得到它。首先在堆上申请一块临时内存,则该内存的地址可以转换成指向一个_CrtMemBlockHeader结构,在此结构中就可以获得这个链表。代码如下:

char pheap = new char;

_CrtMemBlockHeader pheader = pHdr(pheap)->pBlockHeaderNext;

delete pheap;

其中pheader则为链表首指针。

报告生成

前面讲了Visual Leak Detector如何检测、记录内存泄漏及其其调用堆栈。但是如果要这个信息对程序员有用的话,必须转换成可读的形式。Visual Leak Detector使用SymGetLineFromAddr64()及SymFromAddr()生成可读的报告。

// Iterate through each frame in the call stack

for (frame = 0; frame < callstack->size(); frame++) {

// Try to get the source file and line number associated with

// this program counter address

if (pSymGetLineFromAddr64(m_process,

(callstack)[frame], &displacement, &sourceinfo)) {

}

// Try to get the name of the function containing this program

// counter address

if (pSymFromAddr(m_process, (callstack)[frame],

&displacement64, pfunctioninfo)) {

functionname = pfunctioninfo->Name;

}

else {

functionname = "(Function name unavailable)";

}

}

概括讲来,Visual Leak Detector的工作分为3步,首先在初始化注册一个钩子函数;然后在内存分配时该钩子函数被调用以记录下当时的现场;最后检查堆内存分配链表以确定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。有兴趣的读者可以阅读Visual Leak Detector的源代码。

总结

在使用上,Visual Leak Detector简单方便,结果报告一目了然。在原理上,Visual Leak Detector针 对内存泄漏问题的特点,可谓对症下药——内存泄漏不是不容易发现吗?那就每次内存分配是都给记录下来,程序退出时算总账;内存泄漏现象出现时不是已时过境 迁,并非当时泄漏点的现场了吗?那就把现场也记录下来,清清楚楚的告诉使用者那块泄漏的内存就是在如何一个调用过程中泄漏掉的。

Visual Leak Detector是一个简单易用内存泄漏检测工具。现在最新的版本是19a,采用了新的检测机制,并在功能上有了很多改进。不妨体验一下

*** 作系统实现的所有系统调用所构成的集合即程序接口或应用编程接口(ApplicationProgrammingInterface,API)。是应用程序同系统之间的接口。Linux系统调用,包含了大部分常用系统调用和由系统调用派生出的的函数。一、进程控制:fork创建一个新进程clone按指定条件创建子进程execve运行可执行文件exit中止进程_exit立即中止当前进程getdtablesize进程所能打开的最大文件数getpgid获取指定进程组标识号setpgid设置指定进程组标志号getpgrp获取当前进程组标识号setpgrp设置当前进程组标志号getpid获取进程标识号getppid获取父进程标识号getpriority获取调度优先级setpriority设置调度优先级modify_ldt读写进程的本地描述表nanosleep使进程睡眠指定的时间nice改变分时进程的优先级pause挂起进程,等待信号personality设置进程运行域prctl对进程进行特定 *** 作ptrace进程跟踪sched_get_priority_max取得静态优先级的上限sched_get_priority_min取得静态优先级的下限sched_getparam取得进程的调度参数sched_getscheduler取得指定进程的调度策略sched_rr_get_interval取得按RR算法调度的实时进程的时间片长度sched_setparam设置进程的调度参数sched_setscheduler设置指定进程的调度策略和参数sched_yield进程主动让出处理器,并将自己等候调度队列队尾vfork创建一个子进程,以供执行新程序,常与execve等同时使用wait等待子进程终止wait3参见waitwaitpid等待指定子进程终止wait4参见waitpidcapget获取进程权限capset设置进程权限getsid获取会晤标识号setsid设置会晤标识号二、文件系统控制1、文件读写 *** 作fcntl文件控制open打开文件creat创建新文件close关闭文件描述字read读文件write写文件readv从文件读入数据到缓冲数组中writev将缓冲数组里的数据写入文件pread对文件随机读pwrite对文件随机写lseek移动文件指针_llseek在64位地址空间里移动文件指针dup复制已打开的文件描述字dup2按指定条件复制文件描述字flock文件加/解锁pollI/O多路转换truncate截断文件ftruncate参见truncateumask设置文件权限掩码fsync把文件在内存中的部分写回磁盘2、文件系统 *** 作access确定文件的可存取性chdir改变当前工作目录fchdir参见chdirchmod改变文件方式fchmod参见chmodchown改变文件的属主或用户组fchown参见chownlchown参见chownchroot改变根目录stat取文件状态信息lstat参见statfstat参见statstatfs取文件系统信息fstatfs参见statfsreaddir读取目录项getdents读取目录项mkdir创建目录mknod创建索引节点rmdir删除目录rename文件改名link创建链接symlink创建符号链接unlink删除链接readlink读符号链接的值mount安装文件系统umount卸下文件系统ustat取文件系统信息utime改变文件的访问修改时间utimes参见utimequotactl控制磁盘配额三、系统控制ioctlI/O总控制函数_sysctl读/写系统参数acct启用或禁止进程记账getrlimit获取系统资源上限setrlimit设置系统资源上限getrusage获取系统资源使用情况uselib选择要使用的二进制函数库ioperm设置端口I/O权限iopl改变进程I/O权限级别outb低级端口 *** 作reboot重新启动swapon打开交换文件和设备swapoff关闭交换文件和设备bdflush控制bdflush守护进程sysfs取核心支持的文件系统类型sysinfo取得系统信息adjtimex调整系统时钟alarm设置进程的闹钟getitimer获取计时器值setitimer设置计时器值gettimeofday取时间和时区settimeofday设置时间和时区stime设置系统日期和时间time取得系统时间times取进程运行时间uname获取当前UNIX系统的名称、版本和主机等信息vhangup挂起当前终端nfsservctl对NFS守护进程进行控制vm86进入模拟8086模式create_module创建可装载的模块项delete_module删除可装载的模块项init_module初始化模块query_module查询模块信息get_kernel_syms取得核心符号,已被query_module代替四、内存管理brk改变数据段空间的分配sbrk参见brkmlock内存页面加锁munlock内存页面解锁mlockall调用进程所有内存页面加锁munlockall调用进程所有内存页面解锁mmap映射虚拟内存页munmap去除内存页映射mremap重新映射虚拟内存地址msync将映射内存中的数据写回磁盘mprotect设置内存映像保护getpagesize获取页面大小sync将内存缓冲区数据写回硬盘cacheflush将指定缓冲区中的内容写回磁盘五、网络管理getdomainname取域名setdomainname设置域名gethostid获取主机标识号sethostid设置主机标识号gethostname获取本主机名称sethostname设置主机名称六、socket控制socketcallsocket系统调用socket建立socketbind绑定socket到端口connect连接远程主机accept响应socket连接请求send通过socket发送信息sendto发送UDP信息sendmsg参见sendrecv通过socket接收信息recvfrom接收UDP信息recvmsg参见recvlisten监听socket端口select对多路同步I/O进行轮询shutdown关闭socket上的连接getsockname取得本地socket名字getpeername获取通信对方的socket名字getsockopt取端口设置setsockopt设置端口参数sendfile在文件或端口间传输数据socketpair创建一对已联接的无名socket七、用户管理getuid获取用户标识号setuid设置用户标志号getgid获取组标识号setgid设置组标志号getegid获取有效组标识号setegid设置有效组标识号geteuid获取有效用户标识号seteuid设置有效用户标识号setregid分别设置真实和有效的的组标识号setreuid分别设置真实和有效的用户标识号getresgid分别获取真实的,有效的和保存过的组标识号setresgid分别设置真实的,有效的和保存过的组标识号getresuid分别获取真实的,有效的和保存过的用户标识号setresuid分别设置真实的,有效的和保存过的用户标识号setfsgid设置文件系统检查时使用的组标识号setfsuid设置文件系统检查时使用的用户标识号getgroups获取后补组标志清单setgroups设置后补组标志清单八、进程间通信ipc进程间通信总控制调用1、信号sigaction设置对指定信号的处理方法sigprocmask根据参数对信号集中的信号执行阻塞/解除阻塞等 *** 作sigpending为指定的被阻塞信号设置队列sigsuspend挂起进程等待特定信号signal参见signalkill向进程或进程组发信号sigblock向被阻塞信号掩码中添加信号,已被sigprocmask代替siggetmask取得现有阻塞信号掩码,已被sigprocmask代替sigsetmask用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替sigmask将给定的信号转化为掩码,已被sigprocmask代替sigpause作用同sigsuspend,已被sigsuspend代替sigvec为兼容BSD而设的信号处理函数,作用类似sigactionssetmaskANSIC的信号处理函数,作用类似sigaction2、消息msgctl消息控制 *** 作msgget获取消息队列msgsnd发消息msgrcv取消息3、管道pipe创建管道4、信号量semctl信号量控制semget获取一组信号量semop信号量 *** 作5、共享内存shmctl控制共享内存shmget获取共享内存shmat连接共享内存shmdt拆卸共享内存

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/13496454.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2025-09-01
下一篇2025-09-01

发表评论

登录后才能评论

评论列表(0条)

    保存