调试android的native代码_react native android 调试 - CSDN
  • 一、准备工作1、下载并编译Android系统源码这里比较灵活,可以下载公司内部机型的代码,也可以下载原生AOSP的代码环境配置参考 https://source.android.com/source/initializing.html 源码下载参考 ...

    一、准备工作

    1、下载并编译Android系统源码

    这里比较灵活,可以下载公司内部机型的代码,也可以下载原生AOSP的代码

    环境配置参考

    https://source.android.com/source/initializing.html

    源码下载参考

    https://source.android.com/source/downloading.html

    编译运行参考

    https://source.android.com/source/building.html

    全部编译整个代码工程,中间如果有什么问题可以参考

    https://source.android.com/source

    2、设置PC端环境

    如果要调试自己build的版本,就可以使用out目录下的symbols
    PC env setup
    这里带一句,symbols是带有debug信息的二进制库或可执行文件,用以调试

    除了symbols之外我们还需要gdb(client),可以在源码目录的prebuilts目录下找到他们,为了方便,可以直接设置环境变量
    PC env setup2

    3、获取调试的权限

    最好使用userdebug或者eng build,实在不行也可以使用root后的user build系统,但是要同时关掉selinux

    adb shell setenforce 0

    否则无法调试

    4、设置手机端环境

    如果是user build,手机中没有gdbserver,所以需要手动push一个,gdbserver可以去源码目录下的prebuilts目录中搜索一下,但是这里要区分一下gdbserver和gdbserver64
    phone env setup
    如果是要调试64位的进程就需要gdbserver64
    phone env setup2

    通过以下命令push到手机中

    adb root
    adb disable-verity
    adb reboot

    等待重启完成

    adb remount
    adb push prebuilts/misc/android-arm/gdbserver/gdbserver /system/bin/

    phone env setup3

    二、开始调试

    这里假设要调试的是zygote进程,首先要知道zygote进程对应的可执行文件,这里是app_process32,64位的zygote64对应的是app_process64

    1、gdbserver attach到想要调试的进程

    adb shell
    ps | grep zygote
    gdbserver :1991 --attach 303

    这里的端口可以随便指定一个空闲的即可
    gdbserver attach

    2、gdb client连接到gdbserver

    通过执行以下命令进行连接

    adb forward tcp:1991 tcp:1991
    arm-linux-androideabi-gdb
    target remote:1991

    这里需要说一下,如果需要调试的进程是64位的,就要用64位的gdb client然后配合gdbserver64

    aarch64-linux-android-gdb

    3、load对应可执行文件

    file /Volumes/1TB-HD/Images/cancro/16.11.01/compressed_cancro_mm-alpha_2016.11.01.18.08_0e62b9421b/out/target/product/cancro/symbols/system/bin/app_process32

    4、Set sysroot路径

    set sysroot /Volumes/1TB-HD/Images/cancro/16.11.01/compressed_cancro_mm-alpha_2016.11.01.18.08_0e62b9421b/out/target/product/cancro/symbols

    gdb client setup

    5、设置源码目录

    set dir /Volumes/1TB-HD/CodeRoot/CANCRO_ALPHA/

    6、设置断点

    b frameworks/base/core/jni/fd_utils-inl.h:180

    Breakpoint 1 at 0xb6e24f0c: file frameworks/base/core/jni/fd_utils-inl.h, line 180.

    7、继续运行

    c

    Continuing.
    [New Thread 5872]
    [New Thread 5873]
    [New Thread 5874]
    [New Thread 5875]

    操作并等待运行到断点处

    Breakpoint 1, FileDescriptorInfo::Restat (this=0xb4cbd620) at frameworks/base/core/jni/fd_utils-inl.h:182
    warning: Source file is more recent than executable.
    182       ALOGE("Restat, st_nlink == 8, (%s, fd=%d) : f_stat.st_nlink=%llu,0x%llX file_stat.st_nlink=%llu,0x%llX", 

    8、查看变量对应的值

    p f_stat.st_nlink

    $1 = 1

    gdb client setup

    三、扩展功能

    到这里对于刚接触gdb调试同学也有了入门的知识,对于gdb老手估计是要玩飞的节奏。。。

    但是这里要说一下,如果要调试的是framework相关的进程的native代码,可能会受到system server的watchdog的影响,1分钟没有及时响应操作就会触发watchdog而kill到system server进程,zygote也会跟着挂掉,这里有个小技巧可以用一下,就是在调试的过程中,如果需要耗时查看一些运行时状态,可以先执行
    adb shell am hang
    防止超时重启,查看完毕想要继续执行,就Ctrl+c终止掉am hang即可继续执行,后面就重复这个过程即可。
    另外还有一种方式就是用Android Studio在线调试,把断点加在watchdog里面,配置gdb native调试。

    展开全文
  • 然而在实际场景中,随着代码复杂度的增加,要遵守这一原则非常困难,而且随着面向对象、模块化、多线程的引入,更难以判断内存该由谁来释放。为了解决这一难题,C++引入了智能指针和引用计数等。然而引用计数无法...

    对C/C++程序员而言,要说碰到最头疼的问题,无疑就是内存泄漏问题。解决内存泄漏问题似乎很简单,就是秉承一个原则:分配的内存一定要即时释放。然而在实际场景中,随着代码复杂度的增加,要遵守这一原则非常困难,而且随着面向对象、模块化、多线程的引入,更难以判断内存该由谁来释放。为了解决这一难题,C++引入了智能指针和引用计数等。然而引用计数无法解决两个对象相互持有对方引用而引起的内存泄漏。在Android中,引入了强指针(sp)和弱指针(wp)试图解决两个对象相互引用的困境。然而这个皮球又抛到程序员的面前,程序员需要作出决定,何时使用强指针,何时定义弱指针,所以这个方案最终还是不能像Java中的GC(垃圾回收)那样完美的解决内存泄漏问题。

    既然内存泄漏似乎无法避免,我们需要做的就是检查程序是否有内存泄漏,并定位到内存泄漏的代码,然后解决之。这个过程不那么简单,特别是对Android C++代码而言。本文探讨的就是如何检查Android C++代码的内存泄漏,文章基于Android官方的一篇文档:Debugging Native Memory Use,但这篇文章写得过于简略,再加上Android系统及Android工具都在快速升级,有些方法在新的环境下不再适用,在参考了一些网络资料及结合实际环境调试之后,现做一个总结。

     1 

    有时我们在网上找到一篇文章,照着做却出现这样或者那样的问题,原因就在于计算机技术发展太快,各种软件版本迭代频繁,环境不同了,方法就可能需要做一些修改。本文所写的方法需要两个前提条件:

    1. Android系统已经root,因为需要推送库到system;
    2. 有Android系统对应的版本的Android源码,从Android源码build出我们所需要的库。

    我的工作环境为:

    • Ubuntu 16.04 LTS 64位操作系统
    • Android 5.1 源码
    • Nexus 4手机(Android 5.1 AOSP系统)

    如果是其它版本的Android系统,理论上也同样适用。

     2 

    Android系统使用了一个精简版本的libc,称作bionic,代码位于Android源码的bionic/libc目录。为了调试,需要编译出libc_malloc_debug_leak.so和libc_malloc_debug_qemu.so,这两个库在userdebug版本会被编译出。我们可以在userdebug状态下build系统,或者使用mm命令只编译这个模块。

    1. 将libc_malloc_debug_leak.so和libc_malloc_debug_qemu.so推送到Android设备。

    2. 通过如下命令开启Android设备的malloc debug:

     adb root
     adb shell setprop libc.debug.malloc 1
     adb shell stop
     adb shell start

    注意: 开启malloc debug后,系统启动会变慢,请耐心等待,再次重启后会恢复到正常的模式。

    1. 找到开发机HOMEHOME/.android,在里面的ddms.cfg文件下加入一行
      native=true

    2. 下载老版本的Android Tools

    最新的Android Studio提供了Android Device Monitor,里面集成了老版本的DDMS,但这个集成版本功能不全, 所以请下载老版本的Android Tools工具,地址:http://dl.google.com/android/repository/tools_r25.2.5-linux.zip

    1. 解压tools_r25.2.5-linux.zip,启动Tools目录下的ddms
    2. 可以看到DDMS界面多了一个Native Heap的tab页,切换到Native Heap页,在左边的进程列表中选择要调试的app进程,然后点击Snapshot Current Native Heap Usage按钮,抓取app native内存的泄漏,包含有泄漏点的调用栈和地址。

    image

    示例app的源码请参考github: https://github.com/mogoweb/android-testcode ,下面有一个简化的示例malloc_debug_leak。

     3 

    在得到内存地址后,我们还需要定位到代码才行。需要注意的是,动态链接库在经过装载和重定位之后,并不能简单的通过addr2line工具来找到代码行。我们需要找到so在内存中装载的基地址。

    1. 使用adb shell ps命令找到app的PID
    u0_a52    3509  2047  1530248 47880 ffffffff b6e790dc S com.china_liantong.memoryleaktest
    1. 使用adb shell cat /proc/3509/maps命令找到app的内存映像
    a3db7000-a3dcf000 r-xp 00000000 b3:17 276908     /data/app/com.china_liantong.memoryleaktest-2/lib/arm/libnative-lib.so
    a3dcf000-a3dd1000 r--p 00017000 b3:17 276908     /data/app/com.china_liantong.memoryleaktest-2/lib/arm/libnative-lib.so
    a3dd1000-a3dd2000 rw-p 00019000 b3:17 276908     /data/app/com.china_liantong.memoryleaktest-2/lib/arm/libnative-lib.so
    1. 带有r-xp标记的那一行就是代码在内存中的地址范围,用libnative-lib.so内存泄漏点的地址0xa3dbb2d2减去基地址0xa3db7000即可得到代码地址0x42D2
    2. 使用addr2line工具找出对应的代码行:
    alex@alex-ubuntu-dev:~/android/tools$ arm-linux-gnueabi-addr2line -C -f -e /work/myproject/android-testcode/malloc_debug_leak/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so 0x042d2
    Java_com_china_1liantong_memoryleaktest_MainActivity_stringFromJNI
    /work/myproject/android-testcode/malloc_debug_leak/app/src/main/cpp/native-lib.cpp:14
    1. 可以看到内存泄漏发生在第14行,native-lib.cpp的源码如下
    #include <jni.h>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    
    extern "C"
    JNIEXPORT jstring
    
    JNICALL
    Java_com_china_1liantong_memoryleaktest_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        char* p = (char*)malloc(1024 * 1024);
        if (p) {
            strcpy(p, "hello");
        }
        return env->NewStringUTF(hello.c_str());
    }

     总结 

    此malloc的调试原理是:当系统发现我们有libc.debug.malloc的配置时,此时系统会将malloc/free/new/delete 等方法,重新指向到lib_malloc_debug_leak.so里面的对应实现方法,lib_malloc_debug_leak.so里面的方法,多了一些记录信息,将每次的申请时的地址、堆栈等信息记录下来。在需要的时候,通过工具ddms dump出来,分析每个申请的内存,是否正常的释放了,是否出现了内存泄露。

    本文给出了一个简单的示例用以说明此方法的可行性,在实际项目中,可能由于memory cache、memory pool的使用,可能找到的泄露点并非真正的内存泄漏,这个需要程序员作出判断。也可能即使找到了泄漏点,解决起来也需要花一番功夫。不管怎么说,找到可疑内存泄漏,也算万里长征迈出了第一步。

    展开全文
  • 3个要注意的地方 1)不需要拷贝到 /system/bin 下运行,直接push到/mnt/sdcard0/gdbserver, 然后在/mnt/sdcard0/ 下运行即可 2) (gdb) set solib-search-path $PRJ_DIR/obj/local/armeabi ...

    3个要注意的地方


    1)

    1、获得root权限:adb root

    2、设置/system为可读写:adb remount


    2) (gdb) set solib-search-path $PRJ_DIR/obj/local/armeabi
    3) windows下的gdb不支持TUI模式,去掉-tui参数


    使用adb+gdb调试android native代码

    144 
    作者 RoFF 
    2015.12.11 17:13 字数 967 阅读 2929评论 0

    调试原理:调试时在TARGET端(手机或模拟器或开发板等目标平台)运行gdbserver,并将要调试的进程attach到gdbserver上;在HOST(例如PC机)端运行gdb,通过adb在TARGET和HOST之间做端口映射进行通讯。



    adb+gdb/gdbserver配合来调试远程native代码

    Part1:主要工具:adb  gdb/gdbserver

    adb(android debug bridge)是一个多功能的安卓命令行工具,用户可以用来连接模拟器和安卓设备;它是client-server架构,主要包括:client(PC端),server(PC端),daemon进程(模拟器或安卓设备端)三部分。

    adb连接逻辑和基本概念,打开

    gdb(GNU debugger)是常用于GNU OS的调试工具。gdb更多介绍请移步wiki

    Part2:准备工作

    1,编译带有符号表信息的目标文件(例如动态库)

    要使用gdb来调试,首先要编译和加载带有符号表信息的目标文件;需要加上几个编译选项:-O0 -ggdb3  -fno-inline -g,尽量多的带有信息供调试

    特别说明:

    a,如果编译生成的文件跟项目中加载的文件不同名,一定要改名或拷贝成后者的文件名

    b,android项目中使用的动态库文件需要符号表信息(理论上不需要,但是实践证明是这样的,疑惑中)

    最好设置目标文件的输出目录的环境变量 KERNEL_OUTPUT

    2,在项目的Manifest文件的application TAG中添加 “android:debuggable="true"”

    非必须

    注意:项目发布时要删掉该项

    3,拷贝gdbserver到安卓手机上(需要取得root权限;PC上安装NDK)

    gdbserver在安装的ndk目录下(最好设置环境变量ANDROID_NDK,并将$ ANDROID_NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin(64位机)添加到PATH下)

    $adb push $ANDROID_NDK/ndk-bundle/prebuilt/android-arm/gdbserver   /mnt/sdcard

    再将其拷贝到/system/bin下,并加上执行权限

    4,从TARGET上拷贝/system/bin/app_process到本机的目录下,例如 ~/app_process

    如果是64位机器,需要拷贝/system/bin/app_process32和/system/bin/app_process64

    注意:要求必须是调试机器的该文件,换个手机就重新更新一下这个文件

    Part3:配置步骤

    Step_1:  

    (HOST)$ adb forward tcp:6000 tcp:6000                                      

    //HOST上所有6000端口通信数据将被重定向到TARGET的6000端口server上

    Step_2:

    (TARGET)$  ps | grep "" | awk '{print $2}'       

    //查询进程号,可能打印多个进程号,注意区分主进程

    (TARGET)$ su

    (TARGET)# gdbserver :6000 --attach   $process_id                            

    //启动gdbserver并接管调试进程。看到log例如:“Attached; pid = 31834 Listening on sockaddr socket debug-socket”

    //说明attach成功,target处于等待命令状态

    Step_3:

    (HOST)$ arm-linux-androideabi-gdb  ~/app_process   -tui          

    //arm-linux-androideabi-gdb是ANDROID_NDK下

    //使用 -tui 参数可以显示当前执行的代码,很直观,强烈推荐!!!

    (gdb) target remote :6000 

    //端口号跟在target上forward的端口号一致,如果出现连接超时,可以重复step_0和step_1

    (gdb) set solib-search-path    $KERNEL_OUTPUT

    //加载该目录下的所有文件

    (gdb) info sharedlibrary                                                                 

    //用 info sharedlibrary来查看目标库文件的符号表是否加载成功

    //看到例如  "0x9dd38868  0x9de4d340  Yes         $ANDROID_HOME/generic/symbols/system/lib/sogouime"就是成功了 (好亲切的YES呀!)

    Step_4:

    开始调试吧,可以各种打断点,单步执行,打印变量了!!!

    补一下强大的gdb命令吧   list...   break...    step...  continue...

    真得说GDB的打印功能很强大很贴心  能直接打印结构体变量!

    -----------------------------------------------------------------------------

    gdb调试原理

    关于ptrace:linux函数,父进程可以用它来控制子进程,改变执行进程、查看子进程的数据等

    动态库和符号表:

    展开全文
  • 如何调试Android Native Framework weishu 7 个月前 半年前写了一篇文章,介绍 如何调试Android Framework。但是只提到了Framework中Java代码的调试办法,但实际上有很多代码都是用C++实现的;无奈...
    如何调试Android Native Framework
    原文:
    https://zhuanlan.zhihu.com/p/24867284

    如何调试Android Native Framework

    weishuweishu
    7 个月前

    半年前写了一篇文章,介绍 如何调试Android Framework。但是只提到了Framework中Java代码的调试办法,但实际上有很多代码都是用C++实现的;无奈当时并并没有趁手的native调试工具,无法做到像Java调试那样简单直观(gdb+eclipse/ida之流虽然可以但是不完美),于是就搁置下了。

    Android Studio 2.2版本带来了全新的对Android Native代码的开发以及调试支持,另外LLDB的Android调试插件也日渐成熟,我终于可以把这篇文章继续下去了!本文将带来Android Framework中native代码的调试方法。

    在正式介绍如何调试之前,必须先说明一些基本的概念。调试器在调试一个可执行文件的时候,必须知道一些调试信息才能进行调试,这个调试信息可多可少(也可以没有)。最直观的比如行号信息,如果调试器知道行号信息,那么在进行调试的时候就能知道当前执行到了源代码的哪一行,如果调试器还知道对应代码的源文件在哪,那么现代IDE的调试器一般就能顺着源码带你飞了,这就是所谓的源码调试。相反,如果没有行号和源码信息,那么只能进行更低级别的调试了,调试器只能告诉你一些寄存器的值;而当前运行的代码也只是PC寄存器所指向的二进制数据,这些数据要么是虚拟机指令,要么是汇编指令;这就是所谓的无源码调试。显然无源码调试相比源码级别的调试要麻烦的多;接下来将围绕这两个方面分别介绍。

    ## 用Android Studio进行源码调试

    如上文所述,如果需要实现源码调试,必须知道足够的调试信息;在native调试中就是所谓的「调试符号」。但是release版本的动态链接库或者可执行文件一般并不会包含我们需要的调试信息,在Android系统中,`/system/lib/*` 目录下的那些系统so并没有足够的调试信息,因此如果要进行源码调试,必须自己编译Android源代码,才能获取调试信息,进而让调试器协助我们调试。

    Android源码编译是个麻烦事儿,我写过一篇文章介绍 如何使用Docker编译源码;但是,Android版本众多,如果真的需要调试各个版本,在本地进行编译几乎是不可能的——一个版本约占60G空间,如果每个版本都编译,你的Mac还有空间可用吗?因此比较推荐使用云服务进行源码编译;比如使用阿里云的ECS,20M的网速15分钟就能下载完源码;编译速度还勉强,4核8G两个半小时。扯远了 :) 如果你没有精力编译Android源码,我这个 Demo 工程 可以让你尝尝鲜,里面包含一些调试的必要文件,可以体会一下Native调试的感觉。

    如果我们已经拥有了调试符号,那么还需要保证你的符号文件和设备上真正运行的动态链接库或者可执行文件是对应的,不然就是鸡同鸭讲了。最简单的办法就是使用模拟器。我们编译完源码之后,一个主要的编译产物就是 `system.img`,这个 `system.img`会在启动之后挂载到设备的 /system 分区,而system分区包含了Android系统运行时的绝大部分可执行文件和动态链接库,而这些文件就是我们的编译输出,正好可以与编译得到的调试符号进行配合调试。模拟器有一个 `-system`选项用来指定模拟器使用的 system.img文件;于是这个问题也解决了。

    最后一个问题就是,既然是源码调试,当然需要源码了;我们可以在 AOSP 上下载需要的源码即可;需要注意的是,在check分支的时候,必须保证你的分支和编译源码时候的分支是一致的。

    需要说明的是,虽然我们使用Android Studio调试,但是其背后的支撑技术实际上是 LLDB。LLDB是一个相当强大的调试器,如果你现在还不知道它为何物,那真的是孤陋寡闻了!建议先简单学习一下 教程

    万事俱备,Let's go!

    ### 建立Android Studio工程

    实际上任何Android Studio工程都可以进行native源码调试,但是为了方便还是新建一个工程;这个工程是一个空工程,没有任何实际用途;为了体验方便,你可以使用我的这个 Demo 工程 ,里面包含了调试符号以及模拟器需要使用的system.img。一定要注意Android Studio的版本必须是2.2以上(我的是2.2.3稳定版)。

    ### 下载需要调试模块的源码

    如果你本地编译了Android源码,那么就不需要这一步了;但是更多的时候我们只是想调试某一个模块,那么只需要下载这个模块的源码就够了。我这里演示的是调试 ART 运行时,因此直接下载ART模块的源码即可,我编译的Android源码版本是 `android-5.1.1_r9`,因此需要check这个分支的源码,地址在这里:ART-android-5.1.1_r9

    ### 运行模拟器

    由于我们的调试符号需要与运行时的动态链接库对应,因此我们需要借助模拟器;首先创建一个编译出来的调试符号对应的API版本的模拟器,我这里提供的是5.1.1也就是API 22;然后使用编译出来的 system.img 启动模拟器(Demo 工程 的image目录有我编译出来的文件,可以直接使用。):

    emulator -avd 22 -verbose -no-boot-anim -system /path/to/system.img
    

    这个过程灰常灰常长!!我启动这个模拟器花了半个多小时,也是醉。现在是2017年,已经是Android创建的第十个年头,ARM模拟器还是烂的一塌糊涂,无力吐槽。一个能让它快一点的诀窍是创建一个小一点的SD card;我设置的是10M。

    ### 开始调试

    #### 选择native调试模式

    首先我们对调试的宿主工程设置一下,选择native调试功能。点击运行下面的按钮 `Edit Configuration`:

    然后在debugger栏选择Native:

    然后我们点击旁边的 `Debug`小按钮运行调试程序:

    #### 设置调试符号以及关联源码

    在运行程序之后,我们可以在Android Studio的状态栏看到,LLDB调试插件自动帮我们完成了so查找路径的过程,这一点比gdb方便多了!在Android Studio的Debug窗口会自动弹出来,如下:

    我们点击那个 `pause program` 按钮,可以让程序暂停运行:

    上图左边是正在运行的线程的堆栈信息,右边有两个tab,一个用来显示变量的值;一个是lldb交互式调试窗口!我们先切换到lldb窗口,输入如下命令设置一个断点:

    (lldb) br s -n CollectGarbageInternal
    
    Breakpoint 2: where = libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool), address = 0xb4648c20
    

    可以看到,断点已经成功设置;这个断点在libart.so中,不过现在还没有调试符号信息以及源码信息,我们只知道它的地址。接下来我们设置调试符号以及关联源码。

    接下来我们把编译得到的符号文件 libart.so 告诉调试器(符号文件和真正的动态链接库这两个文件名字相同,只不过一个在编译输出的symbols目录) ;在lldb窗口执行:

    (lldb) add-dsym /Users/weishu/dev/github/Android-native-debug/app/symbols/libart.so
    
    symbol file '/Users/weishu/dev/github/Android-native-debug/app/symbols/libart.so' has been added to '/Users/weishu/.lldb/module_cache/remote-android/.cache/C51E51E5-0000-0000-0000-000000000000/libart.so'
    

    注意后面那个目录你的机器上与我的可能不同,需要修改一下。我们再看看有什么变化,看一下刚刚的断点:

    (lldb) br list 2
    
    2: name = 'CollectGarbageInternal', locations = 1, resolved = 1, hit count = 0
    
      2.1: where = libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool) **at heap.cc:2124**, address = 0xb4648c20, resolved, hit count = 0 
    

    行号信息已经加载出来了!!在 `heap.cc` 这个文件的第2124行。不过如果这时候断点命中,依然无法关联到源码。我们看一下调试器所所知道的源码信息:

    (lldb) source info
    
    Lines found in module `libart.so
    
    [0xb4648c20-0xb4648c28): /Volumes/Android/android-5.1.1_r9/art/runtime/gc/heap.cc:2124
    

    纳尼??Volumes/Android/android-5.1.1_r9/ 这个目录是个什么鬼,根本没有这个目录好伐?难道是调试器搞错了?

    在继续介绍之前我们需要了解一些关于「调试符号」的知识;我们拿到的调试符号文件其实是一个DWARF文件,只不过这个文件被嵌入到了ELF文件格式之中,而其中的调试符号则在一些名为 `.debug_*` 的段之中,我们可以用 `readelf -S libart.so` 查看一下:

    编译器在编译libart.so的时候,记录下了**编译时候**源代码与代码偏移之间的对应关系,因此调试器可以从调试符号文件中获取到源码行号信息;如下:

    这下我们明白了上面那个莫名其妙的目录是什么了;原来是在编译`libart.so`的那个机器上存在源码。那么问题来了,我们绝大多数情况下是使用另外一台机器上的源码进行调试的——比如我提供的那个 [Demo工程][2] 包含的带符号libart.so里面保存的源文件信息的目录实际上是我编译的电脑上的目录,而你调试的时候需要使用自己电脑上的目录。知道了问题所在,解决就很简单了,我们需要映射一下;在Android Studio的Debug 窗口的lldb 那个tab执行如下命令:

    (lldb) settings set target.source-map /Volumes/Android/android-5.1.1_r9/ /Users/weishu/dev/github/Android-native-debug/app/source/
    

    第一个参数的意思是编译时候的目录信息,第二个参数是你机器上的源码存放路径;设置成自己的即可。

    这时候,我们再触发断点(点击demo项目的Debug按钮),看看发生了什么?!

    至此,我们已经成功滴完成了在Android Studio中Native代码的源码调试。你可以像调试Java代码一样调试Native代码,step/in/out/over,条件断点,watch point任你飞。你可以借助这个工具去探究Android底层运行原理,比如垃圾回收机制,对象分配机制,Binder通信等等,完全不在话下!

    ## 无源码调试

    接下来再介绍一下操作简单但是使用门槛高的「无源码调试」方式;本来打算继续使用Android Studio的,但是无奈现阶段还有BUG,给官方提了issue但是响应很慢:code.google.com/p/andro。因此我们直接使用 LLDB 调试;当然,用gdb也能进行无源码调试,但是使用lldb比gdb的步骤要简单得多;不信你可以看下文。

    ### 安装Android LLDB工具

    要使用lldb进行调试,首先需要在调试设备上运行一个lldb-server,这个lldb-server attach到我们需要调试的进程,然后我们的开发机与这个server进行通信,就可以进行调试了。熟悉gdb调试的同学应该很清楚这一点。我们可以用Android Studio直接下载这个工具,打开SDK Manager:

    如上图,勾选这个即可;下载的内容会存放到你的 $ANDROID_SDK/lldb 目录下。

    ### 使用步骤

    安装好必要的工具之后,就可以开始调试了;整体步骤比较简单:把lldb-server推送到调试设备并运行这个server,在开发机上连上这个server即可;以下是详细步骤。

    #### 在手机端运行lldb-server

    如果你的调试设备是root的,那么相对来说比较简单;毕竟我们的调试进程lldb-server要attach到被调试的进程是需要一定权限的,如果是root权限那么没有限制;如果没有root,那么我们只能借助`run-as`命令来调试自己的进程;另外,被调试的进程必须是debuggable,不赘述。以下以root的设备为例(比如模拟器)

    1. 首先把lldb-server push到调试设备。lldb-sever这个文件可以在 `$ANDROID_SDK/lldb/<版本号数字>/android/ 目录下找到,确认你被调试设备的CPU构架之后选择你需要的那个文件,比如大多数是arm构架,那么执行:

    adb push lldb-server /data/local/tmp/
    

    2. 在调试设备上运行lldb-server。

    adb shell /data/local/tmp/lldb-server platform \
    --server --listen unix-abstract:///data/local/tmp/debug.sock
    

    如果提示 /data/local/tmp/lldb-server: can't execute: Permission denied,那么给这个文件加上可执行权限之后再执行上述命令:

    adb shell chmod 777 /data/local/tmp/lldb-server
    

    这样,调试server就在设备上运行起来了,注意要这么做需要设备拥有root权限,不然后面无法attach进程进行调试;没有root权限另有办法。另外,这个命令执行之后所在终端会进入阻塞状态,不要管它,如下进行的所有操作需要重新打开一个新的终端。

    #### 连接到lldb-server开始调试

    首先打开终端执行lldb(Mac开发者工具自带这个,Windows不支持),会进入一个交互式的环境,如下图:

    1. 选择使用Android调试插件。执行如下命令:

    platform select remote-android
    

    如果提示没有Android,那么你可能需要升级一下你的XCode;只有新版本的lldb才支持Android插件。

    2. 连接到lldb-server

    这一步比较简单,但是没有任何官方文档有说明;使用办法是我查阅Android Studio的源码学习到的。如下:

     platform connect unix-abstract-connect:///data/local/tmp/debug.sock
    

    正常情况下你执行lldb-server的那个终端应该有了输出:

    3. attach到调试进程。首先你需要查出你要调试的那个进程的pid,直接用ps即可;打开一个新的终端执行:

    ~ adb shell ps | grep lldbtest
    
    u0_a53    2242  724   787496 33084 ffffffff b6e0c474 S com.example.weishu.lldbtest
    

    我要调试的那个进程pid是 `2242`,接下来回到lldb的那个交互式窗口执行:

    process attach -p 2242
    

    如果你的设备没有root,那么这一步就会失败——没有权限去调试一个别的进程;非root设备的调试方法见下文。

    至此,调试环境就建立起来了。不需要像gdb那样设置端口转发,lldb的Android调试插件自动帮我们处理好了这些问题。虽然说了这么多,但是你熟练之后真正的步骤只有两步,灰常简单。

    4. 断点调试

    调试环境建立之后自然就可以进行调试了,如果进行需要学习lldb的使用方法;我这里先演示一下,不关心的可以略过。

    1. 首先下一个断点:

     (lldb) br s -n CollectGarbageInternal
    
    Breakpoint 1: where = libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool), address = 0xb4648c20
    

    2. 触发断点之后,查看当前堆栈:

     (lldb) bt
    
      * thread #8: tid = 2254, 0xb4648c20 libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool), name = 'GCDaemon', stop reason = breakpoint 1.1
    
      * frame #0: 0xb4648c20 libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool)
    
        frame #1: 0xb464a550 libart.so`art::gc::Heap::ConcurrentGC(art::Thread*) + 52
    
        frame #2: 0x72b17161 com.example.weishu.lldbtest
    

    3. 查看寄存器的值

    (lldb) reg read
    
    General Purpose Registers:
            r0 = 0xb4889600
            r1 = 0x00000001
            r2 = 0x00000001
            r3 = 0x00000000
            r4 = 0xb4889600
            r5 = 0xb4835000
            r6 = 0xb47fcfe4  libart.so`art::Runtime::instance_
            r7 = 0xa6714380
            r8 = 0xa6714398
            r9 = 0xb4835000
           r10 = 0x00000000
           r11 = 0xa6714360
           r12 = 0xb47fbb28  libart.so`art::Locks::logging_lock_
            sp = 0xa6714310
            lr = 0xb464a551  libart.so`art::gc::Heap::ConcurrentGC(art::Thread*) + 53
    
            pc = 0xb4648c20  libart.so`art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool)
    
          cpsr = 0x20000030
    

    我们可以看到寄存器 `r0`的值为 `0xb4889600`,这个值就是 `CollectGarbageInternal`函数的第一个参数,this指针,也就是当前Heap对象的地址。在ARM下,r0~r4存放函数的参数,超过四个的参数放在栈上,具体如何利用这些寄存器的信息需要了解一些ARM汇编知识。

    4. 查看运行的汇编代码

    (lldb) di -p
    
    libart.so`art::gc::Heap::CollectGarbageInternal:
    ->  0xb4648c20 <+0>:  push.w {r4, r5, r6, r7, r8, r9, r10, r11, lr}
        0xb4648c24 <+4>:  subw   sp, sp, #0x52c
        0xb4648c28 <+8>:  ldr.w  r9, [pc, #0xa9c]
        0xb4648c2c <+12>: add    r4, sp, #0x84
    

    #### 没有root设备的调试办法

    如果没有root权限,那么我可以借助run-as命令。run-as可以让我们以某一个app的身份执行命令——如果我们以被调试的那个app的身份进行attach,自然是可以成功的。

    假设被调试的app包名为 `com.example.lldb`,那么首先想办法把 `lldb-server`这个文件推送到这个app自身的目录:

    1. `adb push`直接这么做不太方便(还需要知道userid),我们先push到 /data/local/tmp/

    adb push lldb-server /data/local/tmp/
    

    2. 然后执行adb shell,连接到Android shell,执行

    run-as com.example.lldb
    

    3. 拷贝这个文件到本App的目录,并修改权限;(由于有的手机没有cp命令,改用cat)

    cat /data/local/tmp/lldb-server > lldb-server
    chmod 777 lldb-server
    

    4. 运行lldb-server

    lldb-server platform --listen unix-abstract:///data/local/tmp/debug.sock
    

    接下来的步骤就与上面root设备的调试过程完全一样了 :)

    ## 后记

    终于完成了Android调试这一系列的文章,时间跨度长达一年;从Java到C/C++再到汇编级别的调试,从有源码到无源码,从Application层到Framework层,任何代码都可以进行调试。借助强大的IDE以及调试器,我们不仅可以快速定位和解决问题,还可以深入学习任何一个复杂的模块。尤记得用探索用lldb进行native调试的过程,网上没有任何Android方面的教程,唯一的学习资料就是Android Studio调试模块的源码以及LLDB Android插件的源码;这其中碰的壁和踩过的坑不计其数,好在最后终于一一解决,可以睡个安稳觉了 : )

    1. Android Studio你不知道的调试技巧

    2. 如何调试Android Framework

    展开全文
  • 1下载NDK,我使用的是NDK-R9D 2下载Eclipse for jave 3下载CDT安装 ...4下载android SDK +NDK eclipse 插件 https://dl-ssl.google.com/android/eclipse/ 安装完成后设置Andr
  • 但是,现在越来越多的程序出于安全、性能或代码复用的考虑,使用JNI调用Native代码来实现某些功能。 其实,在Android平台上,想要对Native程序进行调试,过程非常简单,主要是用到了GDB。大家知道,Android底层其实...
  • Android Studio Native调试

    2016-05-08 02:33:29
    这节介绍在AndroidStudio中调试我们的示例native应用。 注意:Android Studio一直支持Native开发。一些开发者已经报告了使用Note4运行Lollipop(5.0.x)和使用S6运行5.0.0的问题,它们可能导致一些调试问题。...
  • 我们知道Android Framework层不仅仅是只有java代码,还有许多c/c++代码,比如MediaPlayerService,CameraService等本地系统服务,都是用c和c++实现的。这个时候我们要分析问题和追踪代码最好的方式就是调试。 那我们...
  • Android Studio调试native或者service 如果C++代码只是native部分,从第二步开始.如果要调试的C++代码是service,就从第一步开始. 1,修改AndroidManifest.xml 删除service的android:process属性,让service在android...
  • android native 代码内存泄露 定位方案 java代码的内存定位,暂时我们先不关注。此篇文章,主要围绕c c++代码的内存泄露。 ** *欢迎留言,交流您所使用的内存泄露定位方案。*c c++代码,由于其特殊性质,没有...
  • 1. C++代码必须使用`ndk-build`编译,传入参数`NDK_DEBUG=1`。编译完成后,会在lib目录下生成gdbserver,供后续调试使用。 2. 设置AndroidManifest.xml,在**application**项下面设置`android:debuggable="true
  • 环境: CentOS 5.4 [root@localhost bin]# cat /etc/redhat-release CentOS release 5.4 (Final) [root@localhost bin]# uname -a Linux localhost.localdomain 2.6.18-164.el5 #1 SMP Thu Sep 3 03:33:56 EDT
  • 安卓JNI调试、C++/C代码调试native调试策略
  • 0x00 为了避免我们的so文件被动态分析,我们通常在so中加入一些反调试代码,常见的Android native调试方法有以下几种。 1、直接调用ptrace(PTRACE_TRACEME, 0, 0, 0),参考Android Native调试。 2、根据上面...
  • 现在越来越多的程序出于安全、性能或代码复用的考虑,使用JNI调用Native代码来实现某些功能。 其实,在Android平台上,想要对Native程序进行调试,过程非常简单,主要是用到了GDB。大家知道,Android底层其实就是...
  • 今天做项目时,在React Native中调用了原生组件,点击按钮时,程序就闪退,在React Native中却看不到程序的log,原生代码log只有在android studio中查看!最后找到了Android Studio运行React Native项目的方法。 1,...
  • adb是安卓调试桥(Android Debug Bridge),官方中文文档:https://developer.android.google.cn/studio/command-line/adb.html 开发PC端包括client、server两部分,手机端是一个adbd守护进程,/dev/socket/adbd,...
1 2 3 4 5 ... 20
收藏数 24,750
精华内容 9,900
关键字:

调试android的native代码