从C ++拦截Fortran STOP

时间:2013-10-25 17:48:50

标签: 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

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;
}

编译行(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

跑步:

$ ./a.out 
1
   1.0000000000000000     
2
   2.0000000000000000     
3
   3.0000000000000000     
4
   4.0000000000000000     
5
STOP x >=5 : this kills the program

我如何捕获STOP,然后请求另一个号码。请注意,我不想触及Fortran代码

我尝试过:

  • std::atexit:输入后无法“退回”
  • std::signalSTOP似乎没有发出我可以捕获的信号

4 个答案:

答案 0 :(得分:10)

您可以通过拦截Fortran运行时对exit函数的调用来解决您的问题。见下文。 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

所以调用了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)

我发现了(毫不奇怪)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

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

我在源代码中寻找功能的精确签名(/usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c参见gdb会话)

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

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

步骤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

步骤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   

你去。

答案 1 :(得分: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;
}

调用longjmpsetjmp调用的行中间跳转,setjmp返回作为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     
$

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

答案 2 :(得分:1)

结合使用自定义_gfortran_stop_string函数和longjmp的两个答案,我认为在自定义函数中引发异常将类似,然后在主代码中捕获。所以这就出来了:

main.cpp:

#include<iostream>

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

int main() {  
  double x;
  while(std::cin >> x) {
    try { fsub(x); }
    catch (int rc) { std::cout << "Fortran stopped with rc = " << rc <<std::endl; }
  }
  return 0;
}

catch.cpp:

extern "C" {
    void _gfortran_stop_string (const char*, int);
}

void _gfortran_stop_string (const char *string, int len)
{
        throw 666;
}

然后,编译:

gfortran -c fmodule.f90
g++ -c catch.cpp
g++ main.cpp fmodule.o catch.o -lgfortran

运行:

./a.out
2
   2.0000000000000000     
3
   3.0000000000000000     
5
Fortran stopped with rc = 666
6
Fortran stopped with rc = 666
2
   2.0000000000000000     
3
   3.0000000000000000     
^D

所以,似乎有效:)

答案 3 :(得分:0)

我建议你在调用fortran代码之前分叉你的进程并退出0(编辑:如果STOP退出为零,你将需要一个哨兵退出代码,clanky但是完成工作)在fortran执行之后。这样每一次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;
 }

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

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

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

在第三种情况下,我的解决方案按原样工作。我更喜欢它比其他一些建议的解决方案主要是因为:1)你不必确保构建过程得到适当维护2)fortran“STOP”仍然按预期运行3)它只需要很少的代码行和所有“ fortran STOP解决方案“逻辑坐落在一个地方。所以在长期维护方面,我更喜欢这个。

在第二种情况下,我的上述代码需要进行少量修改,但仍然具有上面列举的优点,但代价是最小的复杂性。

在第一种情况下,无论如何都必须使用fortran代码。