运行这个项目之前,先要确保本地库中不缺少任何符号。这可能很棘手,因为即使缺少符号,共享库也有可能成功编译,而不会告诉你任何有关缺少符号的信息。更糟糕的是,如果缺少符号,而你试图运行程序,运行时将无法正常加载库,要想知道哪里出了问题可能很让人头疼。 对于这个问题,可以用一个很容易的办法解决:编写一个简单的C程序调用这个库(见代码清单2-8)。链接时,编译器会告诉你本地库是否缺少符号,然后可以加以修正。在从应用本身测试之前,如果想从命令行测试本地库,这个测试程序也很有用。 代码清单2-8 本地库的一个简单测试程序 #include <stdio.h> extern int lib_main(int argc, char **argv); //void _start(int argc, char **argv) int main(int argc, char **argv) { int i; printf("Argc=%d Argv=%p\n", argc, argv); for ( i = 0 ; i < argc ; i++ ) { printf("Main argv[%d]=%s\n", i, argv); } printf("Starting Lib main sub\n"); lib_main(argc, argv) ; exit (0); } 用testlib编译这个程序,或者使用辅助脚本手动编译,如下: agcc -c testlib.c ald -o testlib testlib.o -L. -lch02 注意链接时必须有-L. -lch02,这会告诉编译器要链接libch02.so,并在当前文件夹中搜索库。 现在,启动模拟器来测试这个库。 2.2.2 在设备上测试动态库 要测试这个库,需要使用pushlib把文件上传到设备,这个目标展开为以下命令: adb push libcg02.so /data adb push testlib /data 然后登录到设备,切换到/data文件夹,并执行测试程序: $ adb shell # cd /data # chmod 777 lib * test* # ./testlib bionic/linker/linker.c:1581| ERROR: 833 could not load 'libch02.so'
bionic/linker/linker.c:1641| ERROR: failed to link ./testlib bionic/linker/linker.c:1741| ERROR: CANNOT LINK EXECUTABLE './testlib'
这是在模拟器的1.5版本中运行程序。可以看到,库无法加载。下一节会说明出现了什么问题。如果在SDK 1.0 R2版本中尝试这个过程则会成功。 注意,这并不是说无法从Java应用加载库,只是说明本地链接器/system/bin/linker存在一个问题。 2.2.3 用strace调试 出于某种原因,共享库的本地测试程序在SDK 1.0 R2版本中能顺利运行,但是在1.5 R2中无法加载。由输出我们可以得到一个线索:文件bionic/linker/linker.c:1581无法加载库。对于这种情况,有一个简单易用的Linux工具(strace)可以提供帮助。 strace工具会运行指定的命令,直到退出。它将截获并记录进程执行的系统调用以及进程接收的信号。每个系统调用的名称、其参数以及返回值都将输出。这是一个很有用的诊断和调试工具,在没有提供源代码的情况下,可以利用这个工具解决程序中出现的问题。你会发现,即使是正常程序,也能通过跟踪来了解系统及其系统调用的大量信息。 本书源代码提供了strace的一个Android版本。(strace 现在已经内置到Android SDK 1.5及以后版本中。)下面就来看看发生了什么,如代码清单2-9所示。 代码清单2-9 strace工具输出 $ adb push strace /data $ adb shell # cd /data # ./strace ./testlib execve("./testlib", ["./testlib"], [/* 10 vars */]) = 0
getpid() = 835 gettid() = 835 sigaction(SIGILL, {0xb0001a99, [], SA_RESTART}, {SIG_DFL}, 0) = 0 sigaction(SIGABRT, {0xb0001a99, [], SA_RESTART}, {SIG_DFL}, 0) = 0 sigaction(SIGBUS, {0xb0001a99, [], SA_RESTART}, {SIG_DFL}, 0) = 0 stat64("/system/lib/libch02.so", 0xbef9ea58) = -1 ENOENT (No such file or directory) stat64("/lib/libch02.so", 0xbef9ea58) = -1 ENOENT (No such file or directory) mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40000000 mprotect(0x40000000, 4096, PROT_READ) = 0 fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0 brk(0) = 0x11000 brk(0x11000) = 0x11000 brk(0x12000) = 0x12000 mprotect(0x40000000, 4096, PROT_READ|PROT_WRITE) = 0 mprotect(0x40000000, 4096, PROT_READ) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 write(1, "bionic/linker/linker.c:1581| ERR"..., 70bionic/linker/linker.c:1581| ERROR: 835 could not load 'libch02.so' ) = 70 write(1, "bionic/linker/linker.c:1641| ERR"..., 61bionic/linker/linker.c:1641| ERROR: failed to link ./testlib ) = 61 write(1, "bionic/linker/linker.c:1741| ERR"..., 71bionic/linker/linker.c:1741| ERROR: CANNOT LINK EXECUTABLE './testlib' ) = 71 exit_group(-1) = ? Process 835 detached
下面这两行对问题源由给出了一条线索: stat64("/system/lib/libch02.so", 0xbef9ea58) = -1 ENOENT (No such file or directory) stat64("/lib/libch02.so", 0xbef9ea58) = -1 ENOENT (No such file or directory) 链接器首先尝试从设备的/system/lib文件夹打开库。这是一个只读的文件系统,用户定义的库无法保存在这里。接下来,链接器搜索/lib文件夹,这个文件夹不存在,所以链接失败。链接器并没有在当前目录中搜索,问题就出在这里! 好在,这个问题不会妨碍在Java应用中加载库(只要其中没有缺少符号)。 如果在SKD 1.0 R2中运行同样的命令序列,可以看到第二行变成: stat64("./libch02.so", 0xbef9ea58) = 0 OK 所以,程序可以成功运行。 说明 很难讲文件bionic/linker/linker.c从1.0版本到1.5版本有哪些变化,因为Google对此没有提供任何支持。对此我只能猜测:有可能开发人员忘记在当前文件夹搜索,也可以使用一些新的编译选项来告诉链接器在哪里搜索。
2.2.4 静态编译 最后,如果你希望编写一个命令行工具在设备中运行,必须静态地编译。可以考虑代码清单2-10中的简单程序,它会把命令行参数打印到stdout。 代码清单2-10 简单命令行程序 #include <stdio.h> int main(int argc, char **argv) { int i; for ( i = 0 ; i < argc ; i++ ) { printf("Main argv[%d]=%s\n", i, argv); } printf("Hello World\n"); exit( 0); } 如果使用辅助脚本并提供-static选项来编译这个程序,会看到以下错误: agcc -c main.c ald -static -o a.out main.o arm-none-linux-gnueabi-ld: cannot find -lc 链接器无法找到C运行时库libc.so,尽管库路径本身并没有错误。应该记得,ald使用了-nostdlib,所以它不会链接标准库。 如果采用静态编译(使用-static),必须在加载时去掉-nostdlib。因此,正确的链接命令应该如下: user@ubuntu:~/ch02.Project/native$ arm-none-linux-gnueabi-ld --dynamic-linker=/system/bin/linker -rpath /system/lib -rpath /home/user/tmp/android/system/lib -L/home/user/tmp/android/system/lib -static -o a.out main.o -lc -lm /home/user/mydroid/prebuilt/darwin-x86/toolchain/
arm-eabi-4.2.1/lib/gcc/arm-eabi/4.2.1/libgcc.a arm-none-linux-gnueabi-ld: warning: cannot find entry symbol _start; defaulting to 000080e0
现在可以在设备中测试a.out了: $ make pushbin $ adb shell # ./a.out test Main[0]=test
|