perlbench 在 SPEC 2006 线束之外导致段错误
perlbench results in segfault outside the SPEC 2006 harness
这可能过于具体,但张贴在这里可能会帮助其他人compile/run 在默认 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 都可以观察到该行为。
导致此崩溃的原因是什么?
段错误是由指出的行上的变量 n
的垃圾值引起的。检查代码显示该值来自类型对象的字段 arg1
:
struct regnode_1 {
U8 flags;
U8 type;
U16 next_off;
U32 arg1;
};
检查对象的内存位置显示它没有打包,即 next_off
和 arg1
之间有 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
= 0xd1100
这很可疑。 perlbench
中正在进行指针和类型转换,因此类型大小假设可能在某处失败。使用 multilib
编译会产生一个工作基准并检查内存以验证没有填充。
强制结构进入位域修复了执行 64 位编译时的崩溃:
struct regnode_1 {
U8 flags : 8;
U8 type : 8;
U16 next_off : 16;
U32 arg1 : 32;
};
我们的小调查是这样进行的:
起初我们认为这是一些填充问题,但正如彼得在 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
标志启用它并没有改变任何东西。尽管它符合未定义行为的条件,但内存中没有重叠,因为当前正在解析的正则表达式的 elements/nodes 在内存中单独布局。
更深入地检查 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
load/store 指令使用 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 位(如果支持)。
我想通过添加 -DSPEC_CPU_LP64
来解决段错误(CPU2017 中的 -DSPEC_LP64
)就足以补充其他答案。如果 SPEC 小组将此添加到他们的常见问题解答中,那就太好了。这似乎也适用于 gcc
、cactusADM
、povray
和 wrf
。
我们有一个 python 脚本为我们生成配置文件,我会与人们交谈,看看我是否可以分享目前为我们的编译器获取它 运行 的内容。
编辑:似乎无论如何都可以从外面进入,所以给你:spec.py
这可能过于具体,但张贴在这里可能会帮助其他人compile/run 在默认 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 都可以观察到该行为。
导致此崩溃的原因是什么?
段错误是由指出的行上的变量 n
的垃圾值引起的。检查代码显示该值来自类型对象的字段 arg1
:
struct regnode_1 {
U8 flags;
U8 type;
U16 next_off;
U32 arg1;
};
检查对象的内存位置显示它没有打包,即 next_off
和 arg1
之间有 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
= 0xd1100
这很可疑。 perlbench
中正在进行指针和类型转换,因此类型大小假设可能在某处失败。使用 multilib
编译会产生一个工作基准并检查内存以验证没有填充。
强制结构进入位域修复了执行 64 位编译时的崩溃:
struct regnode_1 {
U8 flags : 8;
U8 type : 8;
U16 next_off : 16;
U32 arg1 : 32;
};
我们的小调查是这样进行的:
起初我们认为这是一些填充问题,但正如彼得在 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
标志启用它并没有改变任何东西。尽管它符合未定义行为的条件,但内存中没有重叠,因为当前正在解析的正则表达式的 elements/nodes 在内存中单独布局。
更深入地检查 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
load/store 指令使用 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 位(如果支持)。
我想通过添加 -DSPEC_CPU_LP64
来解决段错误(CPU2017 中的 -DSPEC_LP64
)就足以补充其他答案。如果 SPEC 小组将此添加到他们的常见问题解答中,那就太好了。这似乎也适用于 gcc
、cactusADM
、povray
和 wrf
。
我们有一个 python 脚本为我们生成配置文件,我会与人们交谈,看看我是否可以分享目前为我们的编译器获取它 运行 的内容。
编辑:似乎无论如何都可以从外面进入,所以给你:spec.py