做嵌入式 Linux 开发时候都需要使用交叉编译工具,这些一般是方案厂商提供,但我们通常也可以使用那些打包好的通用交叉编译工具,比如 Debian 上 apt 也有打包好的 arm 交叉编译工具: gcc-arm-linux-gnueabigcc-arm-linux-gnueabihf,对应 c++ 版本以 g++ 开头,这两类主要区别在于 CPU 对于浮点的处理上,涉及到 armelarmhf 架构的区别,详情参照这里,你只需要选择对应开发板的架构工具即可。

由于是 Debian 自己维护更新的包,上述的两个交叉编译工具通常都会更新比较频繁,会支持最新的标准和特性,但是我们的开发板上系统通常都不是最新的,所以在使用时需要注意,否则会出现编译后的程序无法在开发板上运行。

最近在将 mruby 应用到嵌入式环境时就遇到这个问题。

问题和溯因

将静态链接了 mruby 库的测试程序 mirb 放入嵌入式环境上运行时,提示

1
./mirb: /lib/libc.so.6: version `GLIBC_2.16' not found (required by ./mirb)

嵌入式环境没有 2.16 版本以上的 glibc 库,但是理论上测试程序不需要这么高版本的 glibc 库。在嵌入式命令行上用 ldd (有些嵌入式环境可能没有附带这个工具)查看一下可执行文件信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ldd -v mirb
./mirb: /lib/libc.so.6: version `GLIBC_2.16' not found (required by ./mirb)
        libm.so.6 => /lib/libm.so.6 (0x401ed000)
        libc.so.6 => /lib/libc.so.6 (0x40293000)
        /lib/ld-linux.so.3 (0x401c4000)

        Version information:
        ./mirb:
                libm.so.6 (GLIBC_2.4) => /lib/libm.so.6
                libc.so.6 (GLIBC_2.7) => /lib/libc.so.6
                libc.so.6 (GLIBC_2.16) => not found

                libc.so.6 (GLIBC_2.4) => /lib/libc.so.6
        /lib/libm.so.6:
                ld-linux.so.3 (GLIBC_PRIVATE) => /lib/ld-linux.so.3
                libc.so.6 (GLIBC_2.4) => /lib/libc.so.6
        /lib/libc.so.6:
                ld-linux.so.3 (GLIBC_2.4) => /lib/ld-linux.so.3
                ld-linux.so.3 (GLIBC_PRIVATE) => /lib/ld-linux.so.3

可执行程序 mirb 的确引用 2.16 版本的 glibc 库,那就继续查一下引用了 2.16 版本 glibc 库的哪些符号。

在开发主机(不需要在嵌入式主机中)上可以使用 objdump 工具查看可执行程序的符号表(参数 -t):

1
2
$ objdump -t mirb | grep 2.16
00000000       F *UND*	00000000              timespec_get@@GLIBC_2.16

可以看出是 timespec_get 这个函数符号需要 2.16 版本的 glibc 库,那么就得在代码中查一下哪个地方调用了这个函数。

这里 可以看出 mruby 源码的 time.c 文件中使用了 timespec_get 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  tm = (struct mrb_time *)mrb_malloc(mrb, sizeof(*tm));
#if defined(TIME_UTC)
  {
    struct timespec ts;
    if (timespec_get(&ts, TIME_UTC) == 0) {
      mrb_free(mrb, tm);
      mrb_raise(mrb, E_RUNTIME_ERROR, "timespec_get() failed for unknown reasons");
    }
    tm->sec = ts.tv_sec;
    tm->usec = ts.tv_nsec / 1000;
  }
#elif defined(NO_GETTIMEOFDAY)

宏定义 TIME_UTC 控制着代码是否调用 timespec_get 函数。

通过搜索了解到,TIME_UTCtimespec_get 都是 C11 标准里定义的。由于在编译 mruby 时没有指定编译参数,交叉编译工具使用了默认参数导致 C11 标准也被支持,mruby 库也就使用了最新的 C11 标准,引用了新函数 timespec_get,产生了 glibc 2.16 版本的依赖。

解决方法

在知晓原因后,解决方法就简单了。就是在交叉编译 mruby 库时,去指定编译标准(c标准),修改 mruby 的编译配置文件 build_config.rb

1
2
3
4
conf.cc do |cc|
    cc.command = "arm-linux-gnueabi-gcc"
    cc.flags << "-std=gnu99"
end

将编译 c 标准指定为 gnu99(-std=gnu99),这里也可以使用 c99 标准(-std=c99)。

然后重新编译测试程序,放到嵌入式环境上运行,OK。

结语

使用编译器尤其是交叉编译器时,最好要指定编译参数,确定编译标准,否则目标平台可能不支持,导致程序无法运行。在向第三方提供开发库而不是源码的情况下, 需要特别注意,尽量使用统一的编译参数标准,推荐 c99 或者 gnu99。

在遇到类似 /lib/libc.so.6: version GLIBC_X.XX not found 问题时,可以借助 lddobjdump 工具来确定依赖的符号源,寻找解决方法。