Debug调试之道:YunShellExtV164.dll导致的程序效率降低

本文分析了Qt程序打包后运行效率降低的问题,最终定位到百度网盘的YunShellExtV164.dll动态链接库是根本原因。通过多维度排查(GPU性能、程序日志、优化等级、CPU/内存情况等),发现该DLL会持续扫描系统进程并检测互斥体,导致资源被大量消耗。测试表明,卸载百度网盘后问题消失,重新安装后又复现。该问题凸显了第三方DLL注入可能对程序性能产生的严重影响,建议开发者注意此类潜在风险。

作者
WildPointer
发布
2026.02.27
专栏
Debug调试之道
阅读
约 15 分钟 / 849 次原文浏览
Debug调试之道:YunShellExtV164.dll导致的程序效率降低

前言

本篇博客主要讲解的是由于百度网盘自带的YunShellExtV164.dll动态链接库文件导致的程序效率降低问题,效率降低的程序是使用C++/Qt开发的PC端应用。重点讲解排查思路和使用到的工具,由于该Bug涉及的情况比较复杂,所以博主会尽量以通俗易懂的方式进行讲解。


Bug情况描述

在使用Enigma Virtual Box工具将Qt程序打包后,程序运行达到1~2小时会出现运行效率降低的问题,主要表现为程序处理帧数降低,并且资源管理器显示提交的内存有些许增多。以下为当时所排查的路径:

1.排查GPU情况:程序使用了OpenCL编写内核程序,耗时的数据运算会由CPU发送至GPU进行运算,于是排查了GPU的日志以及使用率等因素,主要涉及的工具有Procexp64,AMD Software,TechPowerUp GPU-Z,GPU_Caps_Viewer

2.排查程序日志:程序使用了OpenCV等第三方库进行图像处理,于是排查了第三方库导致的程序效率降低问题,主要涉及的工具有Trea,元宝

3.排查优化情况:程序在Intel的CPU环境中进行开发,测试机是AMD的CPU,于是排查了程序由于优化等级过高使用特定的指令集进行优化,主要涉及的工具有objdump,元宝,Notepad++

4.排查CPU情况:程序的界面刷新主要是使用CPU,于是排查了CPU的核心使用率以及温度等因素,主要涉及的工具有TechPowerUp GPU-Z,Intel(R) Extreme Tuning Utility

5.排查硬件情况:程序使用了USB和光纤等硬件进行数据传输,于是排查了I/O处理等因素,主要涉及的工具有Procexp64

6.排查内存分配情况:程序由于业务原因在接受数据时需要频繁分配内存,于是排查了Page Faults以及缺页导致的硬中断,主要涉及的工具有Procexp64,资源管理器,性能监视器

7.排查程序堆区:由于Bug发生时发生的问题还有提交的内存增多的问题,所以排查了程序的分配的堆情况等因素,主要涉及的工具有Visual Studio,WinDBG

8.排查打包的问题:由于是使用Enigma Virtual Box工具将Qt程序打包后才能复现该问题,所以排查了打包程序的问题,主要涉及的工具有Inno Setup Compiler

9.排查DLL情况:由于在测试的主机上没有该问题发生,于是排查了程序打包后前后所需的DLL等因素,主要涉及的工具有DependenciesGui,WinDBG

后续的小节将根据这些排查的顺序进行讲解,细节将在小节中进行描述,此处只作概述。如果读者对最终的排查方式和结果感兴趣的话可以直接移步至排查DLL情况这一小节,其他小节是博主在排查该问题时重点检查的方向,可以作为后续遇到Bug的参考。


排查GPU情况

由于开发机和测试机的硬件条件不同,在测试机中并没有复现出该Bug。在测试机中存在独立显卡和集成显卡,而Bug发生的开发机只存在一个显卡。并且数据处理操作主要使用GPU运算,而OpenCL内核程序在测试机中会根据数据大小和显存大小进行内存的合理调节的原因(在数据过大时会使用独立显卡,而数据量小的时候则使用集成显卡),所以按照经验重点排查了GPU的情况。以下是排查的步骤以及结论:
1.排查GPU性能参数:由于问题涉及到三个GPU,于是从TechPowerUp(一个专注于计算机硬件和技术的综合性网站)检索了这三个GPU的相关消息如下:

图1.开发机显卡参数信息

图2.测试机集成显卡参数信息

图3.测试机独立显卡参数信息

根据图1,图2和图3显示,我们主要在意的是Clock Speeds和Graphics Features两个参数。其中我们发现在Clock Speeds参数中测试机的两个显卡时钟频率是不一致的,可能会导致由于数据大小的原因程序进行内存的合理调节发生效率降低,所以当时还通过Windows的设备管理器分别关闭独立显卡和集成显卡来排查该问题,但是在测试机中并没有复现该Bug。最终排除了因为显卡的性能不同导致的效率降低的因素(当时关闭独立显卡还导致VTK调用显卡失败,导致程序运行闪退。后续打开独立显卡回复了正常)

2.根据图1,图2和图3中的Graphics Features参数显示,这三个显卡支持的最高驱动版本也不一致,分别为2.1和2.2,所以需要排查是否是OpenCL版本不一致导致的原因。这里我使用了GPU_Caps_Viewer分别查看了开发机和测试机的OpenCL版本,相关参数如下:

图4.OpenCL版本信息

根据图4显示,所有显卡的OpenCL的版本都为2.1,所以排除了由于OpenCL版本不一致导致的原因。并且基于之前的调试经验,程序由于开发的OpenCL内核程序原因,在版本较高的AMD驱动版本中会导致效率降低

3.在程序运行过程中,博主使用了Procexp64和TechPowerUp GPU-Z工具分别对程序的进程中的GPU使用率,温度等数据进行监控,排除了由于GPU使用率过高导致的分配不足和GPU温度过高导致的降频。如图5中显示的Dedicated GPU Memory(专用GPU内存)和Committed GPU Memory(已提交的GPU内存)分别为172.4 MB和242.0 MB,而GPU的Compute1的使用率为7%,而由于程序并没有使用GPU进行3D图形渲染,所以GPU Usage为0%。这些数据可以证明GPU并没有处于满载状态

图5.运行时GPU使用信息

具体的GPU温度以及风扇转速如图6所示,其中GPU Temperature为43.0℃,Fan Speed为15%,Memory Controller Load为3%,这些数据表明GPU温度和散热是正常的

图6.运行时GPU温度信息

4.为了防止监控信息异常和人为监控的疏忽,所以使用了AMD提供的AMD Software程序对GPU的信息进行监控,排除了人为监控疏忽导致的原因

图7.AMD Software监控

总结:在该小节中分别使用了Procexp64,AMD Software,TechPowerUp GPU-Z,GPU_Caps_Viewer工具进行排查,具体分析了OpenCL的版本,显卡时钟频率,GPU使用率以及GPU温度等因素导致的程序效率降低


排查程序日志

为了排查OpenCV等第三方库的函数调用耗时的增多,博主采用了插桩的方法将函数的耗时写入到程序的日志以此进行分析。为什么不采用qDebug()和std::cout等方式输出到Visual Studio的调试窗口呢?原因主要是因为是使用了Enigma Virtual Box工具将Qt程序打包后才能复现该Bug所以采用了写入日志的形式。具体的日志如下图:

图8.程序日志信息

从日志可见的数据进行判断,主要耗时来自可视化设置索引和折线图数据处理的后期的重负载,其次是折线图高斯模糊和热力图OpenCV处理的尾部抖动,这说明当前耗时增多不是单纯数据量因素,而是UI线程/CPU抖动与算法复杂度波动叠加导致,这是排查到目前为止比较可疑的地方。并且有经验表明在使用OpenCV中的resize()函数时,由于OpenCV中内部的多线程处理导致可能出现线程泄露,将其内部的线程数调整为1即可避免该问题,但是效率会降低,具体可以参考文章如下:你在用OpenCV做开发时遇到过哪些坑?https://www.zhihu.com/question/41864394/answer/1984913295396857721总结:在该小节中中分别使用了Trae,元宝等Ai工具对日志进行分析排查,得出结论可能是由于UI线程/CPU抖动与算法复杂度波动叠加导致的效率降低


排查优化情况

我们知道在使用Visual Studio开发程序时,Visual Studio会对项目进行代码优化,分别为以下四个优化等级:

优化选项等级主要目标与特点适用场景

禁用优化

/Od

关闭所有优化。编译最快,生成的代码最直接,便于调试和单步跟踪。

调试阶段

最大化速度

/O2

启用一系列旨在提升执行速度的优化技术(如内联、循环优化、寄存器分配等),可能增加代码大小。

默认选择

最小化大小

/O1

启用旨在减小代码体积的优化。通常会牺牲一些运行速度来换取更小的可执行文件。

对可执行文件大小有严格限制的场景

完全优化

/Ox

比 /O2更激进的速度优化(增加了某些更耗时的优化,如更积极的内联)。

对运行时性能有极致要求的场景,但编译时间可能更长

优先快速代码

/Ot

告诉编译器优先考虑速度(通常与 /O1或 /O2组合使用,是 /O2的默认行为)。

通常不单独使用,是速度优化策略的一部分

优先小型代码

/Os

告诉编译器优先考虑大小(通常与 /O1或 /O2组合使用,是 /O1的默认行为)。

通常不单独使用,是大小优化策略的一部分。

表1.Visual Studio优化等级

在不同的优化等级中会对项目进行不同的调整,如果读者使用过objdump分析目标文件则会发现有些情况自己编写的代码与编译成目标文件的执行过程不一致,这就是优化的结果,本篇文章不对此进行详细描述。我们使用objdump指令分析Visual Studio生成的obj目标文件即可得出代码编译后的汇编代码,分析汇编代码是否使用了特定的指令集进行优化导致效率问题,如图9所示是程序的目标文件分析生成的汇编代码:

图9.objdump输出的汇编代码

根据汇编代码分析得出并没有使用特定于intel优化的指令集,排除了因为过度优化程序导致效率降低的问题

总结:在该小节中objdump,元宝,Notepad++对输出的汇编代码所使用的指令集进行了排查,排除了过度优化导致程序率降低的问题


排查CPU情况

目前程序的界面刷新主要是使用CPU,GPU用于处理耗时重复的数据操作。在项目重构前会使用OpenCV中的cv::UMat类型将原本在CPU执行的图像处理迁移至GPU,后续发现对性能等影响在项目中微乎其微,所重构后调整为了cv::Mat类型,故此除去数据操作以外程序的大部分操作都是在CPU完成的。在排查CPU的情况时,博主使用了由Intel提供的Intel(R) Extreme Tuning Utility工具监控CPU的温度以及各个核心的使用情况,如图10所示:

图10.程序运行时CPU相关信息

根据图10和图6显示,CPU的温度能达到75℃到85℃,而活跃核心数为10,基本上每一个核心都是活跃的但使用率并没有超过负载,频率也是正常的并没有降频。但当前的温度范围是超过TechPowerUp官网所显示的温度的(官网显示80℃是合理的温度范围),简单理解为温度过高散热不足,但是并没有降频。所以目前这些数据还不能完全排除是CPU温度过高导致的

总结:该小节使用了TechPowerUp GPU-Z,Intel(R) Extreme Tuning Utility工具排查了CPU相关的信息,不能完全排除是由于CPU温度过高导致的


排查硬件情况

由于程序使用了USB和光纤等硬件进行数据传输,所以博主还排查了I/O处理等因素,是否是因为将数据存储到固态硬盘和存储到机械硬盘的区别,因为我们知道固态硬盘和机械硬盘的I/O处理速率是不一致的,在程序的高频率的I/O处理下固态硬盘的表现会由于机械硬盘。在程序运行过程中I/O处理信息如下:

图11.程序运行的相关信息

在图11中我们发现该进程自启动以来,累计发起了125907次读取请求和181170次 写入请求,其他操作次数为1996180次,而其他字节数增量为172.0 MB,这代表程序正在通过内存映射文件等方式传输的数据量。这些数据与我们的程序状况相符合,所以排除了因过度进行I/O操作导致的效率降低

        总结:该小节使用了Procexp64排查了程序的I/O操作导致效率降低的问题


排查内存分配情况

当前程序由于业务原因在接受数据时需要频繁分配内存,但是程序当前并没有引入内存池进行内存管理,并且由于Bug发生时在资源管理器中发现提交的内存变多了,如下图:

图12.资源管理器相关信息

而在图11中显示,经过长时间的运行Page Faults的次数是逐渐增多的。这会不会是因为缺页导致的效率问题?于是博主又在此基础上使用了性能监视器监控程序的软缺页情况,使用资源管理器监控硬中短的情况,性能监视器的信息如下图:

图13.性能监视器相关信息

如图13所示,CPU的处理器时间百分比为100%代表所有核心都在满负荷工作,磁盘时间百分比平均值为99.3%。这意味着磁盘在这段时间内持续饱和,而每秒页数曲线几乎紧贴0值平均值很低,几乎没有发生需要访问慢速磁盘的硬缺页错误,并且图12中也显示硬中断每秒为0。这表明CPU 任务不重,而磁盘I/O速度已经完全跟不上系统的数据请求需求,但是内存是足够的。

总结:该小节使用了Procexp64,资源管理器,性能监视器排查了程序内存分配的情况,但是得出的结论与排查硬件情况的结果有冲突。后续分析发现系统I/O的严重问题,正是由当前程序导致的,所以说当前程序在内存分配上存在问题会影响到程序的执行效率


排查程序堆区

        由于Bug发生时发生的问题还有提交的内存增多的问题,所以还使用Visual Studio和WinDBG排查了程序的分配的堆情况等因素。Visual Studio的堆分析如下图所示:

图14.Visual Studio堆分析

在经过长时间的运行后,发现直接在Visual Studio中运行程序是没有成功复现该Bug的。博主到这已经要崩溃了,打包前后是有差异的是不是打包程序的问题?而由于使用Enigma Virtual Box打包以后,使用DependenciesGui是分析不了程序所调用的动态链接库文件的,这是Enigma Virtual Box的特性,后续也会在排查打包问题这一小节中进行讲解。因为发生该Bug时程序并没有崩溃,不能主动生成Dump文件,于是博主采用了任务管理器手动的给运行中的程序生成Dump文件,并对比前后生成的Dump文件中关于堆区的内存分配,具体如下图:

图15.WinDBG堆分析

从图15中可以发现的确堆分配的内存变大了,并且资源管理器也能佐证这个观点,但是不好分析是程序的哪一个函数出现了内存泄露的问题,但是也能证明使用Enigma Virtual Box打包以后程序才出现了问题

总结:该小节使用了Visual Studio,WinDBG对出现的堆区进行了分析,得出使用Enigma Virtual Box打包以后程序才出现了问题,并且堆区分配的内存的确变大了,这是后续主要排查的原因


排查打包的问题

        由于是使用Enigma Virtual Box工具将Qt程序打包后才能复现该问题,所以博主还使用了Inno Setup Compiler对Qt程序进行打包比较,过程如下图:

图16.编写打包脚本

在使用Inno Setup Compiler打包程序以后,安装会把程序所需要的DLL解压出来,这不符合要求,博主更倾向于使用Enigma Virtual Box工具将程序打包成单独的可执行文件。但是使用Inno Setup Compiler打包程序并且运行以后并没有成功复现该Bug,这证明可能是Enigma Virtual Box工具的原因。

总结:该小节使用了Inno Setup Compiler打包程序,能确定的是经过Enigma Virtual Box工具打包Qt程序的时候发生了问题


排查DLL情况

由于在测试的主机上没有该问题发生,于是排查了程序打包后前后所需的DLL等因素。但是由于Enigma Virtual Box工具的原因导致将打包后的可执行文件导入DependenciesGui时发生崩溃,对此博主在这简单讲解一下。Enigma Virtual Box打包的原理是从根本上重写了程序的导入表结构,它会将所有依赖的文件通过虚拟化的方式并打包进单个可执行文件中,并替换程序的入口点,再对文件进行加密并封装,这样会导致大幅修改或完全替换原始程序的导入表,所以在我们将打包后的可执行文件导入DependenciesGui时发生崩溃。下图是使用DependenciesGui打开相同的程序,但是该程序未使用Enigma Virtual Box工具进行打包。

图17.DependenciesGui显示DLL信息

那打包以后的可执行文件要怎么知道导入的动态链接库是哪些呢?这使用博主使用了WinDBG,通过WinDBG绑定可执行文件的方式发现了运行可执行文件时导入了哪些动态链接库,具体如下图:

图18.WinDBG显示的加载信息

根据图18我们可以看到程序在运行的时候加载了一个来自百度网盘的动态链接库,名字为YunShellExtV164.dll。但是当前开发的程序并不会调用什么百度网盘的功能,也与百度网盘没有一丝关系,所以博主认为程序的卡顿与YunShellExtV164.dll有关。

总结:该小节使用了DependenciesGui和WinDBG,经过排查目前主要怀疑的是内存泄露,CPU温度过高和YunShellExtV164.dll


BUG复现

在怀疑是由于百度网盘的YunShellExtV164.dll导致时,博主便把百度网盘卸载了并把YunShellExtV164.dll也删除了,而且百度网盘在开发机上是存在的,而测试机是不存在的。但是这些证据并不是很让人信服,于是在删掉YunShellExtV164.dll以后博主也进行了长时间的压力测试,最终发现Bug已经没有重现的情况。并且由于是第一次见到该Bug,所以博主在卸载百度云盘以后,还重新安装回来尝试复现该Bug,在测试过程中发现安装回百度云盘以后,程序在运行时又加载了YunShellExtV164.dll,并且也重现了该Bug


YunShellExtV164.dll分析

关于YunShellExtV164.dll导致的程序效率降低和内存问题,可以查阅通过IDA反汇编的代码。代码如下:

cpp
pid_buffer = 0i64;    // 初始化进程ID缓冲区指针为零// 获取一个大小为1000的缓冲区,用于存储进程IDif ((unsigned _int8)get_buffer(&pid_buffer, 1000i64)){    // 进入无限循环,用于枚举进程    while ( 1 ){        cbNeeded = 0;  // 实际写入的字节数        pid_ptr = pid_buffer;  // 指向进程ID数组的指针                // 枚举系统进程,将进程ID列表写入pid_buffer        // 参数:缓冲区、缓冲区大小(每个进程ID为4字节,num_1000为1000)、实际写入字节数        if ( !K32EnumProcesses(pid_buffer, 4 * num_1000, &cbNeeded) )            break;  // 枚举失败则退出循环                // 检查是否已读取所有进程(实际需要的字节数小于缓冲区大小)        if ( cbNeeded < 4 * (int)num_1000 ){            // 计算实际得到的进程数量(每个进程ID为4字节,右移2位相当于除以4)            v6 = (unsigned _int64)cbNeeded >> 2;            if ( !v6 )                break;  // 如果没有进程,退出循环                        v7 = cbNeeded;  // 保存实际需要的字节数                        // 遍历每个进程ID            do{                // 将进程ID转换为字符串形式(可能是用于互斥体名称)                v8 = (LPCWSTR *)sub_207D9C00(&v14, *pid_ptr);                                // 尝试打开以进程ID命名的互斥体                // 0x1F0001u = MUTEX_ALL_ACCESS | SYNCHRONIZE                v9 = OpenMutexW(0x1F0001u, 0, *v8);                                // 对转换后的字符串进行引用计数减1操作                v10 = (_QWORD *)(v14 - 24);                if ( _InterlockedExchangeAdd((volatile signed _int32 *)(v14 - 24 + 16), 0xFFFFFFFF) <= 1 )                    (*(void (_fastcall **)(_QWORD))(*(_QWORD *)*v10 + 8i64))(*v10);                                // 如果成功打开互斥体                if ( v9 ){                    // 调用某个处理函数(可能是记录或处理找到的进程)                    sub_207D8BC0(v2, (unsigned int)&v11, 0, (_DWORD)pid_ptr, v7);                    CloseHandle(v9);  // 关闭互斥体句柄                }                                ++pid_ptr;  // 指向下一个进程ID                --v6;       // 减少剩余进程计数            }            while ( v6 );            goto LABEL_15;  // 跳转到循环外的某个标签        }    }}

这段代码是程序效率降低的根本原因,是由于百度网盘通过DLL注入技术,将自身模块中的YunShellExtV164.dll强行加载到系统关键进程中,并执行了类进程遍历和互斥体检测这些高频率、高开销的系统级操作,这相当于在系统心脏里安装了一个不停扫描所有房间的监控器,导致系统资源被持续、不必要地消耗。

24次原文点赞;这里的喜欢仅保存在本机
WildPointer

专注系统编程、工程实践与底层技术,记录 C++、Qt、OpenCV 与 VTK 的学习和实践。