Android系统BUG:修改线程名目标错乱问题探究

背景

我们的APP在某次版本中 更新了腾讯地图SDK,更新之后发现在进入地图页面后,进程的native thread name被修改了。 进程名被修改为 nt-queue-291538

进入业务地图页面前,进程名为

进入业务地图页面后进程名变为 nt-queue-xxxx

问题分析

现象分析

经过反复验证,最终确认该问题的触发场景如下:

地图SDK中,这里第48行的eventQueue是一个线程,对线程调用了start()函数来启动线程。

由于我们引入的滴滴的booster插件,滴滴的booster会对所有线程的start函数做额外的插桩操作,在start函数之前,修改线程名,添加额外的类名信息,方便跟踪线程的创建点。修改后的字节码如下:

通过调试发现,这段业务逻辑会多次触发,并且每次eventQueue 线程都是同一个对象,正常情况下 对同一个线程多次调用start() 系统是会抛出异常的,但这里却能多次调用,这是因为SDK重写了该线程的start()函数,在内部判断线程是否已启动,如果已启动 就不会调用系统的start()函数。

这样的逻辑最终导致多次调用setName(),出现了 "对一个已经start的线程 调用修改线程名"的操作。

那这会有什么差异点呢?从setName的源码可以看出,如果目标线程是未启动完成的,则只会修改Java层的name属性,最后系统线程名会在native层线程真正启动后被修改。 而如果线程是已启动的(isAlive()为true),此时会额外直接调用setNativeName()进行线程名修改,因为此时底层的native thread已经存在了。

场景复现

typescript 复制代码
public class MainActivity extends Activity {
    private Thread bgThread;

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new FrameLayout(this));
        startBgThread();
        modifyBgThreadOnMainThread();
    }

    //创建一个子线程,并启动
    private void startBgThread(){
        bgThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        },"bgThread");
        bgThread.start();
    }

    //在主线程修改子线程的名称
    private void modifyBgThreadOnMainThread(){
        Handler bgHandler = new Handler(Looper.getMainLooper());
        bgHandler.post(new Runnable() {
            @Override
            public void run() {
                bgThread.setName("zxw");
            }
        });
    }
}

以上代码可以稳定复现该场景,通过调试发现,当对一个线程调用setName修改线程名时,如果该线程是已经启动的最终会影响的不是目标线程,而是当前线程 。 而此时由于当前执行环境在主线程 ,最终导致修改了主线程的系统线程名,主线程和进程是同一个线程,因此此时通过 /proc/${pid}/stat 获取线程名时发现进程名变化了

系统BUG原理解析

那么setNativeName 底层具体做了什么导致修改目标线程名时最终会影响的是当前线程呢?跟踪对应版本的系统源码

可以发现,SetThreadName 中最终是通过 pthread_setname_np修改线程名的,但这里修改线程名时传入的是 pthread_self() ,也就是当前线程自身 ,最终导致修改了错误的线程。

问题修复

从上节可以分析出,这明显是一个系统BUG,在跟踪了最新版本的aosp实现后,我们发现这个问题其实已经在 Android 15.0.0_r6 版本被修复了。变更逻辑如下

现在,调用art::SetThreadName时,会传入目标线程的pthread。

最终 调用pthread_setname_np时 会使用传入的pthread,从而修复了该问题。

其实从代码逻辑上来说这是一个很简单的BUG,但直到Android 15.0.0_r6 才被修复

结论

这个BUG只存在于 Android 15.0.0_r5 及更早的版本中

  • Android 15.0.0_r5 及之前:❌ 跨线程修改线程名时,如果目标线程已启动,则会错误地修改当前线程的线程名,而不是目标线程。
  • Android 15.0.0_r6 及之后:✅ 已修复,可以正确跨线程修改线程名

通常,我们修改线程名是在线程启动之前,或者是在目标线程上下文内部修改线程名,这样不会导致这个BUG出现。此次的问题发生原因是 三方地图SDK 重写了start()函数,又多次调用了start函数,导致滴滴的booster插件添加的setName逻辑也被多次触发,而此时调用setName的线程刚好是主线程,因此最终影响了 主进程名称

在同步腾讯地图SDK 避免多次调用start() 函数后,已验证该问题已修复。


在业务侧如果希望全局统一避免类似该问题的发生,可以做全局的插桩,在修改线程名时判断当下线程是否已启动,并且当前线程和目标线程是否不一致,如果不一致则不进行线程名修改,并做日志告警。

相关推荐
阿巴斯甜8 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker9 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952710 小时前
Andorid Google 登录接入文档
android
黄林晴11 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android