最近正好也用了Smalidea,就ZZ的原贴做一些补充。
- 可调试APP
如果Android的系统属性ro.debuggable等于1(用getprop ro.debuggable验证),则所有APP都可调试。如果ro.debuggable等于0,某APP的AndroidManifest.xml中有android:debuggable=”true”,该APP可调试。
对于模拟器,ro.debuggable等于1:
$ adb -s emulator-5554 shell "getprop ro.debuggable" 1
真实手机上ro.debuggable一般等于0,其上绝大多数APP的AndroidManifest.xml中没有android:debuggable=”true”,为了在真实手机上调试这些APP,必须用些歪招,不在此文范围。
ZZ用了:
$ adb -s emulator-5554 shell "getprop | grep ro.debuggable" [ro.debuggable]: [1]
没必要,可以直接”getprop ro.debuggable”。
1. 下载
https://github.com/JesusFreke/smali/
https://github.com/JesusFreke/smali/wiki/smalidea
https://bitbucket.org/JesusFreke/smali/downloads
https://bitbucket.org/JesusFreke/smali/downloads/smalidea-0.03.zip
https://bitbucket.org/JesusFreke/smali/downloads/smali-2.2b4.jar
https://bitbucket.org/JesusFreke/smali/downloads/baksmali-2.2b4.jar
2. 安装
Android Studio->Configure->Plugins->Install plugin from disk->选中smalidea-0.03.zip->Restart Android Studio
3. 用baksmali反编译apk
ZZ用Apktool反编译,实际上Smalidea自带反编译工具
$ java -jar baksmali-2.2b4.jar d -o out some.apk
反编译结果出现在out子目录中
$ java -jar baksmali-2.2b4.jar help d
baksmali有很多参数,下面这个参数你可能会感兴趣:
-a,--api The numeric api level of the file being disassembled (default: 15)
4. 在baksmali反编译基础上新建工程
创建目录:
X:\path\SOME_Smalidea
将baksmali得到的out子目录复制到上述目录,更名为src子目录:
X:\path\SOME_Smalidea\src Android Studio->Import project (Eclipse ADT, Gradle, etc.)->X:\path\SOME_Smalidea->Create project from existing sources Project name SOME_Smalidea Project location X:\path\SOME_Smalidea
在AS左上角选择”Project Files”
SOME_Smalidea->src->右键菜单->Mark Directory As->Sources Root
5. 建立调试通道
对于可调试APP,有两种办法建立调试通道,一种是用DDMS,另一种是用ADB,其实前者也隐式使用ADB。
5.1 启动DDMS
X:\path\sdk\tools\monitor.bat
过去用ddms.bat启动DDMS,这个已经过时了,现在建议用monitor.bat启动DDMS。monitor.bat实际执行:
lib\monitor-x86\monitor.exe lib\monitor-x86_64\monitor.exe
选择哪种CPU架构,由如下命令控制:
java -jar X:\path\sdk\tools\lib\archquery.jar
最终受java的位数控制,如果用64-bits Java,将执行:
X:\path\sdk\tools\lib\monitor-x86_64\monitor.exe
最好用”where java”检查一下当前Java路径,调整环境变量,比如:
set path=X:\path\Java\jre8\bin;%path% X:\path\sdk\tools\monitor.bat X:\path\sdk\tools\lib\monitor-x86_64\monitor.exe
假设启动DDMS失败,用Process Explorer检查是否存在多个monitor.exe,杀掉它们再试。此外可以用Tcpview检查8700/TCP被谁占用。
DDMS有GUI,在左上角区域可以看到所有可调试进程的PID及调试端口。从中选择待调试进程,会发现其多出一个调试端口,第一个端口(原有的)不固定,第二个端口(新增的)是固定的8700/TCP。这两个调试端口地位相当,都可以用于调试,只不过8700有利于固化Android Studio调试配置。8700始终跟着被选中的待调试进程,如果切换了待调试进程,8700将出现在新选中的进程行,上一个被选中的进程行不再出现8700。
关于DDMS、ADB、JDWP,有兴趣者参看:
Dalvik Debugger Support
dalvik/docs/debugger.html
Dalvik VM Debug Monitor
dalvik/docs/debugmon.html
system/core/adb/jdwp_service.c
《格蠢汇编》第20章《漫谈Android系统的调试模型》
5.2 adb forward tcp:hostport jdwp:pid
关掉DDMS,直接用ADB建立调试通道
$ adb -s emulator-5554 shell "ps | grep com.anything.some" USER PID PPID VSIZE RSS WCHAN PC NAME u0_a47 10140 795 187052 28200 ffffffff b7ede827 S com.anything.some
注意双引号的使用,这样可以确保执行Android系统中的grep,而不是PC上的grep,从而使得上述命令在Windows、Linux上都可用。
$ adb -s emulator-5554 forward tcp:8700 jdwp:10140 $ adb -s emulator-5554 forward --list emulator-5554 tcp:8700 jdwp:10140
关闭ADB建立的调试通道:
$ adb -s emulator-5554 forward --remove-all
上述命令的Windows实现可能有BUG,事后用Tcpview查看,adb仍在侦听8700/TCP,必须:
$ adb kill-server
我这里举例是以模拟器为目标,实际上可以是真实手机。
直接用ADB建立调试通道时,不一定使用8700/TCP。此处之所以仍然使用8700,是为了固化Android Studio调试配置。
5.3 启动APP之初就开始调试
前面假设APP已经启动,待调试代码仍可路过。如果待调试代码位于APP启动之初,需要额外操作。
假设目标是模拟器,检查AndroidManifest.xml
$ adb -s emulator-5554 shell am start -D -W -n com.anything.some/.SomeActivity
在模拟器中会看到:
Waiting For Debugger Application SOME (process com.anything.some) is waiting for the debugger to attach
然后启动DDMS或”adb forward tcp:hostport jdwp:pid”,建立调试通道。
假设目标是真实手机:
设置->开发者选项->USB调试
设置->开发者选项->选择调试应用->SOME
设置->开发者选项->等待调试器
去真实手机中正常启动SOME,也会停在”Waiting For Debugger”。
然后启动DDMS或”adb forward tcp:hostport jdwp:pid”,建立调试通道。
6. 在Android Studio中调试
Run->Edit Configurations->点击左上角+号->Remote
Name : DebugVia8700(这个名字可以任意)
Port : 8700(如果与DDMS配合,可以设成另一个端口,但每次都得改)
SOME_Smalidea->src->com/anything/some->b.smali
第72行附近代码:
-------------------------------------------------------------------------- if-nez v0, :cond_3c iget-object v0, p0, Lcom/anything/some/b;->a:Lcom/anything/some/SomeActivity; const v1, 0x7f060025 --------------------------------------------------------------------------
在if-nez左侧空白处单击或按Ctrl-F8(参看Run菜单)设置断点
Run->Debug DebugVia8700
在Console面板中会看到:
Connected to the target VM, address: 'localhost:8700', transport: 'socket'
去模拟器中正常操作SOME,触发断点,调用栈回溯:
com.anything.some.b.onClick(Unknown Source:-1) android.view.View.performClick(View.java:4204) android.view.View$PerformClick.run(View.java:17355) android.os.Handler.handleCallback(Handler.java:725) android.os.Handler.dispatchMessage(Handler.java:92) android.os.Looper.loop(Looper.java:137) android.app.ActivityThread.main(ActivityThread.java:5041) java.lang.reflect.Method.invokeNative(Method.java:-1) java.lang.reflect.Method.invoke(Method.java:511) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) dalvik.system.NativeStart.main(NativeStart.java:-1)
可以单步跟踪,可以修改由this指针定位的成员变量。
对于JVM,我一直梦想着有这样一个Java字节码级别的调试器,可以看调用栈回溯、看单条Bytecode指令的中间状态(比如ifeq是否满足)、修改变量等等,一直没找到。想不到对于DVM,有Smalidea这样的东西,不错。
有些文章提到一个配置操作:
File->Project Structure->Project->Project SDK
对于在baksmali反编译基础上新建工程,此处显示。这些文章认为必须在此设置SDK版本,完全是胡说八道,根本不需要。
7. 排错
注意,”am start -D”仍然要求ro.debuggable=1或android:debuggable=”true”
假设在真实手机上调试一个不可调试APP,”am start -D”不会报错,但这个进程中不会出现JDWP线程,可用如下命令验证:
$ adb -s XXX shell "ps -t | grep -A 8 com.anything.some" $ adb -s XXX shell "ps -t | grep -B 6 JDWP"
对于这种情况,DDMS里看不到”com.anything.some”。如果用”adb forward tcp:8700 jdwp:pid”强行建立调试通道,只是看上去成功了,Tcpview看到adb侦听127.0.0.1:8700/TCP,在Android Studio中尝试调试时,报错:
Error running DebugVia8700: Unable to open debugger port (localhost:8700): java.net.SocketException "Connection reset"
简单点说,如果APP所在进程没有出现JDWP线程,不管你怎么折腾,都不要想着调试了。
如果您需要了解更多内容,可以
加入QQ群:570982169
直接询问:010-68438880