Android NDK断点失效原因及解决方案

这篇文章主要列举了解决几种断点失效的经验和方法,对于那些苦苦挣扎在莫名其妙的问题中的开发者也许有较大的帮助。

概述

首先,让我们看一下一个典型的包括本地代码的Android应用的结构:

应 用代码被打包存放在一个.apk文件中,实际上,.apk文件就是一个ZIP格式压缩包,压缩包中包括一个classes.dex文件(所有的Java代 码都包含其中)和一个或更多存放在lib\<EABI名称>目录下的本地代码库文件。正常情况下,包含本地代码的应用的工作步骤如下所述:
1. Android系统加载Java代码,开始执行
2. Java代码中通过System.loadLibrary()方法加载本地代码库
3. Java代码调用本地代码实现的函数

每个本地代码库都有两个版本:

  • 一个带有调试信息的完整版本,此版本的库文件包含对应的源代码,会将库文件中的二进制代码地址和源代码文件的行相对应。这个版本的文件存放在你的项目路径下的obj\local\armeabi目录中。
  • 一个不带调试信息的版本,这个文件位于libs\armeabi目录下,最后真正打包到apk文件中的库文件是这个版本的文件。

设置断点

当你在源代码文件中的某一行设置断点时,GDB需要先进行一些操作才能真正的创建该断点并且让这个断点可以背触发。
假设说libNative1已经被加载了,内存中起始地址为0×10000,然后用户想要在源代码文件c:\main.cpp的第24行设置了一个断点,GDB将会做如下操作来设置这个断点:

  1. GDB搜索搜有的已加载库文件中搜索”c:\main.cpp”文件对应的库文件,在本文的例子中,就是libNative1.so库文件了。
  2. GDB在obj\local\armeabi目录下超找一个叫做libNative1.so的文件,如上所述,这个目录下的文件是带有调试信息的。GDB在这个库文件中的源代码文件(c:\main.cpp)中读取符号表。
  3. GDB根据读取出来的符号表计算第24行对应的地址偏移量是+0×2。然后将这个偏移量加上库文件libNative1.so的起始地址(0×10000),并且在0×10002的位置设置断点。

这3步操作任意一步失败,断点就不会被创建,或者创建错误从而导致永远都不会被触发。我们在下一小节介绍这种问题的诊断方法以及如何修复它。

诊断问题

本小节将详细描述如何检查由于不规范配置或文件缺失导致断点失效的方法。

A. 确保库文件已被加载

第一件要做的事情是确定本地代码库是不是已经被加载了,并且GDB是不是已经知道这件事情。我们可以在GDB中输入“info shared”命令来查看。

info shared命令结果包含三个重要信息:
1.  linker连接器
2.  libc.so文件
3.  你想要调试的所有本地代码库文件
这提供一个例子:

From                            To                              Syms          Read                             Shared Object Library
……………………………………………………No          com.visualgdb.example.MyAndroidApp
0xb0001000          0xb00069d0           Yes          C:/…/AndroidBinaryCache/…/linker
0×40073300          0x400a12fc              Yes          C:/…/AndroidBinaryCache/…/libc.so
0x5148fccc              0×51491198              Yes           E:/…/obj/local/armeabi/libMyAndroidApp.so

如果有些库文件没在这显示出来,那么可以通过”sharedlibrary”手动强制GDB重新加载一次符号表。
如 果你有些.so文件(例如libMyAndroidApp.so)已经在这里列出来了,但是显示符号没被加载(“Sysms read”那一列的状态是“no”),这说明GDB没法找到带有调试信息的库文件。这时可以在GDB中使用“show solib-search-path”命令:

可以看到你项目的obj/local/armeabi目录路径被打印出来,该目录下应该包含了带有调试信息的.so文件,如果在该目录下没有.so文件的,那么将正确的.so文件复制到该目录下并重新运行”sharedlibrary”命令。
除此之外,你还可以使用”set solib-search-path”命令强制GDB在其他目录下搜索.so文件。
如果你的.so文件还是没有列出来,说明还没有被Java代码加载,请再确认一下Java代码中的System.loadLibrary()方法是不是被正确调用,并且没有产生异常。

B. 确保使用正确的文件路径

还 有一个比较常见的导致断点失效的原因是你再生成和调试项目时使用不同的目录。举个例子,如果你编译库文件时项目源代码位于c:\projects,然后你 又将源代码移到d:\projects目录下,这时在d:\projects\main.cpp文件中设置一个断点就会失败,GDB并不会使用d: \projects\main.cpp这个源文件替代原来目录下的文件。像这一类问题都可以通过检查源文件并且和设置断点时的源文件做比较来解决,你可以 先移除所有断点,然后重新设置这些断点,再看看你的IDE中显示的GDB命令,看看到底是在哪个源文件中设置的断点,是不是和生成库文件时的源文件一致。

从上图可以看到用来设置断点的-break-insert命令可以显示出你使用的源文件路径,再使用”info sources”命令可以看看GDB真正发现的是哪些源文件。

将这条命令的输出结果复制到一个文本编辑器中,然后再去查找你想调试的源文件(比如说说MyAndroidApp.c):

如 果在输出结果里没有你想要调试的源文件,那么说明你家在的.so文件版本错了(关于.so文件的版本问题见上一小节)。如果数据结果有你想要调试的文件, 但是路径和你使用-break-insert命令时显示的源文件路径不一致,这是你就需要再找找什么原因了。这种情况下,在新路径下重新编译生成你的项 目,或者将源文件放回到老的路径下,这样就可以保证你设置断点的源文件和”info sources”命令输出的源文件路径保持一致了。
再次强调,GDB需要你设置断点的源文件路径和info sources命令打印出来的文件路径完全一致。包括jni前面的双斜线。

C.重新检查文件版本并且做一次清理操作

如 果你设置了断点,但是从来没有生效,也有一种可能是你的Android应用加载的.so文件和你的GDB用来计算地址的.so文件版本不一致造成的。导致 这种情况的最常见原因是Android加载时有bug,它没有去加载armeabi-v7a目录下的文件,而是加载了armeabi目录下的库文件。如果 你觉得有可能是这个问题,试试修改编译生成的配置,要么在armeabi平台编译,要么在armeabi-v7a平台编译,千万不要同时再者两个平台下编 译。(【译 者注】原作者前面没有提到armeabi-v7a,现在突然冒出来了,armeabi和armeabi-v7a是不同的CPU类型,后者带有包括浮点运算 等其他功能,通过Android.mk文件中的target_cpu_api来指定,如果选择了armeabi-v7a的话,前面提到的 xxx\armeabi目录都会变成xxx\armeabi-v7a,一个带有NDK开发的Android项目中可能有多个Android.mk,原作者 的意思就是所有的Android.mk文件的target_cpu_api设置都要一样)

英文原文:codeproject,编译:ImportNew - 赵荣
译文链接:http://www.importnew.com/1706.html
【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

关于作者: 赵荣

一个不甘寂寞,热爱Java技术,喜欢Android平台,略懂密码技术,技术兴趣广泛,有些闷骚的胖子码农.

查看赵荣的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部