首先感谢帮我找出问题的大佬,挂一个他的网站 βlog

Anyshare网盘导致GCC编译时随机出现错误!!!

错误信息类似如下几条:

[build] g++.exe: fatal error: cannot execute 'C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/14.1.0/cc1plus.exe': CreateProcess: No such file or directory
[build] compilation terminated.
[build] gcc.exe: fatal error: cannot execute 'C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/14.1.0/cc1.exe': CreateProcess: No such file or directory
[build] compilation terminated.
[build] make (e=5) Access Denied
[build] make (e=5) 拒绝访问

或者在编译某个文件时卡死不动

以上错误几种错误会随机出现,在编译同一个文件时,即使使用完全相同的命令,不同的时间也会有不同的结果,有可能成功编译,也有可能报错。
但对于大型项目,使用cmake或者make等进行批量编译时,每个文件都有可能出现报错,导致大型项目需要重复编译许多次才能成功,极大地影响效率。
Anyshare网盘的技术被广泛应用于各大高校的校内网盘,包括北大网盘

先说结论:

Anyshare网盘使用类似于病毒的DLL注入Hook技术,实现快捷的共享文件下载,即双击文件时自动判断文件是否需要从网盘下载,然后再打开文件。这种方式需要注入到系统底层的DLL,影响Windows API的调用,使得gcc, g++在调用其他进程时(如cc1plus),被Anyshare拦截,随机地出现调用失败,导致无法编译。
这里是Anyshare自己对原理的解释

解决方法:卸载Anyshare相关的网盘

向北京大学计算中心反馈后得到回复: https://bbs.pku.edu.cn/v2/post-read-single.php?bid=668&postid=27924635

这是由于Anyshare网盘在后台运行了一个服务,解决方法可以关闭该服务。也可以按照Anyshare的方法修改配置文件,将Hook设置为最小化模式

详细信息

运行环境

版本 Windows 11 家庭版,64 位操作系统, 基于 x64 的处理器
版本号 23H2
操作系统版本 22631.4317
体验 Windows Feature Experience Pack 1000.22700.1041.0

编译器:(以下版本的编译器均测试过,都会出错)(测试时系统中仅存在一个版本的GCC)

  1. MinGW 版本 GCC 14.2.0 win32-seh-msvcrt-rt_v12-rev0
  2. MinGW 版本 GCC 13.2.0
  3. MinGW 版本 GCC 11.2.0 win32-seh-rt_v9-rev1
  4. MSYS2 ucrt64 版本 GCC 14.1.0

项目环境:使用VSCode, 测试过cmake-3.31.0-rc2-windows-x86_64,以及VSCode中官方CMake插件中的CMake,均会出现错误

系统中安装有北大网盘2.0,但是在编译构建过程中并没有运行该软件,检查发现其也没有打开后台进程。
根据其安装目录信息推测Anyshare版本可能为:

InnoSetupVersion=6.0.0
AppId=eisoo_anyshare3.5_Setup_AppId
AppVersion=1.0.0.0
AppMutex=Local\ShareClient3.5_Mutex
OutputBaseFilename=PRODUCT_SHORT_NAME_x64-2011
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64

排查过程

最早在VSCode中使用CMake插件build出现找不到cc1plus.exe,我并没有关注,因为发现重新构建几次即可成功。到后来文件越来越多,导致构建成功需要的次数越来越多,忍无可忍决心查一下错误。
经过手动使用CMake Configure以及手动使用make并打开verbose打开信息,最终定位到是在类似如下命令时会随机出现报错:

g++  @xxx/includes_CXX.rsp -g -std=gnu++20 -MD -MT xxx/xxx.cpp.obj -MF xxx\xxx.cpp.obj.d -o xxx\xxx.cpp.obj -c xxx\xxx.cpp --verbose

经过简化,并且在项目以外的文件新建简单的测试文件,发现如下命令也会出错:

g++ -o xxx.cpp.obj -c xxx.cpp

并且测试过发现当文件名使用绝对路径时,并且路径更长时,出现找不到cc1plus.exe错误的概率更大,随即怀疑是受到了Windows路径长度的限制,跟着网上的教程打开了Windows的长路径模式,但并没有解决。
我又测试了并行编译和单线程编译,结果两者均有概率报错。

我们查看了MinGW的Github页面的Issue,并在这里找到了与我们类似的问题:https://github.com/niXman/mingw-builds-binaries/issues/14 (甚至最后还保留着我发的部分)
跟随这个Issue,找到了他们在MinGW开发团队网站下留下的thread:https://gcc.gnu.org/pipermail/gcc-help/2022-September/141889.html
他们在这个thread下与开发者互相喷了半天,开发团队认为这是Windows的特性,而Issue提出者认为这是MinGW的Bug。注意到Thread里有提到GCC存在Bug使得即使不是CreateProcess: No such file or directory的错误也报错为该错误。
于是我们也怀疑这是MinGW的BUG,并试图用gdb直接对g++.exe进行调试,读取汇编代码,尝试找到是否真的是CreateProcess函数在报错,无果。之后打算切换为MSYS2版本的GCC进行尝试。

在切换至MSYS2版本的GCC后,并没有解决问题,错误依然随机出现。但我们发现使用单线程进行编译出现错误的概率会比并行编译出错概率小得多,随即怀疑是并行编译出现了冲突的问题。
在VSCode的插件CMake中,点击构建按钮会根据你的CPU核数量自动设置并行任务数进行并行编译,命令形式如下:(注意到后面有个-j 18意思是18个并行任务)

"D:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" --build e:/myproject/build --config Debug --target all -j 18 --

经过网上搜查,我们还怀疑是Windows Defender将g++的某些行为拦截导致的,于是分别测试了开启Defender所有保护,关闭Defender的保护但保留Defender进程,关闭所有Defender相关进程三种情况。
经过大量测试之后,最终发现出错情况与Defender没有相关性。并且即使只使用单线程编译大量文件,虽然概率小但也有可能报错,故暂时放弃多线程矛盾的想法。

后来,我们使用微软的工具Process Monitor,该工具可以查看一段时间内,系统中所有的Windows API调用记录。
使用这个工具记录出错的构建过程,经过长时间的检查后,发现了奇怪的内容:
Process Monitor截图
这个路径是我的北大网盘的安装目录。他在g++进行编译时,莫名其妙使得g++调用了这个路径,因为觉得其很奇怪,故将其卸载。

卸载之后,重新构建整个项目,速度快了1倍,并且多次测试发现不会出现任何错误。

以上所有过程花了我一整周时间

错误出现可能的原理

根据Anyshare的介绍,网盘为了实现功能:对共享的文件双击时,自动识别其是否已经从网盘中下载到本地,如果没有下载则现在下载,再进行双击后的打开操作。
这个功能需要使用DLL注入功能:
AnyShare的DLL注入

  1. AnyShare 向一个正在运行的系统进程注入 winhook.dll(winhook64.dll);
  2. AnyShare 通过设置 HOOK 对某个进程或窗口进行监视,即:对特定事件“挂钩”。一旦预定义特定事件发生-----双击文件,Windows 操作系统即会向钩子Hook发送通知消息;
  3. AnyShare 响应该消息-----下载文件,响应之后再将该消息返回到Windows 操作系统消息队列中。
  4. Windows 操作系统就会继续处理该消息-----打开文件。

说明:HOOK 类似于一个强盗,一旦捕获到特定事件响应处理后,也可以自己吞掉该消息,不返回到消息队列中。

当我们的g++.exe使用Win API CreatProcess调用cc1plus.exe时,触发了AnyShare的winhook注入,于是g++.exe被迫执行AnyShare实现的winhook64.dll中的代码。AnyShare可能会在此时检查该文件是否为共享,是否需要从网上下载等操作,最终执行正常CreateProcess该做的事,然后将信息返回给原来的程序g++.exe。但winhook64.dll出了差错,导致其没有或者返回了错误地信息,让g++.exe以为自己没有找到cc1plus.exe,从而导致了该错误。
AnyShare的sharetool

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐