- Android系统级深入开发
- 韩超 梁泉
- 4098字
- 2020-08-26 21:43:41
3.4.2 调试的方法
由于Android系统基于Linux,Android中的调试方式首先可以利用标准Linux的基本调试的方法,除此之外,Android中还有特定的调试方法。
1.查看系统进程的情况
首先可以在终端使用ps命令查看系统的进程。启动Android的仿真器环境,然后使用adb shell进行连接,使用ps查看各个进程,如下所示:
# ps USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 312 220 c009b74c 0000ca4c S /init root 2 0 0 0 c004e72c 00000000 S kthreadd root 3 2 0 0 c003fdc8 00000000 S ksoftirqd/0 root 4 2 0 0 c004b2c4 00000000 S events/0 root 5 2 0 0 c004b2c4 00000000 S khelper root 6 2 0 0 c004b2c4 00000000 S suspend root 7 2 0 0 c004b2c4 00000000 S kblockd/0 root 8 2 0 0 c004b2c4 00000000 S cqueue root 9 2 0 0 c018179c 00000000 S kseriod root 10 2 0 0 c004b2c4 00000000 S kmmcd root 11 2 0 0 c006fc74 00000000 S pdflush root 12 2 0 0 c006fc74 00000000 S pdflush root 13 2 0 0 c00744e4 00000000 S kswapd0 root 14 2 0 0 c004b2c4 00000000 S aio/0 root 22 2 0 0 c017ef48 00000000 S mtdblockd root 23 2 0 0 c004b2c4 00000000 S kstriped root 24 2 0 0 c004b2c4 00000000 S hid_compat root 25 2 0 0 c004b2c4 00000000 S rpciod/0 root 26 2 0 0 c019d16c 00000000 S mmcqd root 27 1 740 220 c0158eb0 afd0d8ac S /system/bin/sh system 28 1 808 208 c01a94a4 afd0db4c S /system/bin/servicemanager root 29 1 3736 372 ffffffff afd0e1bc S /system/bin/vold root 30 1 3716 316 ffffffff afd0e1bc S /system/bin/netd root 31 1 668 184 c01b52b4 afd0e4dc S /system/bin/debuggerd radio 32 1 5392 480 ffffffff afd0e1bc S /system/bin/rild root 33 1 81940 22164 c009b74c afd0dc74 S zygote media 34 1 19508 1836 ffffffff afd0db4c S /system/bin/mediaserver bluetooth 35 1 1260 288 c009b74c afd0e98c S /system/bin/dbus-daemon root 36 1 812 236 c02181f4 afd0d8ac S /system/bin/installd keystore 37 1 1616 216 c01b52b4 afd0e4dc S /system/bin/keystore root 38 1 740 220 c003da38 afd0e7bc S /system/bin/sh root 39 1 840 280 c00b8fec afd0e90c S /system/bin/qemud root 41 1 3384 176 ffffffff 0000ecc4 S /sbin/adbd root 54 38 796 248 c02181f4 afd0d8ac S /system/bin/qemu-props system 62 33 152636 30928 ffffffff afd0db4c S system_server app_9 121 33 108964 18128 ffffffff afd0eb08 S com.android.inputmethod.latin radio 125 33 120308 19396 ffffffff afd0eb08 S com.android.phone app_27 128 33 108092 21528 ffffffff afd0eb08 S com.android.launcher system 131 33 109420 16932 ffffffff afd0eb08 S com.android.settings app_8 157 33 118524 22096 ffffffff afd0eb08 S android.process.acore app_0 182 33 116196 17064 ffffffff afd0eb08 S com.android.mms app_16 188 33 104244 17520 ffffffff afd0eb08 S android.process.media app_6 207 33 106856 18020 ffffffff afd0eb08 S com.android.email app_10 218 33 103260 16800 ffffffff afd0eb08 S com.android.bluetooth app_12 226 33 10421617020ffffffffafd0eb08Scom.android.providers.calendar app_15 240 33 103028 16900 ffffffff afd0eb08 S com.android.deskclock app_3 248 33 102408 15832 ffffffff afd0eb08 S com.android.protips app_4 255 33 105144 16748 ffffffff afd0eb08 S com.android.quicksearchbox app_13 262 33 103452 16164 ffffffff afd0eb08 S com.android.music root 268 41 740 328 c003da38 afd0e7bc S /system/bin/sh root 282 268 888 332 00000000 afd0d8ac R ps
从Android系统的进程中可以看到,1号和2号进程以0号进程为父进程。init是系统运行的第1个用户空间进程,即Android根目下的init可执行程序,这是一个用户空间的进程。kthreadd是系统的2号进程,这是一个内核进程,其他内核进程都直接或间接以kthreadd为父进程。
系统的各个守护进行由init进行运行,例如Zygote、/system/bin/sh、/system/bin/mediaserver、/system/bin/adbd等,因此它们的父进程号为1,即以init为父进程。
各种文字名称的进程表示Android应用程序的进程,例如android.process.acore、com.android.mms等进程代表的是应用程序进程,它们的父进程都是zygote。例如,在本例中它们的父进程id均为33。
本例中ps命令的进城id为282,其父进程是pid为268的/system/bin/sh,可见这个命令是从sh分支出来的,而pid为268的/system/bin/sh的父进程是pid为41的/system/bin/adbd (adb守护进程)。
使用Linux的proc文件系统,也可以查看进程相关的信息,在/proc目录中,包含了各个进程的信息,每个进程一个目录,就是进程的id。
例如,查看27号进程的目录如下所示:
# ls -l /proc/27/ dr-xr-xr-x root root 2010-08-13 03:39 task dr-x------ root root 2010-08-13 03:39 fd dr-x------ root root 2010-08-13 03:39 fdinfo dr-xr-xr-x root root 2010-08-13 03:39 net -r-------- root root 0 2010-08-13 03:39 environ -r-------- root root 0 2010-08-13 03:39 auxv -r--r--r-- root root 0 2010-08-13 03:39 status -r-------- root root 0 2010-08-13 03:39 personality -r-------- root root 0 2010-08-13 03:39 limits -rw-r--r-- root root 0 2010-08-13 03:39 sched -r--r--r-- root root 0 2010-08-13 03:38 cmdline -r--r--r-- root root 0 2010-08-13 03:34 stat -r--r--r-- root root 0 2010-08-13 03:39 statm -r--r--r-- root root 0 2010-08-13 03:39 maps -rw------- root root 0 2010-08-13 03:39 mem lrwxrwxrwx root root 2010-08-13 03:39 cwd -> / lrwxrwxrwx root root 2010-08-13 03:39 root -> / lrwxrwxrwx root root 2010-08-13 03:39 exe -> /system/bin/sh -r--r--r-- root root 0 2010-08-13 03:39 mounts -r--r--r-- root root 0 2010-08-13 03:39 mountinfo -r-------- root root 0 2010-08-13 03:39 mountstats --w------- root root 0 2010-08-13 03:39 clear_refs -r--r--r-- root root 0 2010-08-13 03:39 smaps -r-------- root root 0 2010-08-13 03:39 pagemap -r--r--r-- root root 0 2010-08-13 03:39 wchan -r--r--r-- root root 0 2010-08-13 03:39 schedstat -r--r--r-- root root 0 2010-08-13 03:39 cgroup -r--r--r-- root root 0 2010-08-13 03:39 oom_score -rw-r--r-- root root 0 2010-08-13 03:39 oom_adj -rw-r--r-- root root 0 2010-08-13 03:39 coredump_filter
在每个进程的目录中,包含了若干个文件的子目录,其中cmdline文件表示了这个进程所在的命令行:
# cat /proc/27/cmdline /system/bin/sh
由此可见,这个27号进程就是shell程序。查看status文件,可以获知这个进程的一些相关信息。对27号进程的查看如下所示:
# cat /proc/27/status Name: sh # 进程名称 State: S (sleeping) # 进程状态 Tgid: 27 # 线程组ID Pid: 27 # 进程ID PPid: 1 # 父进程ID TracerPid: 0 Uid:0 0 0 0 Gid:0 0 0 0 FDSize: 1024 Groups: VmPeak: 744 kB # 虚拟内存相关消息 VmSize: 744 kB VmLck: 0 kB VmHWM: 316 kB VmRSS: 264 kB VmData: 92 kB VmStk: 84 kB VmExe: 80 kB VmLib: 460 kB VmPTE: 12 kB Threads: 1 # 所包含的线程数 SigQ: 0/768 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000284004 SigCgt: 00000000000094ea CapInh: 0000000000000000 CapPrm: fffffffffffffeff CapEff: fffffffffffffeff CapBnd: fffffffffffffeff voluntary_ctxt_switches: 38 nonvoluntary_ctxt_switches: 1
stat文件实际上包含了比status文件更多的信息,但是可读性比较差,因此一般查看status文件。
进程目录中的fd文件,表示了这个进程中所打开的文件的描述符。对27号进程的查看如下所示:
# ls /proc/27/fd 0 1 2 9 1023
进程目录中的task目录表示了这个进程所包含的子进程,也就是这个进程中所包含的线程,目录的名称也就是子进程的id。对27号进程的查看如下所示:
# ls /proc/27/task 27
27号进程只包含一个进程,即27号进程自己。
在Linux操作系统的内核的角度出发,进程和线程没有区别。在用户空间建立的线程(例如,通过pthread建立的)在内核的情况就是一个进程,也就是上述的子进程。事实上,这些子进程的目录结构和进程是一样的。
对于34号进程mediaserver,情况如下所示:
# cat /proc/34/cmdline /system/bin/mediaserver # ls 34 58 59 60 61 98
除了34之外,还包含了58,59,60,61,98这4个文件夹,由此可见,mediaserver进程已经包含了若干个子进程。如果查看其status文件,也能发现相应信息。
2.统计系统性能消息
在Linux中有一些可以统计系统性能的方法,在Android中也有所支持。使用vmstat和top命令可以统计系统中性能的信息。
vmstat(Virtual Meomory Statistics,虚拟内存统计),命令报告关于内核线程、虚拟内存、磁盘、陷阱和CPU活动的统计信息。由vmstat命令生成的报告可以用于平衡系统负载活动。
vmstat的使用如下所示:
# vmstat & # procs memory system cpu r b free mapped anon slab in cs flt us ni sy id wa ir 0 0 3792 21112 50864 3724 27 66 0 1 0 1 99 0 0 0 0 3792 21112 50864 3724 22 53 0 0 0 0 99 0 0 0 0 3792 21112 50864 3724 28 63 0 1 0 2 98 0 0 1 0 1752 22168 51348 3744 94 235 4 43 0 18 39 0 0 10 1 1384 20968 52932 3760 123 555 21 60 0 38 0 2 0 11 1 1700 19340 53424 3852 366 322 8 80 0 21 0 0 0 5 0 1996 19748 53688 3800 150 337 5 82 0 17 0 0 0 4 1 1320 20584 53568 3688 154 584 15 67 0 33 0 0 0 2 1 1632 20448 53624 3500 150 422 18 66 0 33 0 1 0
vmstat的结构中几个比较重要的值的含义如下所示。
r:在运行队列中等待的进程数
b:在等待io的进程数
w:可以进入运行队列但被替换的进程
free:空闲的内存(单位k)
mapped:影射的内存(单位k)
in:每秒的中断数,包括时钟中断
cs:每秒的环境(上下文)切换次数
us:CPU使用时间
sy:CPU系统使用时间
id:闲置时间
top程序主要可以检测各个进程对CPU的消耗情况,信息将一屏一屏的阶段性地打印到屏幕上。
在Android中,第1次top程序的使用如下所示:
# top & User 93%, System 6%, IOW 0%, IRQ 0% User 309 + Nice 0 + Sys 20 + Idle 0 + IOW 0 + IRQ 0 + SIRQ 0 = 329 PID CPU% S #THR VSS RSS PCY UID Name 270 82% S 17 133980K 21628K fg app_5 com.cooliris.media 62 12% S 44 152976K 25928K fg system system_server 301 3% R 1 908K 388K fg root top 4 0% S 1 0K 0K fg root events/0 5 0% S 1 0K 0K fg root khelper 6 0% S 1 0K 0K fg root suspend 7 0% S 1 0K 0K fg root kblockd/0 8 0% S 1 0K 0K fg root cqueue 9 0% S 1 0K 0K fg root kseriod 10 0% S 1 0K 0K fg root kmmcd 11 0% S 1 0K 0K fg root pdflush 12 0% S 1 0K 0K fg root pdflush 13 0% S 1 0K 0K fg root kswapd0 14 0% S 1 0K 0K fg root aio/0
top使用的第2次统计的内容如下所示:
User 14%, System 5%, IOW 0%, IRQ 0% User 47 + Nice 0 + Sys 18 + Idle 249 + IOW 0 + IRQ 0 + SIRQ 0 = 314 PID CPU% S #THR VSS RSS PCY UID Name 34 13% S 14 29084K 2928K fg media /system/bin/mediaserver 62 3% S 44 153040K 26532K fg system system_server 301 3% R 1 916K 404K fg root top 125 0% S 17 119236K 16508K fg radio com.android.phone 39 0% S 1 844K 336K fg root /system/bin/qemud 6 0% S 1 0K 0K fg root suspend 7 0% S 1 0K 0K fg root kblockd/0 8 0% S 1 0K 0K fg root cqueue 9 0% S 1 0K 0K fg root kseriod 10 0% S 1 0K 0K fg root kmmcd 11 0% S 1 0K 0K fg root pdflush 12 0% S 1 0K 0K fg root pdflush 13 0% S 1 0K 0K fg root kswapd0 14 0% S 1 0K 0K fg root aio/0
以上top程序统计的情况是,刚刚打开Gallery(3D版本)程序,因此com.cooliris. media,也就是Gallery3D的程序,这个进程占用的CPU消耗最大,同时top程序本身也占用了一些CPU消耗。然后打开了视频播放程序,因此,/system/bin/mediaserver进程的消耗变为最大。
Andoir中还提供了dumpstat和dumpsys两个工具,可以用于将各个进程的stat中的信息和sys文件系统的内容导出。这两个工具导出的内容都较多,最好在主机端配合adb来使用,例如:
$ adb shell dumpstat | grep CPU
3.内核和驱动的调试
使用dmesg可以查看内核打印出来信息,如下所示:
# dmesg
ioctl是Android中的一个工具,用户可以直接控制设备节点的ioctl命令,这样可以在没有写程序的情况下进行一些测试工作。
ioctl的使用如下所示:
# ioctl -h ioctl [-l <length>] [-a <argsize>] [-rdh] <device> <ioctlnr> -l <lenght> Length of io buffer -a <argsize> Size of each argument (1-8) -r Open device in read only mode -d Direct argument (no iobuffer) -h Print help
例如,使用ioctl程序,查看framebuffer驱动的情况如下所示:
# ioctl -l 16 -r /dev/graphics/fb0 0x4600 sending ioctl 0x4600 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 return buf: 40 01 00 00 e0 01 00 00 40 01 00 00 c0 03 00 00
linux/fb.h中定义了获取framebuffer信息的ioctl命令FBIOGET_VSCREENINFO为0x4600,如下所示:
#define FBIOGET_VSCREENINFO 0x4600
FBIOGET_VSCREENINFO这个ioctl命令使用的结构体为struct fb_var_screeninfo,前面几个字节的内容如下所示
struct fb_var_screeninfo { __u32 xres; /* 可见分辨率 */ __u32 yres; __u32 xres_virtual; /* 虚拟分辨率 */ __u32 yres_virtual; /* ......省略部分内容 */ }
以上的命令使用读取了16字节的消息,得到的信息的含义为:
0x140==320 0x1E0==480 0x3C0==960
因此,表示屏幕的可见的宽为320,可见的高为480,虚拟的宽为320,虚拟的高为960,这就是双缓冲的屏幕。
4.Android的特殊调试命令
在Android的toolbox中包含了一些非标准化的辅助命令,这些命令在Linux的shell中没有,专门为Android系统所用。
netcfg是Android中的一个网络工具,用于配置网络,使用方法如下所示
# netcfg -h usage: netcfg [<interface> {dhcp|up|down}]
在仿真器连接网络的情况上查看:
# netcfg lo UP 127.0.0.1 255.0.0.0 0x00000049 eth0 UP 10.0.2.15 255.255.255.0 0x00001043 tunl0 DOWN 0.0.0.0 0.0.0.0 0x00000080 gre0 DOWN 0.0.0.0 0.0.0.0 0x00000080
Service工具用于和Android一个已经启动的Service进行通信。Service命令的使用方式如下所示:
# service -h Usage: service [-h|-?] service list service check SERVICE service call SERVICE CODE [i32 INT | s16 STR] ... Options: i32: Write the integer INT into the send parcel. s16: Write the UTF-16 string STR into the send parcel.
使用service list可以显示系统已经启动的服务,如下所示:
# service list Found 49 services: 0 phone: [com.android.internal.telephony.ITelephony] 1 iphonesubinfo: [com.android.internal.telephony.IPhoneSubInfo] 2 simphonebook: [com.android.internal.telephony.IIccPhoneBook] 3 isms: [com.android.internal.telephony.ISms] 45 media.audio_policy: [android.media.IAudioPolicyService] 46 media.camera: [android.hardware.ICameraService] 47 media.player: [android.media.IMediaPlayerService] 48 media.audio_flinger: [android.media.IAudioFlinger]
这里显示的服务包括了Java层启动的服务和C中启动的服务。例如以上列出的内容中:phone就是Java层启动的服务,media.player中就是C中启动的服务。
使用service check查看服务的状态,如下所示:
# service check media.player Service media.player: found
使用service call调用进程的情况如下所示:
# service call media.player 3 s16 "file:///sdcard/a.mp4" Result: Parcel( 0x00000000: 00000000 40806d54 a821a0e5 73622a85 '....Tm.@..!..*bs' 0x00000010: 0000017f 00000000 00000000 '............ ') # D/MediaPlayerService( 34): Can't decode by path, use filedescriptor instead
事实上,这种调用已经调用到了MediaPlayerService中的函数,因此打印出了上述的调试信息。
am命令是在控制台中可以非常容易调试程序的工具,例如可以启动活动、启动服务和发送广播等功能。am命令的基本使用方法如下所示:
# am usage: am [subcommand] [options] start an Activity: am start [-D] [-W] <INTENT> -D: enable debugging -W: wait for launch to complete start a Service: am startservice <INTENT> send a broadcast Intent: am broadcast <INTENT> start an Instrumentation: am instrument [flags] <COMPONENT> -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT) -e <NAME> <VALUE>: set argument <NAME> to <VALUE> -p <FILE>: write profiling data to <FILE> -w: wait for instrumentation to finish before returning start profiling: am profile <PROCESS> start <FILE> stop profiling: am profile <PROCESS> stop
使用am start是其中的一个功能,以INTENT作为参数,INTENT使用的选项如下所示:
<INTENT> specifications include these flags: [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>] [-c <CATEGORY> [-c <CATEGORY>] ...] [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...] [--esn <EXTRA_KEY> ...] [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...] [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...] [-n <COMPONENT>] [-f <FLAGS>] [--grant-read-uri-permission] [--grant-write-uri-permission] [--debug-log-resolution] [--activity-brought-to-front] [--activity-clear-top] [--activity-clear-when-task-reset] [--activity-exclude-from-recents] [--activity-launched-from-history] [--activity-multiple-task] [--activity-no-animation] [--activity-no-history] [--activity-no-user-action] [--activity-previous-is-top] [--activity-reorder-to-front] [--activity-reset-task-if-needed] [--activity-single-top] [--receiver-registered-only] [--receiver-replace-pending] [<URI>]
使用am命令启动计算器应用(实际是使用显式Intent)的方式如下所示:
# am start -n com.android.calculator2/com.android.calculator2.Calculator
使用am命显示一幅图片(实际是使用隐式Intent)的方式如下所示:
# am start -a android.intent.action.VIEW -d file:///sdcard/a.jpg -t image/*
5.Logcat工具
logcat是Android中的一个命令行工具,可以用于得到程序的log信息。Logcat的使用方法如下所示:
logcat [options] [filterspecs]
logcat工具的具体选项如表3-2所示。
表3-2 logcat工具的选项
在Android的本地代码中,通常具有如下的设置。
//#define LOG_NDEBUG 0 #define LOG_TAG "XXX" #include <utils/Log.h>
将#define LOG_NDEBUG 0的注释取消,即可获得DEBUG的调试信息,而LOG_TAG表示调试信息的前缀。