《格蠹汇编》读书笔记—windbg的使用(中)


笔记本唤醒失败的原因探究(16章)

1)如何产生dmp文件

方法在前一章说过《格蠹汇编》读书笔记—windbg的使用_u012138730的专栏-CSDN博客_windbg怎么用

当唤醒失败了以后,按ctrll+scrolllock 强制蓝屏(需要注册表的设置) 然后就有dmp文件可以分析了。


2)一些常规的命令

分析dmp文件的时候的一些常规操作,之前也介绍过。

  • 设置调试符号路径

.symfix c:\symbols

  • 自动分析

!analyze -v

3)进一步分析dmp文件

自动分析没有什么价值的时候,尝试用两种方法

  • 第一种:!pcr 了解CPU的当前状态

!pcr是查看处理器控制区,从而了解cpu的当前的状态

!cpuinfo————怎么看当前有几个cpu。

!pcr 0 和 !pcr 1————看0号cpu和1号cpu的具体的状态,看看有什么猫腻。

《格蠹汇编》读书笔记—windbg的使用(中)_第1张图片

上图中1 号cpu的当前线程和空闲线程不一样 ,看看是啥。(如果当前线程就是空闲线程那没什么好看的。)

所以查看查看的当前线程

!thread 8688e568——— 通过这个命令可以查看寄存器的上下文等等。。然后程序指针寄存器ip指向用户态空间,说明1号正在用户态执行。因为系统挂死通常是在内核态,所以先不往这个方向探究。

  • 第二种:!locks 查看锁

这个命令查看存在的内核对象锁

《格蠹汇编》读书笔记—windbg的使用(中)_第2张图片

显示了系统所持有的资源对象。上图显示了3个。

对于每一个对象,都显示了这个对象的持有线程,等待线程,持有方式。

以上图显示有一个线程特别可疑,以上三个对象都是被87474da8这个线程拥有,且第一个和第三个资源都是独占方式持有

!thread 87474da8——发现这个线程属于 WinLogon 进程 并且 整个线程正在处于等待状态 

《格蠹汇编》读书笔记—windbg的使用(中)_第3张图片

疑点1:执行过 serenum ser2pl 这两个驱动程序的函数 (怎么知道是驱动程序的函数呢)

疑点2:ser2pl这个没有符号文件,所以没有显示出来函数名字,但是看到是这个函数导致的线程进行了等待状态,调用了一个wait函数 。 后面要研究一下 ser2pl是个什么东西

继续研究这个线程,关注跟驱动相关的信息。

执行 .thread /p 87474da8 ——设置为当前线程 (跟 !thread 87474da8 有什么不同吗 栈回溯 更加底层了 显示的内容更多了 )

执行 kvn 查看栈回溯 然后看图

《格蠹汇编》读书笔记—windbg的使用(中)_第4张图片

 线索1:可以看到 执行ser2pl 之前的一些操作系统执行睡眠和唤醒任务的关键线程 (标注出

知识点:系统的唤醒阶段:

1)通知执行体,唤醒设备

2)通知windows子系统,点亮显示器
从栈回溯中看到,在第一个阶段就停止了,所以没到点亮显示器的状态。那在唤醒设备的过程中执行到了 ser2pl 。

ser2pl的信息

之前说过是ser2pl导致了线程进入了等待状态,所以看看ser2pl是个什么东西

!drvobj ser2pl———查看 ser2pl驱动信息

《格蠹汇编》读书笔记—windbg的使用(中)_第5张图片

(85163500)

驱动对象的地址(地址A)。

驱动对象设备实例设备对象的地址(地址B) 85163500,

(地址B的类型是 Device_object ,可以用dt _Device_object 85163500: 看结构详情)

(而且还发现这个地址B在栈回溯中参数列出现了很多次。【图16.5 比如11号栈帧】)

设备对象地址 85163500的分析

!devstack 85163500——查看设备栈中该设备对象的位置(左边的小箭头指着呢)

《格蠹汇编》读书笔记—windbg的使用(中)_第6张图片

其中每一行 :

1) 设备对象的地址 2) 驱动名字 3)设备扩展结构的地址   
最后还会给出 设备对象地址 所对应的设备节点的信息 。

对于上面图中的设备栈,

具体:
                设备栈的最底部 USB集线器HUB驱动
                上面一层ACPI脚本的请求
                上面一层ser2pl这个功能驱动
                最上面一层serenm驱动:串行口枚举 是与串行口设备有关的一个驱动程序
汇总: 设备实例 & ServiceName is “Ser2pl”

查找    ser2pl(ServiceName is “Ser2pl”) 位于设备树的哪里 :

!devnode 0 1----显示出包含系统中所有设备的设备树。然后可以看到ser2pl(ServiceName is “Ser2pl”) 是位于什么之下。

即查找ser2pl这个设备的接入方式。

《格蠹汇编》读书笔记—windbg的使用(中)_第7张图片

 结论:插在usbhub下的某个端口而usbhub位于负责管理低速设备的usb控制器usbuhci之下)
所以作者就推断出 ser2pl usb 转 串行口 设备的驱动程序
(作者确实用了一个usb转串行口转接头,那笔记本是串行口 还是 usb口呢)
 

4)从栈回溯的熟悉的函数的入手

  • 找栈回溯的规律

【从上面的图16.5】 ser2pl 和 serenum  函数来回跳转
#3#4 ser2pl 驱动的函数
#8#9 serenum 驱动的函数
#e ser2pl 驱动的函数
#12#13 serenum 驱动的函数

  • 熟悉的函数1—— PoCallDriver 函数原型

(以#11栈帧为例子)
第一个参数:——设备对象 间接指定了要调用的驱动 (也就是说设备对象和设备的驱动是一一对应的?对驱动其实不是很熟悉

!devobj 第一个参数值(设备对象地址————可以发现这个是个 ser2pl驱动

第二个参数——IO请求包 IRP

!IRP 第二个参数—— 请求的什么 IRP 包呢。但是这个命令出错了。因为这个参数被改写了。(原因是这个函数执行过程中,把参数所在的位置的值给重写了,有的函数就会这么做)

【16.5图的两个irp地址 8761f600(2号irp) 85879c60(1号irp) 

  • 熟悉的函数2——PopPresentIrp函数原型

找到PopPresentIrp函数的第二个参数,然后调用>!IRP 8761f600 和 >!IRP 8761f60

《格蠹汇编》读书笔记—windbg的使用(中)_第8张图片

《格蠹汇编》读书笔记—windbg的使用(中)_第9张图片

 显示出来的是 对应于 设备栈的 IRP栈位 [A,B]里面的值的含义(到wdm.h中可以查到 16 和 2 是什么意思)

也就是说对每一个设备的一个IP请求包,IO请求包传给栈顶,然后一级一级往下传。
都是[16,2],跟电源状态有关系,那他们有区别吗 ?有的,下面说。

  • 对于IRP的介绍

 IRP 的 结构 就是具有一个串 IO_STACK_LOCATION 子结构,其中有一个子结构的内容就是获得当前驱动使用的子结构。通过下面这个命令获取

通过 !IRP 8761f60 1(比前面多一个1)

多了更加详细的内容 其中的 currentstackloaction

(85879d60)

ps:驱动程序中 经常通过 irpstack = IoGetCurrentIrpStackLoaction(Irp)来获得 当前驱动使用的iostack位置

通过这个结构的地址 85879d60 来查看他的内容(也就是结构是IO_STACK_LOCATION ),其中有个Parameters参数,分析出 这个 IRP 是用来设置电源状态的(2号IRP)

《格蠹汇编》读书笔记—windbg的使用(中)_第10张图片

也就是说要根据当前驱动的子结构的具体含义。

用同样的方法可以分析出,另外一个IRP是用来通知系统的电源状态发生了变化(1号IRP)
                
【栈回溯图的参数 #11 #10 第一第二个参数】

所以分析出来,这是一个是设置电源,一个是通知电源。

至此,我们分析出,

1. 系统被要求唤醒  系统函数 PopwakeDeviceLst Usb转串口设备发送唤醒通知。

==》 向这个设备栈的最上端设备对象8610d2b0(serenum)发送2号IRP


2.serenum驱动2号IRP向下传递给ser2pl驱动

3.ser2pl驱动 作为负责设备电源状态的 FDO 发现新的电源状态D0 和 自己的当前状态 D3 不一样, 于是调用 PoRequestPowerIrp 发起设备电源变化 1号IRP,企图改变设备的电源状态
 

那执行到第三步的时候 为什么回停滞不前了呢

所以要查看#3号栈帧为什么会调用到#2号栈帧wait函数
看一份跟ser2pl驱动源代码很相似的代码 DDK中的 Power.cserialsetPowerDO函数
 

《格蠹汇编》读书笔记—windbg的使用(中)_第11张图片

 
(ps:很有一个很不常规的操作把 返回值返回一个 设置到第二个参数的位置上了,

#3号栈帧第二个参数EBP+C是第二个参数的位置(记住)) 

发现就是PoCallDriver 返回值为103那个状态。导致了调用Wait函数。

要有人来设置PowerD0Event这个事件才能让这个函数继续进行,否则将继续等待。不太理解为啥会出现这个问题。

    


解救陷入死循环的MSN(17章)

1)电脑风扇突然提速的原因

电脑的风扇突然提速并且保持高速运行一定是系统内温度升高,触发散热系统开始工作,系统持续高负荷运行而导致的,一般有如下情况:

  • 需要处理大量重复事物的软件开始工作,比如病毒扫描程序和索引服务等
  • 某些程序运行故障,进入死循环

2)查看cpu占用率

解决提速的问题一般首先使用 看任务管理器(ctrl+shift+delete

看 cpu占用率

占用率指的是在多任务系统中cpu在某个进程上的运行时间cpu运行总时间的比率)

cpu时间

时间指的是在每个进程上所停留的累积时间


看看我自己的电脑:

《格蠹汇编》读书笔记—windbg的使用(中)_第12张图片

第一个系统空闲进程,空闲进程占用率最高说明这个系统健康,说明大部分时间都在空闲进程状态待着,cpu游刃有余。(空闲进程之前也多次提到过。)

还有一个cpu使用率,是整个电脑的,就是100%减去空闲进程的。


一台电脑如果双核cpu,如果某个进程占用50% 说明这个进程就对于一个核的cpu占用可能就是靠近100%了,乃就是陷入了死循环无疑了。

因为文中出现的是 msn进程卡住了
启动windbg,挂到msn,cpu利用率马上下来了,按g继续执行,就又飙上去了
说明就是这个进程的问题了

3)查看进程的哪个线程卡住了(一直在占用cpu)

要查看msn这个进程发生死循环的位置,首先推断哪个线程:
首先就要看具体是哪个线程作妖,有以下几点经验,之前也会有提到过:
1)如果是gui程序,而且界面不刷新无响应,首先就要看ui线程,而ui线程一般都是0号线程
2)如果线程不多,使用~*kv 看每个线程的栈回溯来判断
3)可以使用命令 >~*e .ttime 看每个线程的运行时间长度,推断可疑线程
4)上工具 process explorer 看每个线程的cpu占用率(双击进程,选择线程页属性页),推断可疑线程


文中符合1),所以先看ui线程,先切换到 ~0s 切换 然后看栈回溯 kn 100


注意第二行的那句warning 比如1#栈帧,并不是说他执行到过SetZoneLevel函数,只是以这个函数为参照物来表示其位置关系。
虽然最后面的栈帧符号显示不全,但是万幸,#0号栈帧有符号信息,是准确的

msvcr80!_vsnwprintf_s 就是 vs2005的运行时模块的用来格式化字符串的vsnprintf的安全版本
下一个断点在 bp msvcr80!_vsnwprintf_s 断在这里 继续执行 又断 说明这个就是在死循环当中呢,但是并不能说明这里是源头,只能说明是这个线程这个函数在被反复执行(有可能是这个函数执行的时间比较长,所以挂到windbg以及中断的时候停在这里的概率比较大)


沿着打印函数继续追查,看看打印的是什么,首先看这个函数的原型,其中第一个参数是内存缓冲区(记住第一个参数是ebp+8,其中ebp是0006fb04,从下图可以看到缓冲区的地址是0x01939ff5),用来存放格式化的结果 

肯定是要执行完,缓冲区才会有结果,所以断在那个函数msvcr80!_vsnwprintf_s 以后,按 gu 让其执行完,然后dU看缓冲区的内容


猜测是打印调试信息 

看看是不是每次都在打印调试信息呢?

执行 >bp msvcr80!_vsnwprintf_s+0x19 "dU poi(@ebp+8);kv 3;gc"
含义 
运行_vsnwprintf_s+0x19到这里 执行""中的命令 
dU poi(@ebp+8):打印缓冲区的内容
kv 3:打印3帧栈回溯
gc:自动恢复执行

然后就是继续执行了,看看能打印出什么东西 
标红的是栈回溯,每次执行路径都是一样的,至少我们推断了这个 _vsnwprintf_s 这个函数不是死循环的始作俑者,因为他会返回到上一层,他只是一个螺丝钉。

《格蠹汇编》读书笔记—windbg的使用(中)_第13张图片

4)死循环的源头函数在哪里

那么怎么推断死循环发生的头

依然是把断点下在msvcr80!_vsnwprintf_s+0x19,然后看更多的栈回溯,
1)选取两次栈回溯,从下到上看最后一个相同的地址 这里 CreateObjectStoreService+0x14969
2)把断点指令bp 下在这里(CreateObjectStoreService+0x14969),再把断点下在下一个相同的  CreateObjectStoreService+0x14c24

《格蠹汇编》读书笔记—windbg的使用(中)_第14张图片

 发现在 CreateObjectStoreService+0x14969 每次都会命中,也就是每次都会走到这里。
但是  CreateObjectStoreService+0x14c24 不会

说明死循环就发生在 CreateObjectStoreService+0x14969 所在的函数中 (注意前面说过,栈回溯中说明了 函数名是不准确的 只是一个位置关系,并不是说这个函数就是CreateObjectStoreService


那么就从这个当前函数的返回地址开始逆向反汇编

当前函数的返回地址返回地址也就是 CreateObjectStoreService+0x14c24

得到调用这个函数的 call  《格蠹汇编》读书笔记—windbg的使用(中)_第15张图片

也就是进入到5f342ff1这个函数去执行,执行完了返回到5f343368(5f343368就是CreateObjectStoreService+0x14c24)

好好理解这句话,结合图。

所以我们找到了死循环函数的入口地址就是 5f342ff1

就是一直都在5f342ff1这个函数里面在执行呢,一直都没有返回。

而这个CreateObjectStoreService+0x14969正是5f342ff1这个函数里面的某一个位置的一个函数调用。

5).大概看看源头函数的细节

((最后发现这个函数是一个遍历链表的函数,可这个链表头尾相连接了,导致函数无休无止的遍历下去了))

执行uf 正向反汇编

uf 5f342ff1

显示结果的图略,总之这个反汇编 一共100多条指令 也就是20 30 行源代码 。因为目的不是破译函数,而是看基本框架,猜测存在的问题,从下面几方面入手

初步看:

1) 没有使用FPO(看到过是啥 忘了 以后记起来再补充)
2) 可以看到call 指令特别多

具体来看:
1) 看局部变量
sub esp,2Ch
说明局部变量区长度0x2C 看到有 ebp-4 、ebp-14 、epb-2C

猜测第一个可能是4字节的整数或者指针 后面 16字节 24字节 可能是结构体

2) 看函数使用的参数 ebp+
ebp+8 第一个参数。看开头有一个 ecx 赋值给 edi 。ecx 其实就是用来传递this 指针 猜测这个是C++类的方法

 图中大概意思:先保存edi,再把ecx赋值给edi,再把edi压入栈,作为参数调用另外一个函数,类函数吧

6).单步跟踪源头函数,找到可疑点,或者说死循环发生的地方

单步跟踪5f342ff1这个函数的执行过程 看看在那里发生的死循环,几次以后发现就在下面三个函数中: 【执行call 比较值 然后决定是向下执行(继续往下执行)是 向上跳 (ebp-4 不等于 0 不相等)


(中间那行)但是这里奇怪的是 并没有比较函数 (5f361555) 返回值,比较的是局部变量上的值 ebp-4 难道是这里有问题?其实也有可能是下面的函数 操作了这个局部变量 也是有可能的。所以进行验证

所以我们验证一下,设置一个数据断点(我记得我在哪里记录过 怎么设置数据断点)
先找到ebp = 0006fc78 然后减掉4 
> ba w4 0006fc74 
设置好上面断点,使用 >bd 暂时 禁止其他断点。

然后继续执行  看看有没有命中断点,果然命中了,还是修改了这个值的 ,停留在划红线的哪个地方,就是那里的上面一行改动了ebp-4吧,所以就是ecx那里了。

《格蠹汇编》读书笔记—windbg的使用(中)_第16张图片

作者大神把图 17.11 的翻译了一下 变成源代码,意思是遍历一个链表 使其指向下一个 然后重复执行。

《格蠹汇编》读书笔记—windbg的使用(中)_第17张图片

《格蠹汇编》读书笔记—windbg的使用(中)_第18张图片

这就是 5f361555这个函数大概就是遍历一个链表的节点,做一些事情,然后过程会改变ebp-4的值,然后外层会判断这个是不是到达终点了(我们说的ebp-4是不是等于0了),如果没有达到说明有下一个节点,继续做事情。

7).使用windbg的循环命令看死循环过程 


>recx=poi(ecx);r ecx ; z(ecx!=0)
把ecx值所对应的地址的值 复制给 ECX
然后先试一下ECX的值
反复操作
知道ECX为0
z(n) 是循环的条件 

其实就是ecx存的是一个地址,地址中存的的是一个值,这个值又是一个地址,最后看看那个地址里面存的值是0,就不循环了。

使用这个循环命令 手动遍历链表 (大概就是ecx是下一个链表节点的指针?,所以就是直接打这条命令然后他就会打印出来如下的一直在打印了。
redo 后面的是记录循环的次数

《格蠹汇编》读书笔记—windbg的使用(中)_第19张图片

然后搜索某一个出现过的地址 发现出现了好几次了 每次出现都是间隔86 说明链表节点86个,然后又循环回去了

8).那么如何跳出循环

从上面的手动循环链表可以发现是是链表首尾相连接了呀,一直都循环不完。所以图17.10中,第二行不可能等于0的,所以就一直往上执行了,一直循环了。

如何跳出循环方法:
1.修正链表数据 比较难
2.修改程序指针寄存器 —————— 使用be命令恢复本章前面设置的断点 CreateObjectStoreService+0x14969  ,然后单步执行到 jne那里 【图中画红线 】然后修改寄存器的值 >R eip=5f3430c4 让跳到下面执行 。因为就是在同一个函数中的执行的 不涉及栈平衡问题
p指令单步执行指令

《格蠹汇编》读书笔记—windbg的使用(中)_第20张图片


寻找系统中的耗电大王(18章)

前情提要:


硬件耗电排名: cpu 显示屏 独立显卡 硬盘

cpu在硬件方面做了很多优化,耗电往往是软件上出现了类似于死循环的事件 

作者又出现了风扇疯狂转的现象,同样调出任务管理器,找到cpu占用率最高和占用时间最高的,发现是one-note

使用windbg挂上这个进程,风扇停下来了,继续执行,又开始了,说明就是这个进程。

确定线程:


    因为onenote界面还是可以操作的,所以大概率不是UI线程的问题,
    使用命令

>~*e?@$tid;.ttime
    分号是命令分隔符 查看每个线程的运行时间以及打印tid线程号
    找到了可疑线程,运行时间惊人。

确定模块:

切换到可疑线程

~~[18e4]s
查看栈回溯

《格蠹汇编》读书笔记—windbg的使用(中)_第21张图片

通过这个图可以发现,线程的启动函数是 msxml3 模块中的 MimeDownloadThread

(MIME 是多用途互联网邮件扩展,著名的互联网协议之一,推断本线程的目标是通过MIME协议下载信息)

然后单步跟踪这个线程发现:

p 单步执行命令

发现这个线程大部分都是在 msxml3 这个模块中运行 而且调用CRT或者系统函数都能顺利返回 说明不是陷入了内核

看看这个模块 lm vm msxml3 使用版本3.0 补丁版本sp11 ( 我的电脑上也有) 

《格蠹汇编》读书笔记—windbg的使用(中)_第22张图片

确定循环范围,确定死循环原因


同样了,确定了线程和模块以后,就需要深入到代码逻辑了,但为什么会出现死循环,一般需要确定:
1)从哪里开始执行,即最上层是在哪个函数中执行的 
2)过程中调用了什么函数导致的一直在死循环,哪里跳不出来
3)接受了什么外部数据

是否好查上面这些信息,取决于现有条件属于哪一种:
1)有源代码
2)没有源代码 有调试符号
3)没有调试符号 只有汇编

如何确定死循环的循环范围:
1)对于函数调用层次少,又有调试符号协助的,可以直接跟踪,本例使用。
    跟踪发现是 MimeDwnParserAction【图18.6】 函数 遇到了 0x8000000a 的错误 
    其实是尝试把一个url送给浏览器下载,得到一个tcp错误, 这个0x8000000a很有可能是oneonte无法下载导致的错误。
2)对于调用层次多,可以用上一章中的比较两次中栈回溯的方法找到原始函数 然后继续分析。


延伸:如果你遇到了无法下载应该怎么办


最好是加一个最大重试次数 不要无限的尝试去下载


.symfix c:\symbols ———— 设置调试符号路径
!analyze -v ————自动分析
!cpuinfo————怎么看当前有几个cpu
!pcr 0 和 !pcr 1————看0号cpu和1号cpu的具体的状态
!thread 8688e568 ———— 切换到 8688e568 这个地址
.thread /p 8688e568 ———— 设置 8688e568 为当前线程
!locks ———— 查看内核对象锁
!drvobj ser2pl ———— 查看 ser2pl这个驱动信息,会显示出来设备对象的地址 85163500
!devstack 85163500 ———— 查看设备对象的设备栈 
!devobj 85163500 ———— 查看设备对象的信息
!devnode 0 1 ———— 显示出包含系统中所有设备的设备树。
!IRP 8761f60(IO请求包IRP地址) ———— 显示设备栈的 IRP栈位,看有什么栈位 
!IRP 8761f60 1(比前面多一个1) ———— 可以就是获得当前驱动使用的子结构 

~0s ———— 切换到0号线程,进程内的序号
~~[18e4]s ———— 切换到18e4号线程,系统编号

dd 地址 长度 ———— 看地址内容
dU 地址 长度 ———— 看地址内容
gu ———— 执行完这个函数
ub 地址 ———— 反汇编这个地址 之前的汇编指令
uf 地址 ———— 正向反汇编 从这个地址开始后面的
ba w4 0006fc74  ———— 设置一个数据断点
bd  ———— 暂时 禁止其他断点。
be ———— 恢复 暂时 禁止其他断点。
recx=poi(ecx);r ecx ; z(ecx!=0) ———— 把ecx值所对应的地址的值 复制给 ECX,然后先试一下ECX的值,反复操作,知道ECX为0。
 ———— 其实就是ecx存的是一个地址,地址中存的的是一个值,这个值又是一个地址,最后看看那个地址里面存的值是0,就不循环了
r ecx ———— 看寄存器的值
R eip=5f3430c4 ———— 设置寄存器的值
p ———— 指令单步执行指令
bp msvcr80!_vsnwprintf_s+0x19 "dU poi(@ebp+8);kv 3;gc" ————— 运行_vsnwprintf_s+0x19到这里 执行""中的命令 dU poi(@ebp+8):打印缓冲区的内容kv 3:打印3帧栈回溯gc:自动恢复执行

~*e?@$tid;.ttime ———— 查看每个线程的运行时间以及打印tid线程号

你可能感兴趣的:(程序员的自我修养,windows,linux)