coredump一般我们说是核心转储,就是在进程异常时的一个快照,保存了异常时的内存、寄存器、堆栈等数据。这些数据存储成一个文件,而且是一个ELF文件格式,可通过readelf读取查看。
在android系统上,一般程序在native或者art中异常后会在data/tombstones下生成对应的tombstone文件,这个文件一般已经包含了很多native程序的重要信息,结合symbol,我们会对异常原因有个初步判断,而且一般的简单问题可以有个初步分析结论。但对于较难的问题,仅有tombstone不足以判断异常原因,尤其是需要查看一些较多分支,对象地址数据没有在tombstone中输出时,我们就需要更多的寄存器和内存信息。这就需要抓起coredump来分析。
#define RLIM_INFINITY 0x7fffffffUL
......
221 rl.rlim_cur = RLIM_INFINITY;
222 rl.rlim_max = RLIM_INFINITY;
223 if (setrlimit(RLIMIT_CORE, &rl) < 0) {
//异常打印
}
上面即设置core文件大小不限。
在android中,不同平台调用时机有区别,以某平台调用时机为例,在zygote fork进程时执行,其时序如下:
//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
487static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
488 jint debug_flags, jobjectArray javaRlimits,
489 jlong permittedCapabilities, jlong effectiveCapabilities,
490 jint mount_external,
491 jstring java_se_info, jstring java_se_name,
492 bool is_system_server, jintArray fdsToClose,
493 jintArray fdsToIgnore,
494 jstring instructionSet, jstring dataDir) {
......
529 pid_t pid = fork();
531 if (pid == 0) {
......
//这里执行java方法
669 env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
670 is_system_server, instructionSet);
671 if (env->ExceptionCheck()) {
672 RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
673 }
674 } else if (pid > 0) {
.....
}
// gCallPostForkChildHooks对应的Java方法如下
832int register_com_android_internal_os_Zygote(JNIEnv* env) {
833 gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
834 gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
835 "(IZLjava/lang/String;)V");
callPostForkChildHooks
postForkChild(×××)
nativePostForkChild(); //Jni
ZygoteHooks_nativePostForkChild(); //native方法
EnableDebugFeatures
EnableDebugger()
setrlimit()
mkdir /data/core 0777 root root
write /proc/sys/kernel/core_pattern "data/core/%E.%p.%e"
即创建一个放置core的路径,且具有读写权限,然后设置了文件的名称,若不设置,则名称为core,可以用cat 查看,路径在可执行程序路径下。
对应某q公司,则设置了一个属性,persist.debug.trace,当其为1时,出发init执行上述两条命令:
device/q**/common/rootdir/etc/init.***.rc
on property:persist.debug.trace=1
mkdir /data/core 0777 root root
write /proc/sys/kernel/core_pattern "data/core/%E.%p.%e"
关于core文件名称的格式意义如下:
%p 出Core进程的PID
%u 出Core进程的UID
%s 造成Core的signal号
%t 出Core的时间,从1970-01-0100:00:00开始的秒数
%e 出core进程对应的可执行文件名
补充:
对于抓取fulldump话,需要增加一个步骤:adb shell echo 0x27 > /proc/pid/coredump_filter,进程重启需要重新执行
1278 snprintf(path,sizeof(path),"/proc/%d/coredump_filter",pid);
......
1283 if (is_full) {
1284 write(fd, "39", 2); /*0x27
1285 } else {
1286 write(fd, "35", 2); /*0x23
1287 }
这一步如果动态或者监控的话,可以通过hook执行。以某android厂商为例,hook了android的debuggerd进程的syscall,然后在发现进程正要处理的是SYS_tgkill信号时,就增加一个方法调用,之后在执行正常的syscall。
160extern "C" long hook_syscall(long number, ...) {
......
165 if (SYS_tgkill == number) {
166 int pid = va_arg(ap,int);
167 int tid = va_arg(ap,int);
168 int signal = va_arg(ap,int);
169 ****_native_debug_process(pid,tid);
170 ret = syscall(SYS_tgkill, pid, tid, signal);
//先在context初始化时hook了pthread_setname_np,在hook_pthread_setname_np执行时设置了syscall的hook函数hook_syscall
180extern "C" int hook_pthread_setname_np(pthread_t thread, const char* name) {
181 MILOGI("hook hook_pthread_setname_np name=%s", name);
182#if defined(__LP64__)
183 const char* sigsendersname = "debuggerd64:sig";
184#else
185 const char* sigsendersname = "debuggerd:sig";
186#endif
187 if (!strcmp(name,sigsendersname)) {
188 SoInfo si;
189 if (si.replace("syscall",(const uintptr_t)&hook_syscall)) {
android上有可能存在selinux的权限问题,可以将se关闭(adb shell setenforce 0),重启后需要重新关闭se。
对于已经启动的进程,rlimit不好更改,目前只能gdb attach到目标进程更改。
coredump一般通过gdb调试,同时需要准备对应版本的symbol文件一起调试。
1,启动gdb
./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-gdb(gdb路径)
或者prebuilts/gdb/linux-x86/bin/gdb
2,加载coredump
core-file coredump_file(coredump文件)
file
3. 设置symbol路径,即当前栈中用的可执行文件的symbol位置
file ~/symbols/out/target/product/项目名称/symbols/system/bin/app_process32 (一般app的话,是app_process,注意32位和64位的区别)
set solib-search-path symbols/system/lib/
4. 然后通过gdb命令查看,几个常用命令如下:
bt:查看对应当前线程栈
set arm force-mode thumb:汇编按照thumb命令解析
info r:查看当前帧寄存器
info thread:列出进程的所有线程
t num:切换到num线程
f num:切换到num帧
disassemble 0x9d73320c-0x30,+0x32:反汇编一段地址
x /10x 0x9d7331fc:按16进制,读取10个指定地址内存
p *(android::JavaBBinder*)0x8bb5c500:把指定地址按照指定数据类型解析数据;
set print pretty on:设置良好的阅读模式
directory ~/disk/androidO/:指定源码路径
其他命令不再列出
:/ # ulimit -a
-t: time(cpu-seconds) unlimited
-f: file(blocks) unlimited
-c: coredump(blocks) 0 //coredump大小为0,即关闭,可以通过ulimit -c size或者ulimit -c unlimited来指定size大小或者不限制大小
-d: data(KiB) unlimited
-s: stack(KiB) 8192
-l: lockedmem(KiB) 65536
-n: nofiles(descriptors) 32768
-p: processes 21499
-i: sigpending 21499
-q: msgqueue(bytes) 819200
-e: maxnice 40
-r: maxrtprio 0
-m: resident-set(KiB) unlimited
-v: address-space(KiB) unlimited
补充一篇不错的类似文章:https://blog.csdn.net/tenfyguo/article/details/8159176