Android系统常用的调试工具

1、概述

本手册重点介绍 Android系统开发过程中的一些调试工具和调试方法,并会不断补充完善,帮助开发者快速上手基础系统调试,并做出正确的分析。

2、ADB 工具

2.1概述
ADB(Android Debug Bridge)是 Android SDK 里的一个工具, 用这个工具可以操作管理
Android 模拟器或真实的 Android 设备。主要功能有:
运行设备的 shell(命令行)
管理模拟器或设备的端口映射
计算机和设备之间上传/下载文件
将本地 apk 软件安装至模拟器或 Android 设备
ADB 是一个“客户端-服务器端”程序,其中客户端主要是指 PC,服务器端是 Android 设
备的实体机器或者虚拟机。根据 PC 连接设备的方式不同, ADB 可以分为两类:
网络 ADB:主机通过有线/无线网络(同一局域网)连接到 STB 设备
USB ADB:主机通过 USB 线连接到 STB 设备
2.2USB ADB 使用说明
USB ADB 使用有以下限制:
只支持 USB OTG 口
不支持多个客户端同时使用(如 cmd 窗口, eclipse 等)
只支持主机连接一个设备,不支持连接多个设备
连接步骤如下:
1、 设备已经运行 Android 系统,设置->开发者选项->已连接到计算机打开, usb 调试开关
打开。
2、 PC 主机只通过 USB 线连接到机器 USB OTG 口,然后电脑通过如下命令与设备相连:
adb shell
3、测试是否连接成功,运”adb devices”命令,如果显示机器的序列号,表示连接成功。

2.3网络 ADB 使用要求
ADB 早期版本只能通过 USB 来对设备调试,从 adb v1.0.25 开始,增加了对通过 tcp/ip 调
试 Android 设备的功能。
如果你需要使用网络 ADB 来调试设备,必须要满足如下条件:
1、设备上面首先要有网口,或者通过 WiFi 连接网络。
2、设备和研发机(PC 机)已经接入局域网,并且设备设有局域网的 IP 地址。
3、要确保研发机和设备能够相互 ping 得通。
4、研发机已经安装了 ADB。
5、确保 Android 设备中 adbd 进程(ADB 的后台进程)已经运行。 adbd 进程将会监听端口
5555 来进行 ADB 连接调试。
2.4SDK 网络 ADB 端口配置
SDK 默认未对网络 ADB 端口进行配置,需要手动修改打开配置。
修改 device/rockchip/rkxxxx/system.prop 文件,添加如下配置:
service.adb.tcp.port=5555
2.5网络 ADB 使用
本节假设设备的 IP 为 192.168.1.5,下文将会用这个 IP 建立 ADB 连接,并调试设备。
1、首先 Android 设备需要先启动,如果可以话,可以确保一下 adbd 启动(ps 命令查看)。
2、在 PC 机的 cmd 中,输入:
adb connect 192.168.1.5:5555
如果连接成功会进行相关的提示,如果失败的话,可以先 kill-server 命令,然后重试连接。
adb kill-server
3、如果连接已经建立,在研发机中,可以输入 ADB 相关的命令进行调试了。比如 adb
shell,将会通过 TCP/IP 连接设备上面。和 USB 调试是一样的。
4、调试完成之后,在研发机上面输入如下的命令断开连接:
adb disconnect 192.168.1.5:5555
2.6手动修改网络 ADB 端口号
若 SDK 未加入 ADB 端口号配置,或是想修改 ADB 端口号,可通过如下方式修改:
1、首先还是正常地通过 USB 连接目标机,在 windows cmd 下执行 adb shell 进入。
2、设置 ADB 监听端口:
#setprop service.adb.tcp.port 5555
3、通过 ps 命令查找 adbd 的 pid
4、重启 adbd
#kill -9,这个 pid 就是上一步找到那个 pid
杀死 adbd 之后, Android 的 init 进程后自动重启 adbd。 adbd 重启后,发现设置了
service.adb.tcp.port,就会自动改为监听网络请求。
2.7ADB 常用命令详解
1、查看设备情况
查看连接到计算机的 Android 设备或者模拟器:
adb devices
返回的结果为连接至开发机的 Android 设备的序列号或是 IP 和端口号(Port)、状态。
2、安装 APK
将指定的 APK 文件安装到设备上:
adb install <apk 文件路径>
示例如下:
adb install “F:\WishTV\WishTV.apk”
重新安装应用:
adb install –r <apk 文件路径>
示例如下:
adb install –r “F:\WishTV\WishTV.apk”
3、卸载 APK
完全卸载:
adb uninstall
示例如下:
adb uninstall com.wishtv
4、使用 rm 移除 APK 文件:
adb shell rm
示例如下:
adb shell
rm “system/app/WishTV.apk”
示例说明:移除“system/app”目录下的“WishTV.apk”文件。
5、进入设备和模拟器的 shell
进入设备或模拟器的 shell 环境:
adb shell
6、从电脑上传文件到设备
用 push 命令可以把本机电脑上的任意文件或者文件夹上传到设备。本地路径一般指本机电
脑;远程路径一般指 ADB 连接的单板设备。
adb push <本地路径><远程路径>
示例如下:
adb push “F:\WishTV\WishTV.apk”“system/app”
示例说明:将本地“WishTV.apk”文件上传到 Android 系统的“system/app”目录下。
7、从设备下载文件到电脑
pull 命令可以把设备上的文件或者文件夹下载到本机电脑中。
adb pull <远程路径><本地路径>
示例如下:
adb pull system/app/Contacts.apk F:
示例说明:将 Android 系统“system/app”目录下的文件或文件夹下载到本地“F:\”目录
下。
8、查看 bug 报告
需要查看系统生成的所有错误消息报告,可以运行 adb bugreport 指令来实现,该指令会将
Android 系统的 dumpsys、 dumpstate 与 logcat 信息都显示出来。
9、查看设备的系统信息
在 adb shell 下查看设备系统信息的具体命令。
adb shell getprop
2.8IPC产品开启网络ADB
①设置以太网IP
ifconfig eth0 10.33.6.56
保证PC可以ping通设备,有的时候需要设置策略路由:

ip rule add from all lookup main pref 18000
②开网络 adb
setprop service.adb.tcp.port 5555 && stop adbd && start adbd
③开始连接
adb connect 10.67.4.57
④断开连接
adb disconnect 192.168.1.110:5555 断开连接

3、Logcat 工具

Android 日志系统提供了记录和查看系统调试信息的功能。日志都是从各种软件和一些系统
的缓冲区中记录下来的,缓冲区可以通过 Logcat 来查看和使用。 Logcat 是调试程序用的最多的
功能。该功能主要是通过打印日志来显示程序的运行情况。由于要打印的日志量非常大,需要对其
进行过滤等操作。
3.1Logcat 命令使用
用 logcat 命令来查看系统日志缓冲区的内容:
基本格式:
[adb] logcat [] []
示例如下:
adb shell
logcat
3.2常用的日志过滤方式
控制日志输出的几种方式:
控制日志输出优先级。
示例如下:
adb shell
logcat *:W
示例说明:显示优先级为 warning 或更高的日志信息。
控制日志标签和输出优先级。
示例如下:
adb shell
logcat ActivityManager:I MyApp:D :S
示例说明:支持所有的日志信息,除了那些标签为“ActivityManager”和优先级为“Info”
以上的、标签为“MyApp”和优先级为“Debug”以上的。
只输出特定标签的日志
示例如下:
adb shell
logcat WishTV:
*:S
或者
adb shell
logcat –s WishTV
示例说明:只输出标签为 WishTV 的日志。
只输出指定优先级和标签的日志
示例如下:
adb shell
logcat WishTV:I *:S
示例说明:只输出优先级为 I,标签为 WishTV 的日志。
常用的是抓取指定log:
logcat|grep [TAG]
setprop persist.log.tag v 设置打印优先级(永久保存),v可变为f c d等
3.3查看上次 log
可以加-L 参数来打印出上次系统复位前的 logcat 信息。若出现拷机异常或者异常掉电的情
况,可通过该命令打印出上一次 Android 运行状态的日志。命令如下:
adb shell
logcat -L

4、Procrank 工具

Procrank 是 Android 自带一款调试工具,运行在设备侧的 shell 环境下,用来输出进程的内
存快照,便于有效的观察进程的内存占用情况。
包括如下内存信息:
VSS: Virtual Set Size 虚拟耗用内存大小(包含共享库占用的内存)
RSS: Resident Set Size 实际使用物理内存大小(包含共享库占用的内存)
PSS: Proportional Set Size 实际使用的物理内存大小(比例分配共享库占用的内存)
USS: Unique Set Size 进程独自占用的物理内存大小(不包含共享库占用的内存)
注意:
USS 大小代表只属于本进程正在使用的内存大小,进程被杀死后会被完整回收;
VSS/RSS 包含了共享库使用的内存,对查看单一进程内存状态没有参考价值;
PSS 是按照比例将共享内存分割后,某单一进程对共享内存区的占用情况。
PSS 不同于RSS,它只是按比例包含其所使用的共享库大小。
例如, 三个进程使用同一个占用 30 内存页的共享库。 对于三个进程中的任何一个,PSS 将只包括 10 个内存页。
PSS 是一个非常有用的数字,因为系统中全部进程以整体的方式被统计, 对于系统中的整体内存使用是一个很好的描述。如果一个进程被终止, 其PSS 中所使用的共享库大小将会重新按比例分配给剩下的仍在运行并且仍在使用该共享库的进程。此种计算方式有轻微的误差,因为当某个进程中止的时候, PSS 没有精确的表示被返还给整个系统的内存大小
USS 是单个进程的全部私有内存大小。亦即全部被该进程独占的内存大小。
USS 是一个非常非常有用的数字, 因为它揭示了运行一个特定进程的真实的内存增量大小。
如果进程被终止, USS 就是实际被返还给系统的内存大小。
USS 是针对某个进程开始有可疑内存泄露的情况,进行检测的最佳数字。
4.1使用 procrank
执行 procrank,前需要先让终端获取到 root 权限
su
命令格式:
procrank [ -W ] [ -v | -r | -p | -u | -h ]
常用指令说明 :
− -v:按照 VSS 排序
− -r:按照 RSS 排序
− -p:按照 PSS 排序
− -u:按照 USS 排序
− -R:转换为递增[递减]方式排序
− -w:只显示 working set 的统计计数
− -W:重置 working set 的统计计数
− -h:帮助
示例:
−输出内存快照:
procrank
−按照 VSS 降序排列输出内存快照:
procrank –v
默认 procrank 输出是通过 PSS 排序。
4.2检索指定内容信息
查看指定进程的内存占用状态,命令格式如下:
procrank | grep [cmdline | PID]
其中 cmdline 表示需要查找的应用程序名, PID 表示需要查找的应用进程。
输出 systemUI 进程的内存占用状态:
procrank | grep “com.android.systemui”
或者:
procrank | grep 3396
4.3跟踪进程内存状态
通过跟踪内存的占用状态,进而分析进程中是否存在内存泄露场景。使用编写脚本的方式,连
续输出进程的内存快照,通过对比 USS 段,可以了解到此进程是否内存泄露。
示例:输出进程名为 com.android.systemui 的应用内存占用状态,查看是否有泄露:
1、编写脚本 test.sh
#!/bin/bash
while true;do
adb shell procrank | grep “com.android.systemui”
sleep 1
done
2、通过 ADB 工具连接到设备后,运行此脚本: ./test.sh。如图所示:

5、Dumpsys 工具

Dumpsys 工具是 Android 系统中自带的一款调试工具,运行在设备侧的 shell 环境下,提供系统中正在运行的服务状态信息功能。 正在运行的服务是指 Android binder 机制中的服务端进程。
dumpsys 输出打印的条件:
1、只能打印已经加载到 ServiceManager 中的服务;
2、如果服务端代码中的 dump 函数没有被实现,则没有信息输出。
5.1使用 Dumpsys
1、查看 Dumpsys 帮助
作用:输出 dumpsys 帮助信息。
dumpsys -help
2、查看 Dumpsys 包含服务列表
作用:输出 dumpsys 所有可打印服务信息,开发者可以关注需要调试服务的名称。
dumpsys -l
3、输出指定服务的信息
作用:输出指定的服务的 dump 信息。
格式: dumpsys [servicename]
示例:输出服务 SurfaceFlinger 的信息,可执行命令:
dumpsys SurfaceFlinger
4、输出指定服务和应有进程的信息
作用:输出指定服务指定应用进程信息。
格式: dumpsys [servicename] [应用名]
示例:输出服务名为 meminfo,进程名为 com.android.systemui 的内存信息,执行命
令:
dumpsys meminfo com.android.systemui
注意:服务名称是大小写敏感的,并且必须输入完整服务名称。
补充:
dumpsys meminfo [pid | packageName] 查看单个进程内存详情:
例:dumpsys meminfo 443 或者 dumpsys meminfo system

Objects:这里通常会通过看Activities、AppContexts来判断是否有内存泄漏,比如刚退出应用,
查看Object中Activities是否为0,如果不为0,则有Activity没有销毁,很有可能存在泄漏。
5、dumpsys meminfo
dumpsys meminfo 展示的是系统整体内存情况, 内存项按进程进行分类:

在这里,Lost RAM = Total RAM - Free RAM - Used RAM
我们知道很多多媒体的应用使用ION来分配memory的.大多数芯片供应商是没有把这部分Memory map到process中,也就没有统计在cached中.而ION为了分配效率会把这部分用过的memory先cached以便下次使用的时候直接从cache中分配,从而加快了分配速度,提高了系统性能.而当系统的memory吃紧时,这部分cached memory会free.这往往是Lost RAM的主要来源。
5.2cat /proc/meminfo
一般我们可以首先使用cat /proc/meminfo查看内存使用情况:

6、gdb 工具

众所周知,linux上gdb是一个功能非常非常强大的工具。android是基于linux的平台,所以google在android上开发了很多类linux的调试工具,如arm-linux-androideabi-gcc-ar, arm-linux-androideabi-gcc, arm-linux-androideabi-g++ 等等,如果掌握了这些工具,就像外科医生可以做出出色的手术,我们可以定位android平台上的各种疑难杂症。
Android对于C/C++代码的调试方式一般选用gdb+gdbserver的方式,其中gdbserver运行在手机端,gdb运行在主机上(如linux)。
以调试mediaserver为例,由于mediaserver是32位的二进制程序,我们需要用32位的gdbserver来调试;如果调试64位的二进制程序 ,需要用到64位的gdbserver64来调试。
6.1设备端准备
1、查看gdbserver是否存在
首先查看手机端,system/bin 目录下是否存在 gdbserver二进制,一般userdebug版本的设备都会有这个二进制。如果没有,直接在代码的prebuilts/misc/android-arm/gdbserver目录中,找到gdbserver,push到设备中。
2、查看mediaserver进程号
ps | grep mediaserver
3、启动gdbserver
gdbserver remote:1235 --attach 233
ifconfig 看设备ip:10.67.4.57 (用于与Linux主机的连接)
其中,remote表示adb连接的主机ip地址
1235表示通讯的端口号,也可以写作其他端口号,需要保证端口号 没有被其他服务占用
233 表示 mediaserver进程号,表示我们要监听的服务是 mediaserver
6.2主机端准备
1、启动主机端的1235端口号
打开cmd,执行adb forward tcp:1235 tcp:1235
2、启动主机端的gdb
$./prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-gdb
GNU gdb (GDB) 7.6
Copyright © 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “–host=x86_64-linux-gnu --target=arm-linux-android”.
For bug reporting instructions, please see:
http://source.android.com/source/report-bugs.html.
(gdb)
3、与设备握手
target remote 10.67.4.57:1235
4、设置监控的二进制
(gdb) file ~/xxx/out/target/product/aries/symbols/system/bin/mediaserver
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from
~/xxx/.repo/out-xxx-userdebug/target/product/xxx/symbols/system/bin/mediaserver…done.
注意此处用 xxx 代替了某些路径,使用时注意使用本地对应的路径, 下同。
rk3288上路径如下:
file ~/rk3288-keda/out/target/product/rk3288_ipc/system/bin/mediaserver
5、设置带符号表的so的路径前缀
(gdb) set solib-absolute-prefix ~/xxx/out/target/product/xxx/symbols
Reading symbols from
~/xxx/out/target/product/xxx/symbols/system/lib/libmediaplayerservice.so…done.
Loaded symbols for
~/xxx/out/target/product/xxx/symbols/system/lib/libmediaplayerservice.so
Reading symbols from
~/xxx/out/target/product/xxx/symbols/system/lib/libutils.so…done.
Loaded symbols for ~/xxx/out/target/product/xxx/symbols/system/lib/libutils.so
……
以上表示将带符号的地址读取到内存中,便于之后使用。
rk3288上路径如下:
set solib-absolute-prefix ~/rk3288-keda/out/target/product/rk3288_ipc/symbols
6、设置.so的搜索路径
(gdb) set solib-search-path ~/xxx/out/target/product/xxx/symbols/system/lib
rk3288上路径如下:
set solib-search-path ~/rk3288-keda/out/target/product/rk3288_ipc/symbols/system/lib
6.3基本调试指令
1、设置断点:
比如我想查看 GenericSource中, new FileSource的代码,加一个breakpoint断点
(gdb) b GenericSource.cpp:427
Breakpoint 1 at 0xf00c3a18: file
frameworks/av/media/libmediaplayerservice/nuplayer/GenericSource.cpp, line 427.
2、查看断点:
(gdb) info b
3、在手机上点击运行时,程序走到427行会停住,gdb中输入 c 继续播放
(gdb) c
Continuing.
等等,也可以执行gdb的其他相关命令。
下面补充一下基本的线程调试方法:
1、查看是哪个线程出了问题 (gdb) info threads
2、切换到出问题的线程 (gdb) threads n
3、查看堆栈 (gdb) bt

7、strace 工具

strace常用来跟踪进程执行时的系统调用和所接收的信号。在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。
7.1strace参数
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认 为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]…
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用\.
-e trace=set
只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指 定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出 的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令
7.2命令用法
1、跟踪指定的系统调用
strace命令的-e选项仅仅被用来展示特定的系统调用(例如,open,write等等)
跟踪一下cat命令的‘open’系统调用:
strace -e open cat dead.letter
2、跟踪进程
strace不但能用在命令上,而且通过使用-p选项能用在运行的进程上
strace -p 1846(进程的PID)
3、strace的统计概要
它包括系统调用的概要,执行时间,错误等等。使用-c选项能够以一种整洁的方式展示:
strace -c ls
4、保存输出结果
通过使用-o选项可以把strace命令的输出结果保存到一个文件中
strace -o process_strace -p 3229(PID)
5、显示时间戳
使用-t选项,可以在每行的输出之前添加时间戳
strace -t ls
显示更精细的时间戳:
strace -tt ls
-ttt也可以向上面那样展示微秒级的时间戳,但是它并不是打印当前时间,而是显示自从epoch(译注:1970年1月1日00:00:00 UTC)以来的所经过的秒数。
6、相对时间
-r选项展示系统调用之间的相对时间戳
strace -r ls

7.3输出参数的含义
root@ubuntu:/usr# strace cat /dev/null
execve(“/bin/cat”, [“cat”, “/dev/null”], [/* 22 vars */]) = 0
brk(0) = 0xab1000
access(“/etc/ld.so.nohwcap”, F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000
access(“/etc/ld.so.preload”, R_OK) = -1 ENOENT (No such file or directory)

brk(0) = 0xab1000
brk(0xad2000) = 0xad2000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), …}) = 0
open(“/dev/null”, O_RDONLY) = 3
fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), …}) = 0
read(3, “”, 32768) = 0
close(3) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。
strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。
7.4strace案例
1、限制strace只跟踪特定的系统调用
如果你已经知道你要找什么,你可以让strace只跟踪一些类型的系统调用。例如,你需要看看在configure脚本里面执行的程序,你需要监视的系统调用就是execve。让strace只记录execve的调用用这个命令:
strace -f -o configure-strace.txt -e execve ./configure
2、用strace调试程序
启动KDE时出现问题:
_KDE_IceTransSocketCreateListener: failed to bind listener
_KDE_IceTransSocketUNIXCreateListener: …SocketCreateListener() failed
_KDE_IceTransMakeAllCOTSServerListeners: failed to create listener for local
Cannot establish any listening sockets DCOPServer self-test failed.
这个错误信息只是一个对KDE来说至关重要的负责进程间通信的程序无法启动,还可以知道这个错误和ICE协议(Inter Client Exchange)有关,除此之外不知道什么是KDE启动出错的原因。
采用strace看一下在启动 dcopserver时到底程序做了什么:
strace -f -F -o ~/dcop-strace.txt dcopserver
这里 -f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/dcop-strace.txt里面,dcopserver是要启动和调试的程序。
打开错误输出文件dcop-strace.txt,文件里有很多统调用的记录。在程序运行出错前的有关记录如下:
27207 mkdir(“/tmp/.ICE-unix”, 0777) = -1 EEXIST (File exists)
27207 lstat64(“/tmp/.ICE-unix”, {st_mode=S_IFDIR|S_ISVTX|0755, st_size=4096, …}) = 0
27207 unlink(“/tmp/.ICE-unix/dcop27207-1066844596”) = -1 ENOENT (No such file or directory)
27207 bind(3, {sin_family=AF_UNIX, path=“/tmp/.ICE-unix/dcop27207-1066844596”}, 38) = -1 EACCES (Permission denied)
27207 write(2, “_KDE_IceTrans”, 13) = 13
27207 write(2, "SocketCreateListener: failed to "…, 46) = 46
27207 close(3) = 0 27207 write(2, “_KDE_IceTrans”, 13) = 13
27207 write(2, “SocketUNIXCreateListener: …Soc”…, 59) = 59
27207 umask(0) = 0 27207 write(2, “_KDE_IceTrans”, 13) = 13
27207 write(2, “MakeAllCOTSServerListeners: fail”…, 64) = 64
27207 write(2, “Cannot establish any listening s”…, 39) = 39
其中第一行显示程序试图创建/tmp/.ICE-unix目录,权限为0777,这个操作因为目录已经存在而失败了。第二个系统调用(lstat64)检查 了目录状态,并显示这个目录的权限是0755,这里出现了第一个程序运行错误的线索:程序试图创建属性为0777的目录,但是已经存在了一个属性为 0755的目录。第三个系统调用(unlink)试图删除一个文件,但是这个文件并不存在。这并不奇怪,因为这个操作只是试图删掉可能存在的老文件。
但是,第四行确认了错误所在。他试图绑定到/tmp/.ICE-unix/dcop27207-1066844596,但是出现了拒绝访问错误。. ICE_unix目录的用户和组都是root,并且只有所有者具有写权限。一个非root用户无法在这个目录下面建立文件,如果把目录属性改成0777, 则前面的操作有可能可以执行,而这正是第一步错误出现时进行过的操作。
所以运行了chmod 0777 /tmp/.ICE-unix之后KDE就可以正常启动了,问题解决了,用strace进行跟踪调试只需要花很短的几分钟时间跟踪程序运行,然后检查并分析输出文件。
3、解决库依赖问题
whoami程序会给出你自己的用户名,这个程序在一些需要知道运行程序的真正用户的脚本程序里面非常有用,whoami的一个示例输出如下:

whoami

root
假设因为某种原因在升级glibc的过程中负责用户名和用户ID转换的库NSS丢失,我们可以通过把nss库改名来模拟这个环境:

mv /lib/libnss_files.so.2 /lib/libnss_files.so.2.backup

whoami

whoami: cannot find username for UID 0
这里你可以看到,运行whoami时出现了错误,ldd程序的输出不会提供有用的帮助:

ldd /usr/bin/whoami

libc.so.6 => /lib/libc.so.6 (0x4001f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
我们只会看到whoami依赖Libc.so.6和ld-linux.so.2,它没有给出运行whoami所必须的其他库。这里时用strace跟踪 whoami时的输出:
strace -o whoami-strace.txt whoami

open(“/lib/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
open(“/lib/i686/mmx/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
stat64(“/lib/i686/mmx”, 0xbffff190) = -1 ENOENT (No such file or directory)
open(“/lib/i686/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
stat64(“/lib/i686”, 0xbffff190) = -1 ENOENT (No such file or directory)
open(“/lib/mmx/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
stat64(“/lib/mmx”, 0xbffff190) = -1 ENOENT (No such file or directory)
open(“/lib/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
stat64(“/lib”, {st_mode=S_IFDIR|0755, st_size=2352, …}) = 0
open(“/usr/lib/i686/mmx/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
stat64(“/usr/lib/i686/mmx”, 0xbffff190) = -1 ENOENT (No such file or directory)
open(“/usr/lib/i686/libnss_files.so.2”, O_RDONLY) = -1 ENOENT (No such file or directory)
你可以发现在不同目录下面查找libnss.so.2的尝试,但是都失败了。如果没有strace这样的工具,很难发现这个错误是由于缺少动态库造成的。现在只需要找到libnss.so.2并把它放回到正确的位置就可以了。

8、ftrace工具

ftrace 是内建于 Linux 内核的跟踪工具,从 2.6.27 开始加入主流内核。使用 ftrace 可以调试或者分析内核中发生的事情。ftrace 提供了不同的跟踪器,以用于不同的场合,比如跟踪内核函数调用、对上下文切换进行跟踪、查看中断被关闭的时长、跟踪内核态中的延迟以及性能问题等。系统开发人员可以使用 ftrace 对内核进行跟踪调试,以找到内核中出现的问题的根源,方便对其进行修复。另外,对内核感兴趣的读者还可以通过 ftrace 来观察内核中发生的活动,了解内核的工作机制。
8.1内核支持
使用 ftrace ,首先要将其编译进内核。内核源码目录下的 /kernel/kernel/trace/Makefile 文件给出了 ftrace 相关的编译选项:

ftrace 相关的配置选项比较多,针对不同的跟踪器有各自对应的配置选项。不同的选项有不同的依赖关系,内核源码目录下的 kernel/trace/Kconfig 文件描述了这些依赖关系。我们可以参考 Makefile 文件和 Konfig 文件,选中自己所需要的跟踪器。
通常在配置内核时,使用 make menuconfig 会更直观一些:
1、Kernel hacking

2、Tracers

进入 Tracers 菜单下,可以看到内核支持的跟踪器列表。我们可以根据自己的需要选中特定的跟踪器。
3、内核支持的跟踪器列表

ftrace 通过 debugfs 向用户态提供了访问接口,所以还需要将debugfs编译进内核。激活对debugfs 的支持,可以直接编辑内核配置文件 .config,设置CONFIG_DEBUG_FS=y或者在 make menuconfig 时到 Kernel hacking 菜单下选中对 debugfs 文件系统的支持。debugfs 编译选项如下:

配置完成后,编译安装新内核,然后启动到新内核。 注意,激活 ftrace 支持后,编译内核时会使用编译器的 -pg 选项,这是在 kernel/kernel/trace/Makefile 文件中定义的:

CC_FLAGS_FTRACE是在kernel/ Makefile下定义的。
使用 -pg 选项会在编译得到的内核映像中加入大量的调试信息。一般情况下,只是在开发测试阶段激活 ftrace 支持,以调试内核,修复 bug 。最终用于发行版的内核则会关闭 -pg 选项,也就无法使用 ftrace。
ftrace 通过debugfs 向用户态提供访问接口。配置内核时激活 debugfs 后会创建目录 /sys/kernel/debug ,debugfs 文件系统就是挂载到该目录。要挂载该目录,需要将如下内容添加到 /etc/fstab 文件:
debugfs /sys/kernel/debug debugfs defaults 0 0
或者可以在运行时挂载:
mount -t debugfs nodev /sys/kernel/debug
激活内核对 ftrace 的支持后会在debugfs 下创建一个 tracing 目录 /sys/kernel/debug/tracing 。该目录下包含了 ftrace 的控制和输出文件。根据编译内核时针对 ftrace 的设定不同,该目录下实际显示的文件和目录与这里也会不同。
rk3288是在device/rockchip/common/init.rockchip.rc脚本里将debugfs 文件系统进行挂载的:

/sys/kernel/debug/tracing下的文件:

8.2ftrace 的数据文件
/sys/kernel/debug/tracing 目录下文件和目录比较多,有些是各种跟踪器共享使用的,有些是特定于某个跟踪器使用的。在操作这些数据文件时,通常使用 echo 命令来修改其值,也可以在程序中通过文件读写相关的函数来操作这些文件的值。下面只对部分文件进行描述,我们可以参考内核源码包中 Documentation/trace 目录下的文档以及 kernel/trace 下的源文件以了解其余文件的用途。
README文件提供了一个简短的使用说明,展示了 ftrace 的操作命令序列。可以通过 cat 命令查看该文件以了解概要的操作流程。
current_tracer用于设置或显示当前使用的跟踪器;使用 echo 将跟踪器名字写入该文件可以切换到不同的跟踪器。系统启动后,其缺省值为 nop ,即不做任何跟踪操作。在执行完一段跟踪任务后,可以通过向该文件写入 nop 来重置跟踪器。
available_tracers记录了当前编译进内核的跟踪器的列表,可以通过 cat 查看其内容;其包含的跟踪器与内核支持的跟踪器列表所激活的选项是对应的。写 current_tracer 文件时用到的跟踪器名字必须在该文件列出的跟踪器名字列表中。
trace文件提供了查看获取到的跟踪信息的接口。可以通过 cat 等命令查看该文件以查看跟踪到的内核活动记录,也可以将其内容保存为记录文件以备后续查看。
tracing_enabled用于控制 current_tracer 中的跟踪器是否可以跟踪内核函数的调用情况。写入 0 会关闭跟踪活动,写入 1 则激活跟踪功能;其缺省值为 1 。
set_graph_function设置要清晰显示调用关系的函数,显示的信息结构类似于 C 语言代码,这样在分析内核运作流程时会更加直观一些。在使用 function_graph 跟踪器时使用;缺省为对所有函数都生成调用关系序列,可以通过写该文件来指定需要特别关注的函数。
buffer_size_kb用于设置单个 CPU 所使用的跟踪缓存的大小。跟踪器会将跟踪到的信息写入缓存,每个 CPU 的跟踪缓存是一样大的。跟踪缓存实现为环形缓冲区的形式,如果跟踪到的信息太多,则旧的信息会被新的跟踪信息覆盖掉。注意,要更改该文件的值需要先将 current_tracer 设置为 nop 才可以。
tracing_on用于控制跟踪的暂停。有时候在观察到某些事件时想暂时关闭跟踪,可以将 0 写入该文件以停止跟踪,这样跟踪缓冲区中比较新的部分是与所关注的事件相关的;写入 1 可以继续跟踪。
available_filter_functions记录了当前可以跟踪的内核函数。对于不在该文件中列出的函数,无法跟踪其活动。
set_ftrace_filter和 set_ftrace_notrace在编译内核时配置了动态 ftrace (选中 CONFIG_DYNAMIC_FTRACE 选项)后使用。前者用于显示指定要跟踪的函数,后者则作用相反,用于指定不跟踪的函数。如果一个函数名同时出现在这两个文件中,则这个函数的执行状况不会被跟踪。这些文件还支持简单形式的含有通配符的表达式,这样可以用一个表达式一次指定多个目标函数;具体使用在后续文章中会有描述。注意,要写入这两个文件的函数名必须可以在文件 available_filter_functions 中看到。缺省为可以跟踪所有内核函数,文件 set_ftrace_notrace 的值则为空。
8.3ftrace 跟踪器
ftrace 当前包含多个跟踪器,用于跟踪不同类型的信息,比如进程调度、中断关闭等。可以查看文件 available_tracers 获取内核当前支持的跟踪器列表。在编译内核时,也可以看到内核支持的跟踪器对应的选项。
nop跟踪器不会跟踪任何内核活动,将 nop 写入 current_tracer 文件可以删除之前所使用的跟踪器,并清空之前收集到的跟踪信息,即刷新 trace 文件。
function跟踪器可以跟踪内核函数的执行情况;可以通过文件 set_ftrace_filter 显示指定要跟踪的函数。
function_graph跟踪器可以显示类似 C 源码的函数调用关系图,这样查看起来比较直观一些;可以通过文件 set_grapch_function 显示指定要生成调用流程图的函数。
sched_switch跟踪器可以对内核中的进程调度活动进行跟踪。
irqsoff跟踪器和 preemptoff跟踪器分别跟踪关闭中断的代码和禁止进程抢占的代码,并记录关闭的最大时长,preemptirqsoff跟踪器则可以看做它们的组合。
ftrace 还支持其它一些跟踪器,比如 initcall、ksym_tracer、mmiotrace、sysprof 等。ftrace 框架支持扩展添加新的跟踪器。我们可以参考内核源码包中 Documentation/trace 目录下的文档以及 kernel/trace 下的源文件,以了解其它跟踪器的用途和如何添加新的跟踪器。
8.4ftrace 操作概述
使用 ftrace 提供的跟踪器来调试或者分析内核时需要如下操作:
切换到目录 /sys/kernel/debug/tracing/ 下
查看 available_tracers 文件,获取当前内核支持的跟踪器列表
关闭 ftrace 跟踪,即将 0 写入文件 tracing_enabled
激活 ftrace_enabled ,否则 function 跟踪器的行为类似于 nop;另外,激活该选项还可以让一些跟踪器比如 irqsoff 获取更丰富的信息。建议使用 ftrace 时将其激活。要激活 ftrace_enabled ,可以通过 proc 文件系统接口来设置:
echo 1 > /proc/sys/kernel/ftrace_enabled
将所选择的跟踪器的名字写入文件 current_tracer
将要跟踪的函数写入文件 set_ftrace_filter ,将不希望跟踪的函数写入文件 set_ftrace_notrace。通常直接操作文件 set_ftrace_filter 就可以了
激活 ftrace 跟踪,即将 1 写入文件 tracing_enabled。还要确保文件 tracing_on 的值也为 1,该文件可以控制跟踪的暂停
如果是对应用程序进行分析的话,启动应用程序的执行,ftrace 会跟踪应用程序运行期间内核的运作情况
通过将 0 写入文件 tracing_on 来暂停跟踪信息的记录,此时跟踪器还在跟踪内核的运行,只是不再向文件 trace 中写入跟踪信息;或者将 0 写入文件 tracing_enabled 来关闭跟踪
查看文件 trace 获取跟踪信息,对内核的运行进行分析调试
接下来将对跟踪器的使用以及跟踪信息的格式通过实例加以讲解。
1、function 跟踪器
function 跟踪器可以跟踪内核函数的调用情况,可用于调试或者分析bug ,还可用于了解和观察 Linux 内核的执行过程。下面给出了使用 function 跟踪器的示例:
[root@linux tracing]# pwd
/sys/kernel/debug/tracing
[root@linux tracing]# echo 0 > tracing_enabled
[root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
[root@linux tracing]# echo function > current_tracer
[root@linux tracing]# echo 1 > tracing_on
[root@linux tracing]# echo 1 > tracing_enabled
让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
[root@linux tracing]# echo 0 > tracing_enabled
[root@linux tracing]# cat trace | head -10

tracer: function

TASK-PID CPU# TIMESTAMP FUNCTION

| | | | |

     <idle>-0     [000] 20654.426521: _raw_spin_lock <-scheduler_tick 
     <idle>-0     [000] 20654.426522: task_tick_idle <-scheduler_tick 
     <idle>-0     [000] 20654.426522: cpumask_weight <-scheduler_tick 
     <idle>-0     [000] 20654.426523: cpumask_weight <-scheduler_tick 
     <idle>-0     [000] 20654.426523: run_posix_cpu_timers <-update_process_times 

-0 [000] 20654.426524: hrtimer_forward <-tick_sched_timer
trace 文件给出的信息格式很清晰。首先,字段“tracer:”给出了当前所使用的跟踪器的名字,这里为 function 跟踪器。然后是跟踪信息记录的格式,TASK 字段对应任务的名字,PID 字段则给出了任务的进程 ID,字段 CPU# 表示运行被跟踪函数的 CPU 号,这里可以看到 idle 进程运行在 0 号 CPU 上,其进程 ID 是 0 ;字段 TIMESTAMP 是时间戳,其格式为“.”,表示执行该函数时对应的时间戳;FUNCTION 一列则给出了被跟踪的函数,函数的调用者通过符号 “<-” 标明,这样可以观察到函数的调用关系。
2、function_graph 跟踪器
在 function 跟踪器给出的信息中,可以通过 FUNCTION 列中的符号 “<-” 来查看函数调用关系,但是由于中间会混合不同函数的调用,导致看起来非常混乱,十分不方便。function_graph 跟踪器则可以提供类似 C 代码的函数调用关系信息。通过写文件 set_graph_function 可以显示指定要生成调用关系的函数,缺省会对所有可跟踪的内核函数生成函数调用关系图。下面给出了使用 function_grapch 跟踪器的示例,示例中将内核函数 __do_fault 作为观察对象,该函数在内核运作过程中会被频繁调用。

在文件 trace 的输出信息中,首先给出的也是当前跟踪器的名字,这里是 function_graph 。接下来是详细的跟踪信息,可以看到,函数的调用关系以类似 C 代码的形式组织。
CPU 字段给出了执行函数的 CPU 号,本例中都为 1 号 CPU。DURATION 字段给出了函数执行的时间长度,以 us 为单位。FUNCTION CALLS 则给出了调用的函数,并显示了调用流程。注意,对于不调用其它函数的函数,其对应行以“;”结尾,而且对应的 DURATION 字段给出其运行时长;对于调用其它函数的函数,则在其“}”对应行给出了运行时长,该时间是一个累加值,包括了其内部调用的函数的执行时长。DURATION 字段给出的时长并不是精确的,它还包含了执行 ftrace 自身的代码所耗费的时间,所以示例中将内部函数时长累加得到的结果会与对应的外围调用函数的执行时长并不一致;不过通过该字段还是可以大致了解函数在时间上的运行开销的。最后通过 echo 命令重置了文件 set_graph_function 。
3、sched_switch 跟踪器
sched_switch 跟踪器可以对进程的调度切换以及之间的唤醒操作进行跟踪:

在 sched_swich 跟踪器获取的跟踪信息中记录了进程间的唤醒操作和调度切换信息,可以通过符号‘ + ’和‘ ==> ’区分;唤醒操作记录给出了当前进程唤醒运行的进程,进程调度切换记录中显示了接替当前进程运行的后续进程。

描述进程状态的格式为“Task-PID:Priority:Task-State”。以示例跟踪信息中的第一条跟踪记录为例,可以看到进程 bash 的 PID 为 1408 ,其对应的内核态优先级为 120 ,当前状态为 S(可中断睡眠状态),当前 bash 并没有唤醒其它进程;从第 3 条记录可以看到,进程 bash 将进程 events/0 唤醒,而在第 4 条记录中发生了进程调度,进程 bash 切换到进程 events/0 执行。
在 Linux 内核中,进程的状态在内核头文件 include/linux/sched.h 中定义,包括可运行状态 TASK_RUNNING(对应跟踪信息中的符号‘ R ’)、可中断阻塞状态 TASK_INTERRUPTIBLE(对应跟踪信息中的符号‘ S ’)等。同时该头文件也定义了用户态进程所使用的优先级的范围,最小值为 MAX_USER_RT_PRIO(值为 100 ),最大值为 MAX_PRIO - 1(对应值为 139 ),缺省为 DEFAULT_PRIO(值为 120 );在本例中,进程优先级都是缺省值 120 。
4、irqsoff 跟踪器
当关闭中断时,CPU 会延迟对设备的状态变化做出反应,有时候这样做会对系统性能造成比较大的影响。irqsoff 跟踪器可以对中断被关闭的状况进行跟踪,有助于发现导致较大延迟的代码;当出现最大延迟时,跟踪器会记录导致延迟的跟踪信息,文件 tracing_max_latency 则记录中断被关闭的最大延时。
从以下的输出信息中,可以看到当前 irqsoff 延迟跟踪器的版本信息。接下来是最大延迟时间,以 us 为单位,本例中为 34380 us ,查看文件 tracing_max_latency 也可以获取该值。从“task:”字段可以知道延迟发生时正在运行的进程为 idle(其 pid 为 0 )。中断的关闭操作是在函数 reschedule_interrupt 中进行的,由“=> started at:”标识,函数 restore_all_ontrace 重新激活了中断,由“=> ended at:”标识;中断关闭的最大延迟发生在函数 trace_hardirqs_on_thunk 中,这个可以从最大延迟时间所在的记录项看到,也可以从延迟记录信息中最后的“=>”标识所对应的记录行知道这一点。

在输出信息中,irqs-off、need_resched 等字段对应于进程结构 struct task_struct 的字段或者状态标志,可以从头文件 arch//include/asm/thread_info.h 中查看进程支持的状态标志,include/linux/sched.h 则给出了结构 struct task_struct 的定义。其中,irqs-off 字段显示是否中断被禁止,为‘ d ’表示中断被禁止;need_resched 字段显示为‘ N ’表示设置了进程状态标志 TIF_NEED_RESCHED。hardirq/softirq 字段表示当前是否发生硬件中断 / 软中断;preempt-depth 表示是否禁止进程抢占,例如在持有自旋锁的情况下进程是不能被抢占的,本例中进程 idle 是可以被其它进程抢占的。结构 struct task_struct 中的 lock_depth 字段是与大内核锁相关的,而最近的内核开发工作中有一部分是与移除大内核锁相关的,这里对该字段不再加以描述。
另外,还有用于跟踪禁止进程抢占的跟踪器 preemptoff 和跟踪中断 / 抢占禁止的跟踪器 preemptirqsoff,它们的使用方式与输出信息格式与 irqsoff 跟踪器类似,这里不再赘述。
5、跟踪指定模块中的函数
前面提过,通过文件 set_ftrace_filter 可以指定要跟踪的函数,缺省目标为所有可跟踪的内核函数;可以将感兴趣的函数通过 echo 写入该文件。为了方便使用,set_ftrace_filter 文件还支持简单格式的通配符。
begin选择所有名字以 begin 字串开头的函数
middle选择所有名字中包含 middle 字串的函数
end选择所有名字以 end 字串结尾的函数
需要注意的是,这三种形式不能组合使用,比如“beginmiddleend”实际的效果与“begin”相同。另外,使用通配符表达式时,需要用单引号将其括起来,如果使用双引号,shell 可能会对字符‘ * ’进行扩展,这样最终跟踪的对象可能与目标函数不一样。
通过该文件还可以指定属于特定模块的函数,这要用到 mod 指令。指定模块的格式为:
echo ‘:mod:[module_name]’ > set_ftrace_filter
下面给出了一个指定跟踪模块 ipv6 中的函数的例子。可以看到,指定跟踪模块 ipv6 中的函数会将文件 set_ftrace_filter 的内容设置为只包含该模块中的函数。
[root@linux tracing]# pwd
/sys/kernel/debug/tracing
[root@linux tracing]# echo ‘:mod:ipv6’ > set_ftrace_filter
[root@linux tracing]# cat set_ftrace_filter | head -5
ipv6_opt_accepted
inet6_net_exit
ipv6_gro_complete
inet6_create
ipv6_addr_copy

8.5ftrace 提供的工具函数
内核头文件 include/linux/kernel.h 中描述了 ftrace 提供的工具函数的原型,这些函数包括 trace_printk、tracing_on/tracing_off 等。
1、使用 trace_printk 打印跟踪信息
ftrace 提供了一个用于向 ftrace 跟踪缓冲区输出跟踪信息的工具函数,叫做 trace_printk(),它的使用方式与 printk() 类似。可以通过 trace 文件读取该函数的输出。从头文件 include/linux/kernel.h 中可以看到,在激活配置 CONFIG_TRACING 后,trace_printk() 定义为宏:
#define trace_printk(fmt, args…) \

下面通过一个示例模块 ftrace_demo 来演示如何使用 trace_printk() 向跟踪缓冲区输出信息,以及如何查看这些信息。这里的示例模块程序中仅提供了初始化和退出函数,这样我们不会因为需要为模块创建必要的访问接口比如设备文件而分散注意力。注意,编译模块时要加入 -pg 选项。

示例模块非常简单,仅仅是在模块初始化函数和退出函数中输出信息。接下来要对模块的运行进行跟踪:

在这个例子中,使用 mod 指令显式指定跟踪模块 ftrace_demo 中的函数,这需要提前加载该模块,否则在写文件 set_ftrace_filter 时会因为找不到该模块报错。这样在第一次加载模块时,其初始化函数 ftrace_demo_init 中调用 trace_printk 打印的语句就跟踪不到了。因此这里会将其卸载,然后激活跟踪,再重新进行模块 ftrace_demo 的加载与卸载操作。最终可以从文件 trace 中看到模块在初始化和退出时调用 trace_printk() 输出的信息。
这里仅仅是为了以简单的模块进行演示,故只定义了模块的 init/exit 函数,重复加载模块也只是为了获取初始化函数输出的跟踪信息。实践中,可以在模块的功能函数中加入对 trace_printk 的调用,这样可以记录模块的运作情况,然后对其特定功能进行调试优化。还可以将对 trace_printk() 的调用通过宏来控制编译,这样可以在调试时将其开启,在最终发布时将其关闭。
2、使用 tracing_on/tracing_off 控制跟踪信息的记录
在跟踪过程中,有时候在检测到某些事件发生时,想要停止跟踪信息的记录,这样,跟踪缓冲区中较新的数据是与该事件有关的。在用户态,可以通过向文件 tracing_on 写入 0 来停止记录跟踪信息,写入 1 会继续记录跟踪信息。而在内核代码中,可以通过函数 tracing_on() 和 tracing_off() 来做到这一点,它们的行为类似于对 /sys/kernel/debug/tracing 下的文件 tracing_on 分别执行写 1 和 写 0 的操作。使用这两个函数,会对跟踪信息的记录控制地更准确一些,这是因为在用户态写文件 tracing_on 到实际暂停跟踪,中间由于上下文切换、系统调度控制等可能已经经过较长的时间,这样会积累大量的跟踪信息,而感兴趣的那部分可能会被覆盖掉了。
使用 tracing_off 的模块 ftrace_demo:

下面对其进行跟踪:

在这个例子中,跟踪开始之前需要确保 tracing_on 的值为 1。跟踪开始后,加载模块 ftrace_demo,其初始化方法 ftrace_demo_init 被调用,该方法会调用 tracing_off() 函数来暂停跟踪信息的记录,这时文件 tracing_on 的值被代码设置为 0。查看文件 trace,可以看到 ftrace_demo_init 相关的记录位于跟踪信息的末端,这是因为从调用 trace_off() 到其生效需要一段时间,这段时间中的内核活动会被记录下来;相比从用户态读写 tracing_on 文件,这段时间开销要小了许多。卸载模块时的情况与此类似。从这里可以看到,在代码中使用 tracing_off() 可以控制将感兴趣的信息保存在跟踪缓冲区的末端位置,不会很快被新的信息所覆盖,便于及时查看。
实际代码中,可以通过特定条件(比如检测到某种异常状况,等等)来控制跟踪信息的记录,函数的使用方式类似如下的形式:
if (condition)
tracing_on() or tracing_off()
跟踪模块运行状况时,使用 ftrace 命令操作序列在用户态进行必要的设置,而在代码中则可以通过 traceing_on() 控制在进入特定代码区域时开启跟踪信息,并在遇到某些条件时通过 tracing_off() 暂停;我们可以在查看完感兴趣的信息后,将 1 写入 tracing_on 文件以继续记录跟踪信息。实践中,可以通过宏来控制是否将对这些函数的调用编译进内核模块,这样可以在调试时将其开启,在最终发布时将其关闭。
用户态的应用程序可以通过直接读写文件 tracing_on 来控制记录跟踪信息的暂停状态,以便了解应用程序运行期间内核中发生的活动。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值