从C++拦截Fortran STOP

Esc*_*alo 5 c c++ fortran shared-libraries fortran-iso-c-binding

我为传统的Fortran库准备了一个C++接口.

遗留库中的一些子例程遵循一个丑陋但可用的状态代码约定来报告错误,我使用这样的状态代码从我的C++代码中抛出一个可读的异常:它工作得很好.

另一方面,有时遗留库调用STOP(终止程序).即使条件可以恢复,它也经常这样做.

我想从C++中捕获这个STOP,到目前为止我还没有成功.

以下代码很简单,但完全代表了手头的问题:

Fortran遗留库fmodule.f90:

module fmodule
  use iso_c_binding
  contains
    subroutine fsub(x) bind(c, name="fsub")
      real(c_double) x
      if(x>=5) then 
         stop 'x >=5 : this kills the program'
      else
         print*, x
      end if
    end subroutine fsub    
end module fmodule
Run Code Online (Sandbox Code Playgroud)

C++接口main.cpp:

#include<iostream>

// prototype for the external Fortran subroutine
extern "C" {
  void fsub(double& x);  
}

int main() {  
  double x;
  while(std::cin >> x) {
    fsub(x);
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译行(GCC 4.8.1/OS X 10.7.4; $表示命令提示符):

$ gfortran -o libfmodule.so fmodule.f90 -shared  -fPIC -Wall
$ g++ main.cpp -L. -lfmodule -std=c++11
Run Code Online (Sandbox Code Playgroud)

运行:

$ ./a.out 
1
   1.0000000000000000     
2
   2.0000000000000000     
3
   3.0000000000000000     
4
   4.0000000000000000     
5
STOP x >=5 : this kills the program
Run Code Online (Sandbox Code Playgroud)

我怎么能捕获STOP并且说,请求另一个号码.请注意,我不想触及Fortran代码.

我尝试过的:

  • std::atexit:一旦我输入它,就不能"回来"
  • std::signal:STOP似乎没有抛出我能捕获的信号

dam*_*ois 10

您可以通过拦截exitFortran运行时对函数的调用来解决您的问题.见下文.a.out是使用您的代码和您提供的编译行创建的.

步骤1.确定调用哪个函数.火起来gdb

$ gdb ./a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
[...]
(gdb) break fsub
Breakpoint 1 at 0x400888
(gdb) run
Starting program: a.out 
5

Breakpoint 1, 0x00007ffff7dfc7e4 in fsub () from ./libfmodule.so
(gdb) step
Single stepping until exit from function fsub,
which has no line number information.
stop_string (string=0x7ffff7dfc8d8 "x >=5 : this kills the programfmodule.f90", len=30) at /usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c:67
Run Code Online (Sandbox Code Playgroud)

所以stop_string被称为.我们需要知道这个函数对应的符号.

第2步.找到stop_string函数的确切名称.它必须位于其中一个共享库中.

$ ldd ./a.out 
    linux-vdso.so.1 =>  (0x00007fff54095000)
    libfmodule.so => ./libfmodule.so (0x00007fa31ab7d000)
    libstdc++.so.6 => /usr/local/gcc/4.7.2/lib64/libstdc++.so.6 (0x00007fa31a875000)
    libm.so.6 => /lib64/libm.so.6 (0x0000003da4000000)
    libgcc_s.so.1 => /usr/local/gcc/4.7.2/lib64/libgcc_s.so.1 (0x00007fa31a643000)
    libc.so.6 => /lib64/libc.so.6 (0x0000003da3c00000)
    libgfortran.so.3 => /usr/local/gcc/4.7.2/lib64/libgfortran.so.3 (0x00007fa31a32f000)
    libquadmath.so.0 => /usr/local/gcc/4.7.2/lib64/libquadmath.so.0 (0x00007fa31a0fa000)
    /lib64/ld-linux-x86-64.so.2 (0x0000003da3800000)
Run Code Online (Sandbox Code Playgroud)

我发现它(毫不奇怪)fortran运行时.

$ readelf -s /usr/local/gcc/4.7.2/lib64/libgfortran.so.3|grep stop_string
  1121: 000000000001b320    63 FUNC    GLOBAL DEFAULT   11 _gfortran_stop_string@@GFORTRAN_1.0
  2417: 000000000001b320    63 FUNC    GLOBAL DEFAULT   11 _gfortran_stop_string
Run Code Online (Sandbox Code Playgroud)

步骤3.编写将替换该功能的函数

我在源代码中寻找函数的精确签名(/usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c参见gdbsession)

$ cat my_exit.c 
#define _GNU_SOURCE
#include <stdio.h>

void _gfortran_stop_string (const char *string, int len)
{
        printf("Let's keep on");
}
Run Code Online (Sandbox Code Playgroud)

步骤4.编译导出该符号的共享对象.

gcc -Wall -fPIC -c -o my_exit.o my_exit.c
gcc -shared -fPIC -Wl,-soname -Wl,libmy_exit.so -o libmy_exit.so my_exit.o
Run Code Online (Sandbox Code Playgroud)

步骤5.使用LD_PRELOAD运行程序,以便我们的新函数优先于运行时的函数

$ LD_PRELOAD=./libmy_exit.so ./a.out 
1
   1.0000000000000000     
2
   2.0000000000000000     
3
   3.0000000000000000     
4
   4.0000000000000000     
5
Let's keep on   5.0000000000000000     
6
Let's keep on   6.0000000000000000     
7
Let's keep on   7.0000000000000000   
Run Code Online (Sandbox Code Playgroud)

你去吧

  • 如果使用`gfortran -c -fdump-tree-all fmodule.f90`编译Fortran代码,然后查看`fmodule.f90.003t.original`:`_gfortran_stop_string(&"),则步骤1和2一起需要5秒钟x> = 5:这会杀死程序"[1] {lb:1 sz:1},30);`更多人应该了解GCC语法树转储 - 这些在很多情况下非常有用. (4认同)
  • 请注意,您所做的操作会阻止"STOP"停止子程序,它将继续执行.这可能会导致以下代码完成各种令人讨厌的事情:越界数组访问,数字欠/溢出等. (3认同)

Hri*_*iev 5

既然你想要的东西会导致不可移植的代码,为什么不使用隐藏的长跳机制来破坏退出机制:

#include<iostream>
#include<csetjmp>
#include<cstdlib>

// prototype for the external Fortran subroutine
extern "C" {
  void fsub(double* x);  
}

volatile bool please_dont_exit = false;
std::jmp_buf jenv;

static void my_exit_handler() {
  if (please_dont_exit) {
    std::cout << "But not yet!\n";
    // Re-register ourself
    std::atexit(my_exit_handler);
    longjmp(jenv, 1);
  }
}

void wrapped_fsub(double& x) {
  please_dont_stop = true;
  if (!setjmp(jenv)) {
    fsub(&x);
  }
  please_dont_stop = false;
}

int main() {
  std::atexit(my_exit_handler);  
  double x;
  while(std::cin >> x) {
    wrapped_fsub(x);
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用调用longjmp在行的中间调用跳转,setjmpsetjmp返回作为第二个参数传递的值longjmp.否则setjmp返回0.样本输出(OS X 10.7.4,GCC 4.7.1):

$ ./a.out 
2
   2.0000000000000000     
6
STOP x >=5 : this kills the program
But not yet!
7
STOP x >=5 : this kills the program
But not yet!
4
   4.0000000000000000
^D     
$
Run Code Online (Sandbox Code Playgroud)

不需要库预加载(无论如何,它在OS X上比在Linux上更多).一句警告 - 退出处理程序按其注册的相反顺序调用.应该注意的是,之后没有注册其他退出处理程序my_exit_handler.


Seb*_*ien 0

我建议你在调用 fortran 代码之前分叉你的进程,并在 fortran 执行后退出 0(编辑:如果 STOP 以零退出,你将需要一个哨兵退出代码,叮当响,但可以完成工作)。这样,每个 Fortran 调用都会以相同的方式完成:就像停止一样。或者,如果“STOP”确保出现错误,则在 Fortran 代码停止时抛出异常,并在 Fortran 执行正常“完成”时发送一些其他消息。

下面是一个从您的代码中得到启发的示例,假设 Fortran“STOP”是一个错误。

 int main() {  
   double x;
   pid_t pid;
   int   exit_code_normal = //some value that is different from all STOP exit code values
   while(std::cin >> x) {
     pid = fork();
     if(pid < 0) {
       // error with the fork handle appropriately
     } else if(pid == 0) {
       fsub(x);
       exit(exit_code_normal);
     } else {
       wait(&status);
       if(status != exit_code_normal)
          // throw your error message.
     }
   }
   return 0;
 }
Run Code Online (Sandbox Code Playgroud)

退出代码可以是常量而不是变量。我认为这并不重要。

在注释之后,如果执行结果位于进程的内存中(而不是写入文件),则执行结果将会丢失。如果是这样的话,我能想到3种可能:

  • Fortran 代码在调用期间会弄乱大量内存,让执行超出 STOP 范围可能首先就不是一个好主意。
  • Fortran 代码只是返回一些值(如果我的 Fortran 不是太生疏的话,通过它的参数),并且可以通过共享内存空间轻松地将其转发回父级。
  • Fortran 子例程的执行作用于外部系统(例如:写入文件),并且不需要返回值。

在第三种情况下,我上面的解决方案按原样工作。与其他建议的解决方案相比,我更喜欢它,主要是因为:1)您不必确保构建过程得到正确维护2)fortran“STOP”仍然按预期运行并且3)它需要很少的代码行和所有“ Fortran STOP 解决方法”逻辑位于一处。因此,就长期维护而言,我更喜欢这样做。

在第二种情况下,我上面的代码需要进行一些小的修改,但仍然以最小的复杂性增加为代价,保留了上面列举的优点。

在第一种情况下,无论如何你都必须弄乱 Fortran 代码。

  • 这很聪明,但是如果“fsub”修改全局状态或返回一个值怎么办?由于写时复制内存映射,父进程不会看到子进程所做的更改。 (2认同)