Перехват Fortran STOP от переполнения стека

Я подготовил интерфейс C ++ к устаревшей библиотеке Fortran.

Некоторые подпрограммы в устаревшей библиотеке следуют уродливому, но пригодному для использования соглашению о кодах состояния, чтобы сообщать об ошибках, и я использую такие коды состояния, чтобы генерировать читабельное исключение из моего кода C ++: это прекрасно работает.

С другой стороны, иногда устаревшая библиотека вызывает STOP (что завершает программу). И он часто делает это, хотя состояние можно исправить.

я бы хотел захватить этот STOP изнутри C ++, и до сих пор я потерпел неудачу.

Следующий код прост, но точно представляет проблему под рукой:

Библиотека наследия Фортрана 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 и, скажем, запросить другой номер. Обратите внимание, что я не хочу трогать код Фортрана.

Что я пробовал:

  • std::atexit: не могу «вернуться» из него, как только я вошел в него
  • std::signal: STOP не похоже, чтобы бросить сигнал, который я могу захватить

5

Решение

Вы можете решить свою проблему, перехватив вызов exit функция из среды исполнения Fortran. Увидеть ниже. 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)

Я нашел это (не удивительно) во время выполнения Фортрана.

$ 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

Вот и ты.

10

Другие решения

Так как то, что вы хотите, в любом случае приведет к непереносимому коду, почему бы просто не разрушить механизм выхода, используя неясный механизм длинного перехода:

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

призвание longjmp прыгает прямо в середине строки с setjmp позвонить и 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,

5

Объединяя два ответа, которые используют пользовательский _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

Итак, похоже на работу 🙂

1

Я предлагаю вам разветвить свой процесс перед вызовом кода Фортрана и выйти из 0 (правка: если STOP завершается с нуля, вам понадобится код выхода дозорного, неуклюжий, но делает работу) после выполнения Фортрана. Таким образом, каждый вызов на Фортране завершается так же, как если бы он прекратился. Или, если «STOP» выдает ошибку, сгенерирует исключение, когда код фортрана останавливается, и отправляет какое-то другое сообщение, когда выполнение фортрана «завершается» нормально.

Ниже приведен пример, вдохновляющий ваш код, предполагая, что 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 возможностях:

  • Код Фортрана теряет много памяти во время вызова, и, во-первых, позволить продолжить выполнение после STOP, вероятно, не очень хорошая идея.
  • Код fortran просто возвращает некоторое значение (через аргумент, если мой fortran не слишком ржавый), и это может быть легко передано родителю через разделенное пространство памяти.
  • Выполнение подпрограммы fortran действует во внешней системе (например, запись в файл), и никаких возвращаемых значений не ожидается.

В третьем случае моё решение выше работает как есть. Я предпочитаю его другим предлагаемым решениям, главным образом потому, что: 1) вам не нужно гарантировать, что процесс сборки поддерживается должным образом 2) fortran «STOP» по-прежнему ведет себя как положено, и 3) ему требуется очень мало строк кода и все » Фортран СТОП обходной путь «логика сидит в одном месте. Так что с точки зрения долгосрочного обслуживания, я предпочитаю это.

Во втором случае мой код выше нуждается в небольшой модификации, но все же обладает перечисленными выше преимуществами за счет минимальной дополнительной сложности.

В 1-м случае вам придется возиться с кодом Фортрана, несмотря ни на что.

0
По вопросам рекламы [email protected]