windbg调试驱动学习总结

VC++ 同时被 2 个专栏收录
381 篇文章 1 订阅
102 篇文章 6 订阅

简单驱动编写与windbg调试

http://trustsec.blog.51cto.com/305338/64694/

一.驱动编写
随着对windows系统的深入研究,越来越多的内核方面的知识被挖掘出来了,今天我们讨论下如何写一个

简单的驱动,并使用现在比较新的windbg调试器进行调试。首先写驱动要对驱动有一个比较全面的认识


一个简单的驱动一般有以下几个部分组成:
1,一个入口点(DriverEntry):用于创建设备对象及符号连接,以及其它初使化操作,如分配池内存等.
2,一个出口(DriverUnload):删除符号连接与设备对象,并释放已经分配的各种资源,如池内存等
3,几个派遣例程:用于响应Ring3程序的请求及其它驱动事件,并做相关处理。
我用一个挂钩SSDT的简单驱动来详细介绍如何写一个驱动和驱动的结构。
在驱动入口点,我们需要使用RtlInitUnicodeString来初始化一个UnicodeString结构:
例如:
RtlInitUnicodeString(&DeviceName,NT_DEVICE_NAME);
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
NT_DEVICE_NAME和 DOS_DEVICE_NAME,往往在头文件中用define进行定义。
(这里有一点要注意如:
#define NT_DEVICE_NAME   L"\\Device\\HookSSDT"
这里的L并不是说明”\\Device\\HookSSDT”是个Unicode字符串,只是表明他是一个宽字符,这里很多

人都搞混。)
我们初始化设备名后就可以调用IoCreateDevice创建一个设备对象了,这里我给大家一个小例子:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
 IN PUNICODE_STRING theRegistryPath)
{
NTSTATUS ntStatus;
UNICODE_STRING DeviceName;
UNICODE_STRING DeviceLinkString;
PDEVICE_OBJECT pDeviceObject;

RtlInitUnicodeString(&DeviceName,NT_DEVICE_NAME);
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
ntStatus = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDeviceObject);
if (!NT_SUCCESS(ntStatus))
{

return ntStatus;
}

ntStatus = IoCreateSymbolicLink(&DeviceLinkString,&DeviceName);

if (!NT_SUCCESS(ntStatus))
{

IoDeleteDevice(pDeviceObject);
return ntStatus;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = 
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DevCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
DispatchDeviceControl;
DriverObject->DriverUnload=OnUnload;
DbgPrint("驱动已经加载");
return STATUS_SUCCESS;
}

这里IoCreateSymbolicLink用来创建一个符号链接以便在应用程序中可以方便的打开驱动对象,与其通

讯。
DriverObject->MajorFunction[IRP_MJ_CREATE] = 
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DevCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
这里是典型的派遣例程,他把IRP的处理过程交给我们的派遣例程去执行,其实我们常常要做的是把IRP

交给下一层的驱动处理。
在这里的DispatchDeviceControl中我们处理由应用程序传来的控制码,首先我们要了解IRP和IRP堆栈的

结构:
I/O请求包数据结构

MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。如

果顶级设备对象的Flags 域为DO_DIRECT_IO,则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个

MDL。如果一个 IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操

作方式,则 I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区

,但它同时也含有该缓冲区锁定内存页的物理地址。为了访问用 户模式缓冲区,驱动程序必须做一点额

外工作。
Flags(ULONG)域包含一些对驱动程序只读的标志。但这些标志与WDM驱动程序无关。
AssociatedIrp(union)域是一个三指针联合。其中,与WDM驱动程序相关的指针是 

AssociatedIrp.SystemBuffer。 SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分


页内存中。对于IRP_MJ_READ和IRP_MJ_WRITE操作,如 果顶级设备指定DO_BUFFERED_IO标志,则I/O管理


器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作,如果 I/O控制功能代码指出需要缓冲区,


则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲


区,这也是创建 IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据,或者是

DeviceIoControl调用中所谓的输入数据。对于读请求,设备 驱动程序把读出的数据填到这个缓冲区,

然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了METHOD_BUFFERED的I/O控制操 作

,驱动程序把所谓的输出数据放到这个缓冲区,然后I/O管理器再把数据复制到用户模式的输出缓冲区。
oStatus(IO_STATUS_BLOCK) 是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构。

IoStatus.Status域将收到一个NTSTATUS代码,而 IoStatus.Information的类型为ULONG_PTR,它将收到

一个信息值,该信息值的确切含义要取决于具体的IRP类型和请求完成的状 态。Information域的一个公

认用法是用于保存数据传输操作,如IRP_MJ_READ,的流量总计。某些PnP请求把这个域作为指向另外一

个结 构的指针,这个结构通常包含查询请求的结果。
RequestorMode将等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需


要查看这个值来决定是否要信任某些参数。
PendingReturned(BOOLEAN)如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。完


成例程通过参考该域来避免自己与派遣例程间的潜在竞争。
Cancel(BOOLEAN)如果为TRUE,则表明IoCancelIrp已被调用,该函数用于取消这个请求。如果为FALSE,


则表明没有调 用IoCancelIrp函数。CancelIrql(KIRQL)是一个IQL值,表明那个专用的取消自旋锁是在

这个IRQL上获取的。当你在取消例程 中释放自旋锁时应参考这个域。
CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置

这个域而不是直接修改该域。
UserBuffer(PVOID) 对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求,该域包含输出缓冲区的用

户模式虚拟地址。该域还用于保存读写请 求缓冲区的用户模式虚拟地址,但指定了DO_BUFFERED_IO或

DO_DIRECT_IO标志的驱动程序,其读写例程通常不需要访问这个域。当处理 一个METHOD_NEITHER控制操

作时,驱动程序能用这个地址创建自己的MDL。
I/O堆栈
任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组

中的每个堆栈单元都对应一个将 处理该IRP的驱动程序,另外还有一个堆栈单元供IRP的创建者使用(见


图)。堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。图中显示 了堆栈单元的结构。


MajorFunction(UCHAR)是该IRP的主功能码。这个代码应该为类似IRP_MJ_READ一样的值,并与驱动程序


对象中 MajorFunction表的某个派遣函数指针相对应。如果该代码存在于某个特殊驱动程序的I/O堆栈单


元中,它有可能一开始是,例如 IRP_MJ_READ,而后被驱动程序转换成其它代码,并沿着驱动程序堆栈


发送到低层驱动程序。
MinorFunction(UCHAR)是该IRP的副功能码。它进一步指出该IRP属于哪个主功能类。例如,IRP_MJ_PNP

请求就有约一打的副功能码,如IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE,等等。
Parameters(union)是几个子结构的联合,每个请求类型都有自己专用的参数,而每个子结构就是一种参

数。这些子结构包括Create (IRP_MJ_CREATE请求)、Read(IRP_MJ_READ请求)、StartDevice(IRP_MJ_PNP

的 IRP_MN_START_DEVICE子类型),等等。
DeviceObject(PDEVICE_OBJECT)是与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责

填写。
FileObject(PFILE_OBJECT)是内核文件对象的地址,IRP的目标就是这个文件对象。驱动程序通常在处理


清除请求(IRP_MJ_CLEANUP)时使用FileObject指针,以区分队列中与该文件对象无关的IRP。
CompletionRoutine(PIO_COMPLETION_ROUTINE)是一个I/O完成例程的地址,该地址是由与这个堆栈单元


对应 的驱动程序的更上一层驱动程序设置的。你绝对不要直接设置这个域,应该调用


IoSetCompletionRoutine函数,该函数知道如何参考下一层 驱动程序的堆栈单元。设备堆栈的最低一级


驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成


例程,但通常没有 自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保


存自己完成例程指针的原因。
Context(PVOID)是一个任意的与上下文相关的值,将作为参数传递给完成例程。你绝对不要直接设置该


域;它由IoSetCompletionRoutine函数自动设置,其值来自该函数的某个参数。
(如果你想知道更详细的介绍请参看相关书籍)
这里我们先定义一个控制码
#define IOCTL_PROTECT_CONTROL CTL_CODE

(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
这里我们使用他来和应用程序通讯,Irp->AssociatedIrp.SystemBuffer这个结构中存放用户模式程序发

送给驱动程 序的数据。这里使用METHOD_BUFFERED方式时,I/O管理器创建一个足够大的内核模式拷贝缓

冲区(与用户模式输入和输出缓冲区中最大的容量相 同)。当派遣例程获得控制时,用户模式的输入数据

被复制到这个拷贝缓冲区。在IRP完成之前,你应该向拷贝缓冲区填入需要发往应用程序的输出数据。当 

IRP完成时,你应该设置IoStatus.Information域等于放入拷贝缓冲区中的输出字节数。然后I/O管理器

把数据复制到用户模式缓冲区并 设置反馈变量
NTSTATUS DispatchDeviceControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
NTSTATUS ntStatus;
PIO_STACK_LOCATION IrpStack;
ULONG IoControlCode;
ULONG inSize;
ULONG outSize;
ULONG *buff;
IrpStack=IoGetCurrentIrpStackLocation(Irp);
IoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch(IoControlCode)
{
case IOCTL_PROTECT_CONTROL:
inSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
outSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
buff = (ULONG*)Irp->AssociatedIrp.SystemBuffer ;
pid=*buff;  //获得要影藏进程的ID
strcpy(Irp->UserBuffer,"Driver Start");
break;
default:
break;
}
ntStatus=Irp->IoStatus.Status;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return ntStatus;
}

要保护一个进程只用在调用ZwOpenProcess时返回一个STATUS_ACCESS_DENIED就可以了,相关的代码如下
NTSTATUS NewZwOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL
)
{
NTSTATUS ntStatus;
if(pid==(ULONG)ClientId->UniqueProcess)
{
DbgPrint("保护进程 PID :%d \n",pid);
return STATUS_ACCESS_DENIED;
}
ntStatus=OldZwOpenProcess(
 ProcessHandle,
 DesiredAccess,
 ObjectAttributes,
 ClientId 
);
return STATUS_SUCCESS;
}

这里我们要先定义相关宏:
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase; //Used only in checked build
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;
#pragma pack()


extern ServiceDescriptorTableEntry KeServiceDescriptorTable; 
#define SYSTEMSERVICE(_function)KeServiceDescriptorTable.ServiceTableBase
[*(PUCHAR)((PUCHAR)_function+1)]


这里通过windbg反汇编可以看出
lkd> u ZwOpenProcess
nt!ZwOpenProcess:
804e6044 b87a000000      mov     eax,7Ah
804e6049 8d542404        lea     edx,[esp+4]
804e604d 9c              pushfd
804e604e 6a08            push    8
804e6050 e8dc150000      call    nt!KiSystemService (804e7631)
804e6055 c21000          ret     10h
nt!ZwOpenProcessToken:
804e6058 b87b000000      mov     eax,7Bh
804e605d 8d542404        lea     edx,[esp+4]
eax后的7Ah就是索引号,便有了宏定义。
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL
);
typedef NTSTATUS (*ZWOPENPROCESS)(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL
);
ZWOPENPROCESS OldZwOpenProcess;
/


NTSTATUS NewZwOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL
);
在DriverEntry中对CR0进行修改然后修改 SSDT表。
_asm
    {
        CLI                  
        MOV    EAX, CR0       
        AND EAX, NOT 10000H 
        MOV    CR0, EAX       
    }


OldZwOpenProcess = (ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess)) ;


(ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess)) = NewZwOpenProcess;
_asm
{


MOV    EAX, CR0        
OR    EAX, 10000H          
MOV    CR0, EAX            
STI                   
}
在卸载例程中对ssdt进行修复驱动的功能就基本完成了,
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
NTSTATUS ntStatus;
UNICODE_STRING DeviceName;
UNICODE_STRING DeviceLinkString;
PDEVICE_OBJECT pDeviceObject;
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&DeviceLinkString);
pDeviceObject = DriverObject->DeviceObject;
IoDeleteDevice(pDeviceObject);
    _asm
    {
        CLI                  
        MOV    EAX, CR0       
        AND EAX, NOT 10000H 
        MOV    CR0, EAX       
    }


(ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess)) = OldZwOpenProcess;
_asm
{


MOV    EAX, CR0        
OR    EAX, 10000H          
MOV    CR0, EAX            
STI                   
}
DbgPrint("驱动卸载完成");
}


在应用程序中我们使用,CreateService来注册一个服务,由于驱动注册了符号链接这样我们就可以像打


开一个文件一样打开驱动,我们用进程快照遍历进程获得ID然后传递给驱动,这样一个简单的保护功能就


完成了。 
TCHAR name[256];


GetCurrentDirectory(256,name);
strcat(name,"\\");
strcat(name,DriverName);
SC_HANDLE sh = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(!sh)
{
return ;
}
SC_HANDLE rh = CreateService(sh,DriverName,DriverName,
SERVICE_ALL_ACCESS,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,
SERVICE_ERROR_CRITICAL,name,NULL,NULL,NULL,NULL,NULL);
if(!rh)
{
if(GetLastError()==ERROR_SERVICE_EXISTS)
{
rh=OpenService(sh,DriverName,SERVICE_ALL_ACCESS);
if(!rh)
{
CloseServiceHandle(sh);
return;
}
}
else
{
CloseServiceHandle(sh);
return;
}


}
if(rh)
{
if(0==StartService(rh,0,NULL))
{
if(ERROR_SERVICE_ALREADY_RUNNING==GetLastError())
{
return;
}
CloseServiceHandle(sh);
CloseServiceHandle(rh);
return;
}
CloseServiceHandle(sh);
CloseServiceHandle(rh);


}


void CProtectDriverDlg::OnStart() 
{
char outbuffer[4096]={0};
DWORD dw;
// TODO: Add your control notification handler code here
device=CreateFile("\\\\.\\HookSSDT",
GENERIC_READ|GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM,
0);
if(device!=INVALID_HANDLE_VALUE)
{
FindProcessPid();
DeviceIoControl(device,IOCTL_PROTECT_CONTROL,&pid,
sizeof(long),outbuffer,4096,&dw,NULL);
MessageBox("驱动已经加载开始保护","注意",NULL);
CloseHandle(device);
}
return;
}


VOID CProtectDriverDlg::FindProcessPid()
{
CString ProcessName;
GetDlgItem(IDC_EDIT)->GetWindowText(ProcessName);
PROCESSENTRY32 pe32;
        // 在使用这个结构之前,先设置它的大小
        pe32.dwSize = sizeof(pe32); 
        
        // 给系统内的所有进程拍一个快照
        HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if(hProcessSnap == INVALID_HANDLE_VALUE)
        {
        
                MessageBox("调用失败","注意", NULL);
                return ;
        }
        
        // 遍历进程快照,轮流显示每个进程的信息
        BOOL bMore = ::Process32First(hProcessSnap, &pe32);
        while(bMore)
        {
                if(pe32.szExeFile==ProcessName)
{
pid=(long)pe32.th32ProcessID;
}
                bMore = ::Process32Next(hProcessSnap, &pe32);
        }


        // 不要忘记清除掉snapshot对象
        ::CloseHandle(hProcessSnap);
        return ;
}
二.驱动调试
下面我介绍下关于驱动的调试,这里以windbg为例:
1.配置windbg双机调试环境,(VMware Workstation)
(1) 创建windbg快捷方式在目标中加上-k com:port=\\.\pipe\com_1,baud=115200,pipe 例如:"E:


\Program Files\Debugging Tools for Windows\windbg.exe" -k com:port=\\.\pipe


\com_1,baud=115200,pipe
(2) 在虚拟机设置里添加一个串行端口(如图),点击下一步;


(3)点击输出到命名管道:


在高级选中,中轮询时主动放弃CPU占用.然后完成。
(4) 在虚拟机中系统的启动项中添加一个启动项:
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional - debug" 


/fastdetect /debug /debugport=com1 /baudrate=115200
(5) 然后跟着我做:首先运行windbg,然后打开虚拟机客户机,选中调试模式


(6)进入要调试的系统后我们可以在windbg中用Debug命令中的Break对目标机进行中断,用F5我们又可


以恢复客户机的运行。


我们设置windbg的Symbol path:我的设置如下
C:\MyCodesSymbols;SRV*C:\MyLocalSymbols*[url]http://msdl.microsoft.com/download/symbols;C:


[/url]\MyLocalSymbols
这里我把自己的.pdb文件放到C:\MyCodesSymbols下,从微软的符号文件服务器上下载符号文件到C:


\MyLocalSymbols中。
现在我们来调试一个驱动试试,吧刚才写的驱动和应用文件拷贝到虚拟客户机中,在windbg中打开驱动


源文件如下:


我们先下一个延迟断点:bu hook!DriverEntry 跟普通断点相比,推迟断点的特色是把对modules!names


的操作延迟到模块加载时进行地址解析,而普通断点是对地址进行操作,或直接把 modules!names转换


为地址。推迟断点的另一个特性是系统重启之后还能记住它们(不是记住的已经转换的地址)。这个特


性使得推迟断点在模块被卸 载之后仍然被会记得,而普通断点就不行了,当模块被卸载之后断点同时会


被移除。
延迟断点可以很方便的使我们的应用程序在特定的位置下断。
如果我们要下一个普通断点可以使用bp命令或者直接在源代码 要下断的地方按f9, 当你想查看已经设置


的断点时,可以使用bl(“Breakpoint Line”)命令.下好断点后我们就可以按f5进行调试了,具体方法


和其他调试器差不多我也就不多罗嗦了。关于windbg命令的详细描述可以参看帮助文 档。
这篇文章我就写到这里,有机会的话,我会给大家更详细的介绍windbg调试器的用法.谢谢大家。


========

驱动调试攻略(WinDbg)  



http://blog.163.com/zhg_tao/blog/static/84116744200862424215/ 


驱动调试是一个系统级调试方式,所以调试工具用WinDbg是最佳选择。驱动调试一般情况下,需要两台


电脑进行。一台作为主机进行驱动调试,另一台作为目标机进行驱动安装。当然如果没有多余的电脑,


我们可以安装VMware模拟系统调试(虚拟机)。只是对电脑的配置要求比较高。本文介绍使用WinDbg及


WMware进行调试的具体设置及方法。这些资料在网络很难找到,笔者也是吸取了他人很多宝贵意见才完


成的,供大家学习讨论。


1.       安装和设置WinDbg,WMware


首先,请到微软官方网站


http://www.microsoft.com/whdc/devtools/debugging/default.mspx,下载最新版本的WinDbg工具,建


议使用6.0以上版本的WinDbg做为调试工具。WMware也可以在网站上找到,当然使用高版本比较好。 安


装过程比较简单,就不在此多做介绍。唯一要注意的是,安装完虚拟机后,请在VMware主菜单“VM”中


选择“Install VM Tools…”,完成系统驱动文件的安装。


       安装完这两个软件后,请在VMware中安装跟你的电脑版本相同的操作系统。例如:你的电脑安装


的是WindowsXP Professional,那么最好在Vmware中安装相同版本。至少是相同的系统,Windows XPßà 


Windows XP、Windows 2000ßà Windows 2000。这样会在驱动调试过程中避免不必要的麻烦。笔者使用的


操作系统为Windows XP Professional。


       我们将使用电脑的COM1口最为调试口。


(1) 编辑虚拟机中C盘根目录下的boot.ini文件。


C:\>attrib -s -h -r boot.ini


C:\>notepad boot.ini


在boot.ini文件中添加以下代码:


multi(0) disk(0) rdisk(0) partition(1) \WINDOWS="Microsoft Windows XP Professional-Debug" 


/fastdetect /debugport=COM1 /baudrate=115200


(2) 打开vmware中winxp的设备管理器,选择端口(com1),如下图。


 (3) 双击这个"com1",在弹出的对话框中,设置如下图。


 (4) 关闭vmware下的操作系统。配制虚拟机的硬件,VM->Settings->Add->加一个Serial Port,选择


"Output to named pipe",然后下一步,第一框里保持默认的 "\\.\pipe\com_1"


第二框里选"This end is the server."


第三框里选"The other end is an application."


选中 "Connect at power on"选中 "Yield CPU on poll"。保存退出。


(5) 回到我们的真实系统(主机)中,重复(2)、(3)两步。


(6) 设置WinDbg的快捷方式,及其运行参数:


 在快捷方式的属性中,将目标(Target)改为:


"C:\Program Files\Debugging Tools for Windows\windbg.exe" -b -k com:pipe,port=\\.\pipe


\com_1,resets=0


命令参数的意义:


-b:一旦主机目标机之间建立起连接,立刻中断目标机


-k:内核调试


Com:设置连接目标机的通信端口(此处为命名管道)和波特率(此处为115200)


-y:设置符号文件路径


(7)设置WinDbg参数


 现在我们假设你的驱动代码放在D:\mydriver目录中;系统级Symbol文件存放在C:\WINDOWS\Symbols目


录中,这些Symbol文件是从微软的网站上http://msdl.microsoft.com/download/symbols下载而来,这


个网址是不可以用IE直接打开的,WinDbgà Fileà Symbol File Path...界面中选择Reload,WinDbg会自


动帮你下载;驱动生成的Symbol文件存放在D:\mydriver\objchk_wxp_x86\i386目录中。


a) 设置驱动Symbol路径,WinDbgà Fileà Symbol File Path...为


SRV*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols; D:\mydriver


\objchk_wxp_x86\i386


b) 设置驱动源文件路径,WinDbgà Fileà Source File Path…为D:\mydriver


c) 为了保险起见,我们同时设置系统变量。


My Computer à Properties à Advanced Tab à Enviroment Variables à Add.


_NT_DEBUG_BAUD_RATE=115200


_NT_SYMBOL_PATH=SRV*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols


_NT_ALT_SYMBOL_PATH = D:\mydriver\objchk_wxp_x86\i386


_NT_SOURCE_PATH = D:\mydriver


(8)启动VMware,将主机目录C:\WINDOWS\Symbols下的拷贝到虚拟机的相同位置。同时请把驱动编译后


生成在D:\mydriver\objchk_wxp_x86\i386目录下的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝


到两个系统的C:\WINDOWS\Symbols目录中。


(9)重新启动VMware,并选择如图所示。这个时候先不敲回车键。


(10) 在主机中运行之前设置好的WinDbg快捷方式。直到在WinDbg命令窗口中出现“Waiting to 


reconnect...”,此时打开VMware,敲回车键进入虚拟机。到这里所有的设置工作就全部完成了。


2.驱动安装及调试


   这里其大家注意,由于我们目前用的是虚拟机,所以安装驱动程序的时候一定要把VMware设置到全屏


状态。


(1) 编辑你要调试的源代码,在你需要调试的代码中添加硬断点。如果不添加硬断点,将没有办法进


入所调试的程序中。具体方法如下:


在你需要调试的程序中加入以下代码:


extern "C" {


#include <wdm.h>


//或者是#include <winddk.h>


}//放在文件开头


DbgBreakPoint();//放在需要调试的地方


重新编译驱动程序,记住一定要更新把重新生成的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝到


两个系统的C:\WINDOWS\Symbols目录中。


(2)       打开VMware,Ctrl+Alt+Enter进入虚拟机全屏模式。插入需要调试设备的硬件,笔者调试的


目标设备为USB。当USB插入主机后,直接由VMware识别到这个USB设备,指定驱动位置,系统将会在你设

置硬断点的地方停下来。这个时候你就可以回到WinDbg,进行单步调试了。你还可以在程序中添加软断

点,“F9”或者“bp MyApp!MyFunction”。

3.两台电脑调试说明

   两台电脑调试跟借助VMware调试有所不同,不同之处有以下几个方面。

(1)       需要自己制作一条Null Modem Cable. 请参考以下电路。将两台电脑连接起来。

管脚信息说明: 

9-Pin NULL Modem Cabling

3 2 Transmit Data

2 3 Receive Data

7 8 Request to Send

8 7 Clear to Send

6,1 4 Data Set Ready and Carrier Detect

5 5 Signal Ground

4 6,1 Data Terminal Ready

25-Pin NULL Modem Cabling

2 3 Transmit Data

3 2 Receive Data

4 5 Request to Send

5 4 Clear to Send

6 20 Data Set Ready and Carrier Detect

7 7 Signal Ground

20 6 Data Terminal Ready 

(2)WinDbg快捷方式参数设置

"C:\Program Files\Debugging Tools for Windows\windbg.exe" -b -k com:port=\\.\ com1,resets=0
========

使用WinDbg和虚拟机调试Windows驱动程序

http://blog.sina.com.cn/iceprettylife

驱动调试攻略(WinDbg)

驱动调试是一个系统级调试方式,所以调试工具用WinDbg是最佳选择。驱动调试一般情况下,需要两台

电脑进行。一台作为主机进行驱动调试,另一台作为目标机进行驱动安装。当然如果没有多余的电脑,

我们可以安装VMware模拟系统调试(虚拟机)。只是对电脑的配置要求比较高。本文介绍使用WinDbg及

WMware进行调试的具体设置及方法。这些资料在网络很难找到,笔者也是吸取了他人很多宝贵意见才完

成的,供大家学习讨论。

VMware Support 中说,自 4.0.18.0 版本之后的 WinDbg 都支持了通过 pipe 来进行调试,不过微软对

此并没有任何说明。所以,在VMware中虚拟被调试的系统,然后通过VMware虚拟一个com端口。使用这个

虚拟的端口,就可以用 WinDbg 进行调试了。

具体步骤如下:
1. 安装和设置WinDbg,WMware


首先,请到微软官方网站


http://www.microsoft.com/whdc/devtools/debugging/default.mspx,下载最新版本的WinDbg工具,建


议使用6.0以上版本的WinDbg做为调试工具。WMware也可以在网站上找到,当然使用高版本比较好。 安

装过程比较简单,就不在此多做介绍。唯一要注意的是,安装完虚拟机后,请在VMware主菜单“VM”中

选择“Install VM Tools…”,完成系统驱动文件的安装。

安装完这两个软件后,请在VMware中安装跟你的电脑版本相同的操作系统。例如:你的电脑安装的是

WindowsXP Professional,那么最好在Vmware中安装相同版本。至少是相同的系统,Windows XPßà 

Windows XP、Windows 2000ßà Windows 2000。这样会在驱动调试过程中避免不必要的麻烦。笔者使用的

操作系统为Windows XP Professional。

我们将使用电脑的COM1口最为调试口。
1.1设置 VMware 的虚拟com

1. 运行 VMware ,点击 "Edit virtual machine settings"

2. 点击 "Add..." 来运行 VMware 的 Hardware Wizard

3. 选择 "Serial Port",点 "下一步"

4. 选择 "Output to named pipe",点 "下一步"

5. 第一框里保持默认的 "//./pipe/com_1"
第二框里选"This end is the server."
第三框里选"The other end is an application."
选中 "Connect at power on"
然后点击 "Advanced>>"

6. 选中 "Yield CPU on poll"(VMware Support 中提到了这一点),然后点完成。

7. 这样就完成了虚拟com的设置。

8. 重新启动一下。
1.2设置 VMware 虚拟出来的 guest os

9. 编辑虚拟机中C盘根目录下的boot.ini文件。

在c:/下,可以找到boot.ini,可以用记事本打开它,或者命令行方式

C:/>attrib -s -h -r boot.ini
C:/>notepad boot.ini

我们需要在 guest os 的启动项上加些参数,才能够使用WinDbg调试它。我们可以在现有的行后面直接

加参数,不过强烈推荐复制一个新行,在新行的后面加参数。这样在调试启动有问题的时候,我们可以

方便的换回原来的启动方式。下面就是我改好的boot.ini。

在boot.ini文件中添加以下代码:
multi(0) disk(0) rdisk(0) partition(1) /WINDOWS="Microsoft Windows XP Professional-Debug" 

/fastdetect /debugport=COM1 /baudrate=115200

10. 在 guest os 的设备管理器中把com1端口的速度也就是"每秒位数"项,设为和上面一样的115200。


打开vmware中winxp的设备管理器,选择端口(com1), 双击这个"com1",在弹出的对话框中。
1.3设置真实系统(主机)com


11. 回到我们的真实系统(主机)中,重复第10步。
1.4设置真实系统(主机)WinDbg


12. 设置WinDbg的快捷方式,及其运行参数:


我们需要告诉WinDbg通过pipe进行连接和连接的速度。


可以在命令提示符(cmd.exe)下加参数 -k com:port=//./pipe/com_1,baud=11520,pipe 运行WinDbg(


VMware Support 中没有提到 baud=11520 这个参数,其实这是个比较重要的参数)。


更方便的方法是在桌面建立一个WinDbg的快捷方式,在该快捷方式的属性,"目标"框中,加上参数 -k 


com:port=//./pipe/com_1,baud=11520,pipe 。这样运行这个快捷方式启动的WinDbg就完成了设置。参


数的具体作用,可以参考 WinDbg 的帮助文件。


在快捷方式的属性中,将目标(Target)改为:
"C:/Program Files/Debugging Tools for Windows/windbg.exe" -b -k 


com:pipe,port=//./pipe/com_1,resets=0


命令参数的意义:
-b:一旦主机目标机之间建立起连接,立刻中断目标机
-k:内核调试
Com:设置连接目标机的通信端口(此处为命名管道)和波特率(此处为115200)
-y:设置符号文件路径


13. 设置WinDbg参数


现在我们假设你的驱动代码放在D:/mydriver目录中;系统级Symbol文件存放在C:/WINDOWS/Symbols目录


中,这些Symbol文件是从微软的网站上http://msdl.microsoft.com/download/symbols下载而来,这个


网址是不可以用IE直接打开的,WinDbgà Fileà Symbol File Path...界面中选择Reload,WinDbg会自动


帮你下载;驱动生成的Symbol文件存放在D:/mydriver/objchk_wxp_x86/i386目录中。


a) 设置驱动Symbol路径,WinDbg->File-> Symbol File Path...为


SRV*C:/WINDOWS/Symbols*http://msdl.microsoft.com/download/symbols; 

D:/mydriver/objchk_wxp_x86/i386

b) 设置驱动源文件路径,WinDbg->File-> Source File Path…为D:/mydriver

c) 为了保险起见,我们同时设置系统变量。

My Computer -> Properties-> Advanced Tab-> Enviroment Variables->

Add._NT_DEBUG_BAUD_RATE=115200


_NT_SYMBOL_PATH=SRV*C:/WINDOWS/Symbols*http://msdl.microsoft.com/download/symbols


_NT_ALT_SYMBOL_PATH = D:/mydriver/objchk_wxp_x86/i386
_NT_SOURCE_PATH = D:/mydriver


14. 启动VMware,将主机目录C:/WINDOWS/Symbols下的拷贝到虚拟机的相同位置。同时请把驱动编译后生


成在D:/mydriver/objchk_wxp_x86/i386目录下的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝到


两个系统的C:/WINDOWS/Symbols目录中。


15. 重新启动VMware,并选择如图所示。这个时候先不敲回车键。


16. 在主机中运行之前设置好的WinDbg快捷方式。直到在WinDbg命令窗口中出现“Waiting to 


reconnect...”,此时打开VMware,敲回车键进入虚拟机。到这里所有的设置工作就全部完成了。
2.驱动安装及调试


这里其大家注意,由于我们目前用的是虚拟机,所以安装驱动程序的时候一定要把VMware设置到全屏状


态。


17. 编辑你要调试的源代码,在你需要调试的代码中添加硬断点。如果不添加硬断点,将没有办法进入


所调试的程序中。具体方法如下:


在你需要调试的程序中加入以下代码:


extern "C" {
#include <wdm.h>
//或者是#include <winddk.h>
}//放在文件开头
DbgBreakPoint();//放在需要调试的地方


重新编译驱动程序,记住一定要更新把重新生成的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝到


两个系统的C:/WINDOWS/Symbols目录中。
18. 打开VMware,Ctrl+Alt+Enter进入虚拟机全屏模式。插入需要调试设备的硬件,笔者调试的目标设备


为USB。当USB插入主机后,直接由VMware识别到这个USB设备,指定驱动位置,系统将会在你设置硬断点


的地方停下来。这个时候你就可以回到WinDbg,进行单步调试了。你还可以在程序中添加软断点,“F9


”或者“bp MyApp!MyFunction”。
3.两台电脑调试说明


两台电脑调试跟借助VMware调试有所不同,不同之处有以下几个方面。


(1) 需要自己制作一条Null Modem Cable. 请参考以下电路。将两台电脑连接起来。


管脚信息说明:


9-Pin NULL Modem Cabling


3 2 Transmit Data


2 3 Receive Data


7 8 Request to Send


8 7 Clear to Send

6,1 4 Data Set Ready and Carrier Detect

5 5 Signal Ground

4 6,1 Data Terminal Ready

25-Pin NULL Modem Cabling

2 3 Transmit Data

3 2 Receive Data

4 5 Request to Send

5 4 Clear to Send

6 20 Data Set Ready and Carrier Detect


7 7 Signal Ground


20 6 Data Terminal Ready


(2)WinDbg快捷方式参数设置
"C:/Program Files/Debugging Tools for Windows/windbg.exe" -b -k com:port=//./ com1,resets=0


//


========

Windbg简明教程

2008-10-08 09:10Windbg是Microsoft公司免费调试器调试集合中的GUI的调试器,支持Source和

Assembly两种模 式的调试。Windbg不仅可以调试应用程序,还可以进行Kernel Debug(新版本对于XP+

操作系统支持Live kernel debug),同时结合Microsoft的Symbol Server调试应用程序和Kernel非常得

利。Windbg支持x86,IA64,AMD64。
Windbg下载地址:http://www.microsoft.com/whdc/devtools/debugging/default.mspx
下面我来说一下Windbg的基本用法:
基本设置
由于Windbg是GUI调试器,所以在设置方面可以通过菜单,也可以通过命令。我只说明最方便的办法:)

,那请先打开Windbg吧
符 号路径设置,Ctrl+S在弹出的窗口中输入你的符号路径,路径的格式只要符合Windows操作系统路径

格式即可,路径可以多个,中间以分号间隔,如: d:/symbols/win2k3_en;个人感觉Windbg的强大功能

之一在于Windbg会自动到Microsoft的服务器上下载符号表文件 (.dbg或.pdb,有时DLL和EXE也会下载

),只要在符合表路径里做如下设置:srv*d:/symbolslocal*http: 


//msdl.microsoft.com/download/symbols,这样如果相关符号表在d:/symbolslocal目录没有找到的话


, Windbg会自动在Microsoft的Symbol Servers上下载。如果你是调试自己的应用程序的话,建议你将


自己应用程序的*.pdb文件的路径放在前面这样对Windbg来说查找起来比较快。
源文件路径设置:Ctrl+P在弹出的窗口里指定你的源代码文件的路径,路径格式只要符合Windows操作系


统的格式即可,可以指定多个,中间以分号间隔。
可以将你的设置进行保存,File->Save Workspace。
开始调试
可以创建一个子进程进行调试,也可以对正在运行的程序进行调试,方法如下:Ctrl+E打开一个应用程


序并可指定运行参数进行调试;F6从对话框中选择当前正在运行的进程调试。
对于调试子进程,通常Windbg会在应用程序运行之前中断,此时你可下一些想要的断点:
以Notepad.exe 为例,如果你还没有符号表文件的而又设置了MicrosoftSymbol Servers的话,你得先等


一下,因为Windbg会自己自动到Microsoft上下载相关的符号表,在命令提示符的位置如果没有出现0: 


000>这个标记表示Windbg正在忙。
表达式格式:
无论在那条指定里都会涉及这个问题:数制,语法。Windbg支持C++和 MASM两个表达式格式,@@用于


即时改变语法格式,即在MASM下使用@@指令来使用C++的语法解释器,反之亦然。Windbg数制的表示0x


表示 16进制,0n表示10进制,0t表示8进制,0y表示2进制;Windbg默认数制为16进制,n指认用于设置

数制;默认表达式语法是MASM,. expr 指令用于设置表达式语法解释器。本文均使用MASM为默认语法解

释器。大小问题,通常如果不特别说明,Windbg对指令不区分大小
断点指令:BP,BM,BA,BL,BC,BD,BE
BP 在指定的地址设置断点
bp notepad!WinMain,在Notepad的WinMain函数处下断点。
断 点的位置可以用符号表示,也以直接使用地址及Windbg的Pseudo-Register(虚拟寄存器),如

$exentry表示进程的入口点,可以使 用bp @$exentry在进程的入口点设置断点,对于Notepad当前入口

点为01006420,也可以直接 bp 01006420,等效于bp notepad!WinMainCRTStartup.
BM 使用模式匹配设置断点,需要符号表支持
bm 值一提,在符号表合法的情况下(符号表中包含私有符号的时候),bm可能通过模式一次下多个断点


,bm mydriver!FastIo*指定可以将所有与FastIo*模式相匹配的函数下断点,如


FastIoRead,FastIoWrite等。但是bm 需要full or export symbols支持,Microsoft的提供的符号表不


是都支持的,通常我们自己编译的程序的符号表(Windbg显示为private pdb symbols)默认是支持的。
BA (Break on Access)
顾名思义,对内存访问下断点。对于在多核或多处理器调试的时候 很有用,对于调试多线程也很有用,


应该说用处很多,比如对一个全局变量设置断点,ba mydriver!gMonitoredDevices,如果如果你认为这


个变量的值被莫名的修改了,相信通过BA设置的断点,你很快就能找到是谁修改 的。
BL(List),BC(Clear),BE(Enable),BD(Disable)
这四个指令是分别用于列表,清除,开启和禁用断点,也是使用非常频繁的指令。
条件断点
以上所提到的断点指令通过与J指令很容易形成条件断点。比如:
bp USER32!GetMessageW "r $t1=poi(esp+4);r $t2=poi(@$t1+4); j(@$t2 = 0x102 ) 'du @$t1+8 

L2;gc';'gc'"
这个条件断点,截取WM_CHAR消息,并将字符(包括中文)显示出来。
条件断点的最简形式:bp Address "j (Condition) 'OptionalCommands'; 'gc' "
Address是指令的地址,Condition是一个条件表达式,如果@eax=1,'OptionalCommands'是在断点被击

中并且表达式成立时要执行的指令;gc指定是从一个条件断点返回,是不可少的一部分。
数据查看指令 d{a|b|c|d|D|f|p|q|u|w|W}
d{b|c|d|D|f|p|q}分别是显示:
byte&ASCII, double-word&ASCII,double-word,double-precision,float,pointer-sized,quad-word数


据;
DA用于显示ASCII,DU用于显示UNICODE;
BYB,BYD,显示binary和Byte及binary和DWORD
补充一个DV,用于查看本地变量用的
这些指令区分大小。
栈指令k[b|p|P|v]
这 四条指令显示的内容类似,但是每个指令都有特色,KB显示三个参数,Kp显示所有的参数,但需要


Full Symbols或Private PDBSymbols支持。KP与Kp相似,只是KP将参数换行显示了。Kv用于显示FPO和调


用约定,KD,用于显示Stack的Dump,在跟踪栈时 比较有用。
这些指令区分大小。
KD显示的内容:
0012fbd0 0012fbf0
0012fbd4 77e2158f USER32!UserCallWinProc+0x18
0012fbd8 0016011e
0012fbdc 00000030
0012fbe0 750a0c3f
0012fbe4 00000000
0012fbe8 00000000
0012fbec dcbaabcd
0012fbf0 0012fc2c
0012fbf4 77e1279c USER32!DefDlgProcWorker+0xbf
0012fbf8 004018e0 DGGuarder!MainDLGproc [j:/mydriver/dgguarder2/dgguarder.c @ 350]
0012fbfc 0016011e
0012fc00 00000030
0012fc04 750a0c3f
0012fc08 00000000
0012fc0c 00629d08
0012fc10 00000030
0012fc14 00619828
0012fc18 77e0f626 USER32!__ClientLoadMenu+0x38
0012fc1c 77e0f635 USER32!__ClientLoadMenu+0x47
KP显示的内容:
ChildEBP RetAddr
0012fbd0 77e2158f DGGuarder!MainDLGproc(
struct HWND__ * hwnd = 0x0016011e,
unsigned int message = 0x30,
unsigned int wParam = 0x750a0c3f,
long lParam = 0)+0x227 [j:/mydriver/dgguarder2/dgguarder.c @ 415]
0012fbf0 77e1279c USER32!UserCallWinProc+0x18
0012fc5c 77e0b981 USER32!DefDlgProcWorker+0xbf
0012fd14 77e140bb USER32!InternalCreateDialog+0x695
0012fd44 77e1410f USER32!InternalDialogBox+0xaa
0012fd64 77df41ec USER32!DialogBoxIndirectParamAorW+0x34
0012fd90 00401cdc USER32!DialogBoxParamA+0x4a
0012fe88 00408684 DGGuarder!WinMain(
struct HINSTANCE__ * hInstance = 0x00400000,
struct HINSTANCE__ * hPrevInstance = 0x00000000,
char * lpCmdLine = 0x00132902 "",
int nCmdShow = 10)+0x5c [j:/mydriver/dgguarder2/dgguarder.c @ 469]
0012ffc0 77e88989 DGGuarder!WinMainCRTStartup(void)+0x194 

[f:/vs70builds/3077/vc/crtbld/crt/src/crt0.c @ 251]
0012fff0 00000000 KERNEL32!BaseProcessStart+0x3d
数据修改指令e{b|d|D|f|p|q|w}
请参见文档吧:)
反汇编指令u,uf
u @$exentry L10
0:001> u @$exentry L10
notepad!WinMainCRTStartup:
01006420 55 push ebp
01006421 8bec mov ebp,esp
01006423 6aff push 0xff
01006425 6888180001 push 0x1001888
0100642a 68d0650001 push 0x10065d0
0100642f 64a100000000 mov eax,fs:[00000000]
01006435 50 push eax
01006436 64892500000000 mov fs:[00000000],esp
0100643d 83c498 add esp,0xffffff98
01006440 53 push ebx
01006441 56 push esi
01006442 57 push edi
01006443 8965e8 mov [ebp-0x18],esp
01006446 c745fc00000000 mov dword ptr [ebp-0x4],0x0
0100644d 6a02 push 0x2
0100644f ff1560110001 call dword ptr [notepad!_imp____set_app_type (01001160)]
uf (Unassemble Function)指令对整个函数进行反汇编
uf GetLanguageCount
0:000> uf GetLanguageCount
DGGuarder!GetLanguageCount [j:/mydriver/dgguarder2/language.c @ 54]:
54 00403c00 55 push ebp
54 00403c01 8bec mov ebp,esp
54 00403c03 81ecc0000000 sub esp,0xc0
54 00403c09 53 push ebx
54 00403c0a 56 push esi
54 00403c0b 57 push edi
54 00403c0c 8dbd40ffffff lea edi,[ebp-0xc0]
54 00403c12 b930000000 mov ecx,0x30
54 00403c17 b8cccccccc mov eax,0xcccccccc
54 00403c1c f3ab rep stosd
56 00403c1e 6a00 push 0x0
56 00403c20 6828b44100 push 0x41b428
56 00403c25 6820b44100 push 0x41b420
56 00403c2a e8b1edffff call DGGuarder!ReadInt (004029e0)
56 00403c2f 83c40c add esp,0xc
58 00403c32 5f pop edi
58 00403c33 5e pop esi
58 00403c34 5b pop ebx
58 00403c35 81c4c0000000 add esp,0xc0
58 00403c3b 3bec cmp ebp,esp
58 00403c3d e85e0f0000 call DGGuarder!_RTC_CheckEsp (00404ba0)
58 00403c42 8be5 mov esp,ebp
58 00403c44 5d pop ebp
58 00403c45 c3 ret
跟踪指令T,TA,TB,TC,WT,P,PA,PC
T指令单步执行,在源码调试状态下,可指源码的一行,根据不同的选项也可以为一行ASM指令;
TA单步跟踪到指定地址,如果没有参数将运行到断点处。
TB执行到分支指令,分支指令包括calls, returns, jumps, counted loops, and while loops
TC执行到Call指令
WT Trace and Watch Data,一条强大指令,对执行流程做Profile,执行一下看看结果吧
P,PA,PC相信不用多做解释,大家也都明白了
源代码操作指令 .,lsf,lsc,ls,l,lsp
.指令打一个源文件,可以打开一个全路径的文件,也可以通过函数地址来打开并定位到源文件中函数的


位置,如. –a myapp!main,. j:/mydriver/mydriver.c
lsf指定一个源文件为当前源文件,使用lsc可显示当前指定的源文件ls可显示源文件的代码。Lsf可以使


用全路径,如果源路径已经设置,也可以直接指定源文件名称。如lsf mydriver.c,lsf 


j:/mydriver/mydriver.c
lsc显示当前源文件
ls显示当前源文件的代码,如ls 200显示第200行
l 用于设置源文件选项
lsp 设置源文件行在调试时显示范围比如,显示当前行的前50,后50,lsp 100
但通常使用Windbg时,可以直接用Ctrl+O来打开并查看源文件
寄存器指令 r
相 信大家对这个指令都很熟悉,在Windbg中r指令除了可以显示修改CPU寄存器之外,Pseudo-Register


可使用这个命令来修改。对eax 操作,r eax 显示其值,r eax=2,修改其值;r $t2=10,修改Pseudo-


Register的值,r @$t2显示其值。
Search 指令 s,#
S 指令对内存区别进行查找,可用于查找数字,字符串,但不支持模式查找。s -d @esp L100 8187bc40


,从esp指向的内存地址0x100个字节内查找 DWORD 8187bc40。查找字符串 s -a 0012ff40 L20 "Hello" 


。s -sa 和 s -su 显示内存可打印的ASCII和Unicode字符串。
#指令可以查询汇编指令模式, # "call[ ]+esp" kernel32 L1000查找call esp 指令。
其他常用指令 lm,!peb,x,dt
lm 查看当前载入的模块
!peb 查看当前进程环境块(PEB)
x 查看模块的符号,如x mydriver!*FastIo*,显示所有与*FastIo*匹配的符号列表
dt 查看类型数据,还可用于查看模块类型的符号列表,如 dt dgguarder!_IMAGE_DOS_HEADER 00400000
从00400000处查看_IMAGE_DOS_HEADER类型的数据
上下文的概念
Windbg下上下文的概念很重要,根据文档中说明有多种上下文概念,
Session Context,
Process Context
Register Context(其实也就是线程上下文)
Local Context(这个关系的本地如果解析本地变量的问题)
调 试Win32应用程序,Session Context和Process Context是确定的,主要是Register Context,也即

Thread Context,可以使用~指令来查看,改变当前的Thread Context 。~*显示所有线程列表,~xs用于

切换上下文(x是数字),如:~1s,将上下文切换到1号线程。
.frame用来设置Local Context。

PAUSE
好了,基本的指令都已经列出了来了,Kernel Debug现在先不写了,其实跟Win32也差不多。如果再有时

间再写吧。本文对指令没有说的太细,详细说明见Windbg文档,希望见谅。

//
========

WINDBG Script简易教程



【下载地址】: http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
【作者声明】: 希望更多人使用WINDBG,然后大概就能看到debugging tools for windows的帮助文件的

中文翻译了吧。花了 几天时间才在翻译软件的帮助下看完debugger commands部分,痛苦死了。也找不

到WINDBG的插件,还是WINDBG没插件功能?为了简 化调试过程,只有学习使用SCRIPT了,现在把这几天

的经验跟大家分享。附件中所有代码经NOTEPAD,REGEDIT等调试,花了几小时,基本通 过。

废话多了,现在是正文。WINDBG的指令比较多,还是英文的,所以我只挑了一部分经常会用到的,并通

过实例去告诉大家那些指令的作用和格式。正如大多数高级语言教程一样,我们先来看看如何写一个

HELLO WORLD的程序。

如果使用.echo "HELLO WORLD"作为例程就太简单了,我希望介绍更多的指令。所以我在OD直接用汇编写

了个程序:

PUSH 0
PUSH 12345678 ;TITLE跟显示内容都在这个跟下一个PUSH
PUSH 12345678 ;我比较懒。。。就用一样的字符了
PUSH 0
MOV EAX,OFFSET MESSAGEBOXW
CALL EAX


先用EAX保存MESSAGEBOXW的指针,然后再CALL。这是为了你在任何一个程序下都能用使用这个SCRIPT。


如果直接 CALL MESSAGBOXW的指针,翻译成机器码是相对于当前位置的偏移,这样写出来的SCRIPT文件


在这个程序能用,别的程序就不能用了。


机器码 6A 00 68 78 56 34 12 68 78 56 34 12 6A 00 b8 68 3d e2 77 ff d0

我虚拟机使用的是WIN 2000 连SP1都不是。。所以我不保证你的机器仍然能正常运行这个程序。为了能

正常使用,你可以随便找一个程序,然后 BP MESSAGEBOXW,中断之后当前的EIP就是了,把ff d0前面的

68 3d e2 77换掉了就可以了。我的目的并不是介绍如何写一个兼 容性差的程序,重点是学会如何写

SCRIPT。


准备工作做好了,在看代码之前,先解释一些指令:


$exentry伪寄存器,数值上等于EP

$t0-$t19,WINDBG为我们提供了20个自定义的伪寄存器


R指令能改变几乎所有寄存器的值,包括EAX等


.dvalloc [/b] size 申请内存空间,带/b 地址,可在指定地址申请空间,不带则自动分配,指定地址


时不一定成功,暂时的经验指定地址越大越容易成功。


e* 地址 在指定内存中写入数据,EW 写入WORD,EB写入BYTE,ED写入DWORD


注意: EW 00400000 12345会产生溢出错误,同理EB 00400000 123也是错的,正确的例子可见后面的代





f 地址 L长度 BYTE 在长度的地址写入数据,你可以在示例中看到效果。同样BYTE的位置只能是BYTE,


多于8位的数据都会造成溢出错误。


m 源地址 L源地址长度 目的地址 复制内存区域


d* 地址 显示地址中的数据,其中db的效果可在示例中看到。


.dvfree /d 地址 size 释放指定地址的内存,这里指定地址用的是/d要与 .dvalloc的/b相区别。


附件中helloworld.txt的代码:

---------------------------------------helloworld.txt--------------------------------

g $exentry

r $t0=00ff0000; $$ $t0:源内存基址

r $t2=@$exentry; $$ $t2:目的内存基址

r $t1=@$t0; $$ $t1:当前指针

.dvalloc /b $t0 1000; $$在00b90000申请1000BYTE的内存空间


ew $t1 006A 0068


db $t0


.echo "ew指令的效果"


r $t3 = @$t1 + 3; $$PUSH DWORD的机器码=68 DWORD
$$这里应该输入字符串的首址
r $t1 = @$t1 + 7; $$懒得计算,所以用$t3存起DWORD的指针
$$输入字符串的时候一起搞定
eb $t1 68 12 34


db $t0


.echo "eb指令的效果"


r $t4 = @$t1 + 1; $$同上


r $t1 = @$t1 + 5


f $t1 l20 6A 00 b8 68 3d e2 77 ff d0


db $t0


.echo "f指令的效果"


r $t1 = @$t1 + 9


r $t5 =$t1 - $t0 + $t2


ed $t3 $t5; $$ 添加字符串的指针回去


ed $t4 $t5


f $t1 l20 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '!' 00 00
$$ 把字符串写进内存中
m $t0 l30 $t2


r $ip=$t2; $$ 修改EIP


.dvfree /d $t0 1000; $$ 释放内存

g

---------------------------------------helloworld.txt完结的分割


线--------------------------------

为了你的速度,请保证symbol path为空,只有在你有源代码或者系统核心的时候它的存在才有意义,否

则你会发现它会非常费时且毫无意义,尤其是你得连上网络下载symbol资源的时候。

使用SCRIPT文件的命令有4个"$<","$$<","$><","$$><",他们的区别就是有没有空格或者 换行符的限制

。使用$$><没有任何限制,这样可使代码更具可读性。要使用附件中的SCRIPT请使用$$><指令。

例如你可以用下面指令访问在D盘下的helloworld.txt。

$$><d:/helloworld.txt


如果你把helloworld.txt放在WINDBG的安装目录,那么你可以使用下面指令:
$$><helloworld.txt


运行完helloworld.txt后你会发现报错了,因为我的代码覆盖了EP。通过上面的例子,我们能用SCRIPT


做什么呢?在合适的时机,把没加密的IAT或者其他什么的,暂存到内存中,在脱壳完毕的时候再自动用


正确的部分把加密部分覆盖掉。


也许在未来,也会遇到这样的需要,程序运行到某部分的时候,中断,然后运行我们自己的代码,运行


完毕之后,我们需要返回到程序原来的流程。为此我把上面的SCRIPT修改了一下,写成BACKTOCODE.TXT





--------------------------------------------


backtocode.txt--------------------------------------


r $t0=00ff0000


r $t1=@$t0


r $t18=$ip ;$$ 用$t18暂存当前EIP,显然$ip=EIP


.dvalloc /b $t0 1000


ew $t1 006A 0068


r $t3 = @$t1 + 3


r $t1 = @$t1 + 7


eb $t1 68 12 34


r $t4 = @$t1 + 1

r $t1 = @$t1 + 5

f $t1 l20 6A 00 b8 68 3d e2 77 ff d0

r $t1 = @$t1 + 9

ed $t3 $t1

ed $t4 $t1

ba e1 $t1 ;$$ 内存运行断点,E代表运行1是长度,在E后面通常是1,断在最后一个指令后的第一个地



f $t1 l20 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '!' 00 00

r $ip=$t0 ;$$ 在分配的内存中直接运行我们自己的代码

g

.dvfree /d $t0 1000


r $ip=$t18 ;$$ 把EIP设置为原来的


.cls ;$$ 当前命令行窗口清屏


p ;$$ 单步步过


-------------------------------------------backtocode.txt结


束-----------------------------------


为了anti anti debug我们需要隐藏标题,虽然WINDBG本身有.wtitle指令,可是那个指令会把它后面的


所有内容当作字符串输入,而且windbg+版本号是无论怎么改都会被默认添加到最后的。这样上面那个


SCRIPT就有用了,就差CODE了。


为了anti anti debug,每次DEBUG的时候,我们都要做一些准备工作,用SCRIPT文件,我们可以自动完


成这些操作,下面是我DEBUG之前都会用到的SCRIPT文件


-----------------------------------------------


start.txt----------------------------------------


r $t0 = 00
eb 7FFDF002 $t0 ;$$去除DEBUG标志
.pcmd -s ".if(eax<70000000 and eax>00120000){da eax;du eax}; .if(edx<70000000 and 


edx>00120000){da edx;du edx}"
g $exentry ;$$入口点


-------------------------------------------start.txt结


束----------------------------------------


如你所见的,这有点少。没办法水平有限,而且写这篇文章的时候我才勉强说是学会用,还是那句重点

是教会大家用WINDBG。WINDBG的初始断点并不是 入口点所以得自己用指令让它自动停在入口点,有的程

序是有TLS表的,对着PE格式的介绍文章,写一个SCRIPT在有TLSCALLBACK的情况下自 动停在

TLSCALLBACK入口是有可能的,你会在文章的最后部分得到相关指令的介绍。现在来说说START.TXT中没

有注释的指令。

; 分号,多条命令的分隔符。从左到右运行。

下面例子中,对MESSAGEBOXW下断后运行,中断之后便会运行r $t0=esp+8指令

bp messageboxw;g;r $t0=esp+8

注意:如果你使用CRTL+BREAK快捷键在中断之前暂停调试也会导致r $t0=esp+8的运行。

.if(条件表达式){命令} 跟C语言中的用法一样。

.pcmd 不带参数则显示每条指令之后自动使用的指令。-s "命令" 设置命令。-c 清除命令。


da 以ASCII显示内存地址,du以UNICODE显示内存地址


在示例中,整条指令的效果表现为,每单步一个指令,便会当EAX,EDX指向的是一个合法地址的时候,


便以ASCII和UNICODE的方式分别显示它的值,就象OD那样。如果熟悉ASCII和UNICODE字符集的范围还能


设置仅当有效字符时才显示结果。
上传的附件 11.rar


在调试的过程中,有时我们希望自动化解决一些问题。例如调试使用了UnhandledExceptionFilter的SEH


,我们需要自动修改 ZwQueryInformationProcess的返回值。或者对于某些API的ANTI DEBUG,如果我们


修改了输入参数,同样不能返回应该返 回的值。学破解不久,一下子要找用了


UnhandledExceptionFilter的软件还真不容易,用别的API代替了。我用OD把NOTEPAD 修改一下,改名为


TEST放在附件中。


流程MESSAGEBOXW,GETCOMMANDLINEW,MESSAGEBOXW输出COMMANDLINE,最后EXITPROCESS。


现在我要做的是改变GETCOMMANDLINEW的输出,和第二个MESSAGEBOXW的输入。现在让我们看看test2.txt


-------------------------------------------


test2.txt-----------------------------------------------


g $exentry
r $t0=0
bp messageboxw "r $t0=$t0+1;j($t0=2)'r $t1=poi(esp+8);f $t1 l4 45;g';g"
bp getcommandlinew "g poi(esp);r $t1=eax+5;f $t1 l4 55;g"
g

------------------------------------------test2.txt完

结---------------------------------------------

首先对相关指令作一些介绍

BP 地址或者函数名 "命令" 命令参数是可选的,存在的情况下,中断的同时会先运行那些命令。

J(条件表达式)'命令1';命令2 相当于.if但是又有点不同命令2只能是1个,后面所有命令会被忽略。

POI() 返回指针的指向位置的内容。

!= 不等于

这里用了条件中断的方法实现,第一个条件中断指令用$t0作为计数器,第二次中断的时候变修改堆栈中

指针指向位置的内存区域。注意到调用API的返回地址在ESP中,直接跳出去,然后修改EAX就可以达到修

改函数输出参数的效果了。


这里提供第二种可行的方法,并且更有可扩展性,现在看看test.txt中的代码。


-------------------------------------------


test.txt------------------------------------------------


g $exentry
r $t0=0
bp messageboxw
bp getcommandlinew
bp exitprocess


.while (eip!=77e7b0bb){
g
.if($ip=77e116cc){
r $t0=$t0+1
.if($t0=2){
r $t1=poi(esp+8)
f $t1 l4 45
}
}
.if($ip=77e7c693){
g poi(esp)
r $t1=eax+5
f $t1 l4 55
}
.elsif($ip=77e7b0bb){
.break
}
}


g


------------------------------------------test.txt完


结----------------------------------------------

仍然先介绍一些指令:
.while(条件表达式){} 跟C语言中的一样,循环结构,直到条件表示式为真
.elsif(){} 跟前面的.if用法一样,它的作用如字面上意思,只是小心别拼错为ELSEIF
.break 跟C语言中的一样,跳出循环。

如果在条件为真的时候不用.break跳出循环就会出错,这点要注意。

这里构造了一个循环结构,并且通过对比EIP的方法来识别函数,同样地因为我的虚拟机是WIN 2000 连

SP1都不是,所以我不肯定该地址在你的机器 中仍然可用。不过这里提供了一个思路,你可以用这个方

法构造一个SCRIPT来加强WINDBG的功能,例如象OD一样中断的时候自动显示所有参数,并且 带上英文提

示那是什么参数。同样地,我们可以做一个自动化分析SCRIPT,分析每个CALL中包含了什么API,并且列

出输入和输出参数,CALL的深 度还指令数,并且自动生成报告文件,假如有人开发出这样一个SCRIPT,

调试分析将会变得容易。WINDBG里面有个相似功能的指令。

WT 自动跟踪并生成报告,几乎跟我上面说的一样。带/l参数的时候可以设置深度,不过很多时候,我们


看到一个CALL并不知道里面究竟有多深,但是我们希望得到一些关于那个CALL的详细信息来判断是否值


得跟进。这里有两个问题:


1 递归,那这个指令不知道运行多久。
2 大量NATIVE API调用,显然大多数情况下,我们并不关心。


比起1,2更加常见,/i参数是用来避开指定模块的,不会用,帮助文件里也没提。。。。希望有大大能


答我这个问题


WINDBG提供了下面3个指令用于保存分析过程进文件,通过适当的开关可以过滤一些无意义的信息,使分


析过程易于观看。


.logopen 文件路径 带/U参数则以UNICODE方式输入文本。重写整个文件,并记录当前命令窗口在使用该


指令之后的所有内容。
.logclose 文件路径 停止记录并关闭文件。
.logappend 文件路径 带/U参数则以UNICODE方式写文件。记录当前命令窗口在使用该指令之后的所有内


容,并添加进文件。


提到了功能强化,大家都知道OD里面有个命令是运行到RET处吧,在WINDBG中似乎没有这样的指令,类似


的有PC,即运行到CALL。我写了一个SCRIPT来模拟OD中的那个指令。现在我们来看看goret.txt

-------------------------------------------

goret.txt------------------------------------------------

r $t0=0
.while(@$t0!=c3){
p
r $t0=by(eip)
.if(@$t0=c3){
.break
}
}

-----------------------------------------goret.txt完

结----------------------------------------------

这里是最后一个示例分析,所以除了解释上面的指令之外也给出一些有价值的指令
not 非 and或者& 与
hi() 取高16位 or或者^ 或
low() 取低16位 xor或者| 异或
by() 取低8位 gu 步出,不知道具体原理,有时会出错
wo() 取低16位 t 步入
mod或者% 模运算


这个SCRIPT使用了一个循环,通过EIP取得当前指令的机器码,低8位既为指令,然后把指令存进$t0作比


较。C3是RET的机器码,等于则跳出循环,否则一直步过。


这个示例表明,我们可以在SCRIPT里分析每一条指令。我们可以在WINDBG中进行2次开发,动态将那些简

单使用JMP+内存指针或者寄存器作为跳转 的乱序的程序重新排序,使花指令失效,并且实现自动清除垃

圾指令,最后生成优化后的汇编代码文件。本论坛翻译区里的变形多态中的关于收缩器的理论已经为我 

们奠定了理论基础。

你可能会需要用到反汇编指令


u 起始地址 l长度 L代表的不是地址长度而是指令的个数

---------------------------------后续讨论,用SCRIPT把WINDBG变成脱壳


机------------------------------

在准备写这篇文章的时候,我又把DEBUGGER COMMANDS看了一次,发现了这个指令

.writemen filename range 将目标内存区域写进文件。RANGE的格式为 地址 l长度


我没试过L后面是否接受寄存器作为参数。也没实际测试过这个指令的具体操作是怎么样的,无论如何,


有这个可能存在。当然我们也可以申请内存区域以程序的方式来完成这个工作,不过我希望它仅用


SCRIPT完成。


假如这的确可行,可以通过下面指令组合来自动寻找文件头,当然也能确定文件大小。


$p 伪寄存器,它将返回前一次用d*指令所显示的内存的内容。


假设00100000 01 02 03 04 05 06 07 08


我使用dd 00100000,那么$p = 04030201


显然我们可以通过这个方法来访问内存。

dw 取一个WORD; dd取DWORD; dw取qword

能访问内存也代表说我们在调试程序中插入的代码也能跟SCRIPT通信,并且把一些SCRIPT无法完成的工

作交给程序执行,然后把结果返回给SCRIPT。

假如l的参数无法通过寄存器来传递,只能依靠用户按照提示进行操作,那么我们有更简单的方法

.imgscan 它将返回所有模块MZ的地址和它的SIZE


----------------------------------这里给出一些可能的疑问和解


答--------------------------------------
Q:在调试SCRIPT文件的时候,我该如何知道寄存器跟内存的变化?


A:我们可以用下面的指令来观察寄存器跟内存的变化


d* 用于显示内存,之前已经提到就不详细说明了


? 寄存器 显示寄存器的值,例如


? poi(esp); ? $t0


这将先显示ESP指向的值,然后显示$t0的值


除了可以使用.echo命令对显示参数作说明之前,也可以使用.printf作格式化输出,它的用法跟C语言中

的printf是一样的

Q:我写的SCRIPT文件出错了,语法跟参数都没错,为什么我找不到出错原因?

A:有的指令要注意的,BA只能在进入程序区域之后才能用。.dvalloc申请过的内存,即使用.dvfree释

放了,也无法在同样的位置再申请,可能是BUG。

*是一个注释命令,它后面所有的内容都会被当作字符
$$则是以分号为结束


.restart指令跟.wtitle指令,不知道为什么不能放在SCRIPT中使用。


还有就是@这个标记,这个标记是告诉WINDBG后面的是一个伪寄存器而不是程序里的某一个变量的符号。


有的指令在没有@标记的时候会报错,例如. while括号里的条件表达式,如果你用了伪寄存器,一定要


在前面加上@否则一定报错。此外用帮助文件里的话来说,使用@,可以让SCRIPT文件运行得 更快,因为


在解读这个代码的时候不需要先搜索一次SYMBOL记录。


Q:我能把功能模块化然后在其他SCRIPT文件中使用吗?


A:我已经测试过$$><指令也能在SCRIPT里面使用


Q:我写的SCRIPT FILE能在64位系统中用吗?


A:如果你仅使用SCRIPT来实现功能,那么很可能与64位兼容。尽量使用伪寄存器。


$ip,$retreg,$csp在32位系统中分别表示EIP,EAX,ESP,而在64位系统中则表示RIP,RAX,RSP也分别


对应Itanuim处理器中的相关寄存器。


通过函数名获取不同版本下的地址,可以通过下面代码:


bp messageboxw ; $$第一个断点,断点ID为0
bp getcommandlinew; $$第二个断点,断点ID为1
r $t10 = $bp0; $$将第一个断点的地址转存$t10
r $t11 = $bp1; $$将第二个断点的地址转存$t11
bc *; $$清除所有断点。

这段代码运行之后MESSAGEBOXW的地址便存于$t10中,而$t11里面的则是getcommandlinew的地址。这里

要说明,断点用完要释放,否则不好估计断点的ID,此外在内核模式中,最多只允许32个断点。

$peb和$teb返回当前进程的PEB和TEB地址,这里的翻译区有介绍如何仅通过PEB或者TEB判断当前操作系

统类型

为不同系统准备不同代码

总算写完了。。。。再废话几句

WINDBG看起来很难用,是因为用的人不多。


即使没有插件功能,WINDBG SCRIPT的功能也已经很强大了。


如果大家都来做SCRIPT,调试分析难度会降低很多,新手也可以通过阅读SCRIPT FILE来学习。搜索引擎


使用得好的确可以学到很多,可惜这跟作 者的表达和使用引擎者的表达有关,很可能相同的内容,因为


表达方式不同就查不到了。就象我学校的图书馆,在电脑搜索逆向工程是什么都找不到的,但是搜索加 


密解密,却看到好几本书。


OD虽然好,始终是RING3的,SOFTICE似乎也已经停止开发了。希望大家都能加入WINDBG的行列


///


========

WinDbg入门教程(1)-调试器的基础知识

2008-10-08 09:22WinDbg 入门教程
介绍


在我的职业生涯中,我看到我们大多数都是使用Visual Studio来进行调试,而不是用其它许多免费的调

试器。你可能有许多理由来使用这样的调试器,比如,在你家里的机器上没装开发环境,但是一个程序

一次次的崩溃。其实根据堆栈的dump就可以判断出IE的崩溃是否是由于一个第三方的插件。

对于WinDbg,我目前为止还没有发现很好的快速入门的教程。这篇文章结合实例讨论了WinDbg的使用。

我首先假设你熟悉调试的基本概念:stepping in, stepping out,断点以及远程调试的基本概念。

注意,这本来是座位一个入门的文档,你可以阅读并且开始使用WinDbg. 如果对于特定的命令有疑问,

请查阅WinDbg的文档。你可以在任何微软提供的调试器中使用这篇文章中提到的命令,比如在VS的命令

行窗口中。


这篇文章是基于WinDbg 6.3.


这仅仅是一系列关于调试技术的文章中的第一篇。在下一篇文章中,我会解释如何针对调试器编写扩展


DLL.
调试器一览


下面大概介绍了你可以从微软网站上下载到的调试器:


· KD-内核调试器。你可以用它来调试蓝屏一类的系统问题。如果是开发设备驱动程序是少不了它的。


· CDB-命令行调试器。这是一个命令行程序


· NTSD-NT调试器。这是一个用户模式调试器,可以用来调试用户模式应用程序。它实际上是一个CDB的


windows UI增强。

· WinDbg-用一个漂亮的UI包装了KD和NTSD。WinDbg即可以调试内核模式,也可以调试用户模式程序。


· VS, VS.net-使用同KD和NTSD相同的调试引擎,并且相比于同样用于调试目的的WinDbg,提供了功能


更丰富的界面。
调试器之间的比较
功能
KD
NTSD
WinDbg
Visual Studio .NET

内核模式调试
Y
N
Y
N


用户模式调试


Y
Y
Y


非托管调试
Y
Y
Y
Y


托管调试


Y
Y
Y

远程调试
Y
Y
Y
Y

附加到进程
Y
Y
Y
Y


从进程分离
Y
Y
Y
Y


SQL调试
N
N
N
Y


WinDbg


WinDbg实际上包装了NTSD和KD并且提供了一个更好用的用户界面。它也提供了命令行开关,比如最小化


启动(-m),附加到一PID指定的进程(-p)以及自动打开崩溃文件(-z)。它支持三种类型的命令。


· Regular commands(比如: k) 用来调试进程


· Dot commands(比如:.sympath)用来控制调试器

· Extension commands(比如: !handle)-这些命令属于可以用来添加到WinDbg的自定义命令;它们用扩

展DLL的输出函数来实现。
PDB文件

PDB文件指的是链接器生成程序数据库文件(Program database files)。私有的PDB文件包括私有以及公

有符号,源代码行号,类型,局部以及全局变量。公有的PDB文件不包含类型,局部变量以及源代码行号

信息。


========

WinDbg入门教程(2)-各种调试场景介绍

2008-10-08 09:26调试场景
远程调试


使用WinDbg进行远程调试是很容易的,而且有很多种可行的方法。在下文中,’调试服务器’指的是运


行在你所要调试的远程机器上的调试器。’调试客户端’指的是控制当前会话的调试器。


· 使用调试器:你需要CDB, NTSD或者WinDbg已经安装在远程机器上。WinDbg客户端可以连接到CDB, 


NTSD或者WinDbg中的任何一个作为服务器,反之亦然。在客户端和服务器直接可以选择TCP或者命名管道


作为通讯协议。

o 在服务器端的启动过程:

§ WinDbg –server npipe:pipe=pipename(注:可以允许多个客户端连或

§ 从WinDbg内部: .server npipe:pipe=pipename(注,连接单个客户端)

你可以用多种协议开启不同的服务会话。并且可用密码来保护一个会话。

o 从客户端连接:

§ WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]

§ 从WinDbg内部: File->Connect to Remote Session: for connection string, enter 


npipe:server=Server, pipe=PipeName [,password=Password]


· 使用Remote.exe: Remote.exe使用命名管道作为通讯的方式。如果你使用的是一个命令行接口的程序


,比如KD,CDB或者NTSD。你可以使用remote.exe来远程调试。注意:使用@q(不是q)来退出客户端,不用


关掉服务端。


o 要启动一个服务端:


§ Remote.exe /s “cdp –p <pid>” test1


o 从客户端连接:


§ Remote.exe /c <machinename> test1


上面的test1是我们所选择的命名管道的名字。


服务端会显示那个客户端从那个服务器连接以及执行过的命令。你可以使用‘qq’命令来退出服务端;


或者使用File->Exit来退出客户端。另外,如果要进行远程调试,你必须属于远程机器的”Debugger 


User”组并且服务器必须允许远程连接。
即时调试


在WinDbg的文档的”Enabling Postmorten Debugging”部分对此有很详细的讨论。简而言之,你可以把


WinDbg设置成默认的即时调试器,命令就是:Windbg –I。这个命令实际上是把注册表中 


HKLM/Software/Microsoft/Windows NT/CurrentVersion/AeDebug的键值设置成WinDbg。如果要把WinDbg


设置成为默认的托管调试器,你需要显示设置如下的注册表键值:
HKLM/Software/Microsoft/.NETFramework/DbgJITDebugLaunchSetting 设置成 2
HKLM/Software/Microsoft/.NETFramework/DbgManagedDebugger 设置成Windbg.(注意其中的启动参数设


置)


通过JIT的设置,当一个应用程序在不是调试的状态下抛出了未处理的异常之时,WinDbg就会被启动。
64位调试


所有这些调试器均支持在AMD64和IA64上的64位调试环境。
托管应用程序的调试


WinDbg 6.3以后的版本支持在Widbey(VS2005和.net 2.0的内部开发代号) .net CLR托管调试。在文档中


针对托管调试有很好的讨论。需要注意的是,对于托管程序来说,没有刚才所说的PDB(译注:托管代码


实际上也是有PDB的,但是这个PDB实际上记录了C#代码和IL代码的对应关系以及相关的一些信息)的概


念,因为所有的程序都是编译成为ILASM。调试器通过CLR来查询所需的附加信息。


有几点需要注意:


你只能在托段函数的代码被执行过至少一次之后才能设置断点。只有这样它才能被编译成汇编代码。记


住以下的几点:


· 关于函数的地址的复杂化以及对应的断点设置:


o CLR有可能丢弃已经编译好的代码,所以函数的入口地址有可能改变。


o 同样的代码有可能被多次编译,如果多个应用程序域没有共享这段代码的话。如果你设置了一个断点


,它就会被设置在当前线程(译注:CLR的逻辑线程)所在的应用程序域内。


o 泛型的特殊实例可能导致同一个函数有不同的地址。.


· 数据存储布局的复杂化以及对应的数据检查:
CLR可能会在运行的时候任意改变数据的存储布局,所以一个结构体成员的偏移量可能会被改变掉. (译


注:实际上是在一个类型被加载的时候决定的数据布局,之后是不会改变的。)
一个类型的信息是在第一次使用的时候被加载,所以你可能不能够查看一个数据成员如果它还没有被使


用过.


· 调试器命令的复杂化


o 当跟踪托管代码的时候,你会需要穿越大段的CLR自己的代码比如JIT编译器的代码,原因可能是你第


一次进入一个函数,或者是你在托管和非托管代码之间进行切换。
调试Windows服务


使用WinDbg,你可以像调试其它应用程序那样调试Windows服务程序。即可以通过附加进程的方法启动


Windows服务,也可以把WinDbg当作一个即时调试器,并且在代码中调用DbgBreakPoint 或者 


DebugBreak,或者在x86机器上加入一条int 3汇编指令。
调试异常


一个调试器会得到两次的异常通知-第一次在应用程序有机会处理异常之前(‘first chance exception


’);如果应用程序没有处理这个异常,这时候调试器就会有机会来处理异常(‘second-chance 


exception’)。如果调试器没有处理二次机会的异常,应用程序就会退出。

.lastevent或者,!analyze –v命令会给你显示异常的记录以及异常抛出所在函数的堆栈跟踪信息。


你也可以使用 .exr, .cxr以及 .ecxr命令来显示异常和上下文记录。同时需要注意的是,你也可以改变

first-chance的处理选项。对应的命令就是: sxe, sxd, sxn和sxi。


能否请教一个问题:
我的Windbg不能设置断点,重新加载Symbols文件的时候也总是有几个加载失败,Symbols file path的

设置是:
SRV*c:/symbols*http://msdl.microsoft.com/download/symbols。
类似的环境变量也设置了,可是怎么弄都不行,又没有什么好的建议?


用;将Symbols file path分开,例如:
d:/dev/src;SRV*c:/symbols*http://msdl.microsoft.com/download/symbols
========

WinDbg 入门指导(3)-WinDbg中常用的功能以及设置

2008-10-08 09:34WinDbg的功能
调试器扩展DLL


所谓的扩展指的是一些DLL,你可以用在调试器内调用并且执行一些自定义的命令。这些DLL必须实现一


些特定的函数,并且要满足一些需求,这样才能被认为是一个扩展DLL。在下一篇文章内,我们将会了解


到怎样写出一个扩展DLL。所谓的bang(!)命令就是从你的扩展DLL内执行的命令。注意这些DLL是被加载


到调试器的进程空间内。
内存转储文件


你可以使用转储功能来取得一个进程的快照信息。一个mini-dump通常比较小,除非你使用了全内存的


minidump(.dump /mf)。通常能够转储句柄信息也是很有用的,命令是 .dump/mfh。一个小型转储通常包


含了所有的线程的堆栈以及一个已被加载的模块的列表。一个全转储包含了更多的信息,比如进程的堆



崩溃转储分析


如果你的windows系统当机,那么它就会在一个文件中转储物理内存中的数据,以及所有的进程信息。可


以通过Control Panel ->System->Advanced->’Startup and Recovery’来配置。你也可以首先把


WinDbg配置成为一个即时调试器,然后就可以取得任意一个非正常终止的进程的转储(.dump)。注意,从


转储文件中分析出代码中的bug往往是一个复杂费力的过程。


使用以下的步骤来分析一个转储文件:


1) 在WinDbg内,通过 File->’Open Crash Dump’, 指向转储文件。


2) WinDgb会给你显示应用程序崩溃之时所执行的指令。


3) 正确设置你的符号文件目录和源代码目录。如果你不能够匹配正确的符号文件,想要弄清楚程序的逻


辑是非常困难的。如果你能够把符号文件匹配到正确版本的源代码,这是就应该很容易分析出Bug原因。


注意,私有符号文件含有行号信息并且会盲目的显示你源代码中的对应行而不进行任何的检查;如果你


的源码版本不对,那么你就不能够看到匹配汇编代码的正确源码。如果你仅仅有公有的符号文件,你会


看到最后一个被调用的函数(栈上的)。


注意调试驱动或者托管代码是与此有很大不同的。参考《The Windows 2000 Device Driver Book》来获

得调试设备驱动的技术。
WinDbg的常用设置
符号文件与文件夹

如果想更有效的调试,你需要符号文件。符号文件可以是老式的COFF格式或者就是PDB格式。PDB就是程


序数据库文件并且包含了公有符号。这些调试器内,你可以使用一系列的地址来让调试器寻找已经加载

的二进制文件的符号。


操作系统的符号文件一般存储在%SYSTEMDIR%Symbols目录。驱动程序的符号文件(.DBG或.PDB)一般存储


在和驱动文件(.sys 文件)相同的目录下。私有符号文件包含的信息包括:函数,局部以及全局变量,以


及用来把汇编代码和源代码关联起来的行号信息;对于客户来说,符号文件一半是公有的-这些文件仅仅


包括公有成员。


你可以通过File-Symbol File Path来设置符号文件目录,或者使用 .sympath命令。如果想要添加到网


络上符号文件的引用,添加以下的内容到你的 .sympath


SRV*downstream_store*http://msdl.microsoft.com/download/symbols


使用的命令就是:


.sympath+ SRV*c:/tmp*http://msdl.microsoft.com/download/symbols


C:/tmp就是download_store,所需要的符号文件会被下载存储至此。注意这个符号服务器仅仅开放了公


有的符号文件。

当调试器把一个二进制文件(DLL或exe)的时候,他会检查比如文件名,时间戳以及校验值。如果你有符

号信息,你就可以在调用栈上看到函数名和他们的参数。如果二进制文件和PDB文件都来自于你自己的应

用程序,你就可以看到比如私有函数,局部变量以及类型这类额外的信息。
源代码路径


你可以通过File->Source File Path来设置源码路径,或者使用.srcpath命令。如果你设置了代码的路


径,当你调试的时候,调试器会通过PDB文件的行号信息来显示相匹配的源代码。
断点,跟踪


· 通过bp命令或者工具栏上的断点图片来设置软断点。


· 通过代码比如DbgBreakPoint() 或者 KdBreakPoint()来设置硬断点。


· 在扩展DLL中使用跟踪函数DbgPrint, KdPrint, OutputDebugString 来把输出显示在WinDbg的输出窗


口中。

========

win7 x64驱动开发经验(三)windbg 双机调试配置 、问题及解决办法



http://www.cnblogs.com/witty/archive/2012/04/23/2466024.html


VMware+Windgb+Win7内核驱动调试


本人在此基础之上根据具体情况有所改动


--------------------------------------------------------------
 

本文主要记录个人安装VMware+Windgb+Win7内核驱动调试的笔记。
 

一、安装环境

 
主机:Windows 7 x64 En U 版

虚拟机:VMware 7.1.4  VM8.0.2 (亲测)


GUestOS(虚拟机): Win7 x64 chs U 版


Windbg: 最新


 
二、虚拟机配置


 
打开相应 vmware 虚拟机上的 “Virtaul Machine Settings“


  


“Hardware ”选项中 ----> 点击“Add" 添加一个串口设备 SeriallPort .



 "Next",在 "Serial Port" 里选中 “Output to named pipe"

 
"next",然后如下设置:

确定之后,回到如下界面,在右脚"Virtual Machine Settings" 页面时,在“I/O Mode” 里选

中“Yield CPU on poll“

 Ok之后就设定完毕了。
 

三、Windbg设置

      下载地址: Windbg
 
       安装之后,设置一个桌面快捷方式,然后,右键->属性,在Target中的引号后面添加如下:-b 


-k com:pipe,port=\\.\pipe\com_1,resets=0


 
            或者是: -b -k com:port=\\.\pipe\com_1,baud=115200,pipe 【二者似乎皆可】
 


四、GuestOS设置 (就是虚拟机里的系统配置)
 


         适用于win7 vistar  如果感觉黑窗口玩不了 请看 7. 这种方法更直观



在administrator权限下, 进入command line模式,  键入bcdedit命令, 会出现以下界面:


然后, 设置端口COM1, baudrate为115200 (除COM1外, 也可以用1394或USB. 1394用起来比COM口快多了, 


当然前提是你需要有1394卡及其驱动. 很恶心的是Vista不再支持1394的文件传输协议, 但是用windbg双

机调试还是可以的)
命令为:
bcdedit /dbgsettings {serial [baudrate:value][debugport:value] | 1394 [channel:value] | usb 

}
 
接着, 我们需要复制一个开机选项, 以进入OS的debug模式
命令为:
bcdedit /copy {current} /d DebugEnty
DebugPoint为选项名称, 名字可以自己定义. 然后复制得到的ID号. 
 
接着增加一个新的选项到引导菜单
bcdedit /displayorder {current} {ID}
这里的{ID}的ID值是刚生成的ID值.
 
激活DEBUG : bcdedit /debug {ID} ON
这里的{ID} 的ID值还是刚才的ID值.




命令执行成功后, 重新启动机器.
7.或者更简单的图形界面设置:在msconfig界面中,选Boot,再选Advanced options,在选择Debug、


Debug port、Baud rate都打上钩。如果所示:
 
选择DebugEntry[debug]作为等入口。启动后,打开windbg.可以看到类似如下的信息:
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86  
Copyright (c) Microsoft Corporation. All rights reserved.  
  
Opened \\.\pipe\com_1  
Waiting to reconnect...  
Connected to Windows 7 7600 x86 compatible target at (Thu Dec 10 17:46:36.928 2009 (GMT

+8)), ptr64 FALSE  
Kernel Debugger connection established.  (Initial Breakpoint requested)  
Symbol search path is: *** Invalid ***  
****************************************************************************  
* Symbol loading may be unreliable without a symbol search path.           *  
* Use .symfix to have the debugger choose a symbol path.                   *  
* After setting your symbol path, use .reload to refresh symbol locations. *  
****************************************************************************  
Executable search path is:   
*********************************************************************  
* Symbols can not be loaded because symbol path is not initialized. *  
*                                                                   *  
* The Symbol Path can be set by:                                    *  
*   using the _NT_SYMBOL_PATH environment variable.                 *  
*   using the -y <symbol_path> argument when starting the debugger. *  
*   using .sympath and .sympath+                                    *  
*********************************************************************  
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntkrpamp.exe -  


 
Windows 7 Kernel Version 7600 MP (1 procs) Free x86 compatible  
Product: WinNt, suite: TerminalServer SingleUserTS  
Built by: 7600.16385.x86fre.win7_rtm.090713-1255  
Machine Name:  
Kernel base = 0x83e0f000 PsLoadedModuleList = 0x83f57810  
Debug session time: Thu Dec 10 17:46:32.658 2009 (GMT+8)  
System Uptime: 0 days 0:06:18.429  
Break instruction exception - code 80000003 (first chance)  
*******************************************************************************  
*                                                                             *  
*   You are seeing this message because you pressed either                    *  
*       CTRL+C (if you run kd.exe) or,                                        *  
*       CTRL+BREAK (if you run WinDBG),                                       *  
*   on your debugger machine's keyboard.                                      *  
*                                                                             *  
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *  
*                                                                             *  
* If you did not intend to break into the debugger, press the "g" key, then   *  
* press the "Enter" key now.  This message might immediately reappear.  If it *  
* does, press "g" and "Enter" again.                                          *  
*                                                                             *  
*******************************************************************************  
nt!DbgBreakPointWithStatus+0x4:  
83e7a394 cc              int     3  
 
五、操作方式提示

我发现,如果在GuestOs -win7启动过程中,如果打开了windbg之后,整个系统就像死机,不动了。估计

是windbg启动后设定了断点做调试,试试按F5,或者go这样就可以恢复原来的状态

----------------------------------问题及解决办


法-----------------------------------------------------------------------------------


Windows7 x64 + VMware + WinDbg 双机调试故障解决
源于: http://blog.tianya.cn/blogger/post_read.asp?BlogID=1439846&PostID=38575768


作者:MuseHero   2012-02-07 09:37 星期二 晴
  最近对虚拟机及里面的OS都进行了升级,在配置Windbg双机调试时,出现两个问题。
  在网上搜了搜,第二个问题几乎没有参考资料,就在国外看到过一例出同样问题的,现把解决方法


放出来,供有同样麻烦的朋友们参考:
  
  第一个问题:
   现像:打开虚拟机后无法连接,提试无法打开连接端口。
   故障原因:WMWare7.x及以后的版本在添加Serial Port时默认为Serial Port2,即COM2端口,而习

惯了用WMWare6.x的朋友们升级7.x时习惯了按旧有设置(COM1)来进行双机调试,就会连接不上了。
  
   解决方案:
   1、在添加Serial Port时将虚拟机硬件配置中的打印机删除,再创建SerialPort时就是


SerialPort1了,其它不用改变,直接可以按原有方式进行双调试的设置。
   2、虚拟的硬件设置不变,在虚拟OS中添加调试引导项时(bcdedit里),把端口设为COM2,也是可

行的。
   以上两个方案任选其一可解决。
  
  第二个问题:
   现像:在虚拟OS开机引导,加载完ClassPnp驱动后,实机中的WinDbg强制关闭(就是窗口突然没了


,进程也结束了)
   故障原因:调试WinDbg时显示故障是“调试目标初始化时出现问题,错误码:0x8000FFFF”。(现


在我也没搞明白这是啥子故障)
   解决方案:
   这个故障是在引导过程中发生的,通过排除发现不加载网络驱动则不会出现故障,所以解决方案也


就很简单了,在虚拟硬件设置中把网络连接中的“开机连接”去掉,让开机时不连接网络。
进入虚拟OS后,如果需要使用网络,再手动连接网络,经测试开机后手动连接不会触发故障出现。
  
   第二个问题用笨方法解决了,但原理没有搞清楚,应该是跟虚拟网络有关,有明白原因的朋友还请


告诉一下子,谢谢了。
(后注:用了几天后发现第二个问题的解决方案并不稳定,时好时坏,最后不得不换装Windows7家庭版


解决,经测试VMWare里面装Windows7 64位旗舰版时会出这个问题,装家庭版未发现有同样的问题。)


友情提示:
在设置双机调试时,总是记不住bcdedit的命令,后来才知道用msconfig就可以直接在图形界面设置了,


同样记忆力差的朋友可以尝试一下子。

========

实战windbg调试驱动例子

http://blog.csdn.net/iiprogram/article/details/632630


关于VM和WINDBG的基本配置请参考相关的文档。(在这里给各位一篇不错的文章,就是jiurl老大写的《 


 利用VM使用windbg  》)


既然我们要分析的是sr.sys,当然我们先要看的就是目标机加载的驱动程序的位置了,sr.sys是XP系统


系统还原的主体部分,它是建立在NTFS文件分区格式下的一个驱动,说白了 sr.sys其实就是ntfs.sys的

filter driver。
kd>!drivers
Base       Code Size      Data Size      Image Name        Creation Time
……
fc3ed000   de80 (  56 k)  2c00 ( 11 k)       sr.sys  Thu Aug 29 16:17:56 2002
……
我查找了与sr.sys相关的资料,不过没找到什么有用的东西,最有用的可能就是MSDN里面关于WINXP 


SYSTEM RESTORE的相关介绍了,不过实在是没有什么帮助,看来只有自己动手丰衣足食了,首先我们先来


大概的分析一下代码,然后由此扩展开来。
Kd>u fc3ed000
sr!SrMakeContextUninteresting <PERF> (sr+0x0):
fc3ed000 4d               dec     ebp
sr!SrMakeContextUninteresting <PERF> (sr+0x1):
fc3ed001 5a               pop     edx
sr!SrMakeContextUninteresting <PERF> (sr+0x2):
fc3ed002 90               nop
sr!SrMakeContextUninteresting <PERF> (sr+0x3):
fc3ed003 0003             add     [ebx],al
sr!SrMakeContextUninteresting <PERF> (sr+0x5):
fc3ed005 0000             add     [eax],al
sr!SrMakeContextUninteresting <PERF> (sr+0x7):
fc3ed007 000400           add     [eax+eax],al
sr!SrMakeContextUninteresting <PERF> (sr+0xa):
fc3ed00a 0000             add     [eax],al
sr!SrMakeContextUninteresting <PERF> (sr+0xc):
fc3ed00c ffff             ???


kd>u
……
我们向后翻查,找到下面的内容:
kd> 
sr!DriverEntry+0x12:
fc3fbcd6 59               pop     ecx
fc3fbcd7 33c0             xor     eax,eax
fc3fbcd9 8b3d40f03efc     mov     edi,[sr!global (fc3ef040)]
fc3fbcdf f3ab             rep     stosd
fc3fbce1 a140f03efc       mov     eax,[sr!global (fc3ef040)]
fc3fbce6 c7005372474c     mov     dword ptr [eax],0x4c477253
fc3fbcec 8b5d08           mov     ebx,[ebp+0x8]
fc3fbcef a140f03efc       mov     eax,[sr!global (fc3ef040)]


不好意思,每个人都有自己的分析习惯,而我喜欢从开始执行的部分分析,我感觉自这样分析比较清晰


,鉴于篇幅,具体的代码分析我不在这里列出(具体的代码分析请参看我的另一篇文章《winxp system 


restore 全面解析》)。
一点建议:上面的代码相当于静态反汇编,不过我建议如果是静态反汇编的话,我们不如直接用IDA,我


们还是来看看动态反汇编的结果,看看里面的具体变化,主要是堆栈、寄存器、各个变量等的值。
重新启动我们的target computer,在连接上的那一刻,按CTRL+Break断下来,然后下断点:
kd>bp 0xfc3fbcc4
kb>g
注意要及时按下CTRL+Break,不要过了,因为sr.sys是系统驱动,在ntldr加载驱动时会被加载,所以等


系统起来之后就断不着了(我就曾经错过了好几次机会)。
好了,终于断到了,我们来看看:
kd> bp 0xfc3fbcc4
kd> g
Breakpoint 0 hit
sr!DriverEntry:
fc3fbcc4 6a54             push    0x54
单步执行:
kd> p
sr!DriverEntry+0x2:
fc3fbcc6 68c8de3efc       push    0xfc3edec8
好了,剩下的就要看看我们的汇编功底了(党和国家考验我们的时候来到了,呵呵)。

至于代码分析我就不在这里过多的叙述了,因为这不属于WINDBG使用的范围。

其实用了这么长时间的WINDBG,主要就是用其做动态反汇编的工作,之所以我不选择SOFTICE有两个原因

:第一,softice 使我的系统变的极不稳定,很多奇怪的问题搞的我措手不及;第二,在使用softice的

过程中,有一些命令会有误差,举一个具体的例子来说,在SoftIce中使用irp命令时,所查看的寄存器


并不一定就是irp,只要查看的寄存器有合法的内容,那么irp命令就尝试去匹配,所以在使用irp命令时


要注意,可以利用windbg的!irp /address/ 1来查看,windbg首先会检验irp的sign,所以基本上不会有


误差。

虽然softice在操作比windbg要好很多,可是我可不想因为一些误差走错路。

好了让我们回到正题,在使用windbg的过程中,我经常使用的命令如下:
.reboot   重新启动Target computer
dd         显示寄存器或者是内存地址的内容
dt 其实这个命令的使用效率并不高,我把它列出来的原因是因为有一个关于它的一个窍门,比如说我想查


看一个数据结构的成员,我可以用如下的命令:
dt –v –r ntdll!_FIEL_OBJECT,我们可以使用该命令查看大部分的数据结构,在windbg列出的结构中包


括了各个数据成员之间的偏移,所以我很喜欢该命令,即便我知道一个数据结构的成员,我也会使用该命令


打印出来,这样更便于理解汇编语言中对数据结构的操作.
!devobj    使用deviceObject结构的内容.
该命令的使用效率并不是很高(起码我几乎没有使用过),不过在调试驱动时会起到关键的作用.
!irp       显示irp数据结构中的内容.
            !irp eax 1
在我的理解中,驱动程序就是对 irp的处理,所以我经常使用该命令查看参数和irp中的数据成员,以便更


好的理解过程.
bp          下断点.
bc          清除断点.
bd          暂时禁止断点.
be          允许执行被禁止的断点.
!drvobj    显示driverObject数据结构中的内容,同样该命令在调式驱动程序时可能会用到.


虽然使用这十个命令,但是对我来说已经是受用不尽了,其实在这些命令中,只有4个是我最常用的:
dd、dt、bp、!irp

还有一点,是我没有提到的:
比如说我们想查看该驱动程序在哪个进程中运行,可以做如下的操作:
kd>!thread        //查看当前的线程
在列出的各项数据中,看到PID了吗?
kd>!process [pid] 0
在其中列出的Image就是当前的进程了。

下面说一下用windbg 调式services

想要调式services,就必须在UserMode下运行windbg,即不用打开Target computer.
首先,安装和启动我们的服务;
其次,启动windbg;
选择File->Attach to a Process菜单;
在出现的Attach to Process对话框中双击我们想要调式的服务;
OK,下断点.
现在我们就可以调试了,不过要注意调式的完整性,因为是本机调试,所以如果我们随意关闭windbg的话
很可能会引起一些其他的后果,比如死机、重起等.


后记:


其实这算不上什么教程,其实本来是打算完全的翻译windbg help的,不过后来觉得确实没有必要,
因为我也没有详细的看过windbg help,只是在使用时遇到新的问题时才去查看的.
========

Windbg内核调试之三: 调试驱动

http://blog.csdn.net/wowolook/article/details/7645399


这次我们通过一个实际调试驱动的例子,来逐步体会Windbg在内核调试中的作用.
由于条件所限,大多数情况下,很多人都是用VMware+Windbg调试内核(VMware的确是个好东西).但这样的


调试需要占用大量的系统资源,对于和我一样急性子的朋友来说这是不可接受的:).利用双机调试就可以


让你一边喝咖啡一边轻松的看结果,而不至于郁闷的等待每次长达数分钟的系统响应.有关双机调试的基

本设置,请参考:http://www.cnblogs.com/Sonic2007/archive/2008/03/20/1114807.html

本次调试驱动所构建的环境如下:

host computer: WinXP+Windbg
Target computer:  Vista SP1
driver object: sys
connect setting: 1394数据线

说明: 1.1394卡在很多机器上都已经没有了,Vista也取消了1394的数据连接协议(调试还是可以的),但不

可否认的是利用1394数据线连接调试要比COM口和USB速率快很多(为什么好用的东西却得不到支持!).2.

本次调试的driver是公司开发的某个软件的驱动程序,拿来尝试在Vista SP1下track.由于涉及到商业机


密,本驱动源代码不便公开.3.Vista SP1就没什么好说的了,前些天才发布,MS又一个失败的典型.


OK,Let's go!
该驱动是一个类型sr.sys(MS的System Restore驱动)的Filter Driver,属于文件系统过滤驱动,加载在文


件系统驱动上层,由Filter Manager负责与用户层和底层通信.连接到目标机后,按下Ctrl+break中断当前


状态.(注:你也可以进入到explorer之后再中断,为了了解驱动加载时的进入点,以及系统启动时内核的装


态,我们中断到这里)

Microsoft (R) Windows Debugger  Version 6.6.0007.5
Copyright (c) Microsoft Corporation. All rights reserved.


Using 1394 for debugging
Opened \\.\DBG1394_INSTANCE01
Waiting to reconnect...
Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: D:\symbolslocal; D:\IR\SystemOK\Restore\Driver\objchk_wlh_x86\i386
Executable search path is: 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntkrnlmp.exe - 
Windows Vista Kernel Version 6000 MP (1 procs) Free x86 compatible
Built by: 6000.16584.x86fre.vista_gdr.071023-1545
Kernel base = 0x81800000 PsLoadedModuleList = 0x81908ad0
System Uptime: not available
WARNING: Whitespace at start of path element
WARNING: Whitespace at start of path element
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*                                                                                           


                                                
*   You are seeing this message because you pressed either                                  


                       
*       CTRL+C (if you run kd.exe) or,                                        
*       CTRL+BREAK (if you run WinDBG),                                      
*   on your debugger machine's keyboard.                                      
*                                                                             
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       
*                                                                             
* If you did not intend to break into the debugger, press the "g" key, then   
* press the "Enter" key now.  This message might immediately reappear.  If it 
* does, press "g" and "Enter" again.                                          
*                                                                             
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
818355e8 cc              int     3

然后,在Command line里键入lm,查看当前系统加载的模块和驱动(会发现我们的driver列在其中):

kd> lm
start    end        module name
80404000 80412000   PCIIDEX    (deferred)             
80412000 80419000   intelide   (deferred)             
80419000 80429000   mountmgr   (deferred)             
80429000 80438000   volmgr     (deferred)             
80438000 8045d000   pci        (deferred)             
8045d000 80465000   msisadrv   (deferred)             
80465000 8046e000   WMILIB     (deferred)             
8046e000 804b1000   acpi       (deferred)             
804b1000 804be000   WDFLDR     (deferred)             
804be000 80539000   Wdf01000   (deferred)             
80539000 8061a000   CI         (deferred)             
8061a000 80655000   CLFS       (deferred)             
80655000 8065d000   BOOTVID    (pdb symbols)          
8065d000 80666000   PSHED      (deferred)             
80666000 806c6000   mcupdate_GenuineIntel   (deferred)             
806c6000 806ce000   kdcom      (deferred)             
81800000 81b95000   nt         (pdb symbols)          
81b95000 81bc9000   hal        (pdb symbols)         
81c06000 81c0e000   spldr      (deferred)             
81c0e000 81c44000   volsnap    (deferred)             
81c44000 81cae000   ksecdd     (deferred)             
81cae000 81db6000   Ntfs       (deferred)             
81db6000 81def000   NETIO      (deferred)             
81def000 81e1a000   msrpc      (deferred)             
81e1a000 81f1e000   ndis       (deferred)             
81f1e000 81f270c0   PxHelp20   (deferred)             
81f28000 81f4f000   sys32v   (private pdb symbols)  
81f4f000 81f5f000   fileinfo   (deferred)             
81f5f000 81f90000   fltmgr     (deferred)             
81f90000 81fae000   ataport    (deferred)             
81fae000 81fb6000   atapi      (deferred)             
81fb6000 82000000   volmgrx    (deferred)             
8234f000 82358000   crcdisk    (deferred)             
82358000 82368000   agp440     (deferred)             
82368000 82389000   CLASSPNP   (deferred)             
82389000 8239a000   disk       (deferred)             
8239a000 823bd000   fvevol     (deferred)             
823bd000 823e2000   ecache     (deferred)             
823e2000 823f1000   mup        (deferred)             
823f1000 82400000   partmgr    (deferred)   


注: 若符号文件没有加载成功,Windbg会提示响应的符号找不到,不过一般Windbg会自己寻找符号文件路


径.实在找不到时,就包含
srv*c:\symbols*http://msdl.microsoft.com/download/symbols, 然后reload一下(!reload).


另外,键入lm t n, 我们可以查看更为详细的模块及驱动信息.
然后,键入!thread和Kp,查看当前的线程详细信息和堆栈(或者Alt+6也可以看stack).注意当前thread的

ID:

kd> !thread
THREAD 84254ae8  Cid 0004.0008  Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0
Not impersonating
Owning Process            84254d90       Image:         System
Wait Start TickCount      0              Ticks: 1 (0:00:00:00.015)
Context Switch Count      1             
UserTime                  00:00:00.0000
KernelTime                00:00:00.0015
Win32 Start Address nt!Phase1Initialization (0x819433ae)
Stack Init 81c06000 Current 81c05db8 Base 81c06000 Limit 81c03000 Call 0
Priority 31 BasePriority 8 PriorityDecrement 0
ChildEBP RetAddr  Args to Child              
81c05af0 818aa92c 00000001 81867999 0002625a nt!RtlpBreakWithStatusInstruction (FPO: 

[1,0,0])
81c05af8 81867999 0002625a 00000000 00000001 nt!KdCheckForDebugBreak+0x22 (FPO: [0,0,0])
81c05b18 81836cfd 81928100 000000d1 81c05b9c nt!KeUpdateRunTime+0x270
81c05b18 81ba4130 81928100 000000d1 81c05b9c nt!KeUpdateSystemTime+0xed (FPO: [0,2] 


TrapFrame @ 81c05b28)
81c05b9c 81ba3fd0 81bb28a0 8181dced 81c05bc8 hal!XmGetCodeByte+0x30 (FPO: [Non-Fpo])
81c05bac 81ba40c5 81bb28a0 0000c000 00001da4 hal!XmEmulateStream+0x88 (FPO: [Non-Fpo])
81c05bc8 81ba374d 00000010 81c05c0c 8181dced hal!XmEmulateInterrupt+0x80 (FPO: [Non-Fpo])
81c05bdc 81ba0a1c 00000010 81c05c0c 00000000 hal!x86BiosExecuteInterruptShadowed+0x43 (FPO: 


[Non-Fpo])
81c05bf8 81ba0a5b 00000010 81c05c0c 00000000 hal!x86BiosCall+0x22 (FPO: [Non-Fpo])
81c05c2c 80656697 80806ae0 8080f438 00000000 hal!HalpBiosDisplayReset+0x25 (FPO: [Non-Fpo])
81c05c58 81b2cd6d 00000001 81b0ab01 80806ae0 BOOTVID!VidInitialize+0x135 (FPO: [Non-Fpo])
81c05c7c 81b3f098 00000001 80806ae0 00000007 nt!InbvDriverInitialize+0x81
81c05d74 819433bb 81c05dc0 819afbad 80806ae0 nt!Phase1InitializationDiscard+0xd0
81c05d7c 819afbad 80806ae0 81c0e680 00000000 nt!Phase1Initialization+0xd
81c05dc0 8189a346 819433ae 80806ae0 00000000 nt!PspSystemThreadStartup+0x9d
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16


kd> kp
ChildEBP RetAddr  
81c05af0 818aa92c nt!RtlpBreakWithStatusInstruction
81c05af8 81867999 nt!KdCheckForDebugBreak+0x22
81c05b18 81836cfd nt!KeUpdateRunTime+0x270
81c05b18 81ba4130 nt!KeUpdateSystemTime+0xed
81c05b9c 81ba3fd0 hal!XmGetCodeByte+0x30
81c05bac 81ba40c5 hal!XmEmulateStream+0x88
81c05bc8 81ba374d hal!XmEmulateInterrupt+0x80
81c05bdc 81ba0a1c hal!x86BiosExecuteInterruptShadowed+0x43
81c05bf8 81ba0a5b hal!x86BiosCall+0x22
81c05c2c 80656697 hal!HalpBiosDisplayReset+0x25
81c05c58 81b2cd6d BOOTVID!VidInitialize+0x135
81c05c7c 81b3f098 nt!InbvDriverInitialize+0x81
81c05d74 819433bb nt!Phase1InitializationDiscard+0xd0
81c05d7c 819afbad nt!Phase1Initialization+0xd
81c05dc0 8189a346 nt!PspSystemThreadStartup+0x9d
00000000 00000000 nt!KiThreadStartup+0x16

键入!process [PID] 0, 查到当前进程:

kd> !process 0004.0008 0
PROCESS 84254d90  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00122000  ObjectTable: 830001d0  HandleCount:   1.
    Image: System
    VadRoot 00000000 Vads 0 Clone 0 Private 0. Modified 0. Locked 0.
    DeviceMap 00000000
    Token                             83003830
    ElapsedTime                       00:00:00.015
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         0
    QuotaPoolUsage[NonPagedPool]      0
    Working Set Sizes (now,min,max)  (4, 0, 0) (16KB, 0KB, 0KB)
    PeakWorkingSetSize                0
    VirtualSize                       0 Mb
    PeakVirtualSize                   0 Mb
    PageFaultCount                    0
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      0


        THREAD 84254ae8  Cid 0004.0008  Teb: 00000000 Win32Thread: 00000000 RUNNING on 

processor 0

在lm命令列出的信息中,start是模块的起始地址,通过键入"u 驱动起始地址",我们可以反汇编出它的代


码:

kd> u 81f28000
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x0):
81f28000 4d              dec     ebp
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x1):
81f28001 5a              pop     edx
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x2):
81f28002 90              nop
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x3):
81f28003 0003            add     byte ptr [ebx],al
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x5):
81f28005 0000            add     byte ptr [eax],al
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0x7):
81f28007 000400          add     byte ptr [eax+eax],al
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0xa):
81f2800a 0000            add     byte ptr [eax],al
sys32v!SysAllocatePostCopyWorkItem <PERF> (sys32v+0xc):
81f2800c ff              ???

逐步查找(enter),最终我们可以发现driver的入口点.这个过程其实是非常慢的,因为系统内核在加载驱

动实际代码的过程中进行了N多次调用.如果我们本身有驱动的代码,也可以直接包含源代码路径,通过在

实际代码中设置断点,让Windbg自己中断到相应的代码位置(在实际调试内核的过程中,这几乎是不可能的

,因为你不会得到Windows内核或某个驱动程序的源代码.Linux系列的某些driver们又另当别论).这里为

了方便,我包含了sys的源代码,增加断点直接走到DriverEntry例程:

kd> u 81f42780
sys32v!DriverEntry [隐藏了address]:
81f42780 8bff            mov     edi,edi
81f42782 55              push    ebp
81f42783 8bec            mov     ebp,esp
81f42785 51              push    ecx
81f42786 c745fc010000c0  mov     dword ptr [ebp-4],0C0000001h
81f4278d a1749cf481      mov     eax,dword ptr [sys32v!SysDbgFlags (81f49c74)]
81f42792 83e004          and     eax,4
81f42795 7424            je      sys32v!DriverEntry+0x3b (81f427bb)

其中显示的汇编代码,是内核调用驱动是进行的操作,其实也和实际代码相对应.

在driver代码中,如果要查看当前参数值,用dv命令:

kd> dv
   DriverObject = 0x84663730
   RegistryPath = 0x8084b560
         status = 8
       dontload = 0

另外,用"dt 参数名"可以看某个参数的当前值.

kd> dt DriverObject
Local var @ 0x81c05af8 Type _DRIVER_OBJECT*
0x84663730 
   +0x000 Type             : 4
   +0x002 Size             : 168
   +0x004 DeviceObject     : (null) 
   +0x008 Flags            : 2
   +0x00c DriverStart      : 0x81f28000 
   +0x010 DriverSize       : 0x27000
   +0x014 DriverSection    : 0x84230a68 
   +0x018 DriverExtension  : 0x846637d8 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\FileSystem\Sys"
   +0x024 HardwareDatabase : 0x81af6ed8 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE

\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : 0x81f4a005     sys32v!GsDriverEntry+0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : (null) 
   +0x038 MajorFunction    : [28] 0x8189a5c1     nt!IopInvalidDeviceRequest+0


这样,我们就可以通过上述的这些命令,逐步分析一个驱动程序在加载和执行过程中的情况。另外,Windbg


还有众多内核调试命令,如!irp可以查看一个对象的数据结构,!devobj可以查看设备对象等,在今后的操


作系统内核学习过程中会不断用到这些命令。

从上面的操作过程我们可以看出,利用Windbg调试驱动程序,或者说进行内核调试,是非常方便的。如

果我们对Windows内核有一定的了解,同时拥有一定汇编语言的功底,就可以有Windbg进行简单的系统排

障(比如系统加载是出现蓝屏,或是某个系统模块出现问题)、驱动学习等。同时,这个过程也可以让我

们更深入的理解操作系统原理。另外,Windbg也可以进行系统服务(service)的调试,这就是User mode

的调试过程了。
========
  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值