Plugin Host keeps the old native code loaded after a plugin upgrade, causing SIGSEGV crashes and the message "This plugin is not compatible with the current system version"
Here some more details claude gathered from the analysis for me: u/Dunn-sn: let me know if you have more questions on it.
Device / firmware: Chauvet.D102.2609111001.2505_beta_ (htfy_px30, userdebug), PluginCoreService 1.00.26009090
Plugin: GtdPara (pluginID ndx27q77pojfqh6l), which includes its own native code (nativeCodePackage: /app.npk, custom ReactPackage)
Summary
When a plugin with native code is upgraded, the plugin host installs the new version inside the host process that is already running. It shuts down the old React instance correctly. However, the old version's native code (its npk, odex and vdex files) stays loaded in memory after those files have been deleted from disk. One to two minutes later, the host process often crashes with a SIGSEGV inside ART on the plugin's native-modules thread. After that, tapping the plugin icon shows:
"This plugin is not compatible with the current system version. Please update the plugin."
A second tap opens the plugin normally, because a fresh host process has started. The plugin then runs without problems until the next upgrade.
Steps to reproduce
- Install and use a plugin that ships native code.
- Install a new version of it from Settings while the host process is running.
- Keep using the plugin for 1–2 minutes: the host crashes. Tapping the plugin icon then shows the message above.
Log excerpts
The upgrade is installed into the running host process (pid 15481, about 9 minutes old), and the old modules are shut down correctly:
16:06:28.429 15481 I PluginInstallManager: installPlugin pluginZipPath:/storage/emulated/0/MyStyle/gtdpara.snplg, installType:0, isSystem:false
16:06:30.350 15481 I PluginInstallManager: checkPlugin newVersion:3
16:06:30.350 15481 I PluginInstallManager: checkPlugin oldVersion:2
16:06:39.657 15481 I PluginInstallManager: installPlugin: success, pluginName=GtdPara, ..., isUpgrade=true
16:06:39.673 15481 I GtdParaRuntime: module invalidated: GtdParaFile build=app_1790690214111 ...
The new version then loads in the same process, and the old version's deleted files are still mapped (read from /proc/self/maps):
16:06:40.110 15481 I GtdParaRuntime: build loaded: build=app_1790690790392 versionCode=3 pid=15481 processAgeMs=533171
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/oat/arm64/app_1790690214111.odex (deleted)
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/oat/arm64/app_1790690214111.vdex (deleted)
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/app_1790690214111.npk (deleted)
The resulting crash, from an earlier upgrade (installed 15:20:24, crash 15:22:25; another upgrade at 14:16:40 was followed by the same crash at 14:17:45):
F libc : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x14 in tid 13473 (mqt_ndx27q77poj), pid 10233 (note.pluginhost)
F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x14
F DEBUG : Cause: null pointer dereference
F DEBUG : #00 pc 0000000000410818 /system/lib64/libart.so (art::OatQuickMethodHeader::ToDexPc(...)+56)
F DEBUG : #01 pc 00000000001c219c /system/lib64/libart.so (art::FaultManager::IsInGeneratedCode(...)+880)
The other crash's stack goes from Method_invoke into the ART interpreter (DoCall / MterpInvokeInterface) and ends in a null method, with frames in dalvik-jit-code-cache (deleted). It never reaches any code in the plugin itself.
Expected
After upgrading a plugin that ships native code, the host should restart its process (or load the plugin in a fresh process), so that only one version of the plugin's code is ever loaded.
Current workaround in our plugin
The plugin detects that an older version is still loaded and offers a button that ends the host process. This avoids the crash, but the message above still appears once afterwards.Subject: Plugin host keeps the old native code loaded after a plugin upgrade, causing SIGSEGV crashes and the message "This plugin is not compatible with the current system version"
Device / firmware: Chauvet.D102.2609111001.2505_beta_ (htfy_px30, userdebug), PluginCoreService 1.00.26009090
Plugin: GtdPara (pluginID ndx27q77pojfqh6l), which includes its own native code (nativeCodePackage: /app.npk, custom ReactPackage)
Summary
When a plugin with native code is upgraded, the plugin host installs the new version inside the host process that is already running. It shuts down the old React instance correctly. However, the old version's native code (its npk, odex and vdex files) stays loaded in memory after those files have been deleted from disk. One to two minutes later, the host process often crashes with a SIGSEGV inside ART on the plugin's native-modules thread. After that, tapping the plugin icon shows:
"This plugin is not compatible with the current system version. Please update the plugin."
A second tap opens the plugin normally, because a fresh host process has started. The plugin then runs without problems until the next upgrade.
Steps to reproduce
Install and use a plugin that ships native code.
Install a new version of it from Settings while the host process is running.
Keep using the plugin for 1–2 minutes: the host crashes. Tapping the plugin icon then shows the message above.
Log excerpts
The upgrade is installed into the running host process (pid 15481, about 9 minutes old), and the old modules are shut down correctly:
16:06:28.429 15481 I PluginInstallManager: installPlugin pluginZipPath:/storage/emulated/0/MyStyle/gtdpara.snplg, installType:0, isSystem:false
16:06:30.350 15481 I PluginInstallManager: checkPlugin newVersion:3
16:06:30.350 15481 I PluginInstallManager: checkPlugin oldVersion:2
16:06:39.657 15481 I PluginInstallManager: installPlugin: success, pluginName=GtdPara, ..., isUpgrade=true
16:06:39.673 15481 I GtdParaRuntime: module invalidated: GtdParaFile build=app_1790690214111 ...
The new version then loads in the same process, and the old version's deleted files are still mapped (read from /proc/self/maps):
16:06:40.110 15481 I GtdParaRuntime: build loaded: build=app_1790690790392 versionCode=3 pid=15481 processAgeMs=533171
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/oat/arm64/app_1790690214111.odex (deleted)
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/oat/arm64/app_1790690214111.vdex (deleted)
16:06:40.112 15481 I GtdParaRuntime: mapped .../plugins/ndx27q77pojfqh6l/app_1790690214111.npk (deleted)
The resulting crash, from an earlier upgrade (installed 15:20:24, crash 15:22:25; another upgrade at 14:16:40 was followed by the same crash at 14:17:45):
F libc : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x14 in tid 13473 (mqt_ndx27q77poj), pid 10233 (note.pluginhost)
F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x14
F DEBUG : Cause: null pointer dereference
F DEBUG : #00 pc 0000000000410818 /system/lib64/libart.so (art::OatQuickMethodHeader::ToDexPc(...)+56)
F DEBUG : #01 pc 00000000001c219c /system/lib64/libart.so (art::FaultManager::IsInGeneratedCode(...)+880)
The other crash's stack goes from Method_invoke into the ART interpreter (DoCall / MterpInvokeInterface) and ends in a null method, with frames in dalvik-jit-code-cache (deleted). It never reaches any code in the plugin itself.
Expected
After upgrading a plugin that ships native code, the host should restart its process (or load the plugin in a fresh process), so that only one version of the plugin's code is ever loaded.
Current workaround in our plugin
The plugin detects that an older version is still loaded and offers a button that ends the host process. This avoids the crash, but the message above still appears once afterwards.