【问题标题】:Illegal instruction when running a minimal OpenMP program运行最小 OpenMP 程序时的非法指令
【发布时间】:2012-07-18 13:35:52
【问题描述】:

这个最小的 OpenMP 程序

#include <omp.h>
int main() 
{
  #pragma omp parallel sections
  {
    #pragma omp section
    {
      while(1) {}
    }

    #pragma omp section
    {  
      while(1) {}
    }
  }
}

使用gcc test.c -fopenmp编译和运行时会产生这个错误:

Illegal instruction (core dumped)

当我用

更改任一循环时
  int i=1;
  while(i++) {}

或它编译和运行没有错误的任何其他条件。看来,1 作为不同线程中的循环条件会导致一些奇怪的行为。为什么?

编辑:我使用的是 gcc 4.6.3

编辑:这是 gcc 中的一个错误,已作为 Bug 54017 提交给 gcc 开发人员。

【问题讨论】:

  • 我可以确认我在编译和运行这段代码时得到了相同的行为。
  • 适用于我的 gcc-4.6、opencc-5.0 和 suncc 12.3。我猜你的旧 gcc 中有一个编译器错误。什么版本?什么平台?
  • 这两个示例的程序集输出是什么样的 (gcc -S)?
  • 这是 GCC 中的一个错误。它甚至出现在 4.7.0 中。请为社会做好事,并将错误报告给 GCC 人。
  • 庆幸即将到来的 GCC 4.7.2 能够正确编译它:)

标签: c multithreading gcc openmp


【解决方案1】:

这显然是 GCC 中的一个错误。 GCC 使用来自 libgompGOMP_sections_start() 例程实现 OpenMP 部分,该例程返回调用线程应执行的基于 1 的部分 ID,如果已分发所有工作项,则返回 0。基本上转换后的代码应该是这样的:

main._omp_fn.0 (void * .omp_data_i)
{
   unsigned int .section.1;

   .section.1 = GOMP_sections_start(2);
L0:
   switch (.section.1)
   {
      case 0:
         // No more sections to run, exit
         goto L2;
      case 1:
         // Do section 1
         while (1) {}
         goto L1;
      case 2:
         // Do section 2
         while (1) {}
         goto L1;
      default:
         // Impossible section value, possible error in libgomp
         __builtin_trap();
   }
L1:
   .section.1 = GOMP_sections_next();
   goto L0;
L2:
   GOMP_sections_end_nowait();
   return;
}

发生的情况是,在您的情况下,default0 情况都会导致 __builtin_trap()__builtin_trap() 是一个内置的 GCC,它应该会异常终止你的程序,在 x86 上它会发出 ud2 指令,使 CPU 发出非法操作码异常。它通常放在代码永远不应该执行的地方,例如来自GOMP_sections_start()GOMP_sections_next() 的所有可能的正确返回值应该被开关中的情况覆盖,如果达到默认值(表示libgomp 中可能存在错误)它应该失败并且你会向开发者投诉:)

编辑: 这绝对不是 OpenMP 的预期行为,iccsuncc 不会发生这种情况。我已将Bug 54017 提交给 GCC Bugzilla。

编辑 2:我更新了文本以更准确地反映 GCC 应该产生的内容。看起来 GCC 对并行区域中的控制流产生了错误的印象,并且做了一些进一步破坏代码生成的“优化”。

【讨论】:

  • 谢谢,我在火车上没有网络。
【解决方案2】:

SIGILL 生成,因为存在非法指令 ud2/ud2a。 根据http://asm.inightmare.org/opcodelst/index.php?op=UD2

此指令导致#UD。英特尔保证,在未来英特尔的 CPU 这条指令将导致#UD。当然所有以前的CPU (186+) 在此操作码上导致 #UD。软件使用的这条指令 用于测试#UD 异常服务例程的编写器。

让我们看看里面:

$ gcc-4.6.2 -fopenmp omp.c -o omp
$ gdb ./omp
...

(gdb) r
Program received signal SIGILL, Illegal instruction.
...
0x08048544 in main._omp_fn.0 ()
(gdb) x/i $pc
0x8048544 <main._omp_fn.0+28>:  ud2a

(gdb) disassemble
Dump of assembler code for function main._omp_fn.0:
0x08048528 <main._omp_fn.0+0>:  push   %ebp
0x08048529 <main._omp_fn.0+1>:  mov    %esp,%ebp
0x0804852b <main._omp_fn.0+3>:  sub    $0x18,%esp
0x0804852e <main._omp_fn.0+6>:  movl   $0x2,(%esp)
0x08048535 <main._omp_fn.0+13>: call   0x80483f0 <GOMP_sections_start@plt>
0x0804853a <main._omp_fn.0+18>: cmp    $0x1,%eax
0x0804853d <main._omp_fn.0+21>: je     0x8048548 <main._omp_fn.0+32>
0x0804853f <main._omp_fn.0+23>: cmp    $0x2,%eax
0x08048542 <main._omp_fn.0+26>: je     0x8048546 <main._omp_fn.0+30>
0x08048544 <main._omp_fn.0+28>: ud2a
0x08048546 <main._omp_fn.0+30>: jmp    0x8048546 <main._omp_fn.0+30>
0x08048548 <main._omp_fn.0+32>: jmp    0x8048548 <main._omp_fn.0+32>
End of assembler dump.

汇编文件中已经有ud2a了:

$ gcc-4.6.2 -fopenmp omp.c -o omp.S -S; cat omp.S

main._omp_fn.0:
.LFB1:
        pushl   %ebp
.LCFI4:
        movl    %esp, %ebp
.LCFI5:
        subl    $24, %esp
.LCFI6:
        movl    $2, (%esp)
        call    GOMP_sections_start
        cmpl    $1, %eax
        je      .L4
        cmpl    $2, %eax
        je      .L5
                .value  0x0b0f

.value 0xb0f是ud2a的代码

在验证 ud2a 是由 gcc 有意插入后(在早期的 openmp 阶段),我试图理解代码。函数main._omp_fn.0是并行代码的主体;它将调用_GOMP_sections_start 并解析其返回码。如果代码等于1,那么我们将跳转到一个无限循环;如果为 2,则跳转到第二个无限循环。但在其他情况下 ud2a 将被执行。 (不知道为什么,但根据 Hristo Iliev 的说法,这是一个 GCC Bug 54017。)

我认为,这个测试很好地检查了有多少 CPU 内核。默认情况下,GCC 的 openmp 库 (libgomp) 将为系统中的每个 CPU 内核启动一个线程(在我的例子中,有 4 个线程)。部分将按顺序选择:第一部分用于第一个线程,第二部分 - 第二个线程,依此类推。

如果我在 1 个或 2 个 CPU 上运行程序,则没有 SIGILL(taskset 的选项是十六进制的 cpu 掩码):

 $ taskset 3 ./omp
 ... running on cpu0 and cpu1 ...
 $ taskset 1 ./omp
 ... running first loop on cpu0; then run second loop on cpu0...

【讨论】:

  • 我在 asm 方面不太强...这告诉我们什么?
  • ud2 不是非法指令。人为地引发#UD(无效操作码)异常是完全合法的x86指令。它由内置的__builtin_trap 发出。
  • @steffen,请更新页面并重新阅读答案;它已更新
  • 我有一个 i7,它是四核的。我得到的 OMP_NUM_THREADS 的信号大于部分/无限循环的数量。因此,当一个线程没有工作时会引发信号。是这样吗?为什么 gcc 会这样做?
  • “(不知道为什么,但根据 Hristo Iliev 的说法,这是一个 GCC 错误 54017。)” - 阅读错误报告,您就会知道原因。
猜你喜欢
  • 2018-07-20
  • 2022-01-16
  • 2023-03-24
  • 2012-11-15
  • 1970-01-01
  • 1970-01-01
  • 2020-07-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多