TypechoJoeTheme

MetMan's Blog

网站页面

Segmentation Faults错误排查解决思路

MetMan博 主
2023-10-27
/
0 评论
/
188 阅读
/
2185 个字
/
百度已收录
10/27
本文最后更新于 2023年10月27日,已超过 328天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

前言

Segmentation Faults可能是每一个programmer都回避不了的错误,国内甚至还有一个以它命名的网站(思否,segmentfault.com)。在诊断、解决这个常见程序错误时,我们可以借助编译器提供的功能快速定位。本文译自Intel® Developer Zone上文章Determining Root Cause of Segmentation Faults SIGSEGV or SIGBUS errors文中主要介绍通过Intel Fortran编译器提供的功能诊断解决Fortran程序段错误。

问题

当我运行由Intel Fortran编译器编译的代码时,在Linux平台得到SISGEGV错误提示(或Mac OS X平台SIGBUS)。这份代码在<XX编译器/平台>上运行多年没出问题。这是Intel编译器一个bug吗?

运行环境:linux 或 Mac OS X

根本原因:有许多可能原因。段错误(segmentation fault)(Mac OS X下是bus error)是一个有多种原因的通用错误。下面我们描述可能的原因并给出建议以避免段错误。

可能原因#1 Fortran指定栈空间耗尽

解决方法: -heap-arrays编译选项

Intel Fortran编译器使用栈空间分配许多临时(local)数组。

非OpenMP和非自动并行应用:如果你的程序未使用OpenMP或Auto-parallelization(-parallel编译选项),那么可以尝试 -heap-arrays 编译选项。OpenMP或Auto-parallelization用户请阅读可能原因 #2关于解除栈空间限制的提示。

-heap-arrays

如果这个解决了SIGSEGVSIGBUS错误的话,可以不用往下读了。你可能想读pdf附件学习关于临时数组何时何处被创建。改变一点代码可以避免一些临时数组,从而减少对临时副本的需求(改善性能)。同时,-heap-arrays编译器选项有一个可选参数[size]来指定大于[size]的数组分配到堆(heap)中的阈值大小,单位为Kbytes,其它小于等于[size]的分配到堆栈中。例如:

-heap-arrays 10

将所有大于10Kbytes的自动和临时数组放入堆中。

可能原因#2 堆栈空间耗尽

解决方法:增加OpenMP应用或其它应用的栈空间大小

第一步是尝试在 Linux 和 MacOS 上增加 shell 堆栈限制。但是,此选项可能会对与 OpenMP 或自动并行化代码的数据共享产生不良影响。因此,建议 OpenMP 和自动并行化用户不要使用 -heap-array,而是尝试取消shell 堆栈大小限制。

Linux, bash:  ulimit -s unlimited
Linux, csh/tcsh: unlimit stacksize

你可以用以下方法检查你的堆栈大小限制:

bash: ulimit -a
csh: limit

对于Mac OS X系统,shell堆栈大小有一个硬上限。对于大多系统,

bash: ulimit -s 65532

即栈限制为64MB。

另一种方法是使用链接器选项来增加可执行文件的默认 shell 堆栈大小

重新运行你的程序,如果这个方法解决此问题了,可以不用往下读了。如果你的应用仍然产生SIGSEGV或SIGBUS错误,继续读吧。

可能原因#2-prime:由于堆或通用内存耗尽导致堆栈耗尽

在进程内存映射中,堆和堆栈相互向对方增长。如果他们碰撞了,这也能引起一个关于堆分配或下一个堆栈分配的段错误。

也可能应用耗尽所有物理内存+swap缓冲区。记住,对于64位应用,虚拟内存实际上是unlimited。然而,事实上可消费的内存大小有一个上限,物理ram+swap空间(典型的是物理内存大小的2倍)。可以通过 free 命令获得该信息。物理内存也可以通过cat /proc/meminfo中'MemTotal'项和'SwapTotal'项看到。系统本身也需要一些空间,所以经验法则是尽可能保持应用的内存占用(memory footprint)在MemTotal的80%左右并且不要超过MemTotal+SwapTotal。

注:物理内存耗尽,也可能被作业系统杀掉,报OOM错误。

使用-g -traceback编译链接来定位代码终止的地方。

可能原因 #3 由于用户代码错误导致堆栈溢出

有许多用户代码错误可能引起堆栈溢出并导致运行时SIGSEGV 或 SIGBUS 错误。由于段错误可能发生在与堆栈最初溢出地方不相关的程序后面部分,从而导致错误很难发现。第一步尝试隔离代码错误发生的地方。通过产生一个函数栈回溯来得到。使用以下选项编译链接:

-g -traceback

当代码出错,通常会得到关于错误发生时的调用栈的报告。如果没有得到一个栈回溯信息,确保在编译和链接时都用到了-g并且确保编译时使用了-traceback。有这样的情况:当程序在内核空间时段错误发生,因此没有用户栈用于trace back。

trace back报告从下往上读。找到最上面的子程序或函数并通过行号来隔离出哪条指令引起错误的。检查这条语句的用户代码错误。如果没有明显用户错误,继续往下。

注:建议查错时一定记得加上-g -traceback

可能原因#4 数组越界.

解决方法:-check bounds

-check bounds编译选项提供数组访问和字符串表达式运行时检查保证索引在数组边界内。这个检查有助于找到索引超出数组申明大小的情况。这个选项对性能影响很大,影响程度取决于应用中多少数组访问被执行。

注:性能影响原因是编译器在应用中插入了很多检查代码。

同时,-check bounds数组越界检查不对最外维指定为*的虚参数组或上下维都为1的数组执行。为开启边界检查,用下面选项编译:

-check bounds -g

检查在运行时执行,而不是在编译时执行。如果这样能发现错误,停下来。没有,继续往下读。

可能原因 #5 把函数当作子程序调用(calling)或把子程序当作函数调用(invoking)

当用户做类似于这样的事,用户代码错误:

!-- main program ---
...
call ThisIsIllegal( some_arguments )
...
!-- end main program ---

!-- ThisIsIllegal ---
integer function ThisIsIllegal( some_arguments )
...
!-- end ThisIsIllegal ---

上面例子中,主程序以子程序调用方式调用ThisIsIllegal,然而ThisIsIllegal申明是函数。这会引起堆栈溢出。为测试这些情况,尝试使用编译选项

-fp-stack-check -g -traceback

用这些选项编译并运行。如果栈由于类似上面的原因崩溃,代码将退出并给出一个stack trace。

可以用一个编译时检查来检查程序接口:

-gen-interfaces -warn interfaces

编译时检查将为你的程序产生INTERFACE块。-warn接口随后将使用这些编译器产生的接口并检查程序调用确保参数和接口在被调用者和调用者之间匹配。注意这个检查只对Fortran源代码起作用。对混编程序不检查接口。

注:尽量使用module,自动生成interface接口。

原因 #6 传递非连续数组部分引起的大的临时数组.

解决方法:使用 -check arg_temp_created 侦测并通过包含显式接口和assumed shaped 数组代码修复

考虑这个例子:

--- main program ---
real(8) :: f(1800,3600,1)
external sub
...
call sub( f(1:900,:,:) )
...
!-- end main program ---

and the "sub" subroutine is in a separately compiled source file:
!-- external subroutine "sub" ---
subroutine sub( f )
real(8) :: f(900,3600,1)
...
!-- end subroutine "sub" ---

这种情况下,‘sub’期望一个连续数组,大小为900*3600*1。然而,调用传递一个内存中非连续的数组。这种情况下,编译器将在调用时产生一个临时数组复制数组"f"非连续块元素使之成为连续数组,这是”sub“所期待的。除非指定-heap-arrays,否则这个临时数组分配在栈中。

为了检测代码里是否发生这样的情况,用下面选项编译:

-check arg_temp_created

并运行程序。当临时参数被创建,将输出信息。为了解决这个问题,创建一个显式接口,并在”sub“使用一个assumed shaped数组将移除临时数组的需要。

!-- main ---
real(8) :: f(1800,3600,1)
interface
subroutine sub(f)
real(8) :: f(:,:,:)
end subroutine sub
end interface
...
call sub( f(1:900,:,:) )
...
!-- end main program ---

!-- "sub" ---
subroutine sub( f )
real(8) :: f(:,:,:)
...
end subroutine sub

记住,虽然这样避免了临时数组,编译器知道"sub"内数组"f"可能是非连续的。因此,一些使用"f"的语句的优化可能关闭,进而影响性能。

不属于以上情况

解决方法:进一步深入分析

99%的SIGSEGV或者SIGBUS错误原因是上面列举的情况。然而,也有其他可能情况导致段错误。

如果你的应用链接外部库,确保库和编译器兼容。外部库是否用Intel编译器编译的?如果是,是否主版本一致-即库用Intel Fortran v9.1编译的,但你的应用用Intel

Fortran v10.x或v11.x编译的? Intel只保证主版本内兼容(9,10,11就是主版本例子)

如果外部库来自于软件销售商或工具:该销售商是否明确Intel编译器兼容,如果确认,

他们用哪个版本验证他们的库?你应该只是用销售商验证过的Intel编译器版本。

如果应用程序在外部库中链接,请确保该库与编译器兼容。外部库是否使用Intel编译器编译?如果是这样,主版本是否相同? 也就是说,该库是使用Intel Fortran 19.0 编译的,但你的应用是使用Intel Fortran v17.x 构建的?英特尔仅保证主版本内的兼容性(19、18 和 17 是主版本的示例)


当所有都失败了...

提交给用户论坛吧!

参考资料

  1. https://www.intel.com/content/www/us/en/developer/articles/troubleshooting/determining-root-cause-of-sigsegv-or-sigbus-errors.html
  2. https://www.intel.com/content/dam/develop/external/us/en/documents/20415-stack-usage-158534.pdf
DEBUGintelfortran
朗读
赞(0)
赞赏
感谢您的支持,我会继续努力哒!
版权属于:

MetMan's Blog

本文链接:

https://blog.metman.top/index.php/archives/62/(转载时请注明本文出处及文章链接)

评论 (0)

互动读者

标签云

最新回复

暂无回复

登录
X
用户名
密码