Fortran调用共享库BMI初始化dflowfm模型时段错误排查
Fortran调用DFlowFM的BMI初始化触发段错误解决方案
背景
我尝试使用Basic Model Interface (BMI)从Fortran程序中初始化DFlowFM模型(已从源码编译生成可执行文件和共享库),通过OpenEarth的Python封装已成功实现该功能,但在Fortran程序中调用时出现无明确提示的段错误,已定位到初始化模型的函数/子程序源码片段,需要协助解决。
问题详情
Fortran程序中尝试以子程序和函数两种方式调用共享库中的例程,均触发错误:forrtl: severe (174): SIGSEGV, segmentation fault occurred,错误追踪指向函数/子程序调用。
完整错误追踪
forrtl: severe (174): SIGSEGV, segmentation fault occurred Image PC Routine Line Source libpthread-2.31.s 00007F04C9E19140 Unknown Unknown Unknown libdflowfm.so.0.0 00007F04CB517080 Unknown Unknown Unknown libdflowfm.so.0.0 00007F04CB517319 Unknown Unknown Unknown testbmifort 000000000040F2E1 MAIN__ 39 bmi_fort-test.f90 testbmifort 000000000040F25D Unknown Unknown Unknown libc-2.31.so 00007F04C9C4CD0A __libc_start_main Unknown Unknown testbmifort 000000000040F17A Unknown Unknown Unknown
GDB调试追踪
39 call init(sharedDLLHandle, c_config_file, strln) (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x00007f03c2181080 in GetDllProcedure () from /opt/dflowfm/lib/libdflowfm.so.0 (gdb) bt #0 0x00007f03c2181080 in GetDllProcedure () from /opt/dflowfm/lib/libdflowfm.so.0 #1 0x00007f03c2181319 in bmi_initialize_ () from /opt/dflowfm/lib/libdflowfm.so.0 #2 0x000000000040f384 in test_df_bmi () at bmi_fort-test.f90:39 #3 0x000000000040f25d in main () #4 0x00007f03c08b6d0a in __libc_start_main (main=0x40f240 <main>, argc=1, argv=0x7fff9bf4d658, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff9bf4d648) at ../csu/libc-start.c:308 #5 0x000000000040f17a in _start ()
测试代码与编译命令
bmi_fort-test.f90
module bmi use iso_c_binding implicit none integer(C_INT64_T), BIND(C, name="MAXSTRLEN") :: MAXSTRLEN ! interface ! integer(C_INT64_T) function init(config) result(iresult) bind(C, name='bmi_initialize_') ! use iso_c_binding ! character*(c_char) :: config ! end function ! end interface interface subroutine init(sharedDLLHandle, config, strln) bind(C, name='bmi_initialize_') use iso_c_binding type(c_ptr), value :: sharedDLLHandle ! integer(C_INT64_T), pointer :: dll_ptr character*(c_char) :: config integer(c_int) :: strln end subroutine end interface end module bmi program test_df_bmi use iso_c_binding use bmi type(c_ptr) :: sharedDLLHandle ! integer(C_INT64_T), pointer :: dll_ptr character*(c_char) :: c_config_file character(*), parameter :: config_file = "/home/bmi-python/test_data/e02_f14_c040_westerscheldt/westerscheldt.mdu" integer(C_INT64_T) :: iresult integer(c_int) :: strln c_config_file = "/home/bmi-python/test_data/e02_f14_c040_westerscheldt/westerscheldt.mdu" strln = len(config_file) ! iresult = init(c_config_file) call init(sharedDLLHandle, c_config_file, strln) end program test_df_bmi
编译命令:ifort bmi_fort-test.f90 -L/opt/dflowfm/lib/ -ldflowfm -o testbmifort -traceback -warn interface -O0
(注:共享库同样使用ifort编译)
已通过nm -D /opt/dflowfm/lib/libdflowfm.so确认目标例程存在:00000000015a5300 T bmi_initialize_,末尾下划线表明这是C例程。
定位到的源码片段
sf1.f90(生成initialize函数的Fortran源码)
... !> The initialize() function accepts a string argument that !! gives the name (and path) of its "main input file", called !! a configuration file. This function should perform all tasks !! that are to take place before entering the model's time loop. integer(c_int) function initialize(c_config_file) result(c_iresult) bind(C, name="initialize") !DEC$ ATTRIBUTES DLLEXPORT :: initialize use iso_c_binding, only: c_char ... character(kind=c_char),intent(in) :: c_config_file(MAXSTRLEN) character(len=strlen(c_config_file)) :: config_file ! Extra local variables integer :: inerr ! number of the initialisation error ... c_iresult = 0 ! TODO: is this return value BMI-compliant? #ifdef HAVE_MPI ... ! Store the name config_file = char_array_to_string(c_config_file, strlen(c_config_file)) ! Now we can initialize with the config_file ... ! TODO: check why these are needed to avoid a segfault KNX = 8 MXB = 10 MAXLAN = 500 MAXPOL = MAXLAN ... CALL INIDAT() call api_loadmodel(config_file) ... c_iresult = flowinit() time_user = tstart_user ! Just terminate if we get an error.... ! if (inerr > 0) stop ! initialize = 0 end function initialize ...
sf2.c(生成bmi_initialize_的C源码)
... long STDCALL BMI_INITIALIZE(int64_t * sharedDLLHandle, char * config_file, int config_file_len) { typedef (STDCALL * MyProc)(chvoid *ar *); MyProc proc = (MyProc)GetDllProcedure(sharedDLLHandle, "initialize"); long error = -1; char * c_config_file = strFcpy(config_file, config_file_len); RemoveTrailingBlanks_dll(c_config_file); if (proc != NULL) { error = 0; (void *)(*proc)(c_config_file); } free(c_config_file); c_config_file = NULL; return error; } ...
附加问题
是否有方法从共享库中检查该例程所需的参数及数据类型?
补充信息:Python成功案例
df_bmi-test.py(成功初始化的Python代码)
import bmi.wrapper as bw dflowfm = bw.BMIWrapper(engine='dflowfm', configfile='test_data/retracted/path.mdu') dflowfm.initialize()
bmi.wrapper相关实现片段
... def wrap(func): """Return wrapped function with type conversion and sanity checks. """ @functools.wraps(func, assigned=('restype', 'argtypes')) def wrapped(*args): if len(args) != len(func.argtypes): logger.warn("{} {} not of same length", args, func.argtypes) typed_args = [] for (arg, argtype) in zip(args, func.argtypes): if argtype == c_char_p: # create a string buffer for strings typed_arg = create_string_buffer(arg) else: # for other types, use the type to do the conversion if hasattr(argtype, 'contents'): # type is a pointer typed_arg = argtype(argtype._type_(arg)) else: typed_arg = argtype(arg) typed_args.append(typed_arg) result = func(*typed_args) if hasattr(result, 'contents'): return result.contents else: return result return wrapped ... def _load_library(self): """Return the fortran library, loaded with """ path = self._library_path() logger.info("Loading library from path {}".format(path)) library_dir = os.path.dirname(path) if platform.system() == 'Windows': import win32api olddir = os.getcwd() os.chdir(library_dir) win32api.SetDllDirectory('.') result = cdll.LoadLibrary(path) if platform.system() == 'Windows': os.chdir(olddir) return result def initialize(self, configfile=None): """Initialize and load the Fortran library (and model, if applicable). The Fortran library is loaded and ctypes is used to annotate functions inside the library. The Fortran library's initialization is called. Normally a path to an ``*.ini`` model file is passed to the :meth:`__init__`. If so, that model is loaded. Note that :meth:`_load_model` changes the working directory to that of the model. """ if configfile is not None: self.configfile = configfile try: self.configfile except AttributeError: raise ValueError("Specify configfile during construction or during initialize") abs_name = os.path.abspath(self.configfile) os.chdir(os.path.dirname(self.configfile) or '.') logmsg = "Loading model {} in directory {}".format( self.configfile, os.path.abspath(os.getcwd()) ) logger.info(logmsg) # Fortran init function. self.library.initialize.argtypes = [c_char_p] self.library.initialize.restype = None # initialize by abs_name because we already chdirred # if configfile is a relative path we would have a problem ierr = wrap(self.library.initialize)(abs_name) if ierr: errormsg = "Loading model {config} failed with exit code {code}" raise RuntimeError(errormsg.format(config=self.configfile, code=ierr)) ...
内容的提问来源于stack exchange,提问作者Veethahavya
相关产品推荐
相关产品推荐

