我目前无法调试一些依赖本机库的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
- 这怎么可能呢? / p>
答案 0 :(得分:10)
基本问题是Dalvik是一个安全点暂停VM,并使用“停止世界”垃圾收集。这意味着,要使GC运行,它必须等待所有线程到达可以确保它们不会改变堆的点。
由于某种原因,你的一个线程没有响应GC线程的暂停请求。它实际上并没有在本机代码中执行;如果是,则线程将处于NATIVE
状态,这被认为是安全的。 (对本机堆的所有访问都通过JNI调用进行门控,所有JNI调用都进行挂起检查。)
出于性能原因,JIT能够以跳过挂起检查的方式将编译代码块链接在一起。如果一个线程需要很长时间来挂起,挂起线程将“解锁”块,并等待一段时间。最终它开始抱怨,并最终 - 它最终放弃并中止VM。
有些设备使用vendor-modified version of Dalvik会出错,并且可能会在紧密循环中发生中止。在这种情况下,我不希望在堆栈顶部看到本机方法。
调试的最佳选择是在不满意的时候附加gdb并尝试弄清楚目标线程正在做什么。本机代码可能以某种方式破坏VM状态或返回堆栈,因此从本机代码返回时线程会被卡住。
编辑后更新: dvmPopFrame()
函数用于从托管堆栈弹出堆栈帧。当VM调用您的本机方法时,它会插入一个“中断”帧,这样当堆栈展开以进行异常处理时,VM不会通过调用站点。 (它也用于VM发出的托管代码方法调用,例如用于反射或<clinit>
。)消息PopFrame missed the break
表示未找到中断帧。
中断帧具有空方法指针。展开堆栈时,dvmPopFrame()会继续,只要它看到一个非空方法指针(意味着它不是一个中断帧)和一个非空前一帧指针(意味着你没有到达顶部)堆)。如果你点击堆栈顶部,你就错过了休息 - 所有Dalvik堆栈都以一个真正的方法开始(如果线程通过JNI连接到VM,有时候是“假的”方法)。
所以我的猜测是本机代码会破坏堆栈,使前一帧指针归零。将此排序的一种技术是让VM调用一个调用实际本机方法的本机方法; “中间人”在堆栈上分配一些东西,将其设置为已知值,调用实际方法,然后在返回之前验证其堆栈分配是否保持不变。
(可能需要使用值来阻止编译器优化它们;如果你使用类似的东西:
if (jniEnv == NULL) {
printf("my stuff is ...", ...);
}
然后它永远不会真正运行,因为JNIEnv*
永远不会为空......但编译器不知道这一点。)
有关Dalvik堆栈布局的完整说明,请参阅dalvik/vm/interp/Stack.h。
从本机代码返回时,线程在RUNNABLE
中是正常的。您的本机方法仍然位于顶部,因为弹出它的代码失败并中止了VM。