用什么命令查看.so 在内存中的基地址,NDK crash tool logcat.so

本帖子已过去太久远了,不再提供回复功能。11743人阅读
Tools(8)
kdump是系统崩溃的时候,用来转储运行内存的一个工具。
系统一旦崩溃,内核就没法正常工作了,这个时候将由kdump提供一个用于捕获当前运行信息的内核,
该内核会将此时内存中的所有运行状态和数据信息收集到一个dump core文件中以便之后分析崩溃原因。
一旦内存信息收集完成,可以让系统将自动重启。
kdump是RHEL5之后才支持的,2006被主线接收为内核的一部分。它的原理简单来说是在内存中保留一块
区域,这块区域用来存放capture kernel,当production kernel发生crash的时候,通过kexec把保留区域的
capure kernel给运行起来,再由捕获内核负责把产品内核的完整信息 - 包括CPU寄存器、堆栈数据等转储
到指定位置的文件中。
kexec是kdump机制的关键,包含两部分:
内核空间的系统调用kexec_load。负责在生产内核启动时将捕获内核加载到指定地址。
用户空间的工具kexec-tools。将捕获内核的地址传递给生产内核,从而在系统崩溃的时候找到捕获内核的地址并运行。
kdump是一种基于kexec的内核崩溃转储机制。当系统崩溃时,kdump使用kexec启动到第二个内核。第二个内核通常
叫做捕获内核,以很小内存启动以捕获转储镜像。第一个内核保留了内存的一部分给第二个内核启动使用。
由于kdump利用kexec启动捕获内核,绕过了BIOS,所以第一个内核的内存得以保留。这是内存崩溃转储的本质。
捕获内核启动后,会像一般内核一样,去运行为它创建的ramdisk上的init程序。而各种转储机制都可以事先在init中实现。
为了在生产内核崩溃时能顺利启动捕获内核,捕获内核以及它的ramdisk是事先放到生产内核的内存中的。
生产内核的内存是通过/proc/vmcore这个文件交给捕获内核的。为了生成它,用户工具在生产内核中分析出内存的使用和
分布等情况,然后把这些信息综合起来生成一个ELF头文件保存起来。捕获内核被引导时会被同时传递这个ELF文件头的
地址,通过分析它,捕获内核就可以生成出/proc/vmcore。有了/proc/vmcore这个文件,捕获内核的ramdisk中的脚本就
可以通过通常的文件读写和网络来实现各种策略了。
RHEL5开始,kexec-tools是默认安装的。
如果需要调试kdump生成的vmcore文件,需要手动安装kernel-debuginfo包。
(1) 预留内存
可以修改内核引导参数,为启动捕获内核预留指定内存。
在/etc/grub.conf (一般为/boot/grub/grub.conf的软链接)中:
,Y是为kdump捕获内核保留的内存,X是保留部分内存的起始位置。
默认为crashkernel=auto,可自行设定如crashkernel=256M。
(2) 配置文件
配置文件为/etc/kdump.conf,以下是几个常用配置:
# path /var/crash
默认的vmcore存放目录为/var/crash/%HOST-%DATE/,包括两个文件:vmcore和vmcore-dmesg.txt
# ssh &user@service&
will copy /proc/vmcore to &user@server&:&path&/%HOST-%DATE/ via SSH
make sure user has necessary write permissions on server.
自动拷贝到远程机器上。
# default &reboot | halt | poweroff | shell | mount_root_run_init&
Actions to perform in case dumping to intended target fails.
转储失败时执行。
(3) 启动服务
# chkconfig kdump on // 开机启动
# service kdump status // start、stop、restart等
(4) 功能验证
Magic System request&key is a magical key combo you can hit which the kernel will respond to regardless
of whatever else it is doing, unless it is completely locked up.
使用sysrq需要编译选项CONFIG_MAGIC_SYSRQ的支持。详细信息可看documentation/sysrq.txt。
故意让系统崩溃,来测试kdump是否正常工作。
# echo c & /proc/sysrq-trigger
Will perform a system crash by a NULL pointer dereference.
A crash dump will be taken if configured.
Magic SysRq还有一些很有趣的值,有的具有很大的破环性,输出在/var/log/messages:
f:call oom_kill to kill a memory hog process. 执行oom killer。
l:shows a stack backtrace for all active CPUs. 打印出所有CPU的stack backtrace。
m:dump current memory info. 打印出内存使用信息。
p:dump the current registers and flags. 打印出所在CPU的寄存器信息。
(5) 捕获内核
捕获内核是一个未压缩的ELF映像文件,查看捕获内核是否加载到内存中:
# cat /sys/kernel/kexec_crash_loaded
缩小捕获内核占用的内存:
# echo N & /sys/kernel/kexec_crash_size
当系统崩溃时,通过kdump可以获得当时的内存转储文件vmcore,但是该如何分析vmcore呢?
crash是一个用于分析内核转储文件的工具,一般和kdump搭配使用。
使用crash时,要求调试内核vmlinux在编译时带有-g选项,即带有调试信息。
如果没有指定vmcore,则默认使用实时系统的内存来分析。
值得一提的是,crash也可以用来分析实时的系统内存,是一个很强大的调试工具。
crash使用gdb作为内部引擎,语法类似于gdb,命令的使用说明可以用&cmd& help来查看。
使用crash需要安装crash工具包和内核调试信息包:
kernel-debuginfo-common
kernel-debuginfo
Analyze Linux crash dump data or a live system.
crash [OPTION] NAMELIST MEMORY-IMAGE&&&&& (dumpfile form)
crash [OPTION] [NAMELIST]&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& (live system form)
使用crash来调试vmcore,至少需要两个参数:
NAMELIST:未压缩的内核映像文件vmlinux,默认位于/usr/lib/debug/lib/modules/$(uname -r)/vmlinux,由
内核调试信息包提供。
MEMORY-IMAGE:内存转储文件vmcore,默认位于/var/crash/%HOST-%DATE/vmcore,由kdump生成。
例如:# crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux&& /var/crash/%HOST-%DATE/vmcore
(1) 错误类型
首先可以在vmcore-dmesg.txt中先查看错误类型,如:
1. divide error: 0000 [#1] SMP,除数为0造成内核崩溃,由1号CPU触发。
2. BUG: unable to handle kernel NULL pointer dereference at 012c,引用空指针。
这样一来就能知道引发内核崩溃的错误类型。
(2) 错误地点
RIP为造成内核崩溃的指令,Call Trace为函数调用栈,通过RIP和Call Trace可以确定函数的调用路径,以及在
哪个函数中的哪条指令引发了错误。
例如RIP为:[&ffffffff812cdb54&] ? tcp_enter_loss+0x1d3/0x23b
[&ffffffff812cdb54&]是指令在内存中的虚拟地址。
tcp_enter_loss是函数名(symbol)。
0x1d3是这条指令相对于tcp_enter_loss入口的偏移,0x23b是函数编译成机器码后的长度。
这样一来就能确定在哪个函数中引发了错误,以及错误的大概位置。
Call Trace为函数的调用栈,是从下往上看的。可以用来分析函数的调用关系。
(3) crash基本输出
# crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux&& /var/crash/%HOST-%DATE/vmcore
KERNEL: /usr/lib/debug/lib/modules/2.6.32-358.el6.x86_64/vmlinux
DUMPFILE: vmcore
[PARTIAL DUMP]
DATE: Fri Sep 19 16:47:01 2014
UPTIME: 7 days, 06:37:46
LOAD AVERAGE: 0.19, 0.05, 0.01
TASKS: 282
NODENAME: localhost.localdomain
RELEASE: 2.6.32-358.el6.x86_64
VERSION: #1 SMP Tue Oct 29 10:18:21 CST 2013
MACHINE: x86_64
(1999 Mhz)
MEMORY: 48 GB
PANIC: &Oops: 0002 [#1] SMP & (check log for details)
COMMAND: &swapper&
TASK: ffffffff81a8d020
[THREAD_INFO: ffffffff81a00000]
STATE: TASK_RUNNING (PANIC)
这些基本输出信息简单明了,可由sys命令触发。
(4) crash常用命令
bt:打印函数调用栈,displays a task's kernel-stack backtrace,可以指定进程号bt &pid&。
log:打印系统消息缓冲区,displays the kernel log_buf contents,如log | tail -n 30。
ps:显示进程的状态,&表示活跃的进程,如ps | grep RU。
sys:显示系统概况。
kmem -i:显示内存使用信息。
dis &addr&:对给定地址进行反汇编。
exception RIP即为造成错误的指令。
关于log命令:
内核首先把消息打印到内核态的ring buffer,用户态的klogd负责读取并转发给syslogd,让它记录到磁盘。
在内核崩溃时,可能无法把消息记录到磁盘,但是ring buffer中一般会有记录。所以log命令有时候能查看
到系统日志中所缺失的信息。
(5) 结构体和变量
查看结构体中所有成员的值,例如:
# ps | grep RU
ffffffff81a8d020
# struct task_struct ffffffff81a8d020
struct task_struct {
state = 0,
stack = 0xffffffff81a00000,
counter = 2
flags = 2097408,
显示整个结构体的定义:
# struct task_struct
struct task_struct {
显示整个结构体的定义,以及每个成员的偏移:
# struct -o task_struct
struct task_struct {
[8] void *
[16] atomic_
显示结构体中的成员定义,以及它的偏移:
# struct task_struct.pid
struct task_struct {
[1192] pid_
显示结构体中成员的值:
# struct task_struct.pid ffffffff81a8d020
查看全局变量的值:
# p sysctl_tcp_rmem
sysctl_tcp_rmem = $4 =
查看percpu全局变量(加前缀per_cpu_):
# p per_cpu__irq_stat
PER-CPU DATA TYPE:
irq_cpustat_t per_cpu__irq_ // 变量类型的声明
PER-CPU ADDRESSES:
[0]: ffff // 0号CPU对应变量的地址
查看0号CPU对应变量的值:
# struct irq_cpustat_t ffff
struct irq_cpustat_t {
__softirq_pending = 0,
__nmi_count = 4780195,
irq0_irqs = 148,
(6) 反汇编和源码行
# dis ffffffffa021ba91 // 反汇编一条指令
# dis -l probe_ 10 // 反汇编从某个地址开始的10条指令
对于内核中的符号:
# sym tcp_v4_do_rcv // 通过symbol,显示虚拟地址和源码位置
# sym ffffffff // 通过虚拟地址,显示symbol和源码位置
对于模块中的符号:
需要先加载相应的模块进来,才能显示符号对应的源码:
# mod // 查看模块
# mod -s module /path/to/module.ko // 加载模块
# sym symbol // 显示符号对应的模块源码,也可以用virtual address
(7) 修改内存
提供动态的修改运行中内核的功能,以供调试,但是RHEL和CentOS上不允许。
wr:modifies the contents of memory.
wr [-u | -k | -p] [-8 | -16 | -32 | -64] [address | symbol] value
使用例子:
# p sysctl_tcp_timestamps
sysctl_tcp_timestamps = $3 = 1
# wr sysctl_tcp_timestamps 0
wr: cannot write to /dev/crash!
我勒个擦,/dev/crash的文件属性是rw,但是crash_fops中并没有提供写函数,所以还是只读的。
这个功能很有用,但被RHEL和CentOS禁止了,所以如需动态修改运行内核还是用systemtap吧。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:976823次
积分:10320
积分:10320
排名:第1277名
原创:150篇
评论:196条
阅读:34381
阅读:23712
文章:11篇
阅读:58934
文章:84篇
阅读:512588
小程序员一个,坐标深圳。
Email: zhangskd
不总在,多包涵
(2)(6)(5)(3)(5)(5)(2)(3)(2)(4)(3)(5)(8)(3)(6)(1)(7)(12)(1)(1)(8)(4)(4)(5)(3)(2)(1)(1)(4)(2)(5)(2)(4)(8)(1)(5)(2)(4)(2)(5)(2)(1)运用DDMS中的native heap 检查Android native 内存泄露 - Android当前位置:& &&&运用DDMS中的native heap 检查Android native 内存运用DDMS中的native heap 检查Android native 内存泄露&&网友分享于:&&浏览:1256次使用DDMS中的native heap 检查Android native 内存泄露
一、 手机端准备工作
1、 安装库文件
内存分配函数
( malloc ,
calloc , etc.)
Android 的 libc
库中。为了跟踪堆内存的分配,需要使用这个库的特别版本,可以将每次内存开销记录下来。
这些特殊版本的
libc_malloc_debug_leak.so
libc_malloc_debug_qemu.so
可以在手机中的/system/lib 下查看是否有这两个库 )
只包含在英文或者用户调试版的 Android中。 如果手机是这两种系统之一,可以跳过下一步。
①下载最接近你手机模型和 Android系统版本( 2.3 、 4.0 等)的 CyanogenMod ROM
。如果没有恰好是你手机的对应版本,那么选择处理器相同的
( 比如 Tegra2)
和 Android系统版本(例如 2.3 )都相同的 rom. 。然后从 system/lib
抽出 libc_malloc_debug_leak.so
和 libc_malloc_debug_qemu.so 。
②以中兴 U880 手机为例,手机本身是移动定制的 Android2.2 系统, CyanogenMod
官网上恰有中兴的 rom,但是系统版本为 2.3.7 ,并且手机需要 root 权限,所以使用 U880 刷机工具,刷U880_2.3.7 root 版 rom 。
2、 替换库
①从 CM 版 rom 中 /system/lib 文件夹中抽出
libc_malloc_debug_leak.so
和 libc_malloc_debug_qemu.so
②把 libc_malloc_debug_leak.so
libc_malloc_debug_qemu.so
通过 USB拷到
SDCARD卡根目录下。
③手机中安装 BusyBox
以便使用 cp
命令复制文件。
④PC端命令行中执行
不同手机返回的值不同,如下两例
/dev/block/mmcblk0p1 /system ext3 ro,noatime,errors=continue,data=ordered 0 0
中兴 U880 :
/dev/block/ mtdblock11
ro,relatime,barrier=1,data=ordered 0 0
第一部手机将
/dev/block/mmcblk0p1
作为设备,
作为文件系统类型。
第二部手机将 /dev/block/ mtdblock11
作为设备, yaffs2
作为文件系统类型。
⑤使用上面得到的设备名,和文件系统类型,重新挂载系统分区为读写格式。如下所示:
mount -o remount,rw -t ext3 /dev/block/mmcblk0p1 /system
中兴 U880 :
mount -o remount,rw -t
/dev/block/ mtdblock11
⑥ 把两个库文件从S DCARD 拷到 /system/lib
cp /sdcard/libc_malloc_debug_leak.so /system/lib/libc_malloc_debug_leak.so
cp /sdcard/libc_malloc_debug_qemu.so /system/lib/libc_malloc_debug_qemu.so
⑦ 设置权限
chmod 0644 /system/lib/libc_malloc_debug_leak.so
chmod 0644 /system/lib/libc_malloc_debug_qemu.so
① 替换完新的库文件后,告知系统使用新的库分配内存。
②命令行中执行
setprop libc.debug.malloc 1
支持的参数:
- perform leak detection
- fill allocated memory to detect overruns
10 - fill memory and add sentinels to detect overruns
20 - use special instrumented malloc/free routines for the emulator
③ 重启框架 ,命令行中继续执行
如果命令成功,设备将在 1、 2 秒后重启, 注意并不是完全重启。
④ 检查配置
将会得到很多信息,其中包括:
[libc.debug.malloc]: [1]
说明配置成功
二、PC设置
在C:\Documents and Settings\Administrator\.android\ ddms.cfg 文件末尾添加
native=true
三、检查泄露
从SDK\tools 中启动独立的 DDMS ( ddms.bat )可以看见 native heap 选项卡
点击snapshot current... 按钮就可以了,如果按下±按钮,在点击 snapshot current
可以比较两次之间新分配了哪些空间。
有人说使用自己用源码编译出来DDMS 和模拟器,下面的 stacktrace 中的 File
列就有值了。我们用的是真机啊。。。没有关系 ~~
找到自己的库函数***.so
后面的 Method 列中的地址。使用 NDK 中的 addr2line 工具,
用法:在命令行中addr2line.exe -e
***\obj\local\armeabi\***.so
注意,一定要是obj 底下的 .so , lib 里面的是不含有符号表的。另外 Method 中的地址一般要将高 3 位置 0 , 80cc6e22 变为 000c6e22 。 80dxxxxx 的怎么变自己领悟吧。。。
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有主题 : 如何定位Android NDK开发中遇到的错误
级别: 新手上路
可可豆: 42 CB
威望: 32 点
在线时间: 1(时)
发自: Web Page
来源于&&分类
如何定位Android NDK开发中遇到的错误&&&
本帖被 偶尔e网事 执行加亮操作()
Android NDK是什么,为什么我们要用NDK?Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序。NDK包括了:从C / C++生成原生代码库所需要的工具和build files。将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files,即.apk文件)中。支持所有未来Android平台的一些列原生系统头文件和库为何要用到NDK?概括来说主要分为以下几种情况:代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。Android JNI是什么?和NDK是什么关系?Java Native Interface(JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI是本地编程接口,它使得在 Java 虚拟机(VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++和汇编语言)编写的应用程序和库进行交互操作。简单来说,可以认为NDK就是能够方便快捷开发.so文件的工具。JNI的过程比较复杂,生成.so需要大量操作,而NDK就是简化了这个过程。NDK的异常会不会导致程序Crash,NDK的常见的有哪些类型异常?NDK编译生成的.so文件作为程序的一部分,在运行发生异常时同样会造成程序崩溃。不同于Java代码异常造成的程序崩溃,在NDK的异常发生时,程序在Android设备上都会立即退出,即通常所说的闪退,而不会弹出“程序xxx无响应,是否立即关闭”之类的提示框。NDK是使用C/C++来进行开发的,熟悉C/C++的程序员都知道,指针和内存管理是最重要也是最容易出问题的地方,稍有不慎就会遇到诸如内存无效访问、无效对象、内存泄露、堆栈溢出等常见的问题,最后都是同一个结果:程序崩溃。例如我们常说的空指针错误,就是当一个内存指针被置为空(NULL)之后再次对其进行访问;另外一个经常出现的错误是,在程序的某个位置释放了某个内存空间,而后在程序的其他位置试图访问该内存地址,这就会产生一个无效地址错误。常见的错误类型如下:初始化错误访问错误数组索引访问越界指针对象访问越界访问空指针对象访问无效指针对象迭代器访问越界内存泄露参数错误堆栈溢出类型转换错误数字除0错误NDK错误发生时,我们能拿到什么信息?利用Android NDK开发本地应用的时候,几乎所有的程序员都遇到过程序崩溃的问题,但它的崩溃会在logcat中打印一堆看起来类似天书的堆栈信息,让人举足无措。单靠添加一行行的打印信息来定位错误代码做在的行数,无疑是一件令人崩溃的事情。在网上搜索“Android NDK崩溃”,可以搜索到很多文章来介绍如何通过Android提供的工具来查找和定位NDK的错误,但大都晦涩难懂。下面以一个实际的例子来说明,首先生成一个错误,然后演示如何通过两种不同的方法,来定位错误的函数名和代码行。首先,看我们在hello-jni程序的代码中做了什么(有关如何创建或导入工程,此处略),看下图:在JNI_OnLoad()的函数中,即so加载时,调用willCrash()函数,而在willCrash()函数中, std::string的这种赋值方法会产生一个空指针错误。这样,在hello-jni程序加载时就会闪退。我们记一下这两个行数:在61行调用了willCrash()函数;在69行发生了崩溃。下面来看看发生崩溃(闪退)时系统打印的logcat日志:[plain]   *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***   Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/:user/test-keys'   pid: 32607, tid: 32607, name: xample.hellojni  &&& com.example.hellojni &&&   signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0;      r0 0; r1 beb123a8  r2 0; r3 0;      r4 5d635f68  r5 5cdc; r6 41efcb18  r7 5d62df44       r8   r9 0; sl 0; fp beb1238c       ip 5d635f7c  sp beb1; lr 5d62ddec  pc 400e; cpsr 0;   backtrace:       #00  pc 0; /system/lib/libc.so        #01  pc 00; /data/app-lib/com.example.hellojni-2/libhello-jni.so       #02  pc   /data/app-lib/com.example.hellojni-2/libhello-jni.so       #03  pc 00; /data/app-lib/com.example.hellojni-2/libhello-jni.so       #04  pc 0; /data/app-lib/com.example.hellojni-2/libhello-jni.so       #05  pc   /system/lib/libdvm.so       #06  pc 0; /system/lib/libdvm.so       #07  pc   /system/lib/libdvm.so       #08  pc 0002b7fc  /system/lib/libdvm.so       #09  pc 00; /system/lib/libdvm.so       #10  pc 0006100b  /system/lib/libdvm.so       #11  pc 0006c6eb  /system/lib/libdvm.so       #12  pc 00067a1f  /system/lib/libdvm.so       #13  pc   /system/lib/libdvm.so       #14  pc 0002b7fc  /system/lib/libdvm.so       #15  pc 0; /system/lib/libdvm.so       #16  pc 0006912d  /system/lib/libdvm.so       #17  pc   /system/lib/libdvm.so       #18  pc 0002b7fc  /system/lib/libdvm.so       #19  pc 00; /system/lib/libdvm.so       #20  pc 00; /system/lib/libdvm.so       #21  pc 0; /system/lib/libandroid_runtime.so       #22  pc 0004e1bd  /system/lib/libandroid_runtime.so       #23  pc 0; /system/bin/app_process       #24  pc 0; /system/lib/libc.so       #25  pc 0; /system/bin/app_process    stack:            beb1;               beb1; 0;             beb1; 0;             beb1234c  beb123c0  [stack]   ……  如果你看过logcat打印的NDK错误时的日志就会知道,我省略了后面很多的内容,很多人看到这么多密密麻麻的日志就已经头晕脑胀了,即使是很多资深的Android开发者,在面对NDK日志时也大都默默的选择了无视。“符号化”NDK错误信息的方法其实,只要你细心的查看,再配合Google 提供的工具,完全可以快速的准确定位出错的代码位置,这个工作我们称之为“符号化”。需要注意的是,如果要对NDK错误进行符号化的工作,需要保留编译过程中产生的包含符号表的so文件,这些文件一般保存在$PROJECT_PATH/obj/local/目录下。第一种方法:ndk-stack这个命令行工具包含在NDK工具的安装目录,和ndk-build和其他一些常用的NDK命令放在一起,比如在我的电脑上,其位置是/android-ndk-r9d/ndk-stack。根据Google官方文档,NDK从r6版本开始提供ndk-stack命令,如果你用的之前的版本,建议还是尽快升级至最新的版本。使用ndk –stack命令也有两种方式使用ndk-stack实时分析日志在运行程序的同时,使用adb获取logcat日志,并通过管道符输出给ndk-stack,同时需要指定包含符号表的so文件位置;如果你的程序包含了多种CPU架构,在这里需求根据错误发生时的手机CPU类型,选择不同的CPU架构目录,如:[plain]   adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi  当崩溃发生时,会得到如下的信息:[plain]   ********** Crash dump: **********  Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/:user/test-keys'  pid: 32607, tid: 32607, name: xample.hellojni  &&& com.example.hellojni &&&  signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0; Stack frame #00  pc 0; /system/lib/libc.so (strlen+72)  Stack frame #01  pc 00; /data/app-lib/com.example.hellojni-2/libhello-jni.so (std::char_traits&char&::length(char const*)+20): Routine std::char_traits&char&::length(char const*) at /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/char_traits.h:229  Stack frame #02  pc   /data/app-lib/com.example.hellojni-2/libhello-jni.so (std::basic_string&char, std::char_traits&char&, std::allocator&char& &::basic_string(char const*, std::allocator&char& const&)+44): Routine basic_string at /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/_string.c:639  Stack frame #03  pc 00; /data/app-lib/com.example.hellojni-2/libhello-jni.so (willCrash()+68): Routine willCrash() at /home/testin/hello-jni/jni/hello-jni.cpp:69  Stack frame #04  pc 0; /data/app-lib/com.example.hellojni-2/libhello-jni.so (JNI_OnLoad+20): Routine JNI_OnLoad at /home/testin/hello-jni/jni/hello-jni.cpp:61  Stack frame #05  pc   /system/lib/libdvm.so (dvmLoadNativeCode(char const*, Object*, char**)+516)  Stack frame #06  pc 0; /system/lib/libdvm.so  Stack frame #07  pc   /system/lib/libdvm.so  Stack frame #08  pc 0002b7fc  /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+180)  Stack frame #09  pc 00; /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+272)  ……(后面略)  我们重点看一下#03和#04,这两行都是在我们自己生成的libhello-jni.so中的报错信息,那么会发现如下关键信息:[plain]   #03 (willCrash()+68): Routine willCrash() at /home/testin/hello-jni/jni/hello-jni.cpp:69  #04 (JNI_OnLoad+20): Routine JNI_OnLoad at /home/testin/hello-jni/jni/hello-jni.cpp:61  回想一下我们的代码,在JNI_OnLoad()函数中(第61行),我们调用了willCrash()函数;在willCrash()函数中(第69行),我们制造了一个错误。这些信息都被准确无误的提取了出来!是不是非常简单?先获取日志,再使用ndk-stack分析这种方法其实和上面的方法没有什么大的区别,仅仅是logcat日志获取的方式不同。可以在程序运行的过程中将logcat日志保存到一个文件,甚至可以在崩溃发生时,快速的将logcat日志保存起来,然后再进行分析,比上面的方法稍微灵活一点,而且日志可以留待以后继续分析。[plain]   adb shell logcat & 1.log  ndk-stack -sym $PROJECT_PATH/obj/local/armeabi –dump 1.log  第二种方法:使用addr2line和objdump命令这个方法适用于那些,不满足于上述ndk-stack的简单用法,而喜欢刨根问底的程序员们,这两个方法可以揭示ndk-stack命令的工作原理是什么,尽管用起来稍微麻烦一点,但是可以满足一下程序员的好奇心。先简单说一下这两个命令,在绝大部分的linux发行版本中都能找到他们,如果你的操作系统是linux,而你测试手机使用的是Intel x86系列,那么你使用系统中自带的命令就可以了。然而,如果仅仅是这样,那么绝大多数人要绝望了,因为恰恰大部分开发者使用的是Windows,而手机很有可能是armeabi系列。别急,在NDK中自带了适用于各个操作系统和CPU架构的工具链,其中就包含了这两个命令,只不过名字稍有变化,你可以在NDK目录的toolchains目录下找到他们。以我的Mac电脑为例,如果我要找的是适用于armeabi架构的工具,那么他们分别为arm-linux-androideabi-addr2line和arm-linux-androideabi-objdump;位置在下面目录中,后续介绍中将省略此位置:[plain]   /Developer/android_sdk/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/  假设你的电脑是windows, CPU架构为mips,那么你要的工具可能包含在这个目录中:[plain]   D:\ android-ndk-r9d\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64\bin\  好了言归正传,如何使用这两个工具,下面具体介绍:1. 找到日志中的关键函数指针其实很简单,就是找到backtrace信息中,属于我们自己的so文件报错的行。首先要找到backtrace信息,有的手机会明确打印一行backtrace(比如我们这次使用的手机),那么这一行下面的一系列以“#两位数字 pc”开头的行就是backtrace信息了。有时可能有的手机并不会打印一行backtrace,那么只要找到一段以“#两位数字 pc ”开头的行,就可以了。其次要找到属于自己的so文件报错的行,这就比较简单了。找到这些行之后,记下这些行中的函数地址2. 使用addr2line查找代码位置执行如下的命令,多个指针地址可以在一个命令中带入,以空格隔开即可[plain]   arm-linux-androideabi-addr2line –e obj/local/armeabi/libhello-jni.so 00; 00;0; 结果如下[plain]   /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/char_traits.h:229  /android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/_string.c:639  /WordSpaces/hello-jni/jni/hello-jni.cpp:69  /WordSpaces hello-jni/jni/hello-jni.cpp:6  从addr2line的结果就能看到,我们拿到了我们自己的错误代码的调用关系和行数,在hello-jni.cpp的69行和61行(另外两行因为使用的是标准函数,可以忽略掉),结果和ndk-stack是一致的,说明ndk-stack也是通过addr2line来获取代码位置的。3. 使用objdump获取函数信息通过addr2line命令,其实我们已经找到了我们代码中出错的位置,已经可以帮助程序员定位问题所在了。但是,这个方法只能获取代码行数,并没有显示函数信息,显得不那么“完美”,对于追求极致的程序员来说,这当然是不够的。下面我们就演示怎么来定位函数信息。使用如下命令导出函数表:[plain]   arm-linux-androideabi-objdump –S obj/local/armeabi/libhello-jni.so & hello.asm  在生成的asm文件中查找刚刚我们定位的两个关键指针0004f58从这两张图可以清楚的看到(要注意的是,在不同的NDK版本和不同的操作系统中,asm文件的格式不是完全相同,但都大同小异,请大家仔细比对),这两个指针分别属于willCrash()和JNI_OnLoad()函数,再结合刚才addr2line的结果,那么这两个地址分别对应的信息就是:[plain]   00004fb4: willCrash() /WordSpaces/hello-jni/jni/hello-jni.cpp:69  00004f58: JNI_OnLoad()/WordSpaces/hello-jni/jni/hello-jni.cpp:61  相当完美,和ndk-stack得到的信息完全一致!使用Testin崩溃分析服务定位NDK错误以上提到的方法,只适合在开发测试期间,如果你的应用或者游戏已经发布上线,而用户经常反馈说崩溃、闪退,指望用户帮你收集信息定位问题,几乎是不可能的。这个时候,我们就需要用其他的手段来捕获崩溃信息。目前业界已经有一些公司推出了崩溃信息收集的服务,通过嵌入SDK,在程序发生崩溃时收集堆栈信息,发送到云服务平台,从而帮助开发者定位错误信息。在这方面,处于领先地位的是国内的Testin和国外的crittercism,其中crittercism需要付费,而且没有专门的中国开发者支持,我们更推荐Testin,其崩溃分析服务是完全免费的。Testin从1.4版本开始支持NDK的崩溃分析,其最新版本已经升级到1.7。当程序发生NDK错误时,其内嵌的SDK会收集程序在用户手机上发生崩溃时的堆栈信息(主要就是上面我们通过logcat日志获取到的函数指针)、设备信息、线程信息等等,SDK将这些信息上报至Testin云服务平台,只要登陆到Testin平台,就可以看到所有用户上报的崩溃信息,包括NDK;并且这些崩溃做过归一化的处理,在不同系统和ROM的版本上打印的信息会略有不同,但是在Testin的网站上这些都做了很好的处理,避免了我们一些重复劳动。上图的红框部分,就是从用户手机上报的,我们自己的so中报错的函数指针地址堆栈信息,就和我们开发时从logcat读到的日志一样,是一些晦涩难懂的指针地址,Testin为NDK崩溃提供了符号化的功能,只要将我们编译过程中产生的包含符号表的so文件上传(上文我们提到过的obj/local/目录下的适用于各个CPU架构的so),就可以自动将函数指针地址定位到函数名称和代码行数。符号化之后,看起来就和我们前面在本地测试的结果是一样的了,一目了然。而且使用这个功能还有一个好处:这些包含符号表的so文件,在每次我们自己编译之后都会改变,很有可能我们刚刚发布一个新版本,这些目录下的so就已经变了,因为开发者会程序的修改程序;在这样的情况下,即使我们拿到了崩溃时的堆栈信息,那也无法再进行符号化了。所以我们在编译打包完成后记得备份我们的so文件。这时我们可以将这些文件上传到Testin进行符号化的工作,Testin会为我们保存和管理不同版本的so文件,确保信息不会丢失。来看一下符号化之后的显示:
爱好移动互联网,移动互联网微信公众账户:ydhlwdyq (移动互联网第一群第一个字母)欢迎收听。
UID: 300874
发帖: 2378
可可豆: 3131 CB
威望: 3189 点
在线时间: 1378(时)
发自: Web Page
回 楼主(godinword) 的帖子
好东西。感谢分享~经常需要Android C++调试的童鞋可以看一看。
级别: 新手上路
可可豆: 42 CB
威望: 32 点
在线时间: 1(时)
发自: Web Page
回 1楼(偶尔e网事) 的帖子
版主大人能否推荐下啊?加个置顶啊 &&&&
爱好移动互联网,移动互联网微信公众账户:ydhlwdyq (移动互联网第一群第一个字母)欢迎收听。
级别: 新手上路
可可豆: 72 CB
威望: 72 点
在线时间: 180(时)
发自: Web Page
LZ:先获取日志,再使用ndk-stack分析真机运行的时候 闪退时候怎么获取日志了
UID: 300874
发帖: 2378
可可豆: 3131 CB
威望: 3189 点
在线时间: 1378(时)
发自: Web Page
回 3楼(liyuping6666) 的帖子
真机连着Eclipse或者直接adb logcat。都会有日志。
级别: 新手上路
可可豆: 72 CB
威望: 72 点
在线时间: 180(时)
发自: Web Page
回 4楼(偶尔e网事) 的帖子
想知道发布的产品 某些手机报错的时候能够捕获错误 自动生成日志 类似dump文件
关注本帖(如果有新回复会站内信通知您)
苹果公司现任CEO是谁?2字 正确答案:库克
发帖、回帖都会得到可观的积分奖励。
按"Ctrl+Enter"直接提交
关注CocoaChina
关注微信 每日推荐
扫一扫 浏览移动版

我要回帖

更多关于 ndk crash 的文章

 

随机推荐