WIN32汇编语言教程:第13章 进程控制 · 13.3 进程调试(1)
在DOS操作系统下,一个程序可以读写系统中的所有内存,所以可以方便地修改任何地方的代码和数据,不管这些代码和数据是不是自己所有的,另外,程序可以自由存取所有的寄存器,自由设置所有的中断,所以程序可以通过设置单步中断或断点中断来跟踪代码的执行。这些功能可以归结为对一个进程进行调试。
在Windows操作系统中,不同进程之间的地址空间是隔离的,要用指令直接存取其他进程地址空间中的代码和数据是不可能的,用户程序也没有权限去截获中断,甚至连在自己的代码段中写数据都是不合法的,那么在Windows中还可以实现类似DOS中的调试功能吗?答案是肯定的,但必须通过专用的API函数来完成,本节要讨论的就是这方面的内容。
13.3.1 获取运行中的进程句柄
要对进程进行某种操作,就必须首先知道该进程的进程句柄或者进程ID,否则一切无从谈起,对于程序自己创建的子进程来说,CreateProcess函数返回了进程句柄和进程ID,但如果需要调试系统中已经运行的进程,那就必须首先获取它们的句柄才行。
Win32中并没有直接获取其他进程句柄的函数,但如果知道进程ID,可以由此得到进程句柄,所以可以首先通过某种途径获取进程ID。
1. 从窗口句柄获取进程句柄
获取进程ID的方法之一是使用GetWindowThreadProcessId函数,这个函数可以从一个窗口句柄获得创建该窗口的进程的进程ID,而通过FindWindow函数得到窗口句柄是很简单的,所以GetWindowThreadProcessId函数的用途相当广泛。该函数的用法是:
invoke GetWindowThreadProcessId,hWnd,lpdwProcessId
其中hWnd参数指定需要用来获取进程ID的窗口句柄,lpdwProcessId指向一个双字变量,函数在这里返回创建窗口的进程ID,函数的返回值是目标进程中创建该窗口的线程的线程句柄(一个有用的副产品!)。
得到了进程ID以后,就可以通过OpenProcess函数来获取该进程的句柄了:
invoke OpenProcess,dwDesiredAccess,bInheritHandle,dwProcessId
.if eax
mov hProcess,eax
.endif
函数的参数定义如下。
● dwDesiredAccess——指定需要对该进程进行的操作,要对目标进程进行某种操作,必须指定操作代码,但是在Windows NT操作系统中,对其他进程操作需要有相应的权限,如需要结束目标进程就必须有PROCESS_TERMINATE权限才行,当权限不够的时候,打开进程的操作就会失败。一般来说,除了系统进程以外,可以对其他进程进行任何操作,操作码可以是以下取值的组合:
■ PROCESS_ALL_ACCESS——等于下面全部操作码的组合。
■ PROCESS_CREATE_THREAD——允许创建远程线程。
■ PROCESS_DUP_HANDLE——允许进程句柄被复制。
■ PROCESS_QUERY_INFORMATION——允许使用GetExitCodeProcess函数查询进程的退出码或使用GetPriorityClass函数查询进程的优先级。
■ PROCESS_SET_INFORMATION——允许使用SetPriorityClass函数设置进程的优先级。
■ PROCESS_TERMINATE——允许终止进程。
■ PROCESS_VM_OPERATION—允许使用WriteProcessMemory函数或VirtualProtectEx函数修改进程的地址空间。
■ PROCESS_VM_READ——允许对进程的地址空间进行读操作。
■ PROCESS_VM_WRITE——允许对进程的地址空间进行写操作。
● bInheritHandle——指明返回的进程句柄是否可以被当前进程的子进程继承,如果参数指定为TRUE,则句柄可以被继承。
● dwProcessId——指定目标进程的进程ID。
如果函数执行成功,返回值是被打开的进程句柄。如果函数执行失败则返回NULL。一般打开失败的原因是由权限不够引起的。当完成对目标进程的操作以后,必须使用CloseHandle将获得的句柄关闭。
2. 从快照函数获取进程句柄
使用GetWindowThreadProcessId获取进程ID的先决条件是进程必须创建了窗口,对于在后台运行的没有窗口的进程该如何处理呢?这就要通过枚举系统中运行的进程来解决了,这个功能可以由CreateToolhelp32Snapshot函数来实现。
通过CreateToolhelp32Snapshot函数可以获得一个进程的列表,可以从列表中得到进程的ID、进程对应的可执行文件名和创建该进程的父进程ID等数据,这个函数支持Windows 9x系列和Windows 2000及以上的系统,不支持Windows NT 4.0(幸好使用NT 4.0的机会已经不多了)。
CreateToolhelp32Snapshot函数的名称比较奇怪,“Snapshot”是快照的意思,难道函数和拍快速成像照片有某种联系吗?没有联系,“快照”只是函数执行方式的一种形像的比喻罢了。就像自然界中的生命循环一样,系统中的进程也是生生不息的,随时都可能有进程被结束,也随时会有新的进程诞生。“快照”保留了函数被调用时的进程列表,在以后读取“快照”数据的过程中如果有进程创建或结束,就不会影响“快照”中的列表,就好比我们照了一张照片后走人,照片还是可以留下来慢慢地看。
所附光盘的Chapter13\ProcessList目录中的例子演示了快照函数的使用方法,程序显示了如图13.4所示的列表框,用户可以选择列表框中的某个进程,并且使用“终止”按钮将它结束。左图是程序在Windows 2000下的运行结果,右图是程序在Windows 98下运行的结果,Windows 2000下的文件名是不带路径的,可见函数在不同操作系统下得到的可执行文件名的表现方式稍微有些不同。
图13.4 “快照”例子的运行界面
目录中的ProcessList.rc文件定义了图13.4所示的对话框。
//##################################################################
#include <resource.h>
//##################################################################
#define ICO_MAIN 1000
#define DLG_MAIN 1000
#define IDC_PROCESS 1001
#define IDC_REFRESH 1002
//##################################################################
ICO_MAIN ICON "Main.ico"
//##################################################################
DLG_MAIN DIALOG 76, 95, 190, 108
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "进程列表"
FONT 9, "宋体"
{
LISTBOX IDC_PROCESS, 8, 5, 173, 86, LBS_STANDARD | LBS_SORT
PUSHBUTTON "刷新(&R)", IDC_REFRESH, 87, 90, 45, 14
DEFPUSHBUTTON "终止(&T)", IDOK, 137, 90, 45, 14, BS_DEFPUSHBUTTON | WS_DISABLED
| WS_TABSTOP
}
//##################################################################