Android Framework代码调试

前言

Android Framework层的代码非常多,在进行frameworks开发&Debug时,如果只能依靠log输出,效率比较低下,而假定可以像普通应用一样进行单步Trace的话,相信效率就会高不少。
本文基于Android Studio,给出了一种可以单步调试Android Framework的简便方法。
如果讲的更通俗一点:

本文提供了一种,“有源码,但是无法通过Android Studio编译”的工程的调试方法

比如原生的那些应用们,Launcher3,SystemUI,Email,XXXXX
比如ActivityManagerService,PackageManagerService,XXXXX

背景知识

attach进程

Android Studio的调试是基于进程的,在Debug前需要先attach到对应的进程。
同样,调试的过程也是基于该进程的,所以我们要搞清楚“运行的代码”到底是在“哪一个进程上的”

在Android的运行环境中,基本上进程名就是对应AndroidManifest.xml中的包名,所以我们可以通过对应的包名,来找到对应的进程名,进行attach即可。
当然了,对于那些Framework中庞大的XXXXManagerService,通过课外阅读我们可以知道,他们是运行在System_Server进程上的

断点BreakPoint

当Attach到对应的进程之后,就要选择在需要的地方做打断点,对于Android Studio而言,常规的断点通常会有三种显示形式:

  • image-20221010154115527:白板,意味着断点不生效,一般来说都是没有attach到正确的进程
  • image-20221010154154427:叉叉,意味着断点不生效,一般来说是source code跟runtime code不匹配

    调试的代码跟实际编译进去的不是同一份,导致java字节码对不上,调试器没办法识别出来。
    一般而言出现叉叉是意味着差别太大了

  • image-20221010154214302:勾子,Just Do It,断点会生效,运行到这里会停下来

    有时候会出现断点乱跳,也有可能是因为Runtime代码不匹配(但是没有夸张到“叉叉”的情况)

断点类型&调试技巧

传送门:Android Studio的调试技巧

请直接参考这篇文章即可,其中有各种断点调试的奇技淫巧供君挑选

游戏开始

构建Fake Project

由于Android Studio的性感Debug被点亮之必要条件是Project建立
image-20221010154248437
这里我们针对frameworks和“有源码但无法编译工程”做两个说明

其实也是一回事….

Frameworks

由于Frameworks是整个android runtime世界的基石,所以对于任意的android app来说,framework的部分都是一样的。
基于这个认识,大体的思路如下:

  1. 建立Fake Helloworld,目的是点亮debug按钮
  2. attach到对应的debug进程,比如System_Server
  3. 找到正确的代码
  4. 加断点,开始调试

其中3.找到正确的代码是一个问题点,我们就这个问题展开说明。

  • 由于要调试的代码在Framework中,在没有配置的情况下没办法直接索引到相关的代码
    • 因此第一步是要先下载一份sdk source code
      image-20221010154303443
    • 下载完以后,由于我们在Target上的代码与Google原生的会有出入,所以需要把source code跟runtime code做一下同步,这部分有两种做法
      • 一种是替换掉刚才步骤1中下载下来的source code
      • 另外一种是修改Android Studio的引用关系,让它在查询source code的时候直接指定到我们的source code
        • 修改:C:\Users\用户名\.AndroidStudio2.2\config\options\jdk.table.xml
          image-20221010154322704

          需要注意的是,由于我们的fake hellowrld的compilesdk是android n,所以替换的路径也要是android n

同步代码的tips

由于我编译的代码是在VM端,而调试的部分是在本地进行的。
如果把工程的索引直接建立到映射盘符(Samba)的远端服务器,会因为网络的因素造成各种卡顿。
但如果不把索引建到映射的盘符,又会因为每次在VM端修改代码,编译以后要手动同步,非常麻烦。
鉴于这种情况,我选择使用git来帮我完成同步的工作。

  1. 以VM端的git repository为base,本地通过git clone的方式把代码弄下来

    git clone XXXX@110.119.120.114:/home/fucking/source/code/frameworks/base/.git

  2. VM端的修改每次都做成一个git commit,然后正常编译
  3. 本地到对应的git repository做git fetch & git rebase or merge or 直接git pull

当然,如果你有更好的办法,也不妨share一下:)

“有源码但无法编译工程”

如果你真的了解了前面的内容,这一块应该也就不难理解了。
对于有“有源码但无法编译工程”,我们可以通过Android Studio的import project来帮助建立一个project。
当然,也可以通过建立fake helloworld,然后把工程的源码们拷贝过去。
之后,就是常规的debug流程了

  1. attach进程
  2. 打断点

切记source code要和runtime code一致!

Attach到任意的进程,们

建立完Fake Helloworld,我们欢天喜地的打开attach debugger,结果发现只有一个进程 or 空白。

这个例子我是通过Android Studio导入了“有源码但无法编译工程”的SystemUI

image-20221010154346987
然而,我实际需要调试的ActivityStarter.java中的方法,显然这是Framework中的代码,运行在SystemServer上。
而对应的,我现在打的断点是“白板”,也就是并没有生效,所以我们需要attach到System_Server进程,点击“Show all processes”
image-20221010154404362
当attach成功之后,对应的debug面板就会出现debug client,同时,在attach到正确的进程后,断点也会生效。
image-20221010154420190

写在最后

另外对于Frameworks而言,aosp的工程还提供了一种编译idegen的方式来进行调试。
具体的过程可以参考:生成idegen的调试方法
这个方法我也有尝试过,但由于生成的android.ipr和android.iml实际是整个source code的索引,所以对于在VM端编译,本地端debug的同学来说依旧逃不出网络传输卡顿的魔爪,所以我这边不是很推荐window + VM的同学来玩。
对于使用Ubuntu or Linux的同学来说,倒是可以试试(本地编译本地调试)
PS:QCOM的源码编译idegen会有error,需要修改一些东西才行,具体由于时间久远已经忘记,且看且调了

最后,祝大家调试愉快:)