首页 > 开发 > 综合 > 正文

调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置

2024-07-21 02:48:11
字体:
来源:转载
供稿:网友
调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置

调试SQLSERVER (一)生成dump文件的方法调试SQLSERVER (三)使用Windbg调试SQLSERVER的一些命令

大家知道在Windows里面,调试可以分为两个领域:

1、内核态调试

2、用户态调试

一般的程序都是运行在用户态,包括SQLSERVER,SQLServer 会依赖于操作系统的Win32/Win64 API去调用I/O或者其他他需要的服务

用户态程序调试和内核态程序调试是不太一样的,即使有些命令是一样的

还有另外一个要注意的是live debug和dump file

live debug:直接附加调试器到进程--使进程hang住,使被调试程序不能继续执行代码

dump file :通常是一个dump文件,dump文件是进程生成的当时这个进程的所有或部分内存内容,并且可以被调试器读取

full dump文件 通常的扩展名是.dmp ,文件里面一般不包含已经paged out 的内存内容

mini dump文件 通常的扩展名是.mdmp ,一般只包含线程栈和加载的模块名

Windbg: 是一个GUI调试器工具并且可以调试内核态和用户态程序,除了Windbg调试器 当然还有其他调试器 ,例如kd, cdb, ntsd


简单复习

应用程序以进程运行,这里我们关注的进程是sqlservr.exe ,在用户态调试下,我们会调试一个单独的进程,无论是使用live debug(附加进程方式)还是读取一个dump文件

在调试的时候都能看到进程的内存空间。在内核态调试里,我们可以看到所有进程和他们的内存空间(注意:在dump文件里面我们访问不到当生成dump文件的时候

已经被paged out 的那部分内存地址)

stack trace:单个线程里面执行的代码的栈,一个线程可以有用户态栈内核态栈,在用户态调试里面,我们只能看到每个线程的用户态栈

一个stack trace是一个线程的一系列函数调用的list

stack是一个LIFO结构(last in first out)意思是后进先出,术语叫做push stack 和pop stack

栈的读取是自底向上的,最上面的调用是当前正在执行的代码

下面的例子

Child SP (Stack Pointer)

RetAddr (Return Address)

stack trace里面可以跟踪系统的执行顺序和调用代码的返回,调试器会构造stack trace 给我们以便于更好的分析

Child-SP          RetAddr           Call Site 00000000`09cbe9e8 00000000`777b2f60 ntdll!NtSignalAndWaitForSingleObject+0xa 00000000`09cbe9f0 00000000`00bdc99e kernel32!SignalObjectAndWait+0x110 00000000`09cbeaa0 00000000`00bc4575 sqlservr+0x1c99e 00000000`09cbed40 00000000`00bc3ea8 sqlservr+0x4575 00000000`09cbed80 00000000`00bdcfad sqlservr+0x3ea8 00000000`09cbf370 00000000`01139d9c sqlservr+0x1cfad 00000000`09cbf430 00000000`032b34c7 sqlservr+0x579d9c 00000000`09cbf650 00000000`00bd2abb sqlservr!TlsGetValueForMsxmlSQL+0x4706d7 00000000`09cbf6c0 00000000`00bd0fda sqlservr+0x12abb 00000000`09cbf7e0 00000000`00bd2665 sqlservr+0x10fda 00000000`09cbf870 00000000`0117abb0 sqlservr+0x12665 00000000`09cbf8e0 00000000`0117c4b0 sqlservr+0x5babb0 00000000`09cbf9a0 00000000`0117a060 sqlservr+0x5bc4b0 00000000`09cbf9d0 00000000`0117a9ef sqlservr+0x5ba060 00000000`09cbfa60 00000000`734937d7 sqlservr+0x5ba9ef 00000000`09cbfaf0 00000000`73493894 MSVCR80!endthreadex+0x47 00000000`09cbfb20 00000000`7775f56d MSVCR80!endthreadex+0x104 00000000`09cbfb50 00000000`77893281 kernel32!BaseThreadInitThunk+0xd 00000000`09cbfb80 00000000`00000000 ntdll!RtlUserThreadStart+0x21

在配置调试器的过程中,最后的步骤是匹配符号,我们打开Windbg的时候我们必须设置我们的符号文件路径

Symbolic Debugging Files(符号调试文件):帮助调试器把内存里面的各自的函数,类,变量名在内存的位置 匹配到相应的内存地址和位移

如果没有Symbolic Debugging Files,那么我们将会看到下面只有memory addresses 的stack trace

符号文件的扩展名通常是pdb(如果大家写C#代码的时候肯定都会看到过在debug的时候看到bin目录下会有debug符号文件),调试器能够很好地解析这种文件格式

Child-SP          RetAddr           Call Site 00000000`09cbe9e8 00000000`777b2f60 ntdll!NtSignalAndWaitForSingleObject+0xa 00000000`09cbe9f0 00000000`00bdc99e kernel32!SignalObjectAndWait+0x110 00000000`09cbeaa0 00000000`00bc4575 sqlservr+0x1c99e 00000000`09cbed40 00000000`00bc3ea8 sqlservr+0x4575 00000000`09cbed80 00000000`00bdcfad sqlservr+0x3ea8 00000000`09cbf370 00000000`01139d9c sqlservr+0x1cfad 00000000`09cbf430 00000000`032b34c7 sqlservr+0x579d9c 00000000`09cbf650 00000000`00bd2abb sqlservr!TlsGetValueForMsxmlSQL+0x4706d7 00000000`09cbf6c0 00000000`00bd0fda sqlservr+0x12abb 00000000`09cbf7e0 00000000`00bd2665 sqlservr+0x10fda 00000000`09cbf870 00000000`0117abb0 sqlservr+0x12665 00000000`09cbf8e0 00000000`0117c4b0 sqlservr+0x5babb0 00000000`09cbf9a0 00000000`0117a060 sqlservr+0x5bc4b0 00000000`09cbf9d0 00000000`0117a9ef sqlservr+0x5ba060 00000000`09cbfa60 00000000`734937d7 sqlservr+0x5ba9ef 00000000`09cbfaf0 00000000`73493894 MSVCR80!endthreadex+0x47 00000000`09cbfb20 00000000`7775f56d MSVCR80!endthreadex+0x104 00000000`09cbfb50 00000000`77893281 kernel32!BaseThreadInitThunk+0xd 00000000`09cbfb80 00000000`00000000 ntdll!RtlUserThreadStart+0x21

上面显示出我们的模块,sqlservr.exe 和正在执行的代码在地址空间里的位移,但是我们不知道具体的函数名,如果有符号映射的帮助(symbols mapped),我们可以获取到更有意义输出

Child-SP          RetAddr           Call Site 00000000`09cbe9e8 00000000`777b2f60 ntdll!NtSignalAndWaitForSingleObject+0xa 00000000`09cbe9f0 00000000`00bdc99e kernel32!SignalObjectAndWait+0x110 00000000`09cbeaa0 00000000`00bc4575 sqlservr!SOS_Scheduler::SwitchContext+0x84e 00000000`09cbed40 00000000`00bc3ea8 sqlservr!SOS_Scheduler::SuspendNonPReemptive+0xc5 00000000`09cbed80 00000000`00bdcfad sqlservr!EventInternal<Spinlock<149,1,0> >::Wait+0x428 00000000`09cbf370 00000000`01139d9c sqlservr!ResQueueBase::Dequeue+0x19d 00000000`09cbf430 00000000`032b34c7 sqlservr!CheckpointLoop+0x1aa 00000000`09cbf650 00000000`00bd2abb sqlservr!ckptproc+0x47 00000000`09cbf6c0 00000000`00bd0fda sqlservr!SOS_Task::Param::Execute+0x11b00000000`09cbf7e0 00000000`00bd2665 sqlservr!SOS_Scheduler::RunTask+0xca 00000000`09cbf870 00000000`0117abb0 sqlservr!SOS_Scheduler::ProcessTasks+0x95 00000000`09cbf8e0 00000000`0117c4b0 sqlservr!SchedulerManager::WorkerEntryPoint+0x110 00000000`09cbf9a0 00000000`0117a060 sqlservr!SystemThread::RunWorker+0x60 00000000`09cbf9d0 00000000`0117a9ef sqlservr!SystemThreadDispatcher::ProcessWorker+0x12c 00000000`09cbfa60 00000000`734937d7 sqlservr!SchedulerManager::ThreadEntryPoint+0x12f 00000000`09cbfaf0 00000000`73493894 MSVCR80!endthreadex+0x47 00000000`09cbfb20 00000000`7775f56d MSVCR80!endthreadex+0x104 00000000`09cbfb50 00000000`77893281 kernel32!BaseThreadInitThunk+0xd 00000000`09cbfb80 00000000`00000000 ntdll!RtlUserThreadStart+0x21

从上面的输出的函数名我们可以看到Checkpoint 线程

上面看到worker thread在CHECKPOINT_QUEUE 队列里面等待CHECKPOINT 命令

大家可以看到加上符号文件之后,调试器会在stack trace相应的位置进行转换,例如在模块名那里加上模块名,在函数名那里加上函数名

<module_name>!<function call>or<module_name>!<class_name>::<method/function call>


设置符号路径

设置符号路径的作用是使调试器可以找到符号文件的位置并进行加载在调试的时候

简单介绍一下符号路径和公私有符号文件

私有符号文件:包含了调试会话中需要的所有符号信息(只对微软内部开放,微软私有财产)公有符号文件:只是有选择地包含一些符号信息(对所有人开放)

符号信息隶属于指定的模块,只有调试器需要用到某个模块时,它的符号信息才有被加载和分析的必要

要在调试器中使用符号,我们必须首先告诉调试器这些符号文件的位置,也就是设置符号路径。符号路径可以是本地文件夹路径、可访问的UNC路径、或者是符号服务器路径

符号服务器:在调试过程中,需要涉及成千上万个符号文件,以及同一个符号文件存在不同平台下的不同符号文件版本的时候。

手动设置符号路径肯定是不现实的,于是引入了符号服务器的概念

符号服务器有一套命名规则,使得调试软件能够正确找到需要的符号文件。一般来说,符号服务器比较大,都是共用的,放在远程主机上。

为了降低网络访问的成本,又引入了符号缓存的概念,即将从服务器上下载到的符号文件,保存在本地缓存中,以后调试器需要符号文件的时候,先从缓存中寻找,找不到的时候再到服务器上下载

  1、设置符号路径

  设置符号路径的语法如下:

.sympath [+] [路径]

  如要覆盖原来的路径设置,使用新路径即可:

.sympath <新路径>

  要在原有路径的基础上添加一个新路径,可使用:

.sympath+ <新增路径>

  如果不带参数,那么输出是当前设置的符号路径:

0:000> .sympathSymbol search path is: <empty>  //尚未设置符号路径

  假如在调试时,我知道需要的符号文件位于一下文件夹"D:/MyPdb“。

0:000> .sympath D:/MyPdb  //覆盖原有符号路径Symbol search path is: D:/MyPdbExpanded Symbol search path is: d:/mypdb

  此时调试器将记录上面新的符号路径,但并不会从这个路径中加载任何符号,要指示调试器加载符号,可以使用元命令reload。

这个命令能枚举出进程地址空间中所有已加载的模块,并且尝试找出与各个模块相关的符号文件。

0:000> .reloadReloading current modules.....

  如果调试器在指定目录无法找到文件,那么它会输出错误提示:

*** ERROR:Symbol file could not be found. Defaulted to export symbols for xxx.dll

  当没有设置本地缓存路径时,那么调试器将使用调试软件的安装路径下的sym文件夹。

  要特别注意的是,使用.sympath改变或新增符号路径后,符号文件并不会自动更新,应再执行.reload命令以进行更新。

  

  2、符号服务器与符号缓存

  设置符号服务器的基本语法是:

SRV*[符号缓存]*服务器地址

  语法有SRV引导,符号缓存和服务器地址的前面各有一个星号引导。

  此外,我们总是应该把微软的公用符号库加入到我们的符号路径中:

.sympath+ srv*<缓存地址>*http://msdl.microsoft.com/download/symbols

  这是一台微软对外公开的服务器,使用http地址访问,不是所有人都能牢记这个网址,所以最好的办法就是使用.symfix命令(自动记忆了上面

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表