之前曾介绍过SQL Server的DUMP文件,现在来看一个从DUMP文件获取查询语句的例子。手工生成DUMP文件可以参考前面的帖子(SQL Server 转储介绍 I 和 SQL Server 转储介绍II),在WIN7或者Windows Server 2008上,还可以在任务管理器用右键进行文件转储。在分析DUMP文件之前,需要设置好windbg的使用环境,主要是设置symbol的路径,以前的帖子里也有介绍:)。
然后就可以使用windbg进行DUMP文件分析了,打开windbg->File->Open Crash Dump 打开具体的转储文件。然后找到具体执行SQL 语句的线程,寻找方法也很简单,在windbg的命令行里面输入~*kv,找到有sqlservr!CMsqlExecContext::ExecuteStmts<1,1>的线程就可以了。然后再用~ns,设置到线程n。
设置到具体线程后,用kv命令查看调用栈信息。接下来就可以根据调用栈来获取具体的SQL 执行语句了。
0:000> ~46s
ntdll!ZwWaitForSingleObject+0xa:
00000000`76f6f6fa c3              ret
0:046> kvnf
 #   Memory  Child-SP          RetAddr           : Args to Child                                                           : Call Site
00           00000000`0fe3b688 000007fe`fd6f10ac : 00000000`80cb61a0 00000000`0fe3b780 00000000`00000001 00000000`0144105c : ntdll!ZwWaitForSingleObject+0xa
01         8 00000000`0fe3b690 00000000`013b889e : 00000000`000007d0 00000000`012b5f80 00000000`00000000 00000000`00000669 : KERNELBASE!WaitForSingleObjectEx+0x79
02        a0 00000000`0fe3b730 00000000`013b8799 : 00000000`00000000 00000000`007c8c00 ffffffff`ffffffff 00000000`0fe3b870 : sqlservr!Np::StatusWriteNoComplPort+0x6e
03        b0 00000000`0fe3b7e0 00000000`013b85cc : 00000000`007ce240 00000000`007ce240 00000000`0fe3b8b0 00000000`00000000 : sqlservr!SNIStatusWriteNoComplPort+0x59
04        60 00000000`0fe3b840 00000000`0101f671 : 00000000`00000000 00000000`00001000 00000000`00000000 00000000`8135cad0 : sqlservr!TDSSNIClient::WriteStatus+0x99
05        30 00000000`0fe3b870 00000000`00f93ba3 : 00000000`80c70750 00000000`80c70c40 00000000`00000001 00000000`80c70750 : sqlservr!write_data+0x1bf
06       1e0 00000000`0fe3ba50 00000000`013acaea : 00000000`80cb61a0 00043300`00100004 00000000`00000000 00000000`00000000 : sqlservr!flush_buffer+0xf3
07        50 00000000`0fe3baa0 00000000`00fb3b69 : 00000000`00000000 00000000`00000000 00000000`8136b690 00000000`00000000 : sqlservr!CKatmaiTds::SendRowImpl+0x19c
08       280 00000000`0fe3bd20 00000000`00f9287e : 00000000`00000000 00000000`00000000 00000000`80c70140 00000000`80c704f0 : sqlservr!CXStmtQuery::ErsqExecuteQuery+0x5ce8
09      2f10 00000000`0fe3ec30 00000000`00f90fe3 : 00000000`8135ecb0 00000000`0144cc96 00000000`80a72140 00000000`00000000 : sqlservr!CMsqlExecContext::ExecuteStmts<1,1>+0xb6c
0a       290 00000000`0fe3eec0 00000000`00f91499 : 00000000`80a72140 00000000`8135d3d0 00000000`8135d300 00000000`00000000 : sqlservr!CMsqlExecContext::FExecute+0x593
0b       180 00000000`0fe3f040 00000000`00f93ff2 : 00000000`00000000 00000000`8135d3d0 00000000`8135dc30 00000000`00000000 : sqlservr!CSQLSource::Execute+0x2f9
0c       120 00000000`0fe3f160 00000000`00f8ebbb : 00000000`8135cfa0 00000000`007c8c00 00000000`00000000 00000000`00000000 : sqlservr!process_request+0x370
0d       2c0 00000000`0fe3f420 00000000`00f12abb : 00000000`00000000 00000000`00000000 00000000`00479988 00726574`6e696f50 : sqlservr!process_commands+0x2b2
0e       200 00000000`0fe3f620 00000000`00f10fda : 00000000`00000000 00000000`00000000 00000000`80cb61a0 00000000`80cb61a0 : sqlservr!SOS_Task::Param::Execute+0x11b
0f       120 00000000`0fe3f740 00000000`00f12665 : 00000000`0fe3f7f8 00000000`00479948 00000000`00479948 00000000`00000000 : sqlservr!SOS_Scheduler::RunTask+0xca
10        90 00000000`0fe3f7d0 00000000`014babb0 : 00000000`00479940 00000000`80cb61a0 00000000`00479948 00000000`00418270 : sqlservr!SOS_Scheduler::ProcessTasks+0x95
11        70 00000000`0fe3f840 00000000`014bc4b0 : 00000000`80cb61a0 00000000`00480080 000007ff`fff9e000 000007ff`fff9f4a8 : sqlservr!SchedulerManager::WorkerEntryPoint+0x110
12        c0 00000000`0fe3f900 00000000`014ba060 : 00000000`80cb61a0 000007ff`fff9f4a8 00000000`00418190 000007fe`fd6f6cf9 : sqlservr!SystemThread::RunWorker+0x60
13        30 00000000`0fe3f930 00000000`014ba9ef : 000007ff`fff9f4a8 000007ff`fff9f4a8 00000000`056f3e30 00000000`00418190 : sqlservr!SystemThreadDispatcher::ProcessWorker+0x12c
14        90 00000000`0fe3f9c0 00000000`680137d7 : 000007ff`fff9f4a8 00000000`0081ac10 00000000`00000000 00000000`00000000 : sqlservr!SchedulerManager::ThreadEntryPoint+0x12f
15        90 00000000`0fe3fa50 00000000`68013894 : 00000000`680c95c0 00000000`0081ac10 00000000`00000000 00000000`00000000 : msvcr80!endthreadex+0x47
16        30 00000000`0fe3fa80 00000000`7685f56d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : msvcr80!endthreadex+0x104
17        30 00000000`0fe3fab0 00000000`76f52cc1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0xd
18        30 00000000`0fe3fae0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d用kvnf命令查看的调用者说明如下:
第一列“#”,表示调用栈的序号;第二列"memory"表示栈所占用的空间;第三列Child-SP则是函数的入栈地址,在有的平台上会显示为ChildEBP,EBP和ESP在汇编里面就是存放栈的具体地址的;第四列的“RetAddr”表示函数返回地址;第五列的“Args to Child ”表示对应函数的参数地址,只显示前3个,但这三个地址在有些情况下会不准确;第六列的“Call Site”则是具体的函数调用名,像sqlservr!CMsqlExecContext::ExecuteStmts<1,1>中的sqlservr表示具体的执行程序,CMsqlExecContext表示具体的类,ExecuteStmts表示具体的方法(从中也可以看出SQL Server是用C++来编写的~)。接下来就可以根据调用栈来获取实际的SQL 语句了。
在这里我们关注的是process_request这个函数:
0c       120 00000000`0fe3f160 00000000`00f8ebbb : 00000000`8135cfa0 00000000`007c8c00 00000000`00000000 00000000`00000000 : sqlservr!process_request+0x370
从第一个参数地址入手,按照下面的步骤就可以获取实际的SQL 执行语句。
0:046> dd 8135cfa0+0x20 l1
00000000`8135cfc0  8135cea0
0:046> dd 8135cea0+0x28 l1
00000000`8135cec8  80317970
0:046> dd 80317970+0x200 l1
00000000`80317b70  813645b0
0:046> dd 813645b0+0x20 l1
00000000`813645d0  81364610
0:046> dd 81364610 l1
00000000`81364610  813646a0
0:046> du 813646a0
00000000`813646a0  "while 1=1..select * from obuntu_"
00000000`813646e0  "testOR_CōOP._ON_?脶"上述命令中的dd和du是查看内存的指令,更灵活的用法可以参考windbg的手册。至于为什么在地址后面要加上一些偏移(如0x20,0x28,0x2000),我们一般是无法获悉的,但一般情况下,我们可以按照上述步骤获取SQL 语句。我也是下面的这两篇帖子中看到分析SQL dump文件,比较感兴趣,所以给大家介绍下。
《How do I find what queries were executing in a SQL memory dump?》
《Finding which queries were executing from a SQL Memory Dump – revisited》
其中第二个帖子里面,因为作者用到了private symbol(微软不公开的),也就是说他有SQL Server的源码,所以我们没办法重现。而第一个帖子里面的使用方法,在我的环境里是可以实现的。其实分析这样的dump文件跟版本和编译环境是有很大关系的,我的环境参数如下:
--SQL Server:
Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (X64)   Jul  9 2008 14:17:44   Copyright (c) 1988-2008 Microsoft Corporation  Enterprise Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) 
--OS:
Windows 7 Version 7600 MP (4 procs) Free x64
Product: WinNt, suite: SingleUserTS Personal
kernel32.dll version: 6.1.7600.16385 (win7_rtm.090713-1255)本来想通过反汇编研究下为什么取那样的偏移,无奈道行尚浅,看到几千上万的汇编代码直接晕过去了。不过我们可以通过编写个C程序,用windbg分析下调用栈的情况。C代码如下:#include <stdio.h>void add(int *a, int *b,int *c)
{
    *c = *a + *b;
}int main()
{
    int a = 10;
    int b = 5;
    int c;
    add(&a,&b,&c);    printf("c:%d\n",c);}编译执行后,用windbg->File->Open Executeable打开编译后的exe文件,然后用bp命令在exe文件里面的add函数打上断点,然后输入g命令直接运行,windbg会在add函数里面停住,接着用kv命令看下具体的调用栈信息。然后可以用dd命令查看add函数对应的三个参数地址的实际值。具体如下:在图中,可以看到“Args to Child”列里面的第一个地址的值为0000000a,也就是十进制10,刚好是a的值;第二个地址的值为00000005,十进制5,是b的值;第三个地址的值为cccccccc,这是因为我们还没执行到让参数c获取值的地方,系统会自动给这样的参数赋值为cccccccc。所以,对于SQL Server文件,我们也可以根据这样的方法来一个个分析函数的值,但花费的时间相对来说会很多的。同时,根据上述例子打断点的思想,我们如果从一个因异常而dump出的文件中分析出了出错的地方,还可以在windbg->File->Attach to a Process,附加SQL Server的进行,进行单步跟踪,同时根据反汇编信息获得具体的错误内容。(但请注意,如果用windbg附加一个正在运行的进程时,在windbg退出后,也会推出进程,所以千万不要在生产环境上做这样的一个实验!!!!)。其实,SQL Server是一个比较稳定的数据库系统,如果出现了dump文件,一般要么是碰到微软的BUG,要么是数据库出现了致命的问题。对于这种东西,如果没有微软的支持,我们是很难定位出具体的问题所在。所以,我们只能规避它,要么重装系统,要么重装SQL Server,要么重建数据库,如果还搞定不了的话,还是求助微软吧。写这帖子,只是给有兴趣的人一点参考,也希望能共同探索~。现在流行微博,也发一个,大家有兴趣的话,可以多多交流:) http://weibo.com/huangqingxin

解决方案 »

  1.   

    有一部分是用c#写的,dll文可给用IL反汇编,反编译出来。
      

  2.   


    呵呵,跟网络没关系的。都是些底层东西,主要是软件调试和反汇编的内容。如果对软件调试的知识感兴趣,可以参考张奎银的《软件调试》,1000多页的书,里面也详细介绍了windbg的知识。
      

  3.   

    支持不在生产环境上做试验,至于windbg退出时,被调试的进程跟着退出还是有解决办法的,那就是先执行.detach命令.
      

  4.   

    要贴图的链接,把图片上传到CSDN的相册,再插入链接,就可以了。
      

  5.   


    学习,对windbg不熟,平时很少用。
      

  6.   

    拜读一下,obuntu又精进了不少!
      

  7.   

    dump是一定要看到啊,像C++的程序,只能创建dump去查看错误