Getting weird crashes in mixed fortran/C program
I am trying to replace some graphics code in a set of fortran programs (not my own code). I got one of the simpler ones ('psvdraw') to work just fine, replacing the fortran postscript-generating code with C calls that call the Cairo library (graphic_output.c). I have been able to successfully work out the cross-language calls without too much trouble.
However, when trying to get the second, larger program ('pssect') to work, calling the same C code, I get Segmentation Faults, or in some cases, the program flow goes back to a fortran routine 'error' (I do not call this, or any fortran routine in my C code).
In trying to diagnose this problem, I linked in a bunch of the fortran code from pssect into psvdraw ('biglib.f'), and got the same errors. Note that none of this added code is actually called! Also the errors occur right at the first call from fortan into the c code. So: psvdraw with biglib.f linked in fails, but psvdraw without it succeeds.
Here are relevant bits of the makefile:
Makefile
COMP77 = gfortran
FFLAGS = -C -v -pedantic -Wunused -fno-underscoring
CC = gcc-4
CFLAGS = -v -pedantic -Wunused
CAIRO_INCLUDE = /sw/include/cairo
CAIRO_LIB = /sw/lib
# PSVDRAW Make setup that works:
psvdraw: psvdraw.o graphic_output.o tlib.o pscom.o
$(COMP77) $(FFLAGS) $@.o graphic_output.o tlib.o pscom.o -L$(CAIRO_LIB) -lcairo -o $@
# PSVDRAW Make setup with errors:
#psvdraw: psvdraw.o graphic_output.o tlib.o pscom.o biglib.o
# $(COMP77) $(FFLAGS) $@.o graphic_output.o pscom.o tlib.o biglib.o -L$(CAIRO_LIB) -lcairo -o $@
pssect: pssect.o graphic_output.o pscom.o tlib.o biglib.o
$(COMP77) $(FFLAGS) $@.o graphic_output.o pscom.o tlib.o biglib.o -L$(CAIRO_LIB) -lcairo -o $@
pssect.o: pssect.f
$(COMP77) $(FFLAGS) -c pssect.f
psvdraw.o: psvdraw.f
$(COMP77) $(FFLAGS) -c psvdraw.f
pscom.o: pscom.f
$(COMP77) $(FFLAGS) -c pscom.f
tlib.o: tlib.f
$(COMP77) $(FFLAGS) -c tlib.f
biglib.o: biglib.f
$(COMP77) $(FFLAGS) -c biglib.f
graphic_output.o: graphic_output.c
$(CC) $(CFLAGS) $(INCL) -c -I$(CAIRO_INCLUDE) graphic_output.c
.c.o:
$(CC) $(CFLAGS) $(INCL) -c $<
.f.o:
$(FC) $(FFLAGS) $(INCL) -c $<
Here is the offending fortran code: Note that the problem occurs right at the beginning of the program:
Beginning of pssect.f:
PROGRAM PSSECT
implicit none
include 'perplex_parameters.h'
integer jop0, ier99
logical vertex, output, first
character*100 fname, yes*1
integer iop0
logical debug
common / basic /iop0, debug
integer isec,icopt,ifull,imsg,io3p
common/ cst103 /isec,icopt,ifull,imsg,io3p
c----------------------------------------------------------------------
c Look for the "debug_yes" file to turn on debugging messages
PRINT *,'Pre-PSOPEN1'
call psopen ('plot2')
PRINT *,'Post-PSOPEN1'
And here is part of the c code that g开发者_运维技巧ets called and produces a fault:
Part of graphic_output.c:
char dmh_debug = 0;
#define DEBUGPRINT(x) if (dmh_debug) {printf x;};
void psopen(char *fname, int fnamelen) {
printf("Debug opened\n");
char *outFileName;
char outputType[255];
char pageWidthString[255];
char pageHeightString[255];
/* Set debug status based upon presence of file named 'debug_yes' in directory */
FILE *debugFile = fopen("debug_yes", "r");
if (debugFile == NULL) {
dmh_debug = 0;
} else {
dmh_debug = 1;
}
fclose(debugFile);
dmh_debug = 1;
DEBUGPRINT(("Debug closed\n"));
fname[fnamelen]='\0';
fname = trim(fname);
outFileName = malloc((strlen(fname) + 50) * sizeof(char));
strcpy(outFileName, fname);
DEBUGPRINT(("Found file name:%s of length: %lu\n", fname, strlen(fname)));
[...]
Results of running the program
pnr-rethington:source dave$ ./pssect
Pre-PSOPEN1
Debug opened
Segmentation fault
If linking in unused code triggers the problem, that would tend to indicate that somewhere (either in the Fortran code or the C code) you're overwriting memory that you shouldn't. Try running the compiled program under Valgrind - it should help pinpoint where this is happening.
I'd still suspect that you have a problem with ones of the calls between Fortran and C, resulting in an inconsistency. How are you making the calls? I think that the most reliable way is to use the ISO C Binding to specify to Fortran how the C routines should be called.
Or, you could consider a graphics package that has a Fortran interface or binding. Then you wouldn't have to work on an interface, since Fortran calls or a Fortran interface would already exist. I've been using DISLIN. Another possibility is PLplot.
With the current approach, I suggest examining the arguments at the entry to the C routines, to make sure that they are correct.
You are calling psopen from Fortran with a single argument, and the C routine expects two arguments. Maybe your Fortran compiler adds the length of the character string as a trailing argument after each string. Or maybe you got lucky on your first attempt and it just happened to work with a "random" value that the C routine found on the stack. In the second case, in another situation, a peculiar crash is likely. At best, this is a non-portable way to interface Fortran and C. You could try adding an integer length to your Fortran call and see what happens. Or print or use the debugger to see the value of the second argument at entry to the C-routine.
The ISO C Binding works much better. It is supported by numerous compilers (e.g., gfortran >= 4.3, ifort) and provides a defined and portable way of connecting Fortran and C. You specify an "interface" in your Fortran code so that the Fortran compiler generate the correct instructions for the C-call.
Still, it might be easier to use a plotting package that already provides a Fortran interface.
I notice from your make file that you are using gfortran. The combination of gfortran (>=4.3) & gcc supports the ISO C Binding. (Part of Fortran 2003.) If you include the interface example and call psopen with two arguments, it should work. The interface goes into the declaration of the Fortran program and is the Fortran description of the C routine psopen. Not guaranteed since I haven't tested it... Strings are a special case -- you match a scaler string in the Fortran program to a string array in the interface, because the C argument is an array of chars, or a pointer to chars.
interface To_psopen
subroutine psopen ( fname, fnamelen ) bind (C, name="psopen")
use iso_c_binding
implicit none
character (kind=c_char, len=1), dimension (100), intent (inout) :: fname
integer (c_int), value, intent (in) :: fnamelen
end subroutine psopen
end interface To_psopen
Running under the debugger (gdb
) should tell you where the segfault happens. Compile all code with -O0 -g
to get accurate information.
精彩评论