Crash Dump调试:Symbol Server/Source Server、PDB原理分析

Crash Dump调试:Symbol Server/Source Server、PDB原理分析

Crash Dump调试:Symbol Server/Source Server、PDB原理分析 - 知乎 (zhihu.com)

背景

  1. UE4引擎时不时要魔改编译。可能大一点的项目是难以避免的吧 ┓( ´∀` )┏
  2. 工程C++会自动编译,有持续集成平台做统一的编译和分发。这样可以不用每个人都编译引擎和工程C++了,代码安全性和开发效率能得到保障;
  3. 每当要调试别人发的Dump,就要满世界找dll,找符号,找代码;

目的

搭建符号服务器,调试分析Crash Dump。

如果你也有跟我一样的痛点,那接下来就来看看怎么解决吧。

接下来,我会先介绍怎么用,最后再说说原理和一些技巧。


搭建符号服务器(如已有请略过)

  1. 在编译机上安装WinDbg,后面需要用到其中的symstoreagestore

Download Debugging Tools for Windows - WinDbg - Windows drivers

注意安装这个版本:(这里才有我们需要的symstoreagestore

安装好之后,应该可以在这里找到这两个工具,请根据机器的架构选用合适的版本。

2. 准备一个脚本,用symstore把Binaries和PDB存入符号服务器,比如这样:

@echo off
rem 脚本传入参数(按顺序): <BIN_DIR>
setlocal EnableDelayedExpansion
rem echo BIN_DIR:%1

rem SYMSTOREPATH 符号服务器地址
rem PRODUCT 你的产品名
rem VERSION 你产品的版本
rem COMMENT 你想加的注释
rem TIMEOUT_DAYS 清理多少天前的符号
rem SYMSTORE_EXE symstore的文件地址
rem AGESTORE_EXE agestore的文件地址
set SYMSTOREPATH=D:\SymbolStore
set PRODUCT="sakura"
set VERSION="0.0.0"
set COMMENT="none"
set TIMEOUT_DAYS=60
set SYMSTORE_EXE="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symstore.exe"
set AGESTORE_EXE="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\agestore.exe" -y

set BIN_DIR=%~f1

%SYMSTORE_EXE% > nul 2> nul
if [%ERRORLEVEL%] EQU [9009] (
	echo "symstore.exe 没找到,请包含它所在文件夹到PATH"
	exit /b 0
)

echo ============== 清除旧符号 ==============
echo 清除%TIMEOUT_DAYS%天前的符号
%AGESTORE_EXE% -days=%TIMEOUT_DAYS% %SYMSTOREPATH%
echo.

echo =============== 上传符号 ===============
echo ++ 本地上传目录: %BIN_DIR%
echo ++ 符号服务器: %SYMSTOREPATH%
echo.

echo ++++++++
echo ++ 上传exe到符号服务器
%SYMSTORE_EXE% add /r /f %BIN_DIR%\*.exe /s %SYMSTOREPATH% /t %PRODUCT% /v %VERSION% /c %COMMENT%
echo.

echo ++++++++
echo ++ 上传dll到符号服务器
%SYMSTORE_EXE% add /r /f %BIN_DIR%\*.dll /s %SYMSTOREPATH% /t %PRODUCT% /v %VERSION% /c %COMMENT%
echo.

echo ++++++++
echo ++ 上传pdb到符号服务器
%SYMSTORE_EXE% add /r /f %BIN_DIR%\*.pdb /s %SYMSTOREPATH% /t %PRODUCT% /v %VERSION% /c %COMMENT%
echo.

echo ++ 上传成功!!!!
echo.

在每次编译完之后,运行。

bat脚本会用agestore自动清理过期的符号。

> upload_symbols.bat ...\UE4\Epic\Engine\Binaries
> upload_symbols.bat ...\UE4\Epic\Engine\Plugins

3. 设置好符号服务器的文件服务器,可以简单地用Samba协议(Windows共享文件夹)或者用IIS或者Apache弄一个。


在VS中设置符号服务器

以VS2019为例,在 工具->选项->调试->符号 把刚才的服务器地址(确保有访问权限)设置好:

小技巧:勾选Load only specified modules,可以显著加速调试载入module的时间。在需要查看符号时,再右键加载所需的module:

就能看到符号名字了:

参考官方文档:

https://docs.microsoft.com/en-us/windows/win32/debug/using-symstore


源码匹配

此时很可能因为pdb中的源码路径和你本地的不一致,导致找不到源码,如下图:

此时除了一个个地去找源码路径之外,有两种方法可以让VS自动定位到所需的源码:

  1. 在Solution设置中配置Debug Source Files查找路径。

这种方法简单但有局限,能应付大部分情况。如下图:

2. Source Server(推荐)

假如产生这个Dump的Binaries已经很旧了,源码已经被改的天翻地覆,除了在本地通过版本控制系统把代码手动还原回去,这种比较笨的办法之外,还有没有更好的做法呢?
又假如要调试的程序本地并没有全部代码,但想大致看一下问题出在哪(比如在QA机器上出现的Crash想准确分发给对应的工程师),有没有更好的做法呢?

有的,答案是Source Server。

Source Server配置方法

编译时:Source Indexing

概要:(以SVN为例)在编译完成后,上传符号之前,用svnindex.cmd工具将版本控制信息写入PDB。

运行要求:

  1. Debugging Tools for Windows(在上面搭建符号服务器时已安装)
  2. ActiveState Perl(Indexing所需的工具,比如:svn.pm,用Perl运行)
  3. 准备svn.exe,并加入到PATH

在PDB生成出来后,上传符号前,执行下面的操作:

svnindex.cmd /source=<SVN ROOT> /symbols=<SYMBOL LOCATION> /debug

此时应该会看到PDB被修改了。

可以用pdbstr.exe检查被写入的内容:

pdbstr.exe -r -p:<PDB file> -s:srcsrv

此时的PDB将比编译生成的PDB多出一些信息。

PDB就准备完成了。

注意svnindex.cmd的source路径只能是SVN根目录。

调试时:从版本控制服务器自动下载源码

  1. 准备svn.exe,并加入到PATH;
  2. 在VS中启用Source Server的支持;

设置完成。

在调试Dump时,会提取PDB中的指令。这可能会带来代码注入风险。比如你调试一个未知来源的程序,自带PDB,这个PDB中是可以嵌入任意代码的。

VS会弹窗提醒。解决方法可以是:

修改svn.pm文件中的SVN_EXTRACT_CMD,将"cmd /c"删掉,直接执行svn.exe,而不是通过cmd.exe。
修改VS安装目录下的srcsrv.ini,将svn.exe加入[trusted command]:

[trusted commands]
svn.exe

至此,代码就可以自动从版本控制服务器上获取了。(^-^)V

代码会缓存到AppData下,不会自动清理,时间长了请自行手动清理。

VS Visualizer 配置

Visualizer 是VS 调试器的自定义可视化方式,比如UE4 里面的很多内置类型是的数据是不那么容易看到的,通常要通过一个Index 去某个表里做二次查询,这时候就可以通过Visualizer 去做可视化,方便调试。

参考资料

Source Server

Using a Source Server

Enable source server support

Source Server + Subversion = Easy Assembly Debugging


原理介绍

通过前面的操作,也基本能猜出个大概了。这篇文章写得挺详细的,我就不赘述了,只简单讲讲自己的理解:

搭建自己的符号服务器

PDB:

PDB里面记录了一系列的调试辅助信息,与Build的Binary一一对应。比如:

  • publics and exports
  • global symbols
  • local symbols
  • type data
  • source files
  • line numbers

最关键的,无非就是代码段地址,对应的源码路径、函数签名和行号。这样在调试的时候,就知道当前的代码地址,对应哪个代码的哪个函数的哪一行了。

DLL和PDB中会有相同的GUID,通过GUID在符号服务器上组织目录存放文件,调试时根据GUID来找对应的PDB下载。

Source Server重建索引时(重新执行`svnindex.cmd`)PDB文件会被修改(哪怕对应的Binary一点都不变,PDB中的索引日期也会变),但GUID会保持不变。

更新后的PDB会被symstore重新上传符号服务器(而不是相同跳过),会覆盖掉符号服务器上GUID相同的旧PDB,不会因此导致硬盘空间不足,但需要注意IO和流量问题。

Symbol Files - Win32 apps

Crash Dump:

Dump简单来说就是进程的内存镜像。把这个进程的虚拟内存(代码段、数据段、堆、栈、全局段等等)部分或者全部转储到磁盘文件,以便进行死后调试。

https://medium.com/@shoheiyokoyama/understanding-memory-layout-4ef452c2e709

User-Mode Dump Files - Windows drivers

其中,又分为Full Dump和Mini Dump,两者最大的区别在于Heap是否做转储。

因为Heap通常非常大,在UE4下动辄10GB以上。而其他内存段相对小很多,比如线程的Stack一般就是几MB。

所以,在Mini Dump下,因为Heap都被剔除了,所以在调试Dump时,看到的是???内存不可访问,比如UE4的FString内部是一个TArray,其字符串Data就是在Heap上的,所以调试Dump时都不可见。

那问题又来了,这样的Mini Dump有什么用呢?如果是因为资源问题导致的Crash,怎么知道是哪个资源呢?

小技巧:可以用在UE4程序后加命令行参数-fullcrashdump来告诉引擎,崩溃时生成Full Dump。这样就可以勉强弥补Mini Dump所带来的不足。

生成Dump

UE4程序Crash的时候会自动生成Dump,因为引擎中提供了Crash Handler:

但假如我想随意生成Dump呢?比如没有Crash而是假死了,怎么知道问题出在哪呢?

有很多种方式可以做到,我个人比较常用的是:

  • 用VS Attach上去,然后Debug->Break All->Save Dump As。可以创建Mini Dump或者Full Dump;
  • 用任务管理器,右键对应的进程->Create dump file。只能创建Full Dump;

其他的方法可以参考这里:

https://www.wintellect.com/how-to-capture-a-minidump-let-me-count-the-ways/


结束

感谢阅读!希望大家能有所收获,不正之处还请指教。

编辑于 2021-05-13 17:06

虚幻 4(游戏引擎)

Dump

软件调试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值