使用Smalidea对无源码APK调试简介

最近正好也用了Smalidea,就ZZ的原贴做一些补充。

  1. 可调试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

Spread the word. Share this post!

Meet The Author

C/ASM程序员

Leave Comment