使用Android Studio 3.0+调试任意环境生成的APK中的Java和Native C/C++代码(APK调试debug)

自从Google抛弃Eclipse ADT转投IntelliJ IDEA搞出Android Studio后,Android的开发调试环境可以说是日臻完善,先是Android Studio 2.x加入了调试原生C/C++代码的功能,一解Android NDK开发调试不方便之困(想当年可是拜ndk开发调试所赐才熟练掌握了gdb命令行的各种操作…),现在的3.x又加入了直接调试任意apk中Java和native .so的功能:

测试了下,虽然过程中遇到了个小问题,不过最终还是成功了,基本能和AS开发的带C++ Support项目原生调试体验一致。下面记录下这个”小问题“的解决方法:
先说明下这个小问题是用AS调试任意apk中的.so时找不到调试符号,且无法定位到源码并命中断点的问题,也就是说是有源码调试!无源码调试理论上应该也可以,但不在本次研究范围之内。

首先,用AS打开要调试的apk(Profile or Debug APK…),稍等片刻,AS就会分析出APK中的一些信息:

同时可以发现箭头所指处的提示,APK包中的native .so缺失调试符号,这个也好理解,不管是ndk-build还是用新的cmake编译native .so,打进APK包中的动态库都是strip过的,不包含调试信息(这个据说可以强制成不strip,不过那样so会大很多,改点东西,重打包,再安装会浪费更多时间,得不偿失),
这时,按照下面窗口的提示:

点击.so然后弹出这个窗口:

点上面的Add,会弹出让你选择带调试信息版的.so,这里演示的这个项目用ndk-build编译,ndk-build会将调试版本.so放在项目的

obj/local/armeabi

这个位置,当然,这个前提是native项目编译时得是debug编译,另外,如果调试的是其他cpu架构的版本,那就需要把armeabi换成对应架构位置。
添加完并且选对关联so后,可以看到左边的APK项目列表里.so的下面已经可以根据调试信息找到相关源码位置了,同时刚才的选so面板下面的Path Mappings里提示了一些号称找不到的符号路径,看了下不是ndk的android库位置就是c、c++标准库的位置,反正这些东西我也不关系,无视就好了。
这时满心欢喜的以为完事具备了,随便找了个cpp里下了个断点,然后调试运行(这时有可能提示没有指定项目的Android SDK,这个在项目Module Settings里的项目Dependencies里选,然后还有一个问题就是AS可能不能自动正常开起lldb原生调试器,这时可以手动在项目的运行配置Run/Debug Configurations里的Debugger签中选择Debug type为Dual),发现程序已经跑过断点该命中的位置了,但是调试器却没有断下了,而此时调试器也确实挂上了目标程序,这就是这个“小问题”之所在,AS并没有像预期那样让lldb正确识别到调试信息,这一点我是这样确认到的,直接暂停native debugger,激活lldb命令行模式,通过命令image list(gdb的info shared)查看加载的库列表:

[116] B1135EAC-87D3-BB21-BA5D-1D714D8A1A23 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\B1135EAC-87D3-BB21-BA5D-1D714D8A1A23\libwebviewchromium_loader.so
[117] 726843B2-0000-0000-0000-000000000000 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\726843B2-0000-0000-0000-000000000000\base.odex
[118] AFD45310-0000-0000-0000-000000000000 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\AFD45310-0000-0000-0000-000000000000\libiconv.so
[119] 19446EC7-0000-0000-0000-000000000000 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\19446EC7-0000-0000-0000-000000000000\libgame.so
[120] 0EF1ED0B-2AB0-8690-7DB4-F92084963A9C C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\0EF1ED0B-2AB0-8690-7DB4-F92084963A9C\libsoundpool.so
[121] 2876E944-BE82-421C-C399-D9F01005C716 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\2876E944-BE82-421C-C399-D9F01005C716\libmediandk.so
[122] CDAAC4F1-26C4-93E2-7950-9180C9EEFE88 C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\CDAAC4F1-26C4-93E2-7950-9180C9EEFE88\eglsubAndroid.so
[123] E8804441-AF17-D292-6AF4-012DBFB7355C C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\E8804441-AF17-D292-6AF4-012DBFB7355C\gralloc.msm8974.so

可以看到libgame加载的是lldb的module_cache路径中的版本,检查了下确实是不带调试信息的!看来上面AS选择的调试版so只是让项目列表里根据调试信息找到了源码,并没有让lldb也识别出调试符号。
这个问题目前我发现可以这样解决(参考的是当年命令行ndk-gdb的思路,手动加载调试信息),首先需要在Java部分System.loadLibrary的地方断点(需要先attach source找到相关Java代码),然后调试运行,断点命中后,单步执行,让目标so加载上,然后暂停lldb原生调试器部分,然后通过”add-dsym”命令,将带调试信息版本so手动添加上:

(lldb)add-dsym D:\Projects\CocosGame\proj.android\obj\local\armeabi\libgame.so
symbol file ‘D:\Projects\CocosGame\proj.android\obj\local\armeabi\libgame.so’ has been added to ‘C:\Users\wzkres\.lldb\module_cache\remote-android\.cache\19446EC7-0000-0000-0000-000000000000\libgame.so’

可以看到符号文件被正确识别了,同时再用image list查看,也可以发现module_cache下的libgame后面还有一行显示的是刚才手动添加的调试符号so的位置,并且AS中源码里的断点图标也会变成有对勾的可命中状态!此时同时继续Java Debugger和Native Debugger后,没有其他错误的话,断点就可以正常命中了,并且可以像调试AS编译项目一样调试了。

PS:不知道这个问题是不是现下AS版本的bug,还是我打开的方式不对,不过至少如上操作后,这个功能是可以正常使用的,虽然由于include搜索路径的问题,代码中可能会有各种红线,但可视化的断点调试总还是比命令行调试方便的多的,实在忍不了的话那就还是用cmake重建AS版项目工程吧。

博主友情提示:

如在您的评论中需要提及如QQ号、微信号、电子邮件地址或其他隐私敏感信息,欢迎通过>>博主专用加密工具v2<<加密后发布,原文只有博主可以看到。