Android adb bugreport工具分析和使用- IT閱讀 - ITREAD01 ...
文章推薦指數: 80 %
adb bugreport > bugreport.txt. 即可生成bugreport文件。
但是有一個問題是,這個生成的文件有的時候異常龐大,能夠達到15M+,想一想對於一個txt文本 ...
IT技術
Androidadbbugreport工具分析和使用
IT技術
·
發表2016-10-11
bugreport是什麽,怎麽用?
android系統想要成為一個功能完備,生態繁榮的操作系統,那就必須提供完整的應用開發環境。
而在應用開發中,app程序的調試分析是日常生產中進程會進行的工作。
Android為了方便開發人員分析整個系統平臺和某個app在運行一段時間之內的所有信息,專門開發了bugreport工具。
這個工具使用起來十分簡單,只要在終端執行(linux或者win):
adbbugreport>bugreport.txt
即可生成bugreport文件。
但是有一個問題是,這個生成的文件有的時候異常龐大,能夠達到15M+,想一想對於一個txt文本格式的文件內容長度達到了15M+是一個什麽概念,如果使用文本工具打開查看將是一個噩夢。
因此Google針對android5.0(api21)以上的系統開發了一個叫做batteryhistorian的分析工具,這個工具就是用來解析這個txt文本文件,然後使用web圖形的形式展現出來,這樣出來的效果更加人性化,更加可讀。
它的基本界面像下面這個樣子:
目前google已經將betteryhistorian開源了,開源項目的地址:
https://github.com/google/battery-historian
google寫了一個比較詳細的說明文檔,大家可以自行查閱一下。
這個工具可以查看以下信息:
Brightness
CPUrunning
Chargingon
Chargingstatus
Health
JobScheduler
Kernelonlyuptime
Level
packageactive
Partialwakelock
Phonescanning
Phonestate
Plug
Plugged
Screen
Temperature
Topapp
Voltage
WIFIon
Wifirunning
Wifisupplicant
數據還是比較詳細的。
當然,同樣的bugreport數據也可以有不同的解析和閱讀方式,你如果不太喜歡google的batteryhistorian的話,你還有別的選擇,那就是選擇Sony開源的ChkBugReport,這個工具提供了不同於batteryhistorian的視角去解讀bugreport文件,見面簡單明了:
這個項目的文檔:
http://developer.sonymobile.com/2012/01/25/new-bugreport-analysis-tool-released-as-open-source/
開源地址首頁:
https://github.com/sonyxperiadev/ChkBugReport
這裏說明一下,筆者使用過ChkBugReport這個工具,感覺很不錯,最好結合google的batteryhistorian;另外ChkBugReport這個工具還有一點bug,不過不影響使用。
bugreport的原理是什麽?
下面我們簡要分析一下adbbugreport運行的原理。
我們知道,使用bugreport只要執行adbbugreport命令就可以了,因此我們的分析肯定是從adbd這個daemon進程開始,我們查看這個進程的代碼的時候發現這裏處理了bugreport選項:
[email protected]/core/adb/commandline.cpp
我們可以清楚地看到,這裏判斷如果附帶的參數是bugreport的話,那就直接調用send_shell_command函數處理,這個函數的代碼比較簡單,我們就不分析了,這個函數的功能就是使用shell執行參數中的命令,因此我們這裏相當於執行了bugreport命令。
在android設備中,bugreport命令存在於system/bin/目錄下,這是一個可執行文件,所以我們要查看這個可執行文件實現的地方,它的實現代碼在/frameworks/native/cmds/bugreport/目錄下:
我們看到,bugreport的實現是比較簡單的,只有一個Android.mk和一個cpp實現代碼,我們先看一下Android.mk文件:
這裏我們看到該目錄下的代碼會被編譯成一個名字叫做bugreport的可執行文件,這就是我們想要找的。
現在我們看一下bugreport.cpp文件的實現,這個文件中代碼比較簡單,只有一個main函數:
//Thisprogramwilltriggerthedumpstateservicetostartacallto
//dumpstate,thenconnecttothedumpstatelocalclienttoreadthe
//output.Allofthedumpstateoutputiswrittentostdout,including
//anyerrorsencounteredwhilereading/writingtheoutput.
intmain(){
//Startthedumpstateservice.
property_set("ctl.start","dumpstate");
//Socketwillnotbeavailableuntilservicestarts.
ints;
for(inti=0;i<20;i++){
s=socket_local_client("dumpstate",ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
if(s>=0)
break;
//Tryagainin1second.
sleep(1);
}
if(s==-1){
printf("Failedtoconnecttodumpstateservice:%s\n",strerror(errno));
return1;
}
//Setatimeoutsothatifnothingisreadin3minutes,we'llstop
//readingandquit.Notimeoutindumpstateislongerthan60seconds,
//sothisgiveslotsofleewayincaseofunforeseentimeouts.
structtimevaltv;
tv.tv_sec=3*60;
tv.tv_usec=0;
if(setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv))==-1){
printf("WARNING:Cannotsetsockettimeout:%s\n",strerror(errno));
}
while(1){
charbuffer[65536];
ssize_tbytes_read=TEMP_FAILURE_RETRY(read(s,buffer,sizeof(buffer)));
if(bytes_read==0){
break;
}elseif(bytes_read==-1){
//EAGAINreallymeanstimeout,sochangetheerrno.
if(errno==EAGAIN){
errno=ETIMEDOUT;
}
printf("\nBugreportreadterminatedabnormally(%s).\n",strerror(errno));
break;
}
ssize_tbytes_to_send=bytes_read;
ssize_tbytes_written;
do{
bytes_written=TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
buffer+bytes_read-bytes_to_send,
bytes_to_send));
if(bytes_written==-1){
printf("Failedtowritedatatostdout:read%zd,tryingtosend%zd(%s)\n",
bytes_read,bytes_to_send,strerror(errno));
return1;
}
bytes_to_send-=bytes_written;
}while(bytes_written!=0&&bytes_to_send>0);
}
close(s);
return0;
}
這裏的代碼非常簡單,主要的邏輯就是:
1.啟動dumpstateservice
2.和dumpstateservice建立socket鏈接
3.從socket中讀取數據,並且答應到stdout中
4.讀取完成之後關閉socket,然後退出
因此,我們分析的重點需要轉移到dumpstate中了。
這裏說明一下,前面啟動dumpstateservice的方法是使用系統屬性來實現,這個屬性的改變消息會被init進程收到,然後init進程會啟動dumpstate這個服務。
dumpstate其實也是一個可執行文件,也存在於system/bin目錄下。
現在我們明白了,其實bugreport就是dumpstate,只是bugreport將dumpstate包裝了一下而已。
現在我們需要分析一下dumpstate的實現,它的實現代碼在:frameworks/native/cmds/dumpstate目錄下,我們看下這個目錄下的代碼結構:
這裏的代碼也是十分簡單,只要少數的幾個實現文件,其中main函數在dumpstate.c文件中,這個main函數我們這裏不詳細分析了,總結下它的主要工作:
1.根據啟動參數,初始化相關資源
2.如果啟動參數中帶有-s的話(init啟動會加上這個參數),就表示使用socket,那麽就啟動socket,並且在這個socket中等待鏈接。
3.如果client端(也就是bugreport進程)鏈接成功,那就初始化所要用到的內存,並且設置優先級為較高優先級,防止被OOM幹掉。
4.然後使用vibrator震動一下(如果設備有這個硬件的話),提示用戶開始截取log了
5.調用dumpstate函數開始真正的dump工作
6.dump完成之後再次調用vibrator震動3次,提示用戶dump完成。
現在我們看下dumpstate函數的實現:
/*dumpsthecurrentsystemstatetostdout*/
staticvoiddumpstate(){
unsignedlongtimeout;
time_tnow=time(NULL);
charbuild[PROPERTY_VALUE_MAX],fingerprint[PROPERTY_VALUE_MAX];
charradio[PROPERTY_VALUE_MAX],bootloader[PROPERTY_VALUE_MAX];
charNetwork[PROPERTY_VALUE_MAX],date[80];
charbuild_type[PROPERTY_VALUE_MAX];
property_get("ro.build.display.id",build,"(unknown)");
property_get("ro.build.fingerprint",fingerprint,"(unknown)");
property_get("ro.build.type",build_type,"(unknown)");
property_get("ro.baseband",radio,"(unknown)");
property_get("ro.bootloader",bootloader,"(unknown)");
property_get("gsm.operator.alpha",network,"(unknown)");
strftime(date,sizeof(date),"%Y-%m-%d%H:%M:%S",localtime(&now));
printf("========================================================\n");
printf("==dumpstate:%s\n",date);
printf("========================================================\n");
printf("\n");
printf("Build:%s\n",build);
printf("Buildfingerprint:'%s'\n",fingerprint);/*formatisimportantforothertools*/
printf("Bootloader:%s\n",bootloader);
printf("Radio:%s\n",radio);
printf("Network:%s\n",network);
printf("Kernel:");
dump_file(NULL,"/proc/version");
printf("Commandline:%s\n",strtok(cmdline_buf,"\n"));
printf("\n");
dump_dev_files("TRUSTYVERSION","/sys/bus/platform/drivers/trusty","trusty_version");
run_command("UPTIME",10,"uptime",NULL);
dump_files("UPTIMEMMCPERF",mmcblk0,skip_not_stat,dump_stat_from_fd);
dump_file("MEMORYINFO","/proc/meminfo");
run_command("CPUINFO",10,"top","-n","1","-d","1","-m","30","-t",NULL);
run_command("PROCRANK",20,"procrank",NULL);
dump_file("VIRTUALMEMORYSTATS","/proc/vmstat");
dump_file("VMALLOCINFO","/proc/vmallocinfo");
dump_file("SLABINFO","/proc/slabinfo");
dump_file("ZONEINFO","/proc/zoneinfo");
dump_file("PAGETYPEINFO","/proc/pagetypeinfo");
dump_file("BUDDYINFO","/proc/buddyinfo");
dump_file("FRAGMENTATIONINFO","/d/extfrag/unusable_index");
dump_file("KERNELWAKELOCKS","/proc/wakelocks");
dump_file("KERNELWAKESOURCES","/d/wakeup_sources");
dump_file("KERNELCPUFREQ","/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
dump_file("KERNELSYNC","/d/sync");
run_command("PROCESSES",10,"ps","-P",NULL);
run_command("PROCESSESANDTHREADS",10,"ps","-t","-p","-P",NULL);
run_command("PROCESSES(SELINUXLABELS)",10,"ps","-Z",NULL);
run_command("LIBRANK",10,"librank",NULL);
do_dmesg();
run_command("LISTOFOPENFILES",10,SU_PATH,"root","lsof",NULL);
for_each_pid(do_showmap,"SMAPSOFALLPROCESSES");
for_each_tid(show_wchan,"BLOCKEDPROCESSWAIT-CHANNELS");
if(screenshot_path[0]){
ALOGI("takingscreenshot\n");
run_command(NULL,10,"/system/bin/screencap","-p",screenshot_path,NULL);
ALOGI("wrotescreenshot:%s\n",screenshot_path);
}
//dump_file("EVENTLOGTAGS","/etc/event-log-tags");
//calculatetimeout
timeout=logcat_timeout("main")+logcat_timeout("system")+logcat_timeout("crash");
if(timeout<20000){
timeout=20000;
}
run_command("SYSTEMLOG",timeout/1000,"logcat","-v","threadtime","-d","*:v",NULL);
timeout=logcat_timeout("events");
if(timeout<20000){
timeout=20000;
}
run_command("EVENTLOG",timeout/1000,"logcat","-b","events","-v","threadtime","-d","*:v",NULL);
timeout=logcat_timeout("radio");
if(timeout<20000){
timeout=20000;
}
run_command("RADIOLOG",timeout/1000,"logcat","-b","radio","-v","threadtime","-d","*:v",NULL);
run_command("LOGSTATISTICS",10,"logcat","-b","all","-S",NULL);
/*showthetraceswecollectedinmain(),ifthatwasdone*/
if(dump_traces_path!=NULL){
dump_file("VMTRACESJUSTNOW",dump_traces_path);
}
/*onlyshowANRtracesifthey'relessthan15minutesold*/
structstatst;
charanr_traces_path[PATH_MAX];
property_get("dalvik.vm.stack-trace-file",anr_traces_path,"");
if(!anr_traces_path[0]){
printf("***NOVMTRACESFILEDEFINED(dalvik.vm.stack-trace-file)\n\n");
}else{
intfd=TEMP_FAILURE_RETRY(open(anr_traces_path,
O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK));
if(fd<0){
printf("***NOANRVMTRACESFILE(%s):%s\n\n",anr_traces_path,strerror(errno));
}else{
dump_file_from_fd("VMTRACESATLASTANR",anr_traces_path,fd);
}
}
/*slowtracesforslowoperations*/
if(anr_traces_path[0]!=0){
inttail=strlen(anr_traces_path)-1;
while(tail>0&&anr_traces_path[tail]!='/'){
tail--;
}
inti=0;
while(1){
sprintf(anr_traces_path+tail+1,"slow%02d.txt",i);
if(stat(anr_traces_path,&st)){
//Notracesfileatthisindex,donewiththefiles.
break;
}
dump_file("VMTRACESWHENSLOW",anr_traces_path);
i++;
}
}
intdumped=0;
for(size_ti=0;i
dumpstate會調用到所有binder中的service的dump函數,因為dumpstate函數執行了這一句:
/*thefulldumpsysisstartingtotakealongtime,soweneed
toincreaseitstimeout.wereallyneedtodothetimeoutsin
dumpsysitself...*/
run_command("DUMPSYS",60,"dumpsys",NULL);
直接執行dumpsys,沒有參數,並且註釋中也說的很清楚,就是采集所有的信息。
這會執行以下service的dump函數(執行dumpsys|grep“DUMPOFSERVICE”可以看到):
DUMPOFSERVICEDockObserver:
DUMPOFSERVICESurfaceFlinger:
DUMPOFSERVICEaccessibility:
DUMPOFSERVICEaccount:
DUMPOFSERVICEactivity:
DUMPOFSERVICEalarm:
DUMPOFSERVICEandroid.security.keystore:
DUMPOFSERVICEandroid.service.gatekeeper.IGateKeeperService:
DUMPOFSERVICEappops:
DUMPOFSERVICEappwidget:
DUMPOFSERVICEassetatlas:
DUMPOFSERVICEaudio:
DUMPOFSERVICEbackup:
DUMPOFSERVICEbattery:
DUMPOFSERVICEbatteryproperties:
DUMPOFSERVICEbatterystats:
DUMPOFSERVICEbluetooth_manager:
DUMPOFSERVICEcarrier_config:
DUMPOFSERVICEclipboard:
DUMPOFSERVICEcommontime_management:
DUMPOFSERVICEconnectivity:
DUMPOFSERVICEconsumer_ir:
DUMPOFSERVICEcontent:
DUMPOFSERVICEcountry_detector:
DUMPOFSERVICEcpuinfo:
DUMPOFSERVICEdbinfo:
DUMPOFSERVICEdevice_policy:
DUMPOFSERVICEdeviceidle:
DUMPOFSERVICEdevicestoragemonitor:
DUMPOFSERVICEdiskstats:
DUMPOFSERVICEdisplay:
DUMPOFSERVICEdisplay.qservice:
DUMPOFSERVICEdreams:
DUMPOFSERVICEdrm.drmManager:
DUMPOFSERVICEdropbox:
DUMPOFSERVICEethernet:
DUMPOFSERVICEfingerprint:
DUMPOFSERVICEgfxinfo:
DUMPOFSERVICEgraphicsstats:
DUMPOFSERVICEimms:
DUMPOFSERVICEinput:
DUMPOFSERVICEinput_method:
DUMPOFSERVICEiPhonesubinfo:
DUMPOFSERVICEisms:
DUMPOFSERVICEisub:
DUMPOFSERVICEjobscheduler:
DUMPOFSERVICElauncherapps:
DUMPOFSERVICElocation:
DUMPOFSERVICElock_settings:
DUMPOFSERVICEmedia.audio_flinger:
DUMPOFSERVICEmedia.audio_policy:
DUMPOFSERVICEmedia.camera:
DUMPOFSERVICEmedia.camera.proxy:
DUMPOFSERVICEmedia.player:
DUMPOFSERVICEmedia.radio:
DUMPOFSERVICEmedia.resource_manager:
DUMPOFSERVICEmedia.sound_trigger_hw:
DUMPOFSERVICEmedia_projection:
DUMPOFSERVICEmedia_router:
DUMPOFSERVICEmedia_session:
DUMPOFSERVICEmeminfo:
DUMPOFSERVICEmidi:
DUMPOFSERVICEmount:
DUMPOFSERVICEnetpolicy:
DUMPOFSERVICEnetstats:
DUMPOFSERVICEnetwork_management:
DUMPOFSERVICEnetwork_score:
DUMPOFSERVICEnfc:
DUMPOFSERVICEnotification:
DUMPOFSERVICEpackage:
DUMPOFSERVICEpermission:
DUMPOFSERVICEpersistent_data_block:
DUMPOFSERVICEphone:
DUMPOFSERVICEpower:
DUMPOFSERVICEprint:
DUMPOFSERVICEprocessinfo:
DUMPOFSERVICEprocstats:
DUMPOFSERVICErestrictions:
DUMPOFSERVICErttmanager:
DUMPOFSERVICEsamplingprofiler:
DUMPOFSERVICEscheduling_policy:
DUMPOFSERVICEsearch:
DUMPOFSERVICEsensorservice:
DUMPOFSERVICEserial:
DUMPOFSERVICEservicediscovery:
DUMPOFSERVICEsimphonebook:
DUMPOFSERVICEsip:
DUMPOFSERVICEstatusbar:
DUMPOFSERVICEtelecom:
DUMPOFSERVICEtelephony.registry:
DUMPOFSERVICEtextservices:
DUMPOFSERVICEtrust:
DUMPOFSERVICEuimode:
DUMPOFSERVICEupdatelock:
DUMPOFSERVICEusagestats:
DUMPOFSERVICEusb:
DUMPOFSERVICEuser:
DUMPOFSERVICEvibrator:
DUMPOFSERVICEvoiceinteraction:
DUMPOFSERVICEwallpaper:
DUMPOFSERVICEwebviewupdate:
DUMPOFSERVICEwifi:
DUMPOFSERVICEwifip2p:
DUMPOFSERVICEwifiscanner:
DUMPOFSERVICEwindow:
這裏總結以下,上面的bugreport整體邏輯如下圖描述(如果圖片太小看不清,請下載圖片並查看):
adbbugreport的其他選項
bugreport本身並沒有什麽選項,主要是通過dumpsys等命令配合完成,詳見batteryhistorian項目主頁:https://github.com/google/battery-historian,以下是個總結:
1).重置電池統計信息:
adbshelldumpsysbatterystats--reset
2).Wakelockanalysis全部wakelock信息:
adbshelldumpsysbatterystats--enablefull-wake-history
3).Kerneltraceanalysis分析內核,主要分析wakeupsource和wakelockactivities,首先使能kernel分析:
$adbroot
$adbshell
#Settheeventstotrace.
$echo"power:wakeup_source_activate">>/d/tracing/set_event
$echo"power:wakeup_source_deactivate">>/d/tracing/set_event
#Thedefaulttracesizeformostdevicesis1MB,whichisrelativelylowandmightcausethelogstooverflow.
#8MBto10MBshouldbeadecentsizefor5-6hoursoflogging.
$echo8192>/d/tracing/buffer_size_kb
$echo1>/d/tracing/tracing_on
然後獲得log:
$echo0>/d/tracing/tracing_on
$adbpull/d/tracing/trace
延伸文章資訊
- 1Android adb bugreport工具分析和使用_createchance的博客
bugreport是什么,怎么用?Android系统想要成为一个功能完备,生态繁荣的操作系统,那就必须提供完整的应用开发环境。而在应用开发中,app程序的调试 ...
- 2Capture and read bug reports | Android Developers
Capture a bug report using adb ... If you do not specify a path for the bugreport, it is saved to...
- 3【Linux】adb bugreport bugreport.zip doesn't work - githubhot
Hi, $ adb bugreport bugreport.zip Android Debug Bridge version 1.0.32 Revision debian. -a - direc...
- 4获取并阅读错误报告| Android 开发者
您可以通过以下几种方式从设备上获取错误报告:使用设备上的生成错误报告开发者选项、Android 模拟器菜单或开发机器上的 adb bugreport 命令。
- 5用adb logcat抓取log - 程序員學院
狀態資訊的有:adb shell dmesg,adb shell dumpstate,adb shell dumpsys,adb bugreport. 講解一下各自作用:. 通過ddms抓的其實...