我目前无法调试一些依赖本机库的Android代码.特别是一个本地调用似乎容易出现"旋转暂停"错误.它通常表现如下:
threadid=2: spin on suspend #2 threadid=48 (pcf=3)
到目前为止,我还没有确切地确定这里失败了什么,除了在大约10条消息之后,我的应用程序遇到了SIGSTKFLT
退出.每次,第一个线程是GC,第二个线程是当前正在执行本机代码的线程.与此消息一起打印的堆栈部分始终在堆栈顶部具有本机方法.
当Dalvik抱怨这件事时究竟发生了什么,我怎样才能开始调试原因以便我能解决它?
编辑:一个有趣的皱纹 - 在本机开发人员做了一些更改后,我现在也看到以下错误:
PopFrame missed the break VM aborting Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1)
对我来说,线程转储在堆栈顶部显示我的本机方法也是非常奇怪的,但是线程状态RUNNABLE
不是NATIVE
- 这怎么可能呢?
基本问题是Dalvik是一个安全点挂起VM,并使用"停止世界"垃圾收集.这意味着,要使GC运行,它必须等待所有线程到达可以确保它们不会改变堆的点.
由于某种原因,您的一个线程没有响应GC线程的暂停请求.它实际上并没有在本机代码中执行; 如果是的话,线程将处于NATIVE
状态,这被认为是安全的.(对本机堆的所有访问都通过JNI调用进行门控,并且所有JNI调用都会执行挂起检查.)
出于性能原因,JIT能够以跳过挂起检查的方式将编译代码块链接在一起.如果一个线程需要很长时间来挂起,挂起线程将"解锁"块,并等待一段时间.最终它开始抱怨,并最终 - 它最终放弃并中止VM.
有些设备使用供应商修改的Dalvik版本会出错,并且在紧密循环中可能会发生中止.在这种情况下,我不希望在堆栈顶部看到本机方法.
调试的最佳选择是在不满意的时候附加gdb并尝试弄清楚目标线程正在做什么.本机代码可能以某种方式破坏VM状态或返回堆栈,因此从本机代码返回时,线程会被卡住.
编辑后更新:该dvmPopFrame()
函数用于从托管堆栈弹出堆栈帧.当VM调用您的本机方法时,它会插入一个"中断"帧,这样当堆栈展开以进行异常处理时,VM不会通过调用站点.(它也用于VM发出的托管代码方法调用,例如用于反射或<clinit>
.)消息PopFrame missed the break
表示未找到中断帧.
中断帧具有空方法指针.展开堆栈时,只要dvmPopFrame()看到非空方法指针(意味着它不是中断帧)和非空前一帧指针(意味着你没有到达堆栈顶部),dvmPopFrame()就会继续.如果你点击堆栈顶部,你就错过了休息 - 所有Dalvik堆栈都以一个真正的方法开始(如果线程通过JNI连接到VM,有时候是"假"方法).
所以我的猜测是本机代码破坏了堆栈,使前一帧指针归零.将此排序的一种技术是让VM调用一个调用实际本机方法的本机方法; "中间人"在堆栈上分配一些东西,将其设置为已知值,调用实际方法,然后在返回之前验证其堆栈分配是否未更改.
(可能需要使用这些值来阻止编译器优化它们;如果使用类似的东西:
if (jniEnv == NULL) { printf("my stuff is ...", ...); }
然后它永远不会真正运行,因为它永远不会JNIEnv*
为空...但编译器不知道.)
有关Dalvik堆栈布局的完整说明,请参阅dalvik/vm/interp/Stack.h.
RUNNABLE
从本机代码返回时,线程是正常的.您的本机方法仍然位于顶部,因为弹出它的代码失败并中止了VM.