网站托管服务方案网站建设办公软件销售技巧
news/
2025/10/5 10:35:26/
文章来源:
网站托管服务方案,网站建设办公软件销售技巧,简洁风网站,wordpress伪静态404 nginx目录
1、进程的虚拟内存分区与小于0x10000的小地址内存区
1.1、进程的虚拟内存分区
1.2、小于0x10000的小地址内存区
2、保存线程上下文的CONTEXT结构体
3、从汇编代码角度去理解多线程运行过程的典型实例
4、调用TerminateThread强制结束线程会导致线程中的资源没有释放…目录
1、进程的虚拟内存分区与小于0x10000的小地址内存区
1.1、进程的虚拟内存分区
1.2、小于0x10000的小地址内存区
2、保存线程上下文的CONTEXT结构体
3、从汇编代码角度去理解多线程运行过程的典型实例
4、调用TerminateThread强制结束线程会导致线程中的资源没有释放的问题
5、调用WaitForSingleObject监测目标程序有没有退出
5.1、WaitForSingleObject函数说明
5.2、调用WaitForSingleObject函数监测线程或进程是否已经退出
5.2.1、子进程实时监测主进程是否已经退出主进程退出了则子进程要自动退出
5.2.2、启动子进程后等待子进程执行完退出后再执行后续操作
6、死锁检测工具LockCop
7、如何以管理员权限启动一个进程
8、如何判断程序是否以管理员权限运行
9、DLL延迟加载与DLL远程注入
9.1、DLL延迟加载
9.2、DLL远程注入
10、SEH结构化异常与C异常
10.1、SEH结构化异常
10.2、C异常
10.3、SEH结构化异常与C异常的区别
11、最后 VC常用功能开发汇总专栏文章列表欢迎订阅持续更新...https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程专栏文章列表欢迎订阅持续更新...https://blog.csdn.net/chenlycly/article/details/125529931C软件分析工具从入门到精通案例集锦专栏文章正在更新中...https://blog.csdn.net/chenlycly/article/details/131405795C/C基础与进阶专栏文章持续更新中...https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术专栏文章持续更新中...https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享专栏文章持续更新中...https://blog.csdn.net/chenlycly/category_2276111.html 《Windows核心编程》是为从事Windows软件开发的C/C程序员精心设计的是Windows开发人员的必备参考。第5版全面覆盖Windows XPWindows Vista和Windows Server 2008中的170个新增函数和Windows特性。书中还讲解了Windows系统如何使用这些特性以及应用程序如何去充分使用这些特性。我通过阅读本书解决了开发过程中遇到的多个问题对Windows编程技术及相关的细节点有了进一步的理解和认知。已经完成构建的《C软件调试与异常排查技术从入门到精通》技术专栏专栏中很多基础或核心内容均来自该书。强烈推荐大家研读一下这本书特别是有工作经验的Windows开发人员效果更好 本文根据最近几年的项目实践大概罗列一下项目中用到的来自于本书的若干知识点与技能供大家借鉴或参考。 写这篇文章是受一篇文章下的一个评论的启发分享一下工作中用到的来自《Windows核心编程》的若干知识点与技能旨在说明本书的重要性希望大家能够去仔细研读这本书 我们要多读书多好书经典的书要多读几遍才好每次都会有新的收获 1、进程的虚拟内存分区与小于0x10000的小地址内存区 在13.2节虚拟地址空间的分区中讲到了进程的虚拟内存分区和小地址内存区又称空指针赋值分区或空指针内存区。 1.1、进程的虚拟内存分区 程序的虚拟内存主要分为用户态虚拟内存区和内核态虚拟内存区比如对于一个32位程序系统会给进程分配4GB的虚拟内存默认情况下用户态虚拟内存占2GB内核态虚拟内存占2GB。 在Visual Studio的工程属性中可以开启/LARGEADDRESSAWARE启用大地址选项链接器 - 系统 - 启动大地址 这样用户态的虚拟内存就会从默认的2GB扩充到3GB内核态内存也就变到了1GB。之前我们新版本的32位软件引入了开源的WebRTC库程序运行起来后占用了大量的虚拟内存在执行一些消耗内存的操作时很容易将用户态的虚拟内存干到接近2GB的上限当接近2GB后再执行到申请较大内存的代码时会申请失败。如果调用malloc申请内存则会返回NULL如果使用new申请内存则会抛出bad_alloc异常。 在测试过程中发现当执行耗内存的操作时程序会时不时地发生闪退但程序中安装的异常捕获模块并没有感知到。于是每次启动程序运行时都将Windbg挂上去复现问题后Windbg感知到中断下来。发现是WebRTC开源库中调用了abort函数触发的中断abort函数内部会产生一个特殊的异常会让正在调试的调试器中断下来。然后查看函数调用堆栈发现WebRTC业务代码中调用malloc去动态申请内存时申请失败了malloc返回NULL然后WebRTC内部认为申请不到内存是Fatal致命的在封装的接口中直接调用了abort强行将程序终止了程序被强行终止了这和程序发生闪退是吻合的。因为并没有产生C异常或者崩溃所以异常捕获模块没有好感知到就不会生成dump文件。 之所以导致程序闪退是因为WebRTC内部调用abort接口强行将程序终止运行了。而动态申请内存失败很有可能是用户态虚拟内存接近或达到上限了没有找到一段连续的内存可供分配了所以malloc返回NULL内存申请失败了。 用户态虚拟内存达到上限可能有两个原因 1程序中有内存泄漏多次泄漏将内存耗尽了 有内存泄漏的代码频繁地被执行内存持续地被占用不释放导致程序占用的虚拟内存接近或达到用户态虚拟内存的上限。对于这种情况则要排查内存泄漏的点彻底消除内存泄漏。2程序中包含了多个业务模块确实要占用大量的用户态内存 程序中包含了多个业务模块确实要占用大量的用户态虚拟内存。比如我们软件中使用WebRTC开源库很庞大初始化时会申请大量的内存一上来很多业务就会将内存分配好即启动时就会占用大量的内存。软件除了WebRTC库还包含了多个业务模块这样加起来占用的虚拟内存就更大了。 对于这种情况我们需要优化代码减少程序对内存的占用。有一个重要的原则需要使用时再去申请不再使用时立即将内存释放掉。 后来我们用Process Explorer工具观察软件在运行过程中占用的总虚拟内存大小用户态的虚拟内存在崩溃时间点左右程序的用户态内存已经接近32位程序默认的2GB的用户态虚拟内存的上限了。进一步排查排除了内存泄漏的存在是因为软件中的多个业务模块确实需要占用大量的内存空间。于是从上层到底层对内存占用进行了一些优化虽然减少出问题的概率但还是会出现闪退的问题。 因为时间仓促涉及到底层的多个模块一时半会很难进行较大的优化。后来就开启了大地址选项即将32位程序的2GB的用户态虚拟内存扩充了3GB暂时将这个问题规避掉。经后续测试验证确实有效地解决了问题。 至于如何开启32位程序的大地址模式只要在exe主程序的工程属性中开启/LARGEADDRESSAWARE大地址选项即可。该配置选项的相关截图上面已经给出来了。 在这里给大家重点推荐一下我的几个热门畅销专栏
专栏1该专栏订阅量接近350个有很强的实战参考价值广受好评专栏文章持续更新中预计更新到200篇以上
C软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931 本专栏根据近几年C软件异常排查的项目实践系统地总结了引发C软件异常的常见原因以及排查C软件异常的常用思路与方法详细讲述了C软件的调试方法与手段以图文并茂的方式给出具体的实战问题分析实例带领大家逐步掌握C软件调试与异常排查的相关技术适合基础进阶和想做技术提升的相关C开发人员 专栏中的文章均是通过项目实战总结出来的通过项目实战积累了大量的异常排查素材和案例有很强的实战参考价值专栏文章还在持续更新中预计文章篇数能更新到200篇以上 专栏2
C/C基础与进阶专栏文章持续更新中...https://blog.csdn.net/chenlycly/category_11931267.html 以多年的开发实战为基础总结并讲解一些的C/C基础与进阶内容以图文并茂的方式对相关知识点进行详细地展开与阐述专栏涉及了C/C领域的多个方面的内容同时给出C/C及网络方面的常见笔试面试题并详细讲述Visual Studio常用调试手段与技巧 专栏3
开源组件及数据库技术https://blog.csdn.net/chenlycly/category_12458859.html 以多年的开发实战为基础分享一些开源组件及数据库技术 1.2、小于0x10000的小地址内存区 Windows系统专门预留了一块从0到0x10000的小地址内存区又称空指针内存区或空指针赋值内存分区 专门为了帮程序员定位访问空指针的问题。 当访问到这个内存区域时就会触发内存访问违例系统会强制将进程终止掉。为什么使用空指针就会访问到这个小地址内存区呢比如类对象指针值为NULL代码中没有判断指针是否为空直接使用该指针去访问指向的类的成员变量即将NULL值作为C类对象的首地址那么类对象的数据成员的内存地址就是相对于所在对象地址NULL的偏移即成员变量的地址是个很小的内存地址而访问变量的值就是去读或写该变量的内存地址中的内容就是去访问变量的内存这样就会去访问一个很小的内存地址即访问空指针赋值区就会触发内存访问违例引发崩溃。 这里涉及了类对象中的成员变量内存在类对象中排布问题成员变量的内存地址都是相对于所在类对象的内存地址的偏移 空指针是引发程序异常的常见原因之一。另外访问已经释放内存的野指针分析思路也是类似的即类的成员变量的地址是相对所在类对象地址的偏移。 这个地方有一点需要提一下使用空指针去调用类的成员函数是否一定会导致崩溃呢答案是不一定不一定会导致崩溃这主要看有没有访问不应该访问的内存。比如被调用函数中没有访问类的数据成员成员变量即不会访问类对象的内存就不会崩溃。 调用的函数执行的是代码段上的指令调用函数本身并不会有异常。 但如果使用空指针去调用虚函数会涉及到虚函数的二次寻址即先去访问类对象中隐含的虚函数表指针中的内容存放的虚函数表的首地址虚函数表指针变量的地址就是其所属的C类对象的首地址就会因为空指针访问很小的内存地址引发内存访问违例。 虚函数调用在C代码中就是一句函数调用看不出完整的调用过程需要从汇编代码的角度去看相关内容可以参见我之前写的文章
几秒读懂C虚函数调用的汇编代码实现https://blog.csdn.net/chenlycly/article/details/121046234
2、保存线程上下文的CONTEXT结构体 在7.7节在实际上下文中谈CONTEXT结构体中讲到了保存线程上下文信息的CONTEXT结构体。在多个线程之间切换时会使用到系统定义的CONTEXT结构体
//
// Context Frame
//
// This frame has a several purposes: 1) it is used as an argument to
// NtContinue, 2) is is used to constuct a call frame for APC delivery,
// and 3) it is used in the user level thread creation routines.
//
// The layout of the record conforms to a standard call frame.
//typedef struct _CONTEXT {//// The flags values within this flag control the contents of// a CONTEXT record.//// If the context record is used as an input parameter, then// for each portion of the context record controlled by a flag// whose value is set, it is assumed that that portion of the// context record contains valid context. If the context record// is being used to modify a threads context, then only that// portion of the threads context will be modified.//// If the context record is used as an IN OUT parameter to capture// the context of a thread, then only those portions of the threads// context corresponding to set flags will be returned.//// The context record is never used as an OUT only parameter.//DWORD ContextFlags;//// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT// included in CONTEXT_FULL.//DWORD Dr0;DWORD Dr1;DWORD Dr2;DWORD Dr3;DWORD Dr6;DWORD Dr7;//// This section is specified/returned if the// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.//FLOATING_SAVE_AREA FloatSave;//// This section is specified/returned if the// ContextFlags word contians the flag CONTEXT_SEGMENTS.//DWORD SegGs;DWORD SegFs;DWORD SegEs;DWORD SegDs;//// This section is specified/returned if the// ContextFlags word contians the flag CONTEXT_INTEGER.//DWORD Edi;DWORD Esi;DWORD Ebx;DWORD Edx;DWORD Ecx;DWORD Eax;//// This section is specified/returned if the// ContextFlags word contians the flag CONTEXT_CONTROL.//DWORD Ebp;DWORD Eip;DWORD SegCs; // MUST BE SANITIZEDDWORD EFlags; // MUST BE SANITIZEDDWORD Esp;DWORD SegSs;//// This section is specified/returned if the ContextFlags word// contains the flag CONTEXT_EXTENDED_REGISTERS.// The format and contexts are processor specific//BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];} CONTEXT;
线程是分配CPU时间片的基本单元线程失去CPU时间片后线程就会被挂起进入了睡眠状态线程获取到了CPU时间片则会从睡眠中唤醒继续执行。 线程失去时间片时会将线程上下文中的寄存器等信息保存到CONTEXT结构体中然后进入挂起睡眠状态当线程获取CPU时间片后则会从之前保存的CONTEXT结构体中把之前保存的寄存器信息给读出来放到当前的寄存器中然后线程继续接着挂起前的状态继续运行。这点对于从汇编角度去理解多线程代码的执行细节很重要。
3、从汇编代码角度去理解多线程运行过程的典型实例 了解汇编不仅可以辅助分析C软件异常问题还可以从汇编的角度去理解很多高级不好理解的代码执行细节。在8.1节原子访问:Interlocked 系列函数中讲到的那个多线程实例虽然很简单但它是从汇编角度去理解多线程代码执行细节的典型实例。从汇编代码的角度才能将这个实例理解透彻的 该例子中定义了一个long型的全局变量然后创建了两个线程线程函数分别是ThreadFunc1和ThreadFunc2这两个线程函数中均对g_x变量进行自加操作在访问共享变量g_x时未加锁同步相关代码如下
// define a global variable
long g_x 0;DWORD WINAPI ThreadFunc1(PVOID pvParam)
{g_x;return 0;
}DWORD WINAPI ThreadFunc2(PVOID pvParam)
{g_x;return 0;
}
这里有个问题当这两个线程函数执行完后全局变量g_x的值会是多少呢一定会是2吗 实际上在两个线程函数执行完后g_x的值不一定为2。这个实例需要从汇编代码的角度去理解从C源码看则很难搞懂这是一个从汇编代码角度去理解代码执行细节的典型实例。 熟悉汇编代码不仅可以辅助排查C软件异常还可以理解很多高级语言无法理解的代码执行细节。 有些人可能觉得代码中就是一个自加的操作一下子就执行完了中间应该不会被打断。会不会被打断其实要看汇编代码的这行C源码对应三行汇编代码对g_x变量的自加这句C代码对应的汇编代码如下
MOV EAX, [g_x] // 将g_x变量的值读到EAX寄存器中
INC EAX // 将EAX中的值执行自加操作
MOV [g_x], EAX // 然后将EAX中的值设置到g_x变量内存中
看C代码g_x只能保证CPU执行某条汇编指令时不会被打断汇编指令是CPU执行的最小粒度但3条汇编指令指令与指令之间是可能被打断的。 为什么说两个线程执行完成后g_x变量的值是不确定的呢比如可能存在两种场景
1场景1最终结果g_x2 假设线程1先快速执行了三行汇编指令未被打断g_x的值变成1。然后紧接着线程2执行在g_x1的基础上累加最终两个线程执行完后g_x等于2。
2场景2最终结果g_x1 假设线程1先执行当执行完前两条汇编指令后线程1失去时间片线程上下文信息保存到CONTEXT结构体中 即线程1前两条汇编指令执行完第3条汇编指令还没来得及执行就失去CPU时间片了 线程2执行一次执行完三条指令当前g_x1。然后线程1获得CPU时间片因为上次执行两条汇编指令后EAX寄存器中的值为1因为线程1获取了时间片保存线程上下文信息的CONTEXT恢复到线程1中EAX1继续执行第3条指令执行完后g_x还是1。 所以这个多线程问题需要从汇编代码的角度去理解从C源码的角度很难想明白。 从本例可以看出即使是简单的变量自加操作多线程操作时也要做同步可以加锁可以使用系统的原子锁Interlocked系列函数比如原子自加函数InterlockedIncrement和原子自减函数InterlockedDecrement
LONG InterlockedIncrement( LPLONG volatile lpAddend // variable to increment
);LONG InterlockedDecrement( LPLONG volatile lpAddend // variable address
);
这些原子函数能保证会被原子地被执行中间不会被打断。 修改后的代码为
// define a global variable
long g_x 0;DWORD WINAPI ThreadFunc1(PVOID pvParam)
{InterlockedIncrementg_x; // 调用原子锁函数InterlockedIncrement实现自加return 0;
}DWORD WINAPI ThreadFunc2(PVOID pvParam)
{InterlockedIncrementg_x; // 调用原子锁函数InterlockedIncrement实现自加return 0;
} 关于为什么要学习汇编以及学习汇编有哪些好处可以查看我之前写的文章为什么要学习汇编学习汇编有哪些好处https://blog.csdn.net/chenlycly/article/details/130935428 关于排查C软件异常所需要掌握的基础汇编知识可以查看我之前写的文章
分析C软件异常需要掌握的汇编知识汇总https://blog.csdn.net/chenlycly/article/details/124758670
4、调用TerminateThread强制结束线程会导致线程中的资源没有释放的问题 在6.5节终止运行线程中讲到了调用TerminateThread结束线程的相关问题。 创建线程时需要指定线程函数当代码执行到线程函数中线程才真正运行起来。线程函数执行完了线程函数退出了线程也就退出了。线程结束了可能是线程函数自然执行完成了也可能是人为地调用TerminateThread接口强制将进程退出了。 当线程函数自然地执行完退出时函数中局部变量的栈内存会自动被释放函数中的资源才会自动地被释放。如果线程函数执行的过程中被TerminateThread函数强行终止了则会导致函数中的资源不会被释放比如 1线程函数中局部变量的栈内存没有释放 函数没有正常地执行完成函数中局部变量占用的栈内存没有释放。2线程函数中申请的资源没有释放 比如线程函数中刚拿到一个锁就被强行终止了释放锁的代码没有执行到导致线程结束了锁也没被释放掉。有次我们在用Windbg排查多线程死锁问题时一个线程在申请锁但通过查看锁信息得知该锁被另一个线程占用着但我们查看进程的线程列表时并没有找到占用锁的那个线程id估计线程已经结束了可能就是因为该线程中途被Terminate掉了还没将锁释放掉线程就被终止了所以出现了这个问题。 此外线程终止与进程终止是有区别的线程被强行终止时系统不会去释放线程中的资源但进程终止时系统会释放进程所有的资源。 关于从C软件调试实战的角度去看多线程编程中的若干细节问题可以查看我之前写的文章
从C软件调试实战的角度去看多线程编程中的若干细节问题https://blog.csdn.net/chenlycly/article/details/134358655
5、调用WaitForSingleObject监测目标程序有没有退出 在4.4节子进程中讲到了可以通过WaitForSingleObject监测目标程序有没有退出。
5.1、WaitForSingleObject函数说明 WaitForSingleObject 函数可以等待以下对象 更改通知控制台输入可等待计时器内存资源通知事件Event互斥量Mutex信号量Semaphore线程进程 当等待对象从非触发状态变成触发状态发出信号 WaitForSingleObject就会返回或者是等待时间超时WaitForSingleObject也会返回。关于WaitForSingleObject的返回值如下所示 对于事件分有信号和无信号两个状态当事件对象编程有信号时WaitForSingleObject立即返回。 对于互斥量和信号量调用WaitForSingleObject获取所有权即WaitForSingleObject返回WAIT_OBJECT_0时获取他们的所有权然后调用ReleaseMutex和ReleaseSemaphore释放对象的所有权。 对于线程和进程线程和进程创建时无信号当线程和进程退出时对应的句柄就变成了有信号这样WaitForSingleObject就返回了。可以通过WaitForSingleObject返回判断线程或进程是否已经退出了
WaitForSingleObject(hThread, INFINITE); // 参数INFINITE表示无限等待
5.2、调用WaitForSingleObject函数监测线程或进程是否已经退出 在主程序运行的过程中启动了一个子进程有时主进程要等待子进程处理结果后根据返回的信息再控制后续代码的执行有时子进程需要感知主进程是否已经退出这两种情况都需要感知另一个进程是否已经退出。对于线程在某些场合下页存在类似的需求。
5.2.1、子进程实时监测主进程是否已经退出主进程退出了则子进程要自动退出 主进程在运行过程中启动了一个子进程启动子进程时将主进程的进程id传给子进程。子进程是依赖于主进程存活的如果主进程退出或者崩溃了则子进程就没有存在的意义了要自动退出所以子进程要实时监测主进程的状态监测主进程有没有退出包括崩溃闪退。 可能有人会说主进程可以在退出时通知子进程子进程收到通知后再自行退出。但主进程可能会发生崩溃或闪退这种情况下一般时没法通知子进程的。 那子进程如何才能实时监测主进程是否退出了呢不管是主进程正常退出还是异常崩溃闪退都要感知到。子进程可以启动一个子线程在子线程中通过主进程传过来的主进程id获取主进程句柄然后调用WaitForSingleObject等待主进程退出可以在子线程中无限等待。如果主进程一旦退出WaitForSingleObject函数就会立即返回这时子进程就可以调用ExitProcess等接口自行退出当前子进程了。 具体的代码实现是子进程中启动一个子线程将主进程传过来的主进程id传给该子线程如下其中MonitorMainProcess是线程函数
HANDLE hThread (HANDLE)_beginthreadex( NULL, 0, MonitorMainProcess, (void*)dwMainProcessId, 0, NULL );
if ( hThread ! NULL )
{CloseHandle( hThread );
}
线程函数MonitorMainProcess实现如下
// 监控主工程
unsigned __stdcall MonitorMainProcess(void * pParam )
{DWORD dwProcessId (DWORD)pParam;HANDLE hProcess OpenProcess( SYNCHRONIZE, FALSE, dwProcessId );if(hProcess NULL){ExitProcess(-1);}// 设置INFINITE无限等待WaitForSingleObject( hProcess, INFINITE );CloseHandle( hProcess );// WaitForSingleObject返回了就表示主进程已经退出直接退出本进程ExitProcess( -1 );
} 进程初始是无信号的进程退出时就变成了有信号这样WaitForSingleObject等待到信号后就返回了这样子进程就直到主进程退出了。 此处需要注意一下调用OpenProcess时必须要设置SYNCHRONIZE参数因为设置该标记参数后才能调用WaitForSingleObject去等待进程。微软MSDN上对SYNCHRONIZE如下 SYNCHRONIZEThe right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. 此外监测主进程是否退出的代码是阻塞式的不能放在主线程中的这就是为什么要启动一个子线程去专门做这个监测任务的原因。
5.2.2、启动子进程后等待子进程执行完退出后再执行后续操作 有时我们需要启动一个子进程去完成某项操作主进程在等待子进程的执行结果需要获取子进程的执行数据 然后主进程再去执行后续操作。主进程在启动子进程后就可以调用WaitForSingleObject等待子进程退出比如如下的代码
// 启动一个子进程去执行一个操作任务
STARTUPINFO s {sizeof(s)};
PROCESS_INFORMATION pi {0};
if( CreateProcess( NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, s, pi ) )
{ // 等待进程执行完毕 WaitForSingleObject( pi.hProcess, INFINITE ); // 关闭进程和主线程句柄CloseHandle( pi.hProcess ); CloseHandle( pi.hThread );
} // ... // 去拿子进程的执行结果去执行后续操作 有时启动一个子线程要等子线程执行完退出后根据处理结果信息去继续执行和上面等待进程退出是类似的。 关于WaitForSingleObject 函数的诸多用途与使用场景总结可以查看我之前写的文章
WaitForSingleObject 函数的诸多用途与使用场景总结https://blog.csdn.net/chenlycly/article/details/135604637%C2%A0
6、死锁检测工具LockCop 在9.8.6节使用等待链遍历API来检测死锁中讲到了死锁检测工具LockCop使用该工具可以检测程序中是否存在多线程死锁 该工具是调用系统WCT Wait Chain Traversal等待链遍历API函数GetThreadWaitChain来判断目标线程是否发生了死锁但是只能监测关键代码段、互斥量等引发的死锁如下所示 所以在使用LockCop工具应该注意 1该工具因为调用的API是Vista以上系统才提供的所以不支持XP系统。 2该工具只能检测临界区死锁和互斥量死锁事件、信号量等引发的死锁是没法监测到的。 3该工具检测不到WaitForMultipleObjects引发的死锁。 7、如何以管理员权限启动一个进程 在4.5.1节自动提升进程的权限讲到了如果以管理员权限启动一个进程。 在Win7及以上系统中如果系统UAC开关打开且当前系统登录用户不是超级管理员Administrator用户如果程序设置了以管理员权限运行则在程序启动时系统会弹出UAC提示框如下所示 提示用户软件可能会对机器进行更改以管理员权限运行的程序可以修改系统关键路径中的文件或注册表等。所以有些软件为了避免系统频繁地弹出UAC提示框故意将程序设置以标准用户权限运行比如我们研究过QQ它默认就是以标准用户权限运行的。 在以标准用户权限运行的进程中如果要启动一个子进程则默认情况下子进程是继承父进程的权限即以标准用户权限运行。但有些场景下我们需要在以标准用户权限运行的进程中启动一个以管理员权限运行的子进程比如执行软件的升级更新时升级程序需要写注册表向HKEY_LOCAL_MACHINE注册表下写入信息需要管理员权限向当前用户的HKEY_CURRENT_USER路径下写入信息则不需要管理员权限、需要向系统关键路径拷贝文件需要有管理员权限才能执行所以被启动起来的升级程序需要以管理员权限启动运行。 那如何在一个以标准用户权限运行的进程中去启动一个以管理员权限运行的程序呢其实很简单调用系统API接口SheeExecuteEx传入runas参数即可如下所示
SHELLEXECUTEINFO si;
RtlZeroMemory( si, sizeof( SHELLEXECUTEINFO ) );
si.cbSize sizeof(SHELLEXECUTEINFO);
si.lpFile _T(D:\\test.exe);
//si.lpParameters lpCmdParam;
si.nShow SW_SHOWNORMAL;
si.lpVerb _T(runas);
BOOL bRet ShellExecuteEx( si );
if ( !bRet ) // 程序启动失败
{TCHAR achLog[256] { 0 };// 先取lasterror值DWORD dwLastErr GetLastError();_stprintf( achLog, _T(ShellExecuteEx failed, GetLastError: %d.), dwLastErr );WriteLog( achLog );// 再取hInstApp错误代码int nHInsVal (int)si.hInstApp;if ( nHInsVal 32 ){_stprintf( achLog, _T(ShellExecuteEx failure, errcode: %d.), nHInsVal );WriteLog( achLog );}
} 关于Windows UAC的详细说明及实际项目中遇到的相关问题可以查看我之前写的文章
Windows UAC权限详解以及因为权限不对等引发的若干问题分享https://blog.csdn.net/chenlycly/article/details/134418715%C2%A0
8、如何判断程序是否以管理员权限运行 有时我们需要判断进程是否以管理员权限运行比如在运行安装包时需要安装包进程以管理员权限运行因为安装包将执行写注册表、注册组件等需要管理员权限的操作。如果没有申请到管理员权限这些需要管理员权限的操作都会执行失败则会导致安装失败。以QQ7.1安装包为例如果当前以标准用户登录到系统中并且UAC关闭双击运行时将申请不到管理员权限QQ会弹出如下的提示框 我们的安装程序可以参考QQ的做法避免出现没有管理员权限导致安装失败的问题即如果没有申请到管理员权限则直接弹出如上类似的提示。要弹出提示则要判断当前安装程序的进程是否以管理员权限运行。那应该如何判断呢相关示例代码如下所示
// 1、通过进程id获取进程句柄
DWORD dwPid 2337; // 目标进程的进程id
HANDLE hProcess ::OpenProcess( /*PROCESS_ALL_ACCESS*/PROCESS_QUERY_INFORMATION, FALSE, dwPid );
if ( hProcess NULL )
{
strTip.Format( _T(OpenProcess to get the process handle failed, possible reason: the process id doesnt exsit, GetLastError: %d), GetLastError() );
AfxMessageBox( strTip );
return;
}// 2、调用IsRunasAdmin函数判断目标进程是否已管理员权限运行
BOOL bRunAsAdmin IsRunasAdmin( hProcess );
if ( bRunAsAdmin )
{strTip.Format( _T(Pid(%d) run as admin!), dwPid );
}
else
{strTip.Format( _T(Pid(%d) dont run as admin!), dwPid );
}
AfxMessageBox( strTip );// 3、判断进程是否已管理员权限运行的函数实现
BOOL IsRunasAdmin( HANDLE hProcess )
{BOOL bElevated FALSE; HANDLE hToken NULL; CString strTip;// Get target process tokenif ( !OpenProcessToken( hProcess/*GetCurrentProcess()*/, TOKEN_QUERY, hToken ) ){strTip.Format( _T(OpenProcessToken failed, GetLastError: %d), GetLastError() );AfxMessageBox( strTip );return FALSE;}TOKEN_ELEVATION tokenEle;DWORD dwRetLen 0; // Retrieve token elevation informationif ( GetTokenInformation( hToken, TokenElevation, tokenEle, sizeof(tokenEle), dwRetLen ) ){ if ( dwRetLen sizeof(tokenEle) ){bElevated tokenEle.TokenIsElevated; }} else{strTip.Format( _T(GetTokenInformation failed, GetLastError: %d), GetLastError() );AfxMessageBox( strTip );}CloseHandle( hToken ); return bElevated;
}
9、DLL延迟加载与DLL远程注入 在第22章讲到了DLL延迟加载与DLL远程注入的相关内容。下面从实际工程应用的角度去大概地讲述上述两方面的内容。
9.1、DLL延迟加载 当启动一个程序时系统会给程序进程分配一个进程空间系统会先将主程序依赖的各个dll库加载到进程空间中当所以依赖的库加载到进程空间中后最后才会将exe主程序加载并运行起来然后进入exe主程序的main函数主程序运行起来。 软件的某些功能是调用某个DLL库的接口实现的但这些功能可能客户不会用或很少用可以将相关的DLL库设置为延迟加载。设置为延迟加载后就不会在启动主程序时就加载DLL库而是在调用到DLL接口时再去加载Windows系统是支持DLL延迟加载的。 将不怎用的功能所在的DLL库设置为延迟加载主要有以下两大好处 1提高程序的启动速度 将DLL库设置为延迟加载后程序启动时就不会加载该DLL库这样就能减少程序启动要加载的DLL库个数可以提升程序的执行速度。2较少程序对虚拟内存的占用 DLL库加载到进程空间中主要有两方面会占用程序的内存空间虚拟内存空间。一方面DLL二进制文件中存放的是要CPU执行的二进制代码加载到进程空间后会占用进程代码段的内存这是程序虚拟内存的一部分。 另一方面DLL库中有些变量DLL加载到进程空间中后有些变量需要分配内存这些变量的内存则是程序数据段的内存也是程序虚拟内存的一部分。所以减少DLL库的加载可以降低进程对虚拟内存的占用。 那如何将DLL设置成延迟加载呢其实很简单在Visual Studio中就可以设置在exe主程序中配置即可。打开exe主程序工程的工程属性到在链接器 - 输入 - 延迟加载的DLL 下输入要延迟加载的DLL文件名称如下所示 如果有多个DLL库需要延迟加载用逗号隔开即可。
9.2、DLL远程注入 有些软件通过DLL模块远程注入实现对目标程序的监控其实在日常工作中一直都有DLL远程注入的身影比如输入法之所以能在所有的软件上进行输入是因为输入法有专门的DLL远程注入到当前系统所有的UI进程中。此外有些第三方安全软件也是通过DLL远程注入的方式注入到目标进程中去对目标进程的实时网络数据进行分析和监控。 这两种DLL远程注入的场景我们在实际的项目问题中都遇到过关于具体的问题细节描述可以参见我之前写的文章
第三方模块远程注入到软件中引发软件异常的若干实战案例分享https://blog.csdn.net/chenlycly/article/details/134545223关于如何实现DLL的远程注入以及相关的细节可以详细查看《Windows核心编程》的第22章此处我们就不详细展开了
10、SEH结构化异常与C异常 第25章未处理异常、向量化异常处理与C异常中讲到了SEH结构化异常与C异常相关的内容。
10.1、SEH结构化异常 SEHStructured Exception Handling结构化异常处理是Windows操作系统上Microsoft对C/C程序语言做的语法扩展用于处理异常事件的程序控制结构。异常事件是打断程序正常执行流程的不在期望之中的硬件、软件事件。硬件异常是CPU抛出的如“除0”、数值溢出等软件异常是操作系统与程序通过RaiseException语句抛出的异常。 Microsoft扩展了C语言的语法用 try-except与try-finally语句来处理异常。try-except结构如下
__try
{// guarded code
}
__except ( expression )
{// exception handler code
}
try-finally结构如下
__try {// guarded code
}
__finally {// termination code
} 异常处理程序可以释放已经获取的资源、显示出错信息与程序内部状态供调试、从错误中恢复、尝试重新执行出错的代码或者关闭程序等等。一个__try语句不能既有__except又有__finally。但try-except与try-finally语句可以嵌套使用。 以调用系统API函数HtmlHelp打开.chm文件为例在项目中发现如果目标路径中的.chm文件不存在HtmlHelp函数内部会产生异常导致程序崩溃为了解决这个问题我们添加了__try...__except进行保护相关代码如下所示
bool OpenChmHelpFile( LPCTSTR lpStrPath )
{HWND hHelpWnd NULL;__try{hHelpWnd HtmlHelp( NULL, lpStrPath, HH_DISPLAY_TOPIC, NULL );}__except( EXCEPTION_EXECUTE_HANDLER ){hHelpWnd NULL;}if ( NULL hHelpWnd ){WriteLog( _T([OpenChmHelpFile] HtmlHelp execute failed, path [%s]!), lpStrPath );return false;}return true;
}
10.2、C异常 C异常处理的声明
try
{// code that could throw an exception
}
[ catch (exception-declaration)
{// code that executes when exception-declaration is thrown// in the try block
}
[catch (exception-declaration)
{// code that handles another exception type
} ] . . . ]// The following syntax shows a throw expression:
throw [expression]
C异常处理使用try、catch和throw三个关键字实现用throw抛出异常然后用try...catch捕获异常。 比如在使用new去动态申请内存时如果进程内存不足申请不到指定大小的堆内存时new内部会抛出bad_alloc异常捕获这个异常的代码如下所示
try
{int *p new int[10000000000];
}
catch (bad_alloc e)
{cout e.what() endl;
}
10.3、SEH结构化异常与C异常的区别 SEH 是操作系统所提供的便利它在任何语言中都可以使用。而C异常处理只有在编写C代码时才可以使用。Microsof 的 Visual C编译器使用操作系统的结构化异常机制来实现 C异常处理机制。所以在创建一个 C try 块时编译器会为我们生成一个 SEH __try 块。C的catch 语句对应SEH 异常过滤程序catch 块中的代码则对应SEH __except块中的代码。而编译器也会为C throw语句生成对WindowsRaiseException函数的调用throw语句所使用的变量则成为RaiseException的附加参数。
11、最后 本文大概地罗列了工作中用到的来自《Windows核心编程》的若干知识点与技能旨在说明本书的重要性希望大家能够仔细研读这本书在提升理论知识水平的同时也能有效地提高分析解决问题的技能。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/928132.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!