怎样重建一个损坏的调用堆栈(callstack)

windbg - 调试栈溢出及一个崩溃示例 栈溢出 程序崩溃,调试显示栈溢出 c0000409 (Security check failure or stack buffer overrun) 系统在此应用程序中检测到基于堆栈的缓冲区溢出。此超限可能会允许恶意用户控制此应用程序。 EXCEPTION_RECORD: (.exr -1) ExceptionAddress: 08f276bd (nvoglv32!vk_optimusGetInstanceProcAddr+0x000c549d) ExceptionCode: c00004 阅读详情
原文作者:Aaron Ballman
原文时间:2011年07月04日
原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupted-stack-crawl/

翻译:magictong

时间:2014年05月29日夜

后记:可惜原始的DUMP文件作者并没有上传



        在我的日常工作中,我经常阅读来之微软WinQual(译注:https://sysdev.microsoft.com/ http://en.wikipedia.org/wiki/Winqual)的报告。这些报告里面一般包含着dump文件(译注:崩溃转储文件,我们一般都是叫dump文件,是一种软件崩溃之后产生的文件,可用于事后调试),从这些dump文件里面我可以分析出一些常用的软件里面到底出了什么问题,造成它崩溃了。总而言之,这是一个超赞的系统,我强烈建议各个独立软件开发商(原文:ISV)去上面注册(尤其是这个系统对任何人都是免费的,只要你的可执行文件是正确签名的)。最近我拿到了一个堆栈已经被严重破坏了的dump文件,我想和大家讨论一下怎么使用Windbg工具来重建它的调用堆栈(callstack)。

        在开始之前,让我们先看看一个原始的调用堆栈是什么样子的,在Windbg里面运行“k”命令即可。
        0:000> k
        ChildEBP RetAddr  
        028b89cc 77c75350 ntdll!KiFastSystemCallRet
        028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
        028b89e0 763e41ec ntdll!RtlExitUserProcess+0x7a
        028b89f4 10056386 kernel32!ExitProcess+0x12
        WARNING: Stack unwind information not available. Following frames may be wrong.
        028b89fc 100565a0 EyeOneIO!I1_SynchronizeWhitebases+0xf0f6
        028b8a0c 10054803 EyeOneIO!I1_SynchronizeWhitebases+0xf310
        00000000 00000000 EyeOneIO!I1_SynchronizeWhitebases+0xd573

        从上面的调用堆栈来看,有几个特征表明这个堆栈已经被破坏了。首先,调用堆栈的基址不可能从0x00000000开始。通常情况下,它从main函数的入口地址开始,或者从一个线程的入口地址开始,但是从上面的调用堆栈来看我们没看看到这个特征。另外,Windbg也发出了“Stack unwind information not available. Following frames may be wrong.”的警告(译注:这句警告的意思就是说,下面的栈帧可能是错误的)。

        第一步,既然堆栈已经错误了,我们当然需要重建当前执行现成的堆栈,并找到当前现成堆栈的起始位置。这里有个简单的扩展命令可以查看,使用!teb即可(译注:!teb用于查看当前线程执行环境):

        0:000> !teb
        TEB at 7ffdb000
            ExceptionList:        028b8a28
            StackBase:            028c0000
            StackLimit:           028b6000
            SubSystemTib:         00000000
            FiberData:            00001e00
            ArbitraryUserPointer: 00000000
            Self:                 7ffdb000
            EnvironmentPointer:   00000000
            ClientId:             00000a4c . 00000e3c
            RpcHandle:            00000000
            Tls Storage:          7ffdb02c
            PEB Address:          7ffdf000
            LastErrorValue:       14007
            LastStatusValue:      c0150008
            Count Owned Locks:    0
            HardErrorMode:        0

        看上面!teb命令显示的结果里面,StackBase和StackLimit告诉了我们当前线程的堆栈在内存中的范围,因此我们现在可以转储这个范围内的地址,然后从里面寻找一些有意义和有用的东西(译注:就是把内存地址和对应的符号地址对应起来,然后寻找和当前的线程有关的调用堆栈)。Windbg里面有个专门的dds命令就是用来做这个事情的,dds命令需要你指定一个起始地址,然后它从给定的起始地址开始转储一定范围内的地址,并且尝试把每个地址里面的内容和符合(symbol)对应起来(译注:假如可以对应的话)。dds转储的内容包含三列数据,第一列显示的是顺序递增的地址,第二列是显示地址里面的数据,第三列是符号名称,如果地址里面的数据可以被成功解析为一个符号的话,否则第三列就是显示的空白。

把真实的栈转储出来看看(省略了一些无关项):
(译注:使用命令 dds 028b6000,要显示更后面的内容可以在028b6000的后面加上一个偏移之后再对新地址使用 dds 命令)

        028b6000  00000000
        ...
        028bf9d8  00000000
        028bf9dc  00000000
        028bf9e0  79035b7f
        028bf9e4  028bfa1c
        028bf9e8  6e760b5b i1IO!i1IO::measureOneStrip+0xbb
        028bf9ec  42b840fc
        ...
        028bfa18  00000000
        028bfa1c  028bfd98
        028bfa20  6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
        028bfa24  42b840fc
        ...
        028bfd94  00000006
        028bfd98  028bfe2c
        028bfd9c  6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222
        028bfda0  013a8520
        028bfda4  79035e2e
        ...
        028bfe28  00000000
        028bfe2c  028bfe38
        028bfe30  763ed0e9 kernel32!BaseThreadInitThunk+0xe
        028bfe34  012118e0
        028bfe38  028bfe78
        028bfe3c  77c516c3 ntdll!__RtlUserThreadStart+0x23
        028bfe40  012118e0
        ...
        028bfe74  00000000
        028bfe78  028bfe90
        028bfe7c  77c51696 ntdll!_RtlUserThreadStart+0x1b
        028bfe80  6e760e40 i1IO!i1IO::_advancedMeasureThreaded
        ...
        028c0000  ????????

        实际上转储出来的堆栈比上面列出来的大得多,不过为了简单起见,我只保留一些相关的部分。

        现在要做的第一件事情就是定位到callstack的起始位置。在这个例子里面,RtlUserThreadStart看起来很像是这个起始位置,因为它是线程的起始调用函数。在找到起始点之后,获取起始点的前一个堆栈地址A(第一列),然后在堆栈的内容里面(第二列)寻找是否有等于A的堆栈B(向低地址寻找,因为堆栈是向低地址增长的),然后再在堆栈内容里面寻找是否有等于B的堆栈地址C……,按照这种方法不停的搜索内存,直到不能再找到任何东西或者找到空地址。
        (译注:这个就是利用的标准函数栈帧的基本原理,对此处不理解的可以去了解下标准函数栈帧,一般没有经过FPO优化的调用函数链,可以通过EBP的值在整个堆栈上面串联起来,其实Windbg自己也是这么找的,而本文讨论的恰恰是因为堆栈被破坏之后,Windbg找不到正确的callstack之后,我们怎么手动恢复的问题)

        在我们这个例子里面,我们从下面的堆栈开始找:

        028bfe78  028bfe90
        028bfe7c  77c51696 ntdll!_RtlUserThreadStart+0x1b

        搜索地址028bfe78,得到下面的堆栈:

        028bfe38  028bfe78
        028bfe3c  77c516c3 ntdll!__RtlUserThreadStart+0x23

        搜索地址028bfe38,得到下面的堆栈:

        028bfe2c  028bfe38
        028bfe30  763ed0e9 kernel32!BaseThreadInitThunk+0xe

        搜索地址028bfe2c,得到下面的堆栈:

        028bfd98  028bfe2c
        028bfd9c  6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222

        搜索地址028bfd98,得到下面的堆栈:

        028bfa1c  028bfd98
        028bfa20  6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467

        搜索地址028bfa1c,得到下面的堆栈:

        028bf9e4  028bfa1c
        028bf9e8  6e760b5b i1IO!i1IO::measureOneStrip+0xbb

        现在,继续搜索028bf9e4已经不能再在堆栈里面找到信息了,也就是说我们可能已经找到了最终出问题的函数位置,我们可以使用Windbg尝试修复我们的callstack,当然我们需要给它我们上面找到的这些信息。其实很简单,只要上面没找错,我们给 k 命令指明一个确定地址,通过 L 参数传递进去(译注:用上面我们最后找到的028bfa1c),那么Windbg马上就会给我们一个更加友好的callstack信息。

        0:000> k L=028bf9e4
        ChildEBP RetAddr  
        028b89cc 77c75350 ntdll!KiFastSystemCallRet
        028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
        028bf9e4 6e760b5b ntdll!RtlExitUserProcess+0x7a
        028bfa1c 6e763387 i1IO!i1IO::measureOneStrip+0xbb
        028bfd98 6e761062 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
        028bfe2c 763ed0e9 i1IO!i1IO::_advancedMeasureThreaded+0x222
        028bfe38 77c516c3 kernel32!BaseThreadInitThunk+0xe
        028bfe78 77c51696 ntdll!__RtlUserThreadStart+0x23
        028bfe90 00000000 ntdll!_RtlUserThreadStart+0x1b

        现在我们看到的callstack是不是更加完整并且合理了?!没有了调用栈帧错误的警告,而且callstack的调用基址也正常了。

希望上面介绍的这种方法能给你的调试工作带来一些帮助。

3 gdb如何从堆栈破坏的堆栈中定位问题(ucontext_t) 本文介绍了在gdb堆栈破坏但有ucontext_t进程上下文信息时调试定位问题的完整过程。通过分析段错误日志和core dump,结合ucontext_t结构中的寄存器信息(包括fault_address、LR返回地址等),成功定位到X11_XUnsetICFocus接口中的指针越界问题。通过反汇编代码和寄存器分析,发现是函数指针变为野指针导致异常。最终通过加线程ID打印验证,确定是多线程环境下先析构后调用导致的访问越界问题。文章详细展示了利用ucontext_t上下文信息进行问题分析的思路和方法。 阅读详情

相关推荐

解决UE5打包错误ExitCode=-532462766:系统性排查与修复指南

在软件开发和游戏引擎应用领域,构建与打包是项目交付的关键环节,它涉及将源代码、资源和依赖项整合为可独立运行的应用程序。其核心原理在于通过自动化工具链协调编译、资源处理和部署流程。这一过程的技术价值在于确保项目在不同环境下的一致性与可移植性,是产品化的重要步骤。然而,在复杂项目尤其是使用虚幻引擎5(UE5)这类大型框架时,打包阶段常因环境配置、缓存状态或第三方模块冲突而出现运行时异常。具体表现为进程非正常退出并返回特定错误代码,例如常见的AutomationTool崩溃问题。这类问题通常源于.NET运行时环境

wentao365的专栏 677

call stack是什么错误_JavaScript 错误处理大全「值得收藏」

作者:疯狂的技术宅转发链接:https://mp.weixin.qq.com/s/-cpUkuzpsPR6uUgWPkbH-A目录编程中有什么错误?JavaScript 中有什么错误?JavaScript 中的错误类型什么是异常?当抛出异常时会发生什么?同步错误处理常规函数的错误处理生成器函数的错误处理异步错误处理计时器错误处理事件的错误处理How about onerror?怎么处理 onerr...

weixin_39544333的博客 379

手把手教你用RMII接口配置以太网PHY芯片(附YT8512实战代码)

本文详细介绍了如何使用RMII接口配置以太网PHY芯片,以国产YT8512为例,提供从硬件设计到软件驱动的全流程实战指南。内容包括RMII接口技术解析、YT8512硬件设计要点、驱动开发实战代码及常见问题排查,帮助开发者快速实现以太网通信功能。

mac99的博客 995

call stack是什么错误_你不知道的 JavaScript 错误和调用栈常识

大多数工程师可能并没留意过 JS 中错误对象、错误堆栈的细节,即使他们每天的日常工作会面临不少的报错,部分同学甚至在 console 的错误面前一脸懵逼,不知道从何开始排查,如果你对本文讲解的内容有系统的了解,就会从容很多。而错误堆栈清理能让你有效去掉噪音信息,聚焦在真正重要的地方,此外,如果理解了 Error 的各种属性到底是什么,你就能更好的利用他。接下来,我们就直奔主题。调用栈的工作机制在探...

weixin_39599166的博客 1338

神经元细胞结构刨析(持续更新)

神经细胞的学习笔记

weixin_46596227的博客 2220

从“跳变”到“连续”:深入理解霍尔传感器插值与PLL角度估算的优劣与选择

本文深入解析霍尔传感器角度估算技术,对比线性插值法和锁相环(PLL)跟踪法的优劣,为电机控制领域的工程师提供实用指南。通过STM32F407和CubeMX的实例,详细探讨两种方法的实现细节、性能对比及优化策略,帮助在不同应用场景下做出最优选择。

weixin_30855761的博客 557

如何重建一个损坏的调用堆栈(callstack)

原文作者:Aaron Ballman原文时间:2011年07月04日原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupted-stack-crawl/翻译:magictong时间:2014年05月29日夜后记:可惜原始的DUMP文件作者并没有上传        在我的日常工作中。我经常阅读来之微软WinQual(译...

weixin_33878457的博客 219

华为OD机考双机位C卷 - 天然蓄水库(Java & Python& JS & C/C++ & GO )

华为OD机考双机位C卷 - 天然蓄水库(OD上机考试双机位C卷)。提供在线OJ刷题。Java、C++、Python、GO语言、C语言、JavaScript题解,并提供完整思路讲解。

算法大师! 1740

UE5动画蓝图崩溃排查:从日志分析到缓存清理的实战指南

在虚幻引擎开发中,内存访问冲突和多线程问题是导致程序崩溃的常见根源。理解内存管理机制和线程安全原理对于构建稳定的实时交互系统至关重要,尤其是在处理复杂的动画逻辑时。动画蓝图作为连接游戏逻辑与角色表现的核心组件,其更新过程涉及骨骼数据访问、资源引用和跨线程通信,任何环节的异常都可能导致访问违规或数据竞争。从工程实践角度,系统性排查需要结合日志分析、引用有效性验证和缓存状态检查。本文聚焦UE5动画蓝图引发的崩溃问题,通过解读EXCEPTION_ACCESS_VIOLATION等关键日志信息,剖析Saved文件夹

weixin_30887919的博客 409

paddle 43 用onnxruntime实现ppyoloe模型的部署(含python和c++版本)

PP-YOLOE+是PP-YOLOE模型的升级版,PP-YOLOE+_l在COCO test-dev2017达到了53.3的mAP, 同时其速度在Tesla V100上达到了78.1 FPS。PP-YOLOE+_s/m/x同样具有卓越的精度速度性价比。paddle官方只给出了基于paddle框架的部署案例,为了更好的部署,为此实现将paddle模型转换为onnx模型,然后在python下实现onnxruntime部署。后续会补充c++ onnxruntime部署

a486259的博客 1914

[SystemC]SystemC中的模块和程序

SystemC 中的层次结构是使用类 sc_module 创建的。sc_module 可以直接使用,也可以使用宏 SC_MODULE “隐藏”。上面的示例 SC_MODULE 创建了一个名为 nand2 的 sc_module 类对象。它的输出纯粹是输入值的函数。因此,模型可以使用最简单的 SystemC 进程,即 SC_METHOD。摘要:本节包含一个完整的简单设计,用于演示 SystemC 中模块和流程的使用。同样,重要的是要注意这不是典型的设计风格 - 但它很好而且易于理解。为什么要看模块和流程?

元直的博客 1867

UE5.3报错排查:从原理到实战的系统性解决方案

在实时渲染与游戏开发中,系统报错排查是保障项目稳定性的核心工程实践。其原理在于对错误信息进行结构化解码,通常涉及资源依赖、编译过程与运行时逻辑等层面。掌握系统化排查方法的技术价值在于,不仅能快速定位问题,更能深入理解引擎工作机制,从而提升开发效率与代码质量。常见的应用场景包括着色器编译失败、蓝图与C++交互异常、资源依赖缺失等。本文聚焦于UE5.3开发中的高频报错,如“着色器编译错误”和“空指针访问”,提供从信息解构到工具使用的完整实战框架,帮助开发者建立从被动修复到主动预防的系统性思维。

cuanji3287的博客 363

Vue - 解决报错 Error: Cannot find module ‘XXX\@vue\cli-service\bin\vue-cli-service.js‘(vue项目编译运行出错,启动失败)

vue报错,Error: Cannot find module ‘@vue\cli-service\bin\vue-cli-service.js‘,vue2项目启动报错,vue3项目运行出错,vue执行npm run serve报错,找不到@vue/cli-service模块,vue项目关于:Error: Cannot find module ‘D:\@vue\cli-service\bin\vue-cli-service.js‘的解决方案,vue项目跑不起来了,Vue项目报错,跑项目报错Cannot fi

🏆 前端领域优质创作者 3114

Android内存管理与性能优化面试真题

概念解析:LRU算法的⼯程实现 LruCache(Least Recently Used,最近最少使⽤)是Android提供的⼀个核⼼缓存⼯具类。它的设计理念⾮常符合⼈类直觉:“如果⼀个数据最近被访问过,那么它在未来被访问的概率12 / 38极⾼;反之,如果⼀个数据很久没被碰过,当内存吃紧时,它就应该第⼀个被踢出局。” 在源码层⾯,LruCache 的底层⼼脏是⼀个 LinkedHashMap。

qq_52269550的博客 689

YOLO26涨点改进 | 全网独家创新,注意力改进篇 | TGRS 2025 | 引入APCM自适应像素级协同机制,动态增强目标区域、抑制背景噪声,助力YOLO26做红外小目标检测有效涨点

本文提出一种基于APCM模块改进的YOLO26网络模型,通过像素级局部-全局协同建模动态增强目标特征并抑制背景噪声。APCM模块采用轻量化设计,无需复杂参数,在特征提取阶段有效提升目标区域特征表达质量,尤其适用于低对比度和复杂场景下的目标检测。实验表明,该方法能显著提高小目标检测精度和模型鲁棒性,同时保持较低计算开销。模块通过局部特征分支和全局特征分支的协同融合,实现了对红外小目标的有效增强,在NUAA-SIRST和IRSTD-1K数据集上性能优于现有方法。文章还提供了完整的代码实现和配置指南。

Ai缝合怪、助力小伙伴高效发论文 188

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Function call stack错误的解决方法

运行Resnet50 出现类似错误,提示Function call stack 一开始没搞懂,以为网络层出现了问题。尝试很多无果。 最后发现是batchsize太大了,导致内存不足。把batchsize调小成功解决问题。

Tesla_td的博客 3133

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

WinDbg查看没有正常显示的函数堆栈信息

工作中难免需要查看dump,现在VC2015对dump的解析已经非常好了,自动化程度做的也很不错,只要设置了symbol路径,则绝大部分dump都可以自动解析。但有的dump,函数堆栈不够清晰,默认情况下windbg/cv2015都无法准确解析,这里就需要windbg来手动处理了。处理步骤如下: 1. 问题示例。 0:025> knb  # ChildEBP RetAddr   WARNI

spacegrass的专栏 5181

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

在堆栈损坏的情况下,如何查找问题之一

很多的时候,由于数组越界导致,函数栈别损坏,导致无法查找bug,gcc提供了一个编译功能,  -finstrument-functions 在函数的进入跟出口出加入对 __cyg_profile_func_enter 及 __cyg_profile_func_exit 的调用。我们可以听过对这两个函数的调用,在堆中重建出一个栈,通过这个栈结构来查找问题的所在,下面是是示例代码 #includ

jc_liuworld的专栏 2136

gdb分析堆栈破坏实例

一、定位bug性质和范围 1、带符号分析dump $ gdb IMActivityServer.symbol core.32530 (gdb) bt #0 0x0000000000a6951a in ?? () #1 0x00000000018c6db8 in ?? () #2 0x8f127f1911ab2800 in ?? () #3 0x00000000018c6d00 in...

mergerly的专栏 6582

Linux:GDB 调试一些函数栈被毁坏的问题

Linux:GDB 调试一些函数栈被毁坏的问题

hhd1988的专栏 2817

windbg入门教程之异常报告深入解读

软件异常 异常,顾名思义是指不符合预期的时间发生,由应用程序和操作系统抛出的异常叫做软件异常。 当然我们均不希望发生这些软件异常,但在实际中却常常会遇到因为不良编码导致的异常,比如访问了空指针、访问非法地址、解析json字符串异常等等。 异常处理 为了应对异常,window系统提供了一些API用于获取异常、处理或者控制异常, 为了理解后续的异常信息,我们先了解三个关于异常的API函数: GetE...

Keep Moving~ 5556

windbg 如何判断pdb文件 没有加载成功

WARNING: Stack unwind information not available. Following frames may be wrong. 明显是你pdb不对,没有成功加载符号表。检查一下你的AppPlus.pdb与你的AppPlus.exe是否一致。

证券指标的专栏 3493
上一篇: C/C++中手动获取调用堆栈
下一篇: 在windows下编译mongodb c driver
study_live
博客等级 码龄19年 20粉丝 27原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值