Esper曾说过,一千个科研民工有一千种编译报错

最近从实验室拿到了SPEC软件包用于测试软件性能,本满心欢喜地以为可以用SPEC提供的预编译文件直接跑,结果install.sh直接的当头一棒,说是预编译文件不支持当前环境,要自己从头编译。

然而谁也没想到的是,这只是噩梦的开始。

首先来到tools/src目录下,使用./buildtools命令开始编译工具链。

  1. gnu make的问题。gnu-make一上场就给我们出了一个难题:

    glob/libglob.a(glob.o): In function `glob_in_dir':
    /opt/make-3.81/glob/glob.c:1361: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1336: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1277: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1250: undefined reference to `__alloca'
    glob/libglob.a(glob.o): In function `glob':
    /opt/make-3.81/glob/glob.c:575: undefined reference to `__alloca'
    glob/libglob.a(glob.o):/opt/make-3.81/glob/glob.c:726: more undefined references to `__alloca' follow
    collect2: error: ld returned 1 exit status
    Makefile:410: recipe for target 'make' failed
    make[2]: *** [make] Error 1
    make[2]: Leaving directory '/opt/make-3.81'
    Makefile:603: recipe for target 'all-recursive' failed
    make[1]: *** [all-recursive] Error 1
    make[1]: Leaving directory '/opt/make-3.81'
    Makefile:326: recipe for target 'all' failed
    make: *** [all] Error 2

    通过查阅资料 [提出问题] [解决问题] 发现,该问题的解决者也没能对问题的产生原因给出明确的解释,好在留下了有效的解决方案: 将make-3.81/glob/glob.c中,第54行的

    # if _GNU_GLOB_INTERFACE_VERSION == GLOB_INTERFACE_VERSION

    改成

    # if _GNU_GLOB_INTERFACE_VERSION >= GLOB_INTERFACE_VERSION

    然后重新编译解决问题。

  2. specmd5sum的问题。
    gcc -DHAVE_CONFIG_H    -I/home/gem5/cpu2006/tools/output/include   -I. -Ilib  -c -o md5sum.o md5sum.c
    In file included from md5sum.c:38:0:
    lib/getline.h:31:1: error: conflicting types for 'getline'
    getline PARAMS ((char **_lineptr, size_t *_n, FILE *_stream));
    ^
    In file included from md5sum.c:26:0:
    /usr/include/stdio.h:678:20: note: previous declaration of 'getline' was here
    extern _IO_ssize_t getline (char **__restrict __lineptr,
                    ^
    In file included from md5sum.c:38:0:
    lib/getline.h:34:1: error: conflicting types for 'getdelim'
    getdelim PARAMS ((char **_lineptr, size_t *_n, int _delimiter, FILE *_stream));
    ^
    In file included from md5sum.c:26:0:
    /usr/include/stdio.h:668:20: note: previous declaration of 'getdelim' was here
    extern _IO_ssize_t getdelim (char **__restrict __lineptr,
                     ^
    make: *** [md5sum.o] Error 1
    + testordie 'error building specmd5sum'
    + test 2 -ne 0
    + echo '!!! error building specmd5sum'
    !!! error building specmd5sum
    + kill -TERM 1299
    + exit 1
    !!!!! buildtools killed

这次的问题出在getline函数上,该函数属于gcc的插件函数,也就是说既不是ANSI C标准,也不是POSIX标准,而是因为经常被使用而作为第三方的gnu扩展加入了gcc中。由于GLIBC 1中没有实现该方法,所以旧版本的程序需要自己实现该方法;而总从GLIBC2开始,该方法就写入了stdio.h中,再用老方法编译就会产生重定义的错误了。

解决该问题的方法之一是对gcc使用-stc=c99(其它C标准,比如C89等等也可以)让它不定义getline函数,而是使用自带的定义。但是这样引入的问题是,实际上SPEC其他组件是采用了gnu扩展标准的,而buildtools脚本又不支持对不同项目给出不同的命令行选项,反而又会导致其他项目编译失败。这里采用第二种方法,不使用自带的getline,而是使用gcc提供的getline函数。具体操作就是注释掉自带的getline.h头文件,参见该博客

  1. Perl的问题。

Perl这里一共有两个问题。第一个是asm/page.h头文件缺失的问题,可以直接注释掉。或许也可以使用-I/usr/include/x86_64-linux-gnu参数将该文件的新位置提供给gcc,但是我没有进行测试。

第二个是动态链接的问题。如果没有正确配置动态链接库libdl的话,Perl就无法提供动态加载模块的功能,导致在后续的Perl模块编译与测试阶段中出现类似的提示:

t/100generic-bzip2.....Can't load module Compress::Raw::Bzip2, dynamic loading not available in this perl.
  (You may need to build a new perl executable which either supports
  dynamic loading or has the Compress::Raw::Bzip2 module statically linked into it.)
 at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10
Compilation failed in require at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10.
BEGIN failed--compilation aborted at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10.
Compilation failed in require at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Bzip2.pm line 11.
BEGIN failed--compilation aborted at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Bzip2.pm line 11.
Compilation failed in require at t/100generic-bzip2.t line 12.
BEGIN failed--compilation aborted at t/100generic-bzip2.t line 12.

这里第二行给出了报错的原因:编译的Perl没有动态加载的功能,换句话说,就是用于动态加载代码的libdl库没有正确配置,导致动态加载一块的代码完全没有编译。回去翻阅Configure的输出,发现早在配置阶段就出现问题了:

dlopen() NOT found.
Do you wish to use dynamic loading? [n] 

由于完全没有给出任何有用的信息,加上咕果上也查不到什么有效的解决方案,在此陷入了困境。

然而,天无绝人之路,后来在解决另一个报错,数学库libm找不到的时候,意外地有了新的进展。可能是由于Configure脚本过老,无法识别到gcc数学库参数,额外附加库这项配置变成了none

Checking for optional libraries...
What libraries to use? [none]

由于实际上是使用了数学库的,导致在后续编译中会出现找不到符号的问题:

../libperl.a(pp.o): In function `Perl_pp_pow':
pp.c:(.text+0x27b6): undefined reference to `pow'
../libperl.a(pp.o): In function `Perl_pp_modulo':
pp.c:(.text+0x366d): undefined reference to `fmod'
../libperl.a(pp.o): In function `Perl_pp_atan2':
pp.c:(.text+0x824d): undefined reference to `atan2'
../libperl.a(pp.o): In function `Perl_pp_sin':
pp.c:(.text+0x8341): undefined reference to `sin'
../libperl.a(pp.o): In function `Perl_pp_cos':
pp.c:(.text+0x8481): undefined reference to `cos'
../libperl.a(pp.o): In function `Perl_pp_exp':
pp.c:(.text+0x87a1): undefined reference to `exp'
../libperl.a(pp.o): In function `Perl_pp_log':
pp.c:(.text+0x88e8): undefined reference to `log'
../libperl.a(pp.o): In function `Perl_pp_sqrt':
pp.c:(.text+0x8b9f): undefined reference to `sqrt'
../libperl.a(pp.o): In function `Perl_pp_crypt':
pp.c:(.text+0xabee): undefined reference to `crypt'
collect2: error: ld returned 1 exit status

看到前面提到的一篇博客中,通过修改buildtools的方法解决了该问题,并且我也进行了验证,确实有效。然而总觉得不够清真,为了解决一个系统上的问题修改了全局配置。后来了解到Perl的Configure可以通过hintfile为特定平台提供默认参数,从而解决了该问题。hintfile将会被用类似source的方式将变量引入当前脚本中,可以作为Configure选项的默认值。由于没有找到详细的文档,hintfile的变量名要自己猜,但是可以根据Configure中用于用户交互的信息进行变量名的猜测。在Linux系统中,Configure大概率会选择hints/linux.sh作为hintfile进行加载。

后来发现手动添加库依赖的变量名为libs,把-lm加入该变量值即可解决问题。

而后在阅读最新版Perl的hints/linux.sh(参见 https://github.com/Perl/perl5/blob/blead/hints/linux.sh )的时候发现,针对g++和dlopen有一段硬编码的配置:

# If using g++, the Configure scan for dlopen() and (especially)
# dlerror() might fail, easier just to forcibly hint them in.
case "$cc" in
*g++*)
  d_dlopen='define'
  d_dlerror='define'
  ;;
esac

其中提到g++和dlopen具有一定的不兼容性,Configure在这种条件下可能识别不出来libdl,抱着死马当活马医的心态,把这段配置复制到SPEC2006中Perl的hintfile中,发现没有任何变化,还是报出相同的错误。(显然不会有任何变化,默认使用的是cc/gcc命令,不会显式地拿g++来编译C代码的)

然后意识到问题之后,直接把里面的两行关键代码留下来,再次编译,报错发生了变化。

首先是通过hintfile的内置变量赋值,强行绕过了Configuredlopen检测。脚本给出了如下的警告信息,意味着虽然没有检测到libdl,但是程序忠实地按照变量配置,开启了含有libdl库的代码编译:

dlopen() NOT found.
*** WHOA THERE!!! ***
    The recommended value for $d_dlopen on this machine was "define"!
    Keep the recommended value? [y]
Do you wish to use dynamic loading? [y]

然后报出了新的错误:

        Making DynaLoader (static)
Processing hints file hints/linux.pl
Writing Makefile for DynaLoader
make[1]: Entering directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
make[1]: Leaving directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
make[1]: Entering directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
../../miniperl "-I../../lib" "-I../../lib" DynaLoader_pm.PL DynaLoader.pm
../../miniperl "-I../../lib" "-I../../lib" XSLoader_pm.PL XSLoader.pm
cp XSLoader.pm ../../lib/XSLoader.pm
cp DynaLoader.pm ../../lib/DynaLoader.pm
AutoSplitting ../../lib/DynaLoader.pm (../../lib/auto/DynaLoader)
rm -f DynaLoader.xs
cp dl_dlopen.xs DynaLoader.xs
../../miniperl "-I../../lib" "-I../../lib" ../../lib/ExtUtils/xsubpp -noprototypes -typemap ../../lib/ExtUtils/typemap DynaLoader.xs > DynaLoader.xsc && mv DyaLoader.xsc DynaLoader.c
cc -c   -I/home/esper/cpu2006/tools/output/include -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFSET_BITS=64 -O2   -DVERSION=\"1.05\" -DXS_VERSION=\"1.05\"  "-I../.."  -DPERL_CORE -DLIBC="" DynaLoader.c
rm -rf ../../lib/auto/DynaLoader/DynaLoader.a
/usr/bin/ar cr ../../lib/auto/DynaLoader/DynaLoader.a DynaLoader.o && : ../../lib/auto/DynaLoader/DynaLoader.a
chmod 755 ../../lib/auto/DynaLoader/DynaLoader.a
make[1]: Leaving directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
cc -o perl  -L/home/esper/cpu2006/tools/output/lib -Wl,-E perlmain.o lib/auto/DynaLoader/DynaLoader.a  libperl.a `cat ext.libs` -lm -lcrypt -lutil -lc
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_find_symbol':
DynaLoader.c:(.text+0x3b3): undefined reference to `dlsym'
DynaLoader.c:(.text+0x441): undefined reference to `dlerror'
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_unload_file':
DynaLoader.c:(.text+0x4ea): undefined reference to `dlclose'
DynaLoader.c:(.text+0x559): undefined reference to `dlclose'
DynaLoader.c:(.text+0x566): undefined reference to `dlerror'
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_load_file':
DynaLoader.c:(.text+0x645): undefined reference to `dlopen'
DynaLoader.c:(.text+0x6c9): undefined reference to `dlerror'
collect2: error: ld returned 1 exit status
make: *** [perl] Error 1
+ testordie 'error building Perl'
+ test 2 -ne 0
+ echo '!!! error building Perl'
!!! error building Perl
+ kill -TERM 550221
+ exit 1
!!!!! buildtools killed

我的眼神瞬间犀利了起来.jpg

undefined reference to ...,这不是最经典的忘记加-lXXX链接参数的结果吗?

除错终了后我神速开hintfile。目的唯一libs!!!!!我爆速修改链接选项。warning!
此处省略100余字

修改文件过后,果然编译成功!删去上文根据设想添加的两行d_XXX强制配置后,编译仍然通过,这证实了我的设想。

然而,Perl的测试中仍然有几个是处于失败状态的,大约1%,十来个左右。由于测试范围往往大于使用需求,对于这几个没通过的测试我就没理它了,直接yes进行后续编译操作。

总结就是,Perl编译动态加载模块失败的原因与数学库一样,都是因为识别不到系统配置,默认配置又不提供有效值造成的。在perl-5.8.8/hints/linux.sh末尾加上如下两行解决上述编译问题:

libs='-lm -lcrypt -lutil -lc -ldl'
libpth='/usr/local/lib64 /lib64 /usr/lib64'
  1. 自此之后编译上就应该没有本质上的问题了。如果是直接从.tar.gz文件解压的话,复制出来的src文件夹及下级文件权限都是444,即只有读权限,而后续某些模块的测试涉及到对测试文件的读写操作,会产生Permission Denied一类的报错信息。如果对自己的操作自信,不会损坏文件的话,可以使用偷懒的方法,直接给所有文件/文件夹写权限:
    sudo chmod u+w -R .

    tools/src目录下执行该命令即可。

中间还有个小插曲是,一开始以为是编译器版本的问题,整了gcc 4和5的docker来编译看能不能成功。结果不但没成功,还报出了新的You haven't done a "make depend" yet!错误,后来发现原因是gcc镜像的FROM用的是Debian系的,/bin/sh默认指向了dash而不是bash。用ln -snf /bin/bash /bin/sh覆盖修改这个链接即可。更多资料可参见 https://sjp38.github.io/post/spec_cpu2006_install/

ps. 如果在后续测试阶段发现某些测试怎么跑都跑不对,可以检查一下是不是拿到的SPEC有问题。我这里用到的SPEC是被实验室重新打包过的,里面好多原始数据文件被改过了,debug了好久才发现:(

标签: none

添加新评论