一、概念说明
在应用崩溃的时候,我们将会获取到两个信息:
- signal: 信号量,下文将会详细的说明不同的信号量及其含义。
- code: 错误码, 除了几个所有信号量(signal) 公共的错误码(code),一般不同信号量(signal)有特定的错误码(code),可以看做是信号量(signal)的补充说明。
二、信号量(signal) 和 错误码(code)说明
1. SIGILL (4)
ILL是 illegal instruction 非法指令。SIGILL 是当一个进程尝试执行一个非法指令时发送给它的信号。
常见原因有:
-
CPU架构不匹配;
-
so 文件被破坏;
-
代码段被破坏,如栈爆了、内存被踩;
-
主动崩溃,如 _builtintrap() 也会使用非法指令来实现。
si_addr 为出错的指令。
该信号量中常见的错误码说明:
Code | 说明 |
---|---|
ILL_ILLOPC | 非法的操作码(opcode) |
ILL_ILLOPN | 非法的操作数(operand) |
ILL_ILLADR | 非法的寻址模式 |
ILL_ILLTRP | 非法的trap |
ILL_PRVOPC | 特权操作码(Privileged opcode) |
ILL_PRVREG | 特权寄存器(Privileged register) |
ILL_COPROC | 协处理器错误 |
ILL_BADSTK | 内部堆栈错误 |
2. SIGTRAP (5)
gdb 调试设置断点等操作使用的信号。
Code | 说明 |
---|---|
TRAP_BRKPT | TRAP_BRKPT |
TRAP_TRACE | TRAP_TRACE |
TRAP_BRANCH | TRAP_BRANCH |
TRAP_HWBKPT | TRAP_HWBKPT |
3. SIGABRT (6)
ABRT是abort program的缩写。对应的数值为 6。该信号意味着异常退出;通常是调用abort(), raise(), kill(6), pthread_kill(6) 时候出现。当错误码为 SI_USER 时表示是被其它程序杀死,一般情况是由于ANR被 system_server 杀死;
其他错误码一般是业务自己调用 abort() 等函数退出,此时错误码一般认为无效。此类问题比较简单,自行对照abort的代码查找原因。
4. SIGBUS (7)
总线错误,意味着系统检测到硬件问题后发送给进程的信号。
通常该信号的产生不是因为硬件有物理上的损坏,而是代码实现有 bug 导致,如地址不对齐,或者不存在的物理地址等。地址不对齐可能是没有注意对齐,如果是概率问题,则可能是内存释放了、被踩了、导致计算偏移地址时候出错。
si_addr 为所访问的非法地址。
例如在android 9的libart里面就有这种BUG:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: PQ1A.190105.004/179:userdebug/test-keys'
Revision: '0'
ABI: 'arm64'
pid: 2147, tid: 2147, name: m.app.animation >>> com.fiill.app.animation <<<
signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0x656c62x0 0000006f90ee8648 x1 0000000041504e4d x2 0000007ff0cf5be8 x3 0000006f90668ed8x4 0000000012c12da6 x5 0000007ff0cf5c46 x6 6f00630015000000 x7 770067002e006d00x8 0000000000656c62 x9 0000006f90e4b700 x10 0000000000000032 x11 69006e0061002e00x12 6900740061006d00 x13 000000006e006f00 x14 0000000070ca1348 x15 0000000000003ebax16 0000006f90714140 x17 000000701265db30 x18 0000000000000000 x19 0000007ff0cf5be8x20 0000000041504e4d x21 0000006f90e2b6f8 x22 0000006f90e2b6f8 x23 0000007ff0cf6088x24 0000007ff0cf5c14 x25 00000070168895e0 x26 000000007090b600 x27 0000000071178cb0x28 0000000012c12c20 x29 0000007ff0cf5bd0sp 0000007ff0cf5bb0 lr 0000006f90558a30 pc 0000000000656c62backtrace:#00 pc 0000000000656c62 <unknown>#01 pc 0000000000473a2c /system/lib64/libart.so (art::RuntimeCallbacks::DdmPublishChunk(unsigned int, art::ArrayRef<unsigned char const> const&)+56)#02 pc 00000000003f0f14 /system/lib64/libart.so (art::DdmServer_nativeSendChunk(_JNIEnv*, _jclass*, int, _jbyteArray*, int, int)+356)#03 pc 000000000007fb74 /system/framework/arm64/boot-core-libart.oat (offset 0x79000) (org.apache.harmony.dalvik.ddmc.DdmServer.nativeSendChunk+196)#04 pc 000000000013e9bc /system/framework/arm64/boot-core-libart.oat (offset 0x79000) (org.apache.harmony.dalvik.ddmc.DdmServer.sendChunk+76)#05 pc 00000000007e92ac /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.ddm.DdmHandleAppName.sendAPNM+284)#06 pc 0000000000879a78 /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread.handleBindApplication+952)#07 pc 0000000000876034 /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread$H.handleMessage+6916)#08 pc 0000000000ab7724 /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.os.Handler.dispatchMessage+180)#09 pc 0000000000aba820 /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.os.Looper.loop+1264)#10 pc 0000000000882e28 /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread.main+664)#11 pc 0000000000554c4c /system/lib64/libart.so (art_quick_invoke_static_stub+604)04-24 23:22:04.599 2147 2155 E m.app.animatio: Weird, can't send JDWP process pid to ADB. Aborting connection.: Connection reset by peer
04-24 23:22:04.599 2147 2155 E m.app.animatio: Failed to setup adb connection.
该问题的解决patch:
---diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
old mode 100644
new mode 100755
index 4c2d4d7..ee9e92a
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -198,16 +198,28 @@art::Runtime::Current()->EndThreadBirth();}data->this_->RunPollLoop(self);
- int detach_result = art::Runtime::Current()->GetJavaVM()->DetachCurrentThread();
- CHECK_EQ(detach_result, 0);// Get rid of the connectiongState = nullptr;delete data->this_;+ int detach_result = art::Runtime::Current()->GetJavaVM()->DetachCurrentThread();
+ CHECK_EQ(detach_result, 0);
+return nullptr;}+AdbConnectionState::~AdbConnectionState() {
+ // Remove the startup callback.
+ LOG(ERROR) << "~AdbConnectionState(), RemoveDdmCallback";
+ art::Thread* self = art::Thread::Current();
+ if (self != nullptr) {
+ art::ScopedObjectAccess soa(self);
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveDebuggerControlCallback(&controller_);
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveDdmCallback(&ddm_callback_);
+ }
+}
+void AdbConnectionState::StartDebuggerThreads() {// First do all the final setup we need.CHECK_EQ(adb_write_event_fd_.get(), -1);
diff --git a/adbconnection/adbconnection.h b/adbconnection/adbconnection.h
old mode 100644
new mode 100755
index 04e39bf..702c810
--- a/adbconnection/adbconnection.h
+++ b/adbconnection/adbconnection.h
@@ -73,6 +73,7 @@class AdbConnectionState {public:explicit AdbConnectionState(const std::string& name);
+ ~AdbConnectionState();// Called on the listening thread to start dealing with new input. thr is used to attach the new// thread to the runtime.
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
old mode 100644
new mode 100755
index 758917c..b1cb6ae
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -24,8 +24,15 @@#include "monitor.h"#include "thread.h"+#include "thread-current-inl.h"
+#include "base/mutex-inl.h"
+namespace art {+RuntimeCallbacks::RuntimeCallbacks()
+ : callback_lock_(new ReaderWriterMutex("Runtime callbacks lock",
+ kInstrumentEntrypointsLock)) {}
+template <typename T>ALWAYS_INLINEstatic inline void Remove(T* cb, std::vector<T*>* data) {
@@ -36,14 +43,17 @@}void RuntimeCallbacks::AddDdmCallback(DdmCallback* cb) {
+ WriterMutexLock mu(Thread::Current(), *callback_lock_);ddm_callbacks_.push_back(cb);}void RuntimeCallbacks::RemoveDdmCallback(DdmCallback* cb) {
+ WriterMutexLock mu(Thread::Current(), *callback_lock_);Remove(cb, &ddm_callbacks_);}void RuntimeCallbacks::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
+ ReaderMutexLock mu(Thread::Current(), *callback_lock_);for (DdmCallback* cb : ddm_callbacks_) {cb->DdmPublishChunk(type, data);}
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
old mode 100644
new mode 100755
index 9f0410d..5252d21
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -138,6 +138,8 @@class RuntimeCallbacks {public:
+ RuntimeCallbacks();
+void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);@@ -231,6 +233,8 @@REQUIRES_SHARED(Locks::mutator_lock_);private:
+ std::unique_ptr<ReaderWriterMutex> callback_lock_ ; //ACQUIRED_AFTER(mutator_lock_)
+std::vector<ThreadLifecycleCallback*> thread_callbacks_GUARDED_BY(Locks::mutator_lock_);std::vector<ClassLoadCallback*> class_callbacks_
@@ -246,7 +250,7 @@std::vector<MethodInspectionCallback*> method_inspection_callbacks_GUARDED_BY(Locks::mutator_lock_);std::vector<DdmCallback*> ddm_callbacks_
- GUARDED_BY(Locks::mutator_lock_);
+ GUARDED_BY(callback_lock_);std::vector<DebuggerControlCallback*> debugger_control_callbacks_GUARDED_BY(Locks::mutator_lock_);};
该信号量中常见的错误码说明:
Code | 说明 |
---|---|
BUS_ADRALN | 访问的地址不对齐。32位处理器一般要求指针是4字节对齐的 |
BUS_ADRERR | 访问不存在的物理地址。一般是由于 mmap 的文件发生 truncated 导致。常见于文件访问过程中,被删除或者替换;或 mmap 到内存后,继续向文件写入且导致文件 truncated,再读取时就会出现该错误;另外, mmap 且访问超过文件实际大小的空间时,也可能会出现该错误 |
BUS_OBJERR | 特定对象的硬件错误 |
BUS_MCEERR_AR | BUS_MCEERR_AR |
BUS_MCEERR_AQ | BUS_MCEERR_AQ |
5. SIGFPE (8)
FPE是floating-point exception(浮点异常)的首字母缩写拼接而成。
该信号一般是算术运算相关问题导致的。比较少见,略。
si_addr 为失败的指令。
该信号量中常见的错误码说明:
Code | 说明 |
---|---|
FPE_INTDIV | 整数除以 0 |
FPE_INTOVF | 整数溢出 |
FPE_FLTDIV | 浮点数除以 0 |
FPE_FLTOVF | 浮点数向上溢出 |
FPE_FLTUND | 浮点数向下溢出 |
FPE_FLTRES | 浮点数结果不精确 |
FPE_FLTINV | 无效的浮点运算 |
FPE_FLTSUB | 下标超出范围 |
6. SIGSEGV (11)
SEGV 是 segmentation violation 的缩写。该信号意味着一个进程执行了一个无效的内存引用,或发生段错误。这是最常见的地址错误。
si_addr 为所访问的无效地址。
这个地址可能是被踩、空指针、释放后再引用等内存错误。常见的是空指针解引用。
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR) Cause: null pointer dereference
该信号量中常见的错误码说明:
Code | 说明 |
---|---|
SEGV_MAPERR | 地址不在 /proc/self/map 映射中 |
SEGV_ACCERR | 没有访问权限 |
7. SIGPIPE(13)
管道错误。通常在进程间通信产生。比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止仍继续往管道写,写进程就会收到 SIGPIPE 信号。
有一种情况,例如控制台被测试工具调用dumpstate重定向(到电脑端)了。重定向到的位置(电脑端测试工具)发生了异常,各个模块有标准输出的时候就会出现EPIPE信号。如果接收信号模块没有拦截EPIPE信号,就会被关闭。通常出现该问题时候,JAVA部分会打印PIPE,调用栈通常是println()或者iostream()
01-31 12:40:06.547 723 5127 W FastPrintWriter: java.io.IOException: write failed: EPIPE (Broken pipe)
01-31 12:40:06.547 723 5127 W FastPrintWriter: Caused by: android.system.ErrnoException: write failed: EPIPE (Broken pipe)
01-31 12:40:06.717 723 9335 W FastPrintWriter: java.io.IOException: write failed: EPIPE (Broken pipe)
01-31 12:40:06.717 723 9335 W FastPrintWriter: Caused by: android.system.ErrnoException: write failed: EPIPE (Broken pipe)
8. SIGTERM (15)
TERM 是 Termination 的缩写。与 SIGKILL (signal 9) 类似,不过 SIGKILL 不可捕获,而 SIGTERM 可被捕获。一般常见于 APP 处于后台时,被系统 vold 进程使用 SIGTERM 杀死,该情形基本没有意义,可忽略。另外,chromium 多进程架构中,经常也会使用 SIGTERM 杀死出现了异常的子进程。
9. SIGSTKFLT (16)
STKFLT是stack fault 的缩写。
按照官方文档说明,该信号量意味着协处理器栈故障。
根据网上的部分问题结论说明,在内存耗尽时,一般 malloc 返回 NULL 且设置 errno 为 ENOMEM,但有些系统可能会使用 SIGSTKFLT 信号代替。
10. SIGSYS (31)
通常是因为无效的 linux 内核系统调用而产生。在 android O (android 8.0) 中,部分不安全的系统调用被移除,若代码中仍然使用它们,则会出现 SIGSYS。
三,其他还有不在tombstone的信号描述
SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号。
SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
SIGSTOP
暂停(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl+Z)发出这个信号
SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行。Unix环境下,当一个进程以后台形式启动,但尝试去读写控制台终端时,将会触发SIGTTIN(读)和SIGTTOU(写)信号量,接着,进程将会暂停(linux默认情况下),read/write将会返回错误。这个时候,shell将会发送通知给用户,提醒用户切换此进程为前台进程,以便继续执行。由后台切换至前台的方式是fg命令,前台转为后台则为CTRL+Z快捷键。
那么问题来了,如何才能在不把进程切换至前台的情况下,读写控制器不会被暂停?答案:只要忽略SIGTTIN和SIGTTOU信号量即可:signal(SIGTTOU, SIG_IGN)。
stty stop/-stop命令是用于设置收到SIGTTOU信号量后是否执行暂停,因为有些系统的默认行为不一致,比如mac是默认忽略,而linux是默认启用。stty -a可以查看当前tty的配置参数。
SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到。具体见上面SIGTTIN
SIGURG
SIGURG, urgent, 紧急的。有”紧急”数据或out-of-band数据到达socket时产生.
SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
SIGWINCH
Windows Change, 窗口大小改变时发出.
SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.
SIGPWR
Power failure。
四,其他
- 在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP。
- 不能恢复至默认动作的信号有:SIGILL,SIGTRAP。
- 默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ。
- 默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM。
- 默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU。
- 默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH。
- 此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。