Android系统中调试动态链接库so文件的步骤

时间:2022-03-28 15:52:08

http://blog.wjmjimmie.cn/2010/08/02/Android%E7%B3%BB%E7%BB%9F%E4%B8%AD%E8%B0%83%E8%AF%95%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93so%E6%96%87%E4%BB%B6%E7%9A%84%E6%AD%A5%E9%AA%A4/


(于2010年8月5日更新,提示可用gdbtui调试,以及调试动态链接库有时遇到的调试问题,在第3.2节和第4节增加)

参考文章:
gdbserver调试共享库 http://www.limodev.cn/blog/archives/393
android中c/c++程序的调试(eclipse) http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1480.entry#

参考代码:(文件内容见最下面)
~/mydroid/android-1.6_r2/development/self/HelloWorld/Android.mk
~/mydroid/android-1.6_r2/development/self/HelloWorld/HelloWorld.c
~/mydroid/android-1.6_r2/development/self/libtest/Android.mk
~/mydroid/android-1.6_r2/development/self/libtest/libtest.h
~/mydroid/android-1.6_r2/development/self/libtest/libtest.c

使用的平台:
系统平台:ubuntu10.4 LTS
源代码版本:Android 1.6r2
Emulator平台:Android 1.6(SDK是Android2.2,这个版本没大关系了)

注:编译Android略过,编译目标是默认的generic release版,若需要调试dalvikvm等系统自带程序,最好编译debug版本。
(以下默认shell都已执行过 $ source build/envsetup.sh)
更改debug版的命令是:

$ tapas

(选择device debug generic eng)
(小提示:执行“lunch 2”命令,可以将Android编译目标更改为编译主机程序,编译的dalvikvm可以直接在ubuntu下运行,某种情况下还是很有用的)

0. 准备工作
启动模拟器

$ emulator -avd aaa

在Android源代码根目录执行

source build/envsetup.sh

1. 使用没有被strip版本的执行文件HelloWorld和libtest.so
在Android源代码根目录下依次执行

make libtest
make HelloWorld

没有被strip的版本所在位置是
~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/bin/HelloWorld
~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/lib/libtest.so

将HelloWorld push到/data/bin/目录下,将libtest.so push到/system/lib/目录下

$ adb push ~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/bin/HelloWorld /data/bin/
$ adb remount
$ adb push ~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/lib/libtest.so /system/lib/

2. 启动gdbserver
Android1.6模拟器中自带gdbserver程序,在Android模拟器中执行如下命令:

# gdbserver :1234 /data/bin/HelloWorld

正常的话结果应该是:(不正常的话就自己查吧)

Process /data/bin/HelloWorld created; pid = 206
Listening on port 1234

不要忘记上面的pid,忘记了就ps呗

3. 启动arm-eabi-gdb进行调试连接

3.1 首先设置模拟器端口转发:

$ adb forward tcp:1234 tcp:1234

3.2 启动arm-eabi-gdb:(android源代码prebuild目录下有相应文件,有多个版本,没查有什么区别,我用的是4.4)
(2010/08/05+:用arm-eabi-gdbtui调试也可,只是上下左右方向键是用来控制源代码显示的。另外,有 些程序不需要第5步就可以调试动态链接库,目前不知道为什么,但要在gdb中调用set solib相关的两个命令(参照3.3节),并且是绝对路经,其实之前用相对路径没有效果。进入动态链接库调试时可以使用n,尽管显示代码位置会到处乱 跳,但在关键函数处倒也正确。)

$ ~/mydroid/android-1.6_r2/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-gdb ~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/bin/HelloWorld

启动后会显示:

GNU gdb 6.6
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-elf-linux"...

3.3 首先执行set solib-×××的两个命令:(可选,只调试HelloWorld及其动态库的话不执行也无碍,最好是绝对路径,用自动扩展的写法可能不行)

(gdb) set solib-absolute-prefix /home/j/mydroid/android-1.6_r2/out/target/product/generic/symbols/
(gdb) set solib-search-path /home/j/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/lib/

3.4 连接gdbserver调试:

(gdb) target remote :1234

成功的话会显示:

__dl__start () at bionic/linker/arch/arm/begin.S:35
35      mov r0, sp
Current language:  auto; currently asm

如果没执行set solib-×××的两个命令会显示如下信息,但没关系

warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb0000100 in ?? ()

4. 进行gdb调试
首先执行两个命令:

(gdb) b main
(gdb) c

这是调试会停在HelloWorld.c的main函数处,提示信息如下:

Continuing.
Error while mapping shared library sections:
libc.so: No such file or directory.
Error while mapping shared library sections:
libstdc++.so: No such file or directory.
Error while mapping shared library sections:
libm.so: No such file or directory.
Error while mapping shared library sections:
libtest.so: No such file or directory.
Breakpoint 1, main ()
at /home/j/mydroid/android-1.6_r2/development/self/HelloWorld/HelloWorld.c:6
6       printf("main 1");
Current language:  auto; currently c

最后几行表示调试正常,如果你编的debug版的Android,前几行会显示加载动态库成功,但也不能直接进入动态链接库进行调试的:)至少我这里不能。
(2010/08/05+:这个时候可以通过info share来判断。执行info share命令会显示当前动态链接库加载的内存地址,若显示的地址与第5节获得的地址相同,则说明可以调试动态链接库,并且应该可以用n命令,若不同,则请尝试第5节的办法。)

这时你可以通过基本的gdb调试命令进行调试:

(gdb) n     (执行下一条)
(gdb) b 10  (在第10行设断点)
(gdb) list  (显示源代码信息)
(gdb) c (继续至下一断点或结束)

(注:其实我不了解gdb调试,还是google一下比较好)

若你在func()处执行s命令进入func()函数体,就会提示错误,也无法进行正常调试了,如何调,请继续下一节—

5. 加载动态库so文件进行调试
这部分的工作参考 gdbserver调试共享库 这篇文章就可以了,不过我还是重复一下Android平台的步骤:

5.1 查看动态链接库加载的内存地址

$ adb shell cat /proc/206/maps

结果中会有如下几行:

00009000-0000a000 rwxp 00001000 1f:01 712        /data/bin/HelloWorld
40000000-40008000 r-xs 00000000 00:07 186        /dev/ashmem/system_properties (deleted)
80000000-80001000 r-xp 00000000 1f:00 713        /system/lib/libtest.so
80001000-80002000 rwxp 00001000 1f:00 713        /system/lib/libtest.so
afc00000-afc21000 r-xp 00000000 1f:00 478        /system/lib/libm.so

记录下80000000这个地址。

5.2 查看动态链接库libtest.so中text段的偏移地

$ ./prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-objdump -h ~/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/lib/libtest.so |grep .text

结果显示为:

5 .text         00000034  00000344  00000344  00000344  2**2

记录00000344这个地址。

5.3 在gdb中加载动态链接库
在gdb命令行执行如下命令:(其中0×80000344是80000000+00000344=80000344)

(gdb) add-symbol-file /home/j/mydroid/android-1.6_r2/out/target/product/generic/symbols/system/lib/libtest.so 0x80000344

5.4 进入动态链接库进行调试
有两种进入动态链接库进行调试的方式:

5.4.1 通过s命令进入
设置断点在func()函数被调用处:

(gdb) b 10
(gdb) c

当调试停在HelloWorld.c的第十行时,执行s命令:

Breakpoint 2, main ()
at /home/j/mydroid/android-1.6_r2/development/self/HelloWorld/HelloWorld.c:10
10          func();
(gdb) s

这时就会显示libtest.c的代码,执行list命令进行查看:

func () at /home/j/mydroid/android-1.6_r2/development/self/libtest/libtest.c:3
3   void func(){
(gdb) list1   #include "stdio.h"
2   #include "libtest.h"
3   void func(){
4       printf("libtest 1");
5       printf("Hello, This is a function in libtest!");
6       printf("libtest 2");
7   }

5.4.2 通过设置断点进

在libtest.c中设置断点:

(gdb) b libtest.c:5Breakpoint 3 at 0x80000352: file /home/j/mydroid/android-1.6_r2/development/self/libtest/libtest.c, line 5.(gdb) cContinuing.
Breakpoint 3, func ()
at /home/j/mydroid/android-1.6_r2/development/self/libtest/libtest.c:5
5       printf("Hello, This is a function in libtes

t!”);

如此,就可以进行了动态链接库的调试了。

注意:我在使用时,在进入到动态链接库中调试时无法使用n命令,可以使用c finish b等命令。

下面这两个页面有改进的调试:
用gdbserver调试共享库(改进版) http://blog.csdn.net/absurd/archive/2007/09/20/1793646.aspx
gdbserver调试共享库(终结版) http://www.limodev.cn/blog/archives/477

参考文章:
gdbserver调试共享库 http://www.limodev.cn/blog/archives/393
android中c/c++程序的调试(eclipse) http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1480.entry#

附文件内容:

./HelloWorld/Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
HelloWorld.c

LOCAL_MODULE:= HelloWorld

LOCAL_SHARED_LIBRARIES += \
libtest

include $(BUILD_EXECUTABLE)

./HelloWorld/HelloWorld.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "stdio.h"
#include "../libtest/libtest.h"

int main()
{
printf("main 1");
int a=10;
while(a--){
printf("while 1");
func();
printf("while 2");
};
printf("Hello Android\n");
printf("main 2");
}

./libtest/Android.mk

1
2
3
4
5
6
7
8
9
10
11
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_PRELINK_MODULE := false

LOCAL_SRC_FILES := \
libtest.c

LOCAL_MODULE:= libtest

include $(BUILD_SHARED_LIBRARY)

./libtest/libtest.h

1
void func();

./libtest/libtest.c

1
2
3
4
5
6
7
#include "stdio.h"
#include "libtest.h"
void func(){
printf("libtest 1");
printf("Hello, This is a function in libtest!");
printf("libtest 2");
}