perlbench 导致 SPEC 2006 线束之外的段错误

Posted

技术标签:

【中文标题】perlbench 导致 SPEC 2006 线束之外的段错误【英文标题】:perlbench results in segfault outside the SPEC 2006 harness 【发布时间】:2017-03-06 07:25:02 【问题描述】:

这可能过于具体,但在这里发布,因为它可能会帮助其他试图在默认 SPEC 基准测试工具之外编译/运行 SPEC 2006 基准测试的人。 (我们这样做的原因是比较编译策略和代码覆盖率,而 SPEC 工具只关注生成代码的性能。

执行 perlbench 的 ref 运行时,基准测试会因分段错误而崩溃:

    Program received signal SIGSEGV, Segmentation fault.
0x00000000004f6868 in S_regmatch (prog=0x832144)

    at <path-to-spec>/CPU2006/400.perlbench/src/regexec.c:3024
3024            PL_reg_start_tmp[n] = locinput;
(gdb) bt
#0  0x00000000004f6868 in S_regmatch (prog=0x832144)
    at <path-to-spec>/CPU2006/400.perlbench/src/regexec.c:3024
#1  0x00000000004f22cf in S_regtry (prog=0x8320c0, startpos=0x831e70 "o")
    at <path-to-spec>/CPU2006/400.perlbench/src/regexec.c:2196
#2  0x00000000004eba71 in Perl_regexec_flags (prog=0x8320c0, stringarg=0x831e70 "o", strend=0x831e71 "", 
    strbeg=0x831e70 "o", minend=0, sv=0x7e2528, data=0x0, flags=3)
    at <path-to-spec>/CPU2006/400.perlbench/src/regexec.c:1910
#3  0x00000000004b33bb in Perl_pp_match ()
    at <path-to-spec>/CPU2006/400.perlbench/src/pp_hot.c:1340
#4  0x00000000004fcde4 in Perl_runops_standard ()
    at <path-to-spec>/CPU2006/400.perlbench/src/run.c:37
#5  0x000000000046bf57 in S_run_body (oldscope=1)
    at <path-to-spec>/CPU2006/400.perlbench/src/perl.c:2017
#6  0x000000000046b9f6 in perl_run (my_perl=0x7bf010)
    at <path-to-spec>/CPU2006/400.perlbench/src/perl.c:1934
#7  0x000000000047add2 in main (argc=4, argv=0x7fffffffe178, env=0x7fffffffe1a0)
    at <path-to-spec>/CPU2006/400.perlbench/src/perlmain.c:98

执行环境是 64 位 Linux,使用最新的 gcc 和 clang 都可以观察到该行为。

是什么导致了这个崩溃?

【问题讨论】:

这看起来与 github.com/briandfoy/perlbench 不同,但如果不是,您可以在 GitHub 上创建问题。 确实不一样。我不知道 github.com/briandfoy/perlbench 。这个问题中的一个是 SPEC CPU 2006 基准测试套件的一部分,它使用(旧版本的)perl 解释器作为 C 编译器和/或硬件、操作系统等的基准测试。 【参考方案1】:

段错误是由指出行上的变量n 的垃圾值引起的。检查代码显示该值来自类型对象的字段arg1

struct regnode_1 
    U8  flags;
    U8  type;
    U16 next_off;
    U32 arg1;
;

检查对象的内存位置显示它没有被打包,即next_offarg1之间有32位填充:

(gdb) x/16xb scan
0x7f4978:       0xde    0x2d    0x02    0x00    0x00    0x00    0x00    0x00
0x7f4980:       0x00    0x11    0x0d    0x00    0x00    0x00    0x00    0x00
(gdb) print/x n
$1 = 0xd1100

这很可疑。 perlbench 中正在进行指针和类型转换,因此类型大小假设可能在某处失败。使用multilib 进行编译会产生一个工作基准,并检查内存以验证没有填充。

将结构强制放入位域可修复执行 64 位编译时的崩溃问题:

struct regnode_1 
    U8  flags : 8;
    U8  type : 8;
    U16 next_off : 16;
    U32 arg1 : 32;
;

【讨论】:

在 C 语言的 GNU C 方言中,您可能还可以使用 __attribute__((packed)) 来获得相同的效果。使用位域获取结构打包的有趣方法,无需任何不可移植的语法。 我们首先尝试使用__attribute__((packed))(它应该适用于 GCC 和 clang),但这并没有改变内存布局(可能我们用错了)。然后我们假设位字段也无济于事,所以在尝试这个简单的方法之前我们经历了很多不同的方法并且它奏效了。但这绝对是胶带。 gcc 如果你把它放在struct 关键字之前,而不是struct __attribute((packed)) regnode_1 之前,就会有一个警告,至少用-Wall 启用。顺便说一句,在 x86-64 SystemV ABI 中,我没有看到任何只有正常定义的填充。 Windows ABI 结构布局规则是否会导致此结构的填充? Take a look on Godbolt,默认针对 x86-64 Linux (System V ABI)。我添加了一些已注释掉的 unsigned :1 匿名填充,以表明它对打包/非打包结构确实有不同的影响,但没有它们是一样的。【参考方案2】:

我们的小调查是这样进行的:

起初我们认为这是一些填充问题,但正如彼得在 Godbolt 上指出的那样,没有发生这样的事情。所以,结构的包装与否并没有改变任何东西。

然后,我开始怀疑 Perl 处理指针的(明显扭曲的)方式。大多数演员都违反了标准定义的严格别名。由于分段错误发生在指针转换上,即:

struct regnode 
    U8  flags;
    U8  type;
    U16 next_off;
;

struct regnode_1 
    U8  flags;
    U8  type;
    U16 next_off;
    U32 arg1;
;

但是,使用 -fstrict-aliasing 标志启用它并没有改变任何东西。尽管它被认为是未定义的行为,但内存中没有重叠,因为当前正在解析的正则表达式的元素/节点在内存中是单独布局的。

深入并检查 LLVM IR 是否有问题的 switch 块,我在 regexec.ll 中得到了这个

; truncated
%876 = load %struct.regnode*, %struct.regnode** %scan, align 8, !dbg !8005
%877 = bitcast %struct.regnode* %876 to %struct.regnode_1*, !dbg !8005
%arg11715 = getelementptr inbounds %struct.regnode_1, %struct.regnode_1* %877, i32 0, i32 3, !dbg !8005
%878 = load i64, i64* %arg11715, align 8, !dbg !8005
store i64 %878, i64* %n, align 8, !dbg !8006
; truncated

加载/存储指令使用 64 位整数,这意味着 C 中的指针被解释为指向 8 字节整数(而不是 4)。因此,在当前正则表达式节点 struct 边界之外收集 2 个字节以计算 arg1 的值。该值又被用作数组索引,当它超出数组边界时,最终会导致段错误崩溃。

回到跟踪 U32 被解释为 64 位无符号整数的位置。查看文件spec_config.h,条件编译(至少在我的机器中)导致一个以

开头的预处理器块
#elif !defined(SPEC_CPU_GOOFY_DATAMODEL)

根据周围区域的代码注释,它应该对应于 ILP32 数据模型(另请参见 this)。但是,U32TYPE 被定义为 unsigned long,在我的机器上是 64 位。

因此,解决方法是将定义更改为

#define U32TYPE uint32_t

正如this 中所述,保证完全 32 位(如果支持)。

【讨论】:

【参考方案3】:

我想补充其他答案,说我们添加-DSPEC_CPU_LP64 来解决段错误(CPU2017 中的-DSPEC_LP64)就足够了。如果 SPEC 小组将此添加到他们的常见问题解答中,那就太好了。这似乎也适用于gcccactusADMpovraywrf

我们有一个为我们生成配置文件的 python 脚本,我会与人们交谈,看看我是否可以分享我们目前拥有的东西,让它为我们的编译器运行。

编辑:无论如何似乎都可以从外面访问,所以你去吧:spec.py

【讨论】:

我发誓我找不到任何相关的预处理器 def,但显然,我一定错过了基准标志配置文件中的某些内容。我会再看看你的建议,并在时间允许的时候报告。 好吧,当我遇到那个段错误时,我主要是在为 CPU2017 准备脚本,看起来就像我之前的那个人,同时为 2006 年调整它。我不知道我应该如何猜到正确的标志出乎意料。

以上是关于perlbench 导致 SPEC 2006 线束之外的段错误的主要内容,如果未能解决你的问题,请参考以下文章

Spec2006使用说明

SPEC CPU2006怎么用,http://www.spec.org/cpu2006/Docs/

SPEC2006

SPEC2006

如何使用 SPEC CPU2006

SPEC CPU 2006的测试项目