飘易手里有一个CEF定制浏览器的项目,版本采用了32位,用户需要使用这个定制的浏览器访问小红书创作服务平台,之前一切顺利,可以正常操作发布笔记、视频等。但最近,用户反馈说操作的过程中页面会出现白屏,而且出现概率极高。
飘易跟踪了下问题,发现主要是小红书创作服务平台采用了WASM技术栈,Chromium内核中,WebAssembly (Wasm) 是默认开启的。由于 32 位环境内存地址空间有限(仅 2GB 或 4GB),小红书等重度使用 Wasm 的网站极易触发 Out of Memory (OOM) 错误,导致渲染进程崩溃,表现为“白屏”。
为什么会白屏?
1、32 位虚拟内存瓶颈:32 位进程的寻址空间非常紧张。Wasm 通常需要预分配一块连续的巨大内存段(Memory Object)。在碎片化的 32 位内存中,经常无法找到足够大的连续空间,导致分配失败。
2、GPU 加速冲突:在某些旧机器或 32 位环境下,GPU 加速可能不稳。

解决方向有2个:
1、切换Chromium内核到64位版本,64位版本下Wasm可分配的内存大幅增加,OOM白屏的概率大幅降低,极低的概率才会出现。
2、如果不能切换到64位,只能使用32位版本的Chromium内核的情况下,可以采取以下措施。
我们查看32位的Chromium日志,可以发现:
failed to asynchronously prepare wasm: RangeError: WebAssembly.instantiate(): Out of memory: wasm memory! [0207/113502.395:ERROR:gpu_init.cc(446)] Passthrough is not supported, GL is disabled, ANGLE is ...
这个日志一般表示在尝试启动硬件加速时失败了。GL is disabled 意味着图形库未能加载,导致浏览器不得不回退到软件渲染或直接导致渲染进程在初始化阶段不稳定。这通常是“Render Gone”最常见的诱因。
日志显示页面正在加载大量复杂的 JS 库(如 onnxruntime, zeus-engine, echarts 等相关资源) :
【资源密集型】:网页加载了 ort-wasm-simd-threaded.jsep.wasm 等 WebAssembly 模块 。这些模块对内存和计算资源要求极高。
【现象】:如果你的程序是 32 位(x86),加载此类重度资源极易触发 OOM (Out Of Memory),导致渲染进程被操作系统强制终止。
至此,我们知道底层的原因了,因为小红书创作服务平台改版后大量采用WebAssembly模块,有些用于采集视频的封面等,而这些模块对于32位Chromium内核浏览器来说,非常不友好;从侧面来看,可能小红书开发团队也没有充分测试32位版本的浏览器,或者甚至就是放任不管的态度。但小红书作为一个比较知名的大厂,不兼容32位版本的浏览器,我还是有点怀疑的,具体啥原因,飘易也不得而知。
如果项目由于历史原因或其他原因,只能使用32位版本的话,我们有2种方案可以考虑:
1、可以禁用 WebAssembly模块。
2、不禁用WebAssembly模块,但扩展32位版本的可用虚拟地址 LargeAddressAware。
默认情况下,32 位 Windows 进程只能使用 2GB 内存。开启这个标志位后,可以让进程在 64 位系统上运行时获得 4GB 空间(在 32 位系统上开启 3GB 开关后可获 3GB)。
操作方法: 将你的exe执行程序路径填入,使用 PowerShell 执行(或者VS自带的 editbin 工具 也行):
param([string]$FilePath)
if (-not (Test-Path $FilePath)) {
Write-Error "文件不存在: $FilePath"
return
}
try {
$stream = [System.IO.File]::Open($FilePath, 'Open', 'ReadWrite')
# 1. 转到 PE 头部偏移量指针
$stream.Seek(0x3C, [System.IO.SeekOrigin]::Begin) | Out-Null
$buffer = New-Object byte[] 4
$stream.Read($buffer, 0, 4) | Out-Null
# 2. 计算 PE 头部起始位置
$peOffset = [System.BitConverter]::ToUInt32($buffer, 0)
# 3. 定位到 Characteristics 标志位 (PE偏移 + 22字节)
$stream.Seek($peOffset + 22, [System.IO.SeekOrigin]::Begin) | Out-Null
$currentHeader = $stream.ReadByte()
# 4. 写入修改后的标志位
$stream.Seek(-1, [System.IO.SeekOrigin]::Current) | Out-Null
$stream.WriteByte($currentHeader -bor 0x20)
$stream.Close()
Write-Host "成功!已修改: $FilePath" -ForegroundColor Green
}
catch {
Write-Error "处理失败: $_"
}代码核心操作:
读取并修改标志位 (The Surgery) 0x20 在二进制中代表 IMAGE_FILE_LARGE_ADDRESS_AWARE 这个开关。
这个操作确保该开关被“打开”,而不管其他开关(如是否为只读、是否为系统文件等)的状态。
回写:将修改后的字节写回到文件的同一个位置。
效果: 为 Chromium 腾出更多的虚拟地址空间,极大降低 Wasm 申请不到连续内存的概率。
打开 VS 2022 开发人员命令提示符(或VS 2019):
dumpbin /headers "Piaoyi.ORG_Xhs.exe" | findstr "Application can handle >2GB addresses"
返回:Application can handle large (>2GB) addresses 即开启成功!
注意:除了你的主程序exe外,还有 Chromium 内核的渲染进程 BrowserSubprocess.exe 也需要修改开启 LargeAddressAware。
对于小红书来说,禁用 WebAssembly 模块,可能不能采集封面,但是对于整体发布笔记的流程影响不大,这种情况下,我们直接禁用 WebAssembly模块。
32 位环境的内存地址空间(Address Space)确实无法支撑这类连续的大内存申请,禁用 WebAssembly (Wasm) 可以强制前端回退到纯 JavaScript 模式,从而大幅降低内存峰值。
第1步,Chromium 内核启动参数里增加:
// 1. 禁用 WebAssembly 执行
// 这是最直接的开关
settings.CefCommandLineArgs.Add("js-flags", "--no-expose-wasm");
// 2. 备用辅助参数(如果上面的不生效,增加这个强制禁用翻译器)
// settings.CefCommandLineArgs.Add("js-flags", "--no-wasm-runtime");
// 3. 既然是 32 位环境,建议配合以下内存限制参数使用
settings.CefCommandLineArgs.Add("js-flags", "--max-old-space_size=1024"); // 限制 JS 堆内存
// 4. 再次提醒:既然禁用了 Wasm,建议把 GPU 也关掉,减少地址空间占用
settings.CefCommandLineArgs.Add("disable-gpu", "1");
settings.CefCommandLineArgs.Add("disable-gpu-compositing", "1");第2步,为了防止偷跑,我们继续增加一个托底方案,js注入脚本:
Frame.ExecuteJavaScriptAsync(@"
if (typeof WebAssembly !== 'undefined') {
WebAssembly = undefined;
}
");
以上2种方案可供大家参考,如有更多问题,欢迎探讨。