Chinaunix首页 | 论坛 | 博客
  • 博客访问: 145773
  • 博文数量: 124
  • 博客积分: 70
  • 博客等级: 民兵
  • 技术积分: 1745
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-24 13:49
文章分类

全部博文(124)

文章存档

2011年(55)

2010年(14)

2009年(30)

2008年(25)

我的朋友

分类: WINDOWS

2009-11-10 11:57:18

能够进行用户态,内核态,dump等的调试。
 
支持命令行,以及自定义命令行的调试。
 
命令一览:
1. 标准命令 (内建)
  g :   run
  t :   trace into
  p:    step over
  wt :  watch trace
 
  pa  : step 2 addr , not enter sub
  ta  : trace 2 addr , enter sub
  pc  : step 2 next call
  tc  : trace 2 next call
  tb : kernel only , 2 next branch
  pt : step 2 next ret
  tt : trace 2 next ret
  gu : run 2 return
 
 
  r : 读取,修改通用寄存器
  d/e/s : 观察,编辑,搜索内存数据。
  k : 观察call stack
  bp : 软件断点
  ba : 硬件断点
  bl : list
  bc/bd/be: clear / disable/enable
  ~ : 显示和控制线程
    ~* ; kb
  | : 显示进程
     |.
  ? : 评估表达式
  ?? : 评估c++表达式
    ?  $iment(addr)
  a : 汇编,输入汇编代码
     a eip+x ; cc
  u: 反汇编
     u eip
  dg : 显示段寄选择子
        dg cs
  $ : 执行命令文件的$命令,或者内置命令的开始符号,比如? $iment(addr) , ln addr
  sx : set exception 命令
       1. start windbg
       2. open calc
       3. sxe ld:alipy
       4. du addr , show alipy.ime
 
  vertarget : 目标os的信息
  !peb , 被调试进程的peb块信息
  x 显示符号
  ln addr : addr附近的符号信息
  ld : 加载符号
  lm : 列举模块
 
 
2. 元命令 (内建)
  .sympath , 目前符号路径
  .logfile .logopen .logappend .logclose
  .load .unload .unloadall .chain 管理扩展模块
  .remote : 远程调试
  .if .else ... : 自定义命令编写
 
 
3. 扩展命令(非内建)
  windbg.exe 同目录下的winxp目录下的都是。
  .chain    讲列绝搜索目录以及已经加载的扩展模块。
  
  使用扩展命令的格式是![扩展模块名].<扩展名> 参数,比如!peb ; 采用.load or .loadby 加载。
 
 
作为一个重要的例子,讲述一下如何进行windbg远程调试。
 
....
 
另外一个例子是如何进行双机内核调试,以vpc为例子。
 
.....
 
 
 
表达式计算
?? reinterpret_cast(0xfff)
 
以?? 来计算c++表达式,这也是我们最常用的。
 
比如计算4则混合运算。
 
另外.expr 是计算MASM模式的表达式,这个表达式尽管不如c++表达式那么吸引人,不过其特有很多内建表达式支持,还是很不错的。
 
MASM 表达式也可以支持常见的 : +-*/  >> <<  % mod  = == > < >= <= != & and ^ xor
| or ;
还可以支持 hi / low 得到32bit的高or低;
by /wo : byte or word
dwo / qwo : DWORD , QWORD
poi : 从指定地址得到指针长度数字.
 
$iment(start addr of module)
比如:
 
 
为了方便,windbg定义了很多伪寄存器。
 
Pseudo-register
 
Description
$ea The effective address of the last instruction that was executed. If this instruction does not have an effective address, the debugger displays "Bad register error". If this instruction has two effective addresses, the debugger displays the first address.
$ea2 The second effective address of the last instruction that was executed. If this instruction does not have two effective addresses, the debugger displays "Bad register error".
$exp The last expression that was evaluated.
$ra The return address that is currently on the stack.

This address is especially useful in execution commands. For example, g @$ra continues until the return address is found (although is a more precise effective way of "stepping out" of the current function).
 

$ip The instruction pointer register.

x86-based processors: The same as eip.
Itanium-based processors: Related to iip. (For more information, see the note following this table.)
x64-based processors: The same as rip.

$eventip The instruction pointer at the time of the current event. This pointer typically matches $ip, unless you switched threads or manually changed the value of the instruction pointer.
 
$previp The instruction pointer at the time of the previous event. (Breaking into the debugger counts as an event.)
$relip An instruction pointer that is related to the current event. When you are branch tracing, this pointer is the pointer to the branch source.
$scopeip The instruction pointer for the current (also known as the scope).
$exentry The address of the entry point of the first executable of the current process.
$retreg The primary return value register.

x86-based processors: The same as eax.
Itanium-based processors: The same as ret0.
x64-based processors: The same as rax.

$retreg64 The primary return value register, in 64-bit format.

x86 processor: The same as the edx:eax pair.

$csp The current call stack pointer. This pointer is the register that is most representative of call stack depth.

x86-based processors: The same as esp.
Itanium-based processors: The same as bsp.
x64-based processors: The same as rsp.

$p The value that the last command printed.
$proc The address of the current process (that is, the address of the EPROCESS block).
$thread The address of the current thread. In kernel-mode debugging, this address is the address of the ETHREAD block. In user-mode debugging, this address is the address of the thread environment block (TEB).
$peb The address of the process environment block (PEB) of the current process.
$teb The address of the thread environment block (TEB) of the current thread.
$tpid The process ID (PID) for the process that owns the current thread.
$tid The thread ID for the current thread.
$bpNumber The address of the corresponding breakpoint. For example, $bp3 (or $bp03) refers to the breakpoint whose breakpoint ID is 3. Number is always a decimal number. If no breakpoint has an ID of Number, $bpNumber evaluates to zero. For more information about breakpoints, see .
$frame The current frame index. This index is the same frame number that the command uses.
$dbgtime The current time, according to the computer that the debugger is running on.
$callret The return value of the last function that called or that is used in an command. The data type of $callret is the data type of this return value.
$lastclrex Managed debugging only: The address of the last-encountered common language runtime (CLR) exception object.
$ptrsize The size of a pointer. In kernel mode, this size is the pointer size on the target computer.
$pagesize The number of bytes in one page of memory. In kernel mode, this size is the page size on the target computer.
 
另外windbg 提供了$t0-$t19 20个给用户自己使用的伪寄存器。
 
 
为了让windbg解析速度提高,当你明确要使用伪寄存器时候,可以明确添加 @ 作为前缀。
比如 ln @$exentry  就可以列出当前被调试的exe的入口符号。
 
 
$$ * 均为注释,差别前者;分割,后则直到行末。
 
g @$ra  就表示执行到最近一个函数的返回, step over
 
 
下面据一个稍微复杂些的例子:
例子来bptest.
 
 
 
几个上下文概念:
会话上下文/进程上下文/线程上下文/寄存器上下文/局部上下文
 
1. 会话即session,windows意义上指一个登录终端,一般来说只有一个,比如你rmote上去后就有两个。
2. windows vista为了增强安全性,让所有的系统服务运行在session 0。
 
进程上下文,往往在kd模式下,会考虑,因为那个时候,kd或许处于任意上下文中。
kd> !process 0 0
列举所有进程后
 
kd> .process
将当前的上下文设置为某进程上下文
 
kd>k
即显示此进程的call stack。
 
线程上下文
1. kd>!process
可以列绝出此进程的所有线程
 
2. kd>.thread
即可将线程上下文切换到此
 
3. 寄存器上下文
  cpu只有一套寄存器,因此任意时刻寄存器只能表示一个线程(因为cpu看到的单位是线程,不是进程,进程是os看到的)
 
4. 局部上下文
.frame 命令即可切换,一般来说当前位于顶部call stack的frame
 
 
下面再讲一下符号,当然也是结合windbg来讲解的。
1. _NT_SYMBOL_PATH 这个环境变量很重要,当它被设置后,windbg,vs均会以此为依据进行符号查找。
   一般来说设置如下: srv*d:\symbols*
2. 符号的状态,一般来说lm命令是最常用的,通过lm v m 即可进行详细信息的模式匹配查找,比如: lm v m kernel* ; 这样就可以列绝所有以kernel开头的模块的符号加载状况;
  还有就是x 命令,比如 x *!*main* 即可列绝,所有模块中由main字符的符号信息。
3. 符号状态检查,符号有很多种状态,比如符号不匹配,校验不正确等。  但是一般来说你可以通过.reload /f /i 来解决校验以及时间戳问题,如果这样都不ok,那么你的符号显然是不匹配的。
4。 设置目标代码的符号路径, 比如再windgb的symbol path种可以添加 d:\xxsym表示xx程序的符号在此查找。
 
 
当程序比较复杂,或者有多人开发时候,更好的方式是设置自己的symstore,下面简单讲解一下做法,以简单的文件共享作为说明:
1. 找随便一台机器,只要磁盘有一定空间即可,当然本例以windows作为例子。
   建立c:\xxsym 并且共享,允许匿名访问
2. 比如目标程序的某个版本的符号目录是d:\xx\pdb ,那么执行如下:
  
symstore add /r /f D:\xx\PDB\*.pdb /s /t "xx" /v "0.1" /c "coment"
 
这样就讲xx程序某个版本的符号在远程符号机器上建立了symstore
3. 任意的windbg设置符号路径如下:
 srv*d:\symbols*\\wwsymserver\xxsym*
 
4. 这样当项目组内成员调试xx程序时候,symstore将自动加载符号。就像下载微软的符号一样。
下面是一个客户端调试testbp时候,从我的vpc 2007的winxp上更新符号:
 
 
  
 
下面再讲讲事件处理, 这儿说的是调试事件。
一般来说,调试器在处理异常和事件时候,一般有2此机会。所谓的first change , second change.
通常当存在调试器时候,os的处理方式如下:
1. 在异常发生前,os会将这个机会先通知调试器,问调试器是否要处理. first change
2. 一般来说调试器不会处理这个,而是返回未处理给调试器即可;为什么呢? think 一下。
3. os 检查是否存在VEH,SEH,有的话就让它们处理。当此过程结束后,如果异常被程序处理过了,则debugger不会得到处理机会。否则os将第二次机会交给debugger,通常这个时候,就出现了一个unhandled exception ,对于应用程序则中止,kenerl则蓝屏。
 
 
下面我们来看一个dz的异常。
exception_samples.
 
int divisor = 0;
 int divident = 1;
 int result;
 
 __try
 {
  result = divident / divisor;
 }
 __except(::GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ?
  (divisor=1 ,printf("continued ...\n"), EXCEPTION_CONTINUE_EXECUTION)
   : EXCEPTION_CONTINUE_SEARCH)
 {
  printf("in handler ...\n");
 }
 
这个例子大家认为执行的结果是如何的?
VC6 : 像你看到的代码那样
VS2005:  死循环
 
vc6 的反汇编:
 
 
也就数说idiv 操作的时候是将除数以直接访问临时变量,所以当except 过滤种修改了ebp-24h 相当于也直接影响了idiv的结果。
 
那么vs2005呢?
 
 
 
因为idiv直接以ecx作为操作数,因此except修改的临时变量没有影响到它,所以死循环了。
 
这儿就不分析具体原因了,只是暂时了一个用windbg分析问题的一种方式。

{
   软件调试11.4 提到了这个问题,一种实验室的做法是在exception_recorder中修改EIP指针,令其指向回复指令的上一条。不过显然这种做法太过于依赖编译器的实现。
   比较正确说法是,vc编译器中将所有的c++异常都强制为无法恢复;
  即不应但返回EXCEPTION_CONTINUE_EXECUTION . 如果强制返回则可能导致EXCEPTION_NONCONTINUE_EXECUPTION 异常。

  这样做的道理是: 当异常发生时侯,通过程序方式恢复执行,往往收到诸多限制,实际上往往并不可行。
  因此执行异常块代码,往往是一种更好的选择。
}
 
下面我们主要看first change, second change.
 
从代码来看,程序设置了SEH。
再看看windbg的dz的设置
 
  dz - Integer divide-by-zero - break - not handled
 
它表示的意思是,dz firstchange/second change 均交给调试器处理;
 
像上述的例子中,vc6的例子将出现以此first change,然后退出。
vc8中,将始终出现first change,因为异常不断的被触发。
 
然后我们将except过滤表达式的返回值,修改为 search_continue,再看看.
 
 
当debugger拿到第二此chance时候,往往程序就没有changce了,呵呵。
 
如果你直接运行此程序,就会crash。
 
 
下面再看一下wt命令,这也是一条非常有用的命令。
 
往往可以用于观察某一个函数调用的细节,性能的大致分析等。
 
先来看一段source.
#include "stdafx.h"
#include
void wt_test2()
{
 ::GetTickCount();
}
void wt_test()
{
 wt_test2();
 ::Sleep(1);
}
int _tmain(int argc, _TCHAR* argv[])
{
 wt_test();
 return 0;
}
 
 
 
对于此表只要记住一点,比如最后一行,第一列=8,第二列=42, 它表示,wt_test!wmain 自己的代码执行了8条指令,而由于本函数的执行间接执行了其它函数的代码42条指令。这样大致就评估出共有50条指令被执行。
 
最后的表格,显示的是被统计函数以及其内部被call函数的激活次数,以及执行执行的情况。
 
 
windbg比较高级的主题,是编写自定义的脚本或者命令,来执行一些复杂的操作,这儿还是据软件调试中的一个例子.
 
比如PEB是进程环境变量模块。结合前面讲到的evaluate or ? 评估命令以及 $peb 伪指令,我们就可以通过 ? @$peb 打印. addr1
 
dt *!*PEB*  将print ntdll!_PEB ,显然这个是最像的。
dt ntdll!_PEB addr1
dt ntdll!_PEB_LDR_DATA 0x00251ea0
这个时候就得到了一个列表,但是非常遗憾这个列表是一个_LIST_ENTRY 的通用数据结构,为了继续能够像本书的例子那样分析,不得不找出w2ksrc进行查找,结果还真找到:
 
ImageEntry = (PLDR_DATA_TABLE_ENTRY)Peb->Ldr->InLoadOrderModuleList.Flink;
 
这样还是使出老办法 dt *!*LDR_DATA* 结果找到了 ntdll!_LDR_DATA_TABLE_ENTRY
而这个是最符合的。
因此
dt ntdll!_LDR_DATA_TABLE_ENTRY addr
 
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY Fulldllname 0x251ee0
   +0x024 FullDllName : _UNICODE_STRING "C:\wt_test\debug\wt_test.exe"
呵呵,我们现在就是演示编写一个脚本,让它枚举整个peb块的所有模块名称.
 
 
 r? $t0 =
 r? $t1 = (ntdll!_LDR_DATA_TABLE_ENTRY *)@$t0
 r? $t2 = (ntdll!_LDR_DATA_TABLE_ENTRY *)@$t0
 $$ 当entry不为空,且不等于head元素
 .while(@$t1 != 0)
{
   $$ 建立引用
   as /msu ${/v:$mod} @@c++()  
   $$ 强制解析$符号
   .block   
   {
     .echo ${$mod}
   }
  
   $$ 删除引用
   ad ${/v:$mod} 
   $$ move2next
   r? $t1= (ntdll!_LDR_DATA_TABLE_ENTRY *)@$t1->InLoadOrderLinks.Flink
   .if(@$t1 == @$t2)
   {
       .echo end
       .break
   }
}
.echo ok
 
 
 
阅读(2512) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~