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