android应用空间的调试方法

下面都是android应用层空间的调试方法总结

1 android native 和后台服务的调试方法

android系统中调试Java非常容易,一般遇到错误都在logcat中打印出错时函数的调用关系,和出错的具体行数。而C库或是可执行的后台服务中出错时只看到一些二进制信息。


下面我就以实际的列子来说明这种情况调试方法和步骤:
我在跟踪wpa_supplicant的问题时,我在wpa_supplicant_8/wpa_supplicant/bss.c文件的wpa_bss_copy_res函数中,添加了了一条打印信息:wpa_dbg(dst,MSG_ERROR, "---------------src->tsf:%llu ||"MACSTR "\n", src->tsf,MAC2STR(src->bssid));

static void wpa_bss_copy_res(struct wpa_bss *dst, struct wpa_scan_res *src,
			     struct os_time *fetch_time)
{
	dst->flags = src->flags;
	os_memcpy(dst->bssid, src->bssid, ETH_ALEN);
	dst->freq = src->freq;
	dst->beacon_int = src->beacon_int;
	dst->caps = src->caps;
	dst->qual = src->qual;
	dst->noise = src->noise;
	dst->level = src->level;
	dst->tsf = src->tsf;
	wpa_dbg(dst,MSG_ERROR, "---------------src->tsf:%llu ||"MACSTR "\n", src->tsf,MAC2STR(src->bssid));//是我添加的调试log
	calculate_update_time(fetch_time, src->age, &dst->last_update);
}

然后就是这条打印信息,导致我打开wifi的时候,出现如下典型的错误信息:



上述信息中,backtrace部分才是我们真正需要的信息,他显示了程序跑飞时的调用流程。
我在板子上跑的wpa_supplicant是stripe symbol的可执行程序,所有只有二进制,
没有对应的标号信息。不过android很体贴的为我们保留了一份带symbos的wpa_supplcant可执行程序。
在android编译输出的如下路径下:
out/target/product/wmid/symbols/system/bin,通过file命令,你可以看到wpa_supplcant是
not stripped的。


然后通过addr2line工具将上面pc对应的16进制值转换成函数调用返回时所在文件的行数,
从而可以确定函数的调用流程:

android应用空间的调试方法_第1张图片

根据以上addr2line得到的函数调用的回朔信息,我们结合源代码可以得出,出问题时,函数
的调用流程如下:

process_global_event
|-->do_process_drv_event
   |-->send_scan_event
	 |-->wpa_supplicant_event
        |-->wpa_supplicant_event_scan_results
           |-->_wpa_supplicant_event_scan_results
             |-->wpa_supplicant_get_scan_results
               |-->wpa_bss_update_scan_res
				|-->wpa_bss_add
						wpa_bss_copy_res
							wpa_dbg
								||
							wpa_msg
							  wpa_msg_cb
								||
							  wpa_supplicant_ctrl_iface_msg_cb


即出问题时,正是我们在wpa_bss_copy_res函数中,添加的wpa_dbg函数引起的。原因是
我故意给wpa_dbg函数传递了一个非struct wpa_supplicant结构体的指针值,导致后面在
wpa_supplicant_ctrl_iface_msg_cb函数中,把这个指针当作struct wpa_supplicant结构来引用,
所以出现地址错误。

android应用空间的调试方法_第2张图片

2 gdb调试

2.1:gdb远程调试的方法

gdb远程调试功能需要在linux-x86的环境下才能运行。譬如在ubuntu或debia环境下或

是linux的虚拟机下,调试步骤如下:

2.1.1: fetch adb path

首先获取linux pc端下的adb的路径,或者将adb的路径设置到~/.bashrc下:

export PATH=$ADB_PATH:$PATH默认android fs编译出来时在如下的路径:

out/host/`uname -s | tr "[[:upper:]]" "[[:lower:]]"`-x86/bin/adb 

2.1.2: fetch gdb path and python lib path

需要获取gdb的client端和python 库的路径(gdb需要依赖python),android4.4以后,

一般都是在如下路径:GDB=prebuilts/gcc/$(uname -s | tr "[[:upper:]]" "[[:lower:]]")-x86

/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gdb

PYTHON_DIR=prebuilts/python/$(uname -s | tr "[[:upper:]]" "[[:lower:]]")-x86/2.7.5

接下来我们需要将pc 端上的gdb(简称为host)跟目标版上的gdb service(简称为target)

连接起来,让他们可以相互通讯

2.1.3: connect gdb client and gdb service

$ADB forward tcp:$GDB_PORT tcp:$GDB_PORT

在这里需要规定下gdb service的侦听端口和gdb client的连接端口,通过adb forward来实现,

这样client与service就可以通过adb来通讯了。至于adb forward具体干些什么事情,

请参考我的另一篇blog:android usb adb流程

2.1.4: start gdbserver on the target with attached target prog, and listening on $GDB_PORT

接下来需要在目标版上起动gdb server,在指定的端口上侦听gdb client过来的请求,并且attach

上需要调试的进程。使用如下命令:$ADB shell gdbserver :$GDB_PORT --attach $TARGET_PID &

2.1.5:设置pc端下gdb的库和可执行程序的搜索路径,

注意这些库和可执行程序,是包含了调试信息的。在android编译输出的如下路径:

out/target/product/wmid/symbols/system,而目标版上的二进制程序则是不包含调试信息的。

远程调试的好处就在这里,简单说,就是将包含调试信息的程序放在pc端,target端的程序是

不包含调试信息的(通过stripped命令去掉了调试信息),这样节省存储空间,gdb client会将

从gdb service那边得到的二进制信息结合pc端的带调试信息的程序,就可以翻译成human-readable

的信息展示出来并且幸运的是,android的编译环境都为我们想好了这些,所以在android编译的输出路径上,

都保存了两份,一份是带调试信息,一份是不带调试信息的。

2.1.6: start gdb client and execute $GDBINIT script

起动pc段的gdb,并同时指定要调试的程序名字和路径。

$GDB -x $GDBINIT $PROG

PROG就是要调试的程序,注意他们pc端下的路径,是包含调试信息的程序。

至此gdb调试环境就已经搭建完成。以上过程,可以通过一个脚本来实现,每次调试时,

只需要执行以下这个脚本,即可以开始gdb的调试。运行该脚本的方法:

run-gdb.sh置于android fs sources的root 目录下。然后执行如下命令:

run-gdb.sh attach prog

上面prog就是你需要调试的程序,譬如如果要通过gdb调试wpa_supplicant,则可以执行如下命令:

run-gdb.sh attach wpa_supplicant

2.2:gdb调试死锁的方法

在gdb的提示符下,输入命令:thread apply all bt,即可以显示所调试进程下所有线程各

自的调用堆栈。或者在提示符下输入:info threads,即可以查看当前进程下所有的线程,

并且可以thread + number(简写:b n)命令来切换到指定ID(即number)的线程,然后执行

bt(backstrace)即可以打印当前线程下的调用堆栈。然后仔细查看那些停在获取锁的地方的线程,

然后查看这些线程都各自拥有那些锁,找出导致死锁的线程。

2.3:gdb常用命令

info threads

thread apply all bt

bt

print expr(p)

break

info break

run(r)

continue(c)

next(n)

quit(q)

3 android下特有的调试命令debuggerd(/system/bin/debuggerd)

在android下有个/system/bin/debuggerd后台服务,就是被android用来在需要的使用打印线

程的掉哟你堆栈的。这个debuggerd命令,在我们平时调试andoid的死锁程序也是很有用的。

应该多主动使用。基本流程就是先通过ps命令查看你要调试的进程的pid,然后执行命令:

debuggerd -b $pid,即可以将该$pid下所有的线程的堆栈都打印出来,类似于gdb中的

thread apply all bt命令,但是这个命令更方便。

4 ANR调试

ANR is short forApplication No Response,一般主线程超过5秒未有处理就会ANR
1 :首先分析 log

LOG可以看出ANR的类型,CPU的使用情况:

如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR

如果CPU使用量很少,说明主线程被BLOCK

如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的


2: trace.txt文件查看调用stack.
手动获取trace的方法:
  1. $chmod 777 /data/anr

  2. rm /data/anr/traces.txt

  3. ps

  4. kill -3 PID

需要注意的是:pid是进程的id号,并且一般都是针对pid大于1000的进程。对于那些native的deamon,执行kill -3 $pid则不能生成trace.txt文件
3: 看代码
根据栈回朔的信息来查看代码,仔细查看出问题点周围的代码是否有问题。
4:仔细查看ANR的成因(iowait?block?memoryleak?
通过查看anr,还可以发现线程的死锁问题。

5 service和dumpsys命令的使用

service list命令可以列出目前android系统所有的服务进程(注意都是binder的服务进程)。

dumpsys $service,则可以打印指定服务里的一些重要信息,这样就可以动态的查看

service里面目前的状态,从而发现是否有问题。

另外通过service call命令在控制台就可以有选择的调用你指定的服务中的指定api函数的功能;从而验证该api是否工作正常

例如:

service call WMTWifiService 3 i32 0 i32 17  

命令解析如下:

service call WMTWifiService(服务的名字,binder的知名服务,即注册到serviceManager中的名字) 3(binder服务接口中的方法对应的编号) i32 0(参数1 参数值) i32 17(参数2 参数值)



你可能感兴趣的:(android,debug)