Windows调试技巧&工具

前言

我们使用调试工具的时候,一般是要处理以下问题:

  1. crash
  2. anr application no response
  3. memory leak
  4. dead lock
  5. high cpu usage

Windows平台上有很多对应的处理方案,不过现有的方案一般都有一个通病:一旦打开某些调试功能或者系统设置,调试目标的性能就会成倍下降。这一点有时候会对调试来带很大的干扰,比如出现目标程序UI线程卡顿,导致无法正常操作,或者没发现要调查的问题,反而是发现了一堆性能方面的问题。

crash

崩溃的情况可以从几个正交的维度来划分:是否必现,开发的机器上是否可以复现,是否抓到crash dump。

一旦接手一个crash问题排查,可以按照下面的步骤来排查:

  1. 发现问题的机器上是否可以复现。可以复现的情况又分为:
    • 必现 : 这个情况是最简单了。
    • 高概率复现 :这个也比较简单。
    • 低概率复现,甚至只出现一次 :这个最棘手,作为开发最怕遇到这类问题。但是理论上讲,只要能发现一次就一定有一个必现的路径,只不过实践中碰到的概率低而已。套用一句话来描述就是:crash只有0次和无数次。
  2. 能复现的话,是否有复现路径。
    • 有的话,开发的机器上是否可以复现:那么直接用调试器来看就好了,这个最简单了,调试器就可以直接抓到crash点。
    • 开发的机器上不能复现的话,qa的机器上是否可以复现:可以通过远程调试的方法来处理。
    • 如果开发和测试的机器都不能复现的话,可能就需要联系客户了。这个要做好准备,这时候可以使用的方法就是:日志+dump。
      • 使用dump还要看是否能(尽量)跑debug版本,因为有时候release版本缺少一些符号信息,可能会出现即使复现了,但由于缺少符号,缺少active信息而导致无法进一步查问题。
      • 提一下,日志对于crash问题来说往往帮助不大,不过聊胜于无。
  3. 走到这一步,那就是说这个问题很可能不好复现,而且拿不到dump信息,那么这时候问题的解决就很可能没希望了。留下最后一个办法就是:耐心。排查代码,不论使用什么方法,在怀疑的点上加日志,做好准备抓dump,部署到目标环境,然后静候crash发生。

对于上面的场景,其实概括起来处理方法就三种:

  1. 本地调试:一般是使用MSVC
  2. 远程调试:MSVC或者WinDbg都可以
  3. 抓dump分析:MSVC或者WinDbg都可以,生成dump的方式,可以在资源管理器里面找到目标进程,右键执行创建转储文件;或者使用MSVC/WinDbg的创建dump功能。

anr

首先,对于anr的问题要注意的一个问题是,一定要确定anr确实发生了,因为有时候可能是程序运行环境配置较差导致的,静置一会儿看看是否就没问题了。当然如果干脆就不允许出现anr,那么应该有两方面的考虑:给出最低环境配置和找出造成anr的问题代码(比较有追求的做法)。

确定是anr问题了,那么接下来就要采取一些措施,比如动态抓dump看UI线程卡在哪里了。或者直接制造一个crash,也是得到dump,调查一下UI线程卡在哪里了。当然如果能上(本地或者远程)调试器的话是最好的了,anr的时候直接让程序停下来就可以在call stack中看到UI线程在干嘛。

另外提一下,mac上有一个不错的工具,可以在活动监视器里面采样,从而直观地看到哪行代码有问题。Windows系统上就只能用msvc工具来查了,但毕竟并不是所有(测试/客户的)环境都有调试工具,不是很好查;同时这个问题还可能与high cpu usage或者dead lock有点关联。

memory leak

内存泄露问题,可以:

dead lock

发现死锁本身就是一个问题,比如非UI线程死锁,可能就没有UI线程卡死那么直观。可能是某些对该线程的同步调用卡住,或者异步调用没有结果返回才能发现。

发现之后可以抓dump,或者上调试器。

high cpu usage

Windows上由于缺少一个对进程的各个线程采样统计的工具(我没发现),所以这个问题查起来没有那么直接。WinDbg上可以用!runaway来查CPU的使用时间,但是这个使用时间,我们无法指定开始统计和结束统计的时间点,也就是说真正耗时的操作可能会被很早运行的线程运行时长超过,导致我们定位不出来。

Profile

PC Profile工具

手工复原堆栈

参考 Manually Walking a Stack

堆栈失效,可能是由于对无效地址的调用导致调试器丢失了返回地址的位置;或者您可能遇到了无法直接获取堆栈跟踪的堆栈指针;或者可能存在其他一些调试器问题。

思路是:

  • 先从检查符号开始 x *! 命令
  • dd esp/rsp 查看栈指针附近的信息,找出可能是函数指针的值,这里要注意排除掉下面的值:
    • 一般整数比较小,dd的时候大部分位是0,这类值不太可能是函数地址
    • 英文字符的范围一般在 [20,7f] 区间
    • 指向栈上的指针变量大小一般与esp/rsp接近
    • 函数指针一般不太可能重复
    • 状态码通常以 ac (c00000d6) 开头
    • 最后,栈的地址应该位于模块的区间内,也就是在第一步x命令找到的模块的地址区间内
  • 这样得到一组函数指针备选集,然后逐个使用ln address查看对应符号,得到一组可能的函数调用符号
  • 然后通过反汇编指令u 函数指针备选集合中的每一个元素的前一个地址,看看得到的结果是否匹配上面那组符号,从而构建出调用堆栈来。

GFlags

Windows上好多调试功能都需要 gflags 这个工具。

GFlags(全局标志编辑器)gflags.exe 启用和禁用高级调试、诊断和故障排除功能

gflags官方有一个实例说明:GFlags Examples

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值