I prepared a C++ interface to a legacy Fortran library.
Some subroutines in the legacy library follow an ugly but usable status code convention to report errors, and I use such status codes to throw a readable exception from my C++ code: it works great.
On the other hand, sometimes the legacy library calls STOP
(which terminates the program). And it often does it even though the condition is recoverable.
I would like to capture this STOP
from within C++, and so far I have been unsuccessful.
The following code is simple, but exactly represents the problem at hand:
The Fortran legacy library 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
The C++ Interface 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;
}
The compilation lines (GCC 4.8.1 / OS X 10.7.4; $
denotes command prompt ):
$ gfortran -o libfmodule.so fmodule.f90 -shared -fPIC -Wall
$ g++ main.cpp -L. -lfmodule -std=c++11
The run:
$ ./a.out
1
1.0000000000000000
2
2.0000000000000000
3
3.0000000000000000
4
4.0000000000000000
5
STOP x >=5 : this kills the program
How could I capture the STOP
and, say, request another number. Notice that I do not want to touch the Fortran code.
What I have tried:
std::atexit
: cannot "come back" from it once I have entered itstd::signal
: STOP
does not seem to throw a signal which I can captureYou can solve your problem by intercepting the call to the exit
function from the Fortran runtime. See below. a.out
is created with your code and the compilation lines you give.
Step 1. Figure out which function is called. Fire up 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
So stop_string
is called. We need to know to which symbol this function corresponds.
Step 2. Find the exact name of the stop_string
function. It must be in one of the shared libraries.
$ 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)
I found it in (no surprise) the fortran runtime.
$ 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
Step 3. Write a function that will replace that function
I look for the precise signature of the function in the source code (/usr/local/src/gcc-4.7.2/libgfortran/runtime/stop.c
see gdb
session)
$ cat my_exit.c
#define _GNU_SOURCE
#include <stdio.h>
void _gfortran_stop_string (const char *string, int len)
{
printf("Let's keep on");
}
Step 4. Compile a shared object exporting that symbol.
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
Step 5. Run the program with LD_PRELOAD so that our new function has precedence over the one form the runtime
$ 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
There you go.
Since what you want would result in non-portable code anyway, why not just subvert the exit mechanism using the obscure long jump mechanism:
#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;
}
Calling longjmp
jumps right in the middle of the line with the setjmp
call and setjmp
returns the value passed as the second argument of longjmp
. Otherwise setjmp
returns 0. Sample output (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
$
No library preloading required (which anyway is a bit more involved on OS X than on Linux). A word of warning though - exit handlers are called in reverse order of their registration. One should be careful that no other exit handlers are registered after my_exit_handler
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With