bug-make
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Loading dynamic objects on Windows


From: Eli Zaretskii
Subject: Loading dynamic objects on Windows
Date: Tue, 30 Apr 2013 22:04:52 +0300

I ran all of the load and loadapi tests in the test suite and found a
couple of problems in the current implementation that were based on
unportable assumptions.  The solutions touch to some extent the
platform independent code and build requirements, so I'd like to
discuss them here before I actually push them.  (For the impatient:
see the patches near the end of this mail.)

The two problems I discovered and fixed are:

1. How to build the extension shared libraries.

The current code and instructions (and also the test suite) simply
compile and link the extensions with -shared compiler switch.  This
assumes the linker lets you get away with unresolved externals (the
gmk_* functions provided by Make), and let these references be
resolved at run time, when the extension is loaded by dlopen/dlsym.

This doesn't work on Windows.  There, the linker _must_ see some
symbols that allow it to resolve the references _at_link_time_.  So if
I try the original command used by the load test, i.e.:

  gcc -I../../.. -g -shared -fPIC -o testload.dll testload.c

it complains about unresolved externals and bails out.

There are 2 ways of solving this on Windows:

 . Call the gmk_* functions via pointers (so that the linker doesn't
   see the function calls at all).  These pointers need to be assigned
   the addresses of the functions at the dlopen time, and they need to
   have the exact same type as the functions they will call.

   I did manage to get this to work, but found that this method has a
   lot of limitations.  For example, since the pointer variables need
   to have the same names as the functions (to make it possible to
   compile the same extension on Unix and on Windows), these variables
   need to be static (to put them in gnumake.h header), and the
   function to be called by dlopen needs also to be static.  But using
   static functions and variables means that an extension that has
   more than 1 C file will have several unrelated copied of these...

   So I prefer the second alternative, which is:

 . Use an import library.  An import library is a library of stubs
   that provide enough info for the linker to be happy at link time,
   and leave the actual resolution of the references at dlopen time.
   GCC can create such a library for select functions that are
   declared with a special type.  Then the import library is submitted
   to the linker when the extension is built, and that's it -- the
   extension can freely call functions exported by Make, as if they
   were defined in the extension.

   (Normally, a shared library exports its functions and an import
   library is used to link the main program.  But our case is the
   opposite: we need the main program to export some functions.  But
   the mechanism works either way.)

This requires minor changes to the build_w32.bat script which builds
Make, and also to the test suite (if we want it to be runnable on
Windows; I ran the test by hand).  Additional changes are needed to
put the necessary decorations on the functions exported by Make.  It
also requires that the import library be distributed with the Make
binaries for Windows.

2. When Make decides that it needs to remake a dynamic object, it does
so with the object still loaded.  Windows does not allow to overwrite
a shared library that is being used by some program, so the remake
fails.

To fix this, I record the pointer returned by dlopen in the file
structure of the dynamic object, and call dlclose before running the
job to remake it.  This means that any features defined by the object
cannot be used for remaking the object.  I hope this does not impose
any real restrictions, since if the object is needed to remake itself,
it means that object cannot be created from scratch.

That's it.  Now I let the patch talk:

--- commands.c~2        2013-04-28 06:41:56.000000000 +0300
+++ commands.c  2013-04-30 11:42:46.345909500 +0300
@@ -14,6 +14,8 @@ A PARTICULAR PURPOSE.  See the GNU Gener
 You should have received a copy of the GNU General Public License along with
 this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
+#include <dlfcn.h>
+
 #include "makeint.h"
 #include "dep.h"
 #include "filedef.h"
@@ -468,6 +470,12 @@ execute_file_commands (struct file *file
 
   set_file_variables (file);
 
+  /* If this is a loaded dynamic object, unload it before remaking.
+     Some systems don't allow to overwrite a loaded shared
+     library.  */
+  if (file->dlopen_ptr)
+    dlclose (file->dlopen_ptr);
+
   /* Start the commands running.  */
   new_job (file);
 }


--- filedef.h~2 2013-04-29 06:47:10.000000000 +0300
+++ filedef.h   2013-04-30 11:56:47.364106900 +0300
@@ -61,6 +61,8 @@ struct file
     int command_flags;         /* Flags OR'd in for cmds; see commands.h.  */
     char update_status;         /* Status of the last attempt to update,
                                   or -1 if none has been made.  */
+    void *dlopen_ptr;          /* For dynamic loaded objects: pointer to
+                                  pass to dlclose to unload the object.  */
     enum cmd_state             /* State of the commands.  */
       {                /* Note: It is important that cs_not_started be zero.  
*/
        cs_not_started,         /* Not yet started.  */


--- gnumake.h~1 2013-04-28 06:41:57.000000000 +0300
+++ gnumake.h   2013-04-30 09:58:14.387983000 +0300
@@ -26,13 +26,23 @@ typedef struct
     unsigned long lineno;
   } gmk_floc;
 
+#ifdef _WIN32
+# ifdef MAIN
+#  define GMK_EXPORT  __declspec(dllexport)
+# else
+#  define GMK_EXPORT  __declspec(dllimport)
+# endif
+#else
+# define GMK_EXPORT
+#endif
+
 
 /* Run $(eval ...) on the provided string BUFFER.  */
-void gmk_eval (const char *buffer, const gmk_floc *floc);
+void GMK_EXPORT gmk_eval (const char *buffer, const gmk_floc *floc);
 
 /* Run GNU make expansion on the provided string STR.
    Returns an allocated buffer that the caller must free.  */
-char *gmk_expand (const char *str);
+char * GMK_EXPORT gmk_expand (const char *str);
 
 /* Register a new GNU make function NAME (maximum of 255 chars long).
    When the function is expanded in the makefile, FUNC will be invoked with
@@ -49,8 +59,9 @@ char *gmk_expand (const char *str);
    If EXPAND_ARGS is 0, the arguments to the function will not be expanded
    before FUNC is called.  If EXPAND_ARGS is non-0, they will be expanded.
 */
-void gmk_add_function (const char *name,
-                       char *(*func)(const char *nm, int argc, char **argv),
-                       int min_args, int max_args, int expand_args);
+void GMK_EXPORT gmk_add_function (const char *name,
+                                 char *(*func)(const char *nm,
+                                               int argc, char **argv),
+                                 int min_args, int max_args, int expand_args);
 
 #endif  /* _GNUMAKE_H_ */


--- load.c~2    2013-04-29 14:52:21.870479300 +0300
+++ load.c      2013-04-30 11:33:49.346863900 +0300
@@ -32,11 +32,13 @@ this program.  If not, see <http://www.g
 
 static load_func_t
 load_object (const gmk_floc *flocp, int noerror,
-             const char *ldname, const char *symname)
+             const char *ldname, const char *symname, void **dlp)
 {
   static void *global_dl = NULL;
   load_func_t symp;
 
+  *dlp = NULL;
+
   if (! global_dl)
     {
       global_dl = dlopen (NULL, RTLD_NOW|RTLD_GLOBAL);
@@ -46,7 +48,6 @@ load_object (const gmk_floc *flocp, int 
 
   symp = (load_func_t) dlsym (global_dl, symname);
   if (! symp) {
-    void *dlp = NULL;
 
     /* If the path has no "/", try the current directory first.  */
     if (! strchr (ldname, '/')
@@ -54,14 +55,14 @@ load_object (const gmk_floc *flocp, int 
        && ! strchr (ldname, '\\')
 #endif
        )
-      dlp = dlopen (concat (2, "./", ldname), RTLD_LAZY|RTLD_GLOBAL);
+      *dlp = dlopen (concat (2, "./", ldname), RTLD_LAZY|RTLD_GLOBAL);
 
     /* If we haven't opened it yet, try the default search path.  */
-    if (! dlp)
-      dlp = dlopen (ldname, RTLD_LAZY|RTLD_GLOBAL);
+    if (! *dlp)
+      *dlp = dlopen (ldname, RTLD_LAZY|RTLD_GLOBAL);
 
     /* Still no?  Then fail.  */
-    if (! dlp)
+    if (! *dlp)
       {
         if (noerror)
           DB (DB_BASIC, ("%s", dlerror()));
@@ -70,7 +71,7 @@ load_object (const gmk_floc *flocp, int 
         return NULL;
       }
 
-    symp = dlsym (dlp, symname);
+    symp = dlsym (*dlp, symname);
     if (! symp)
       fatal (flocp, _("Failed to load symbol %s from %s: %s"),
              symname, ldname, dlerror());
@@ -80,7 +81,7 @@ load_object (const gmk_floc *flocp, int 
 }
 
 int
-load_file (const gmk_floc *flocp, const char **ldname, int noerror)
+load_file (const gmk_floc *flocp, const char **ldname, int noerror, void **dlp)
 {
   int nmlen = strlen (*ldname);
   char *new = alloca (nmlen + CSTRLEN (SYMBOL_EXTENSION) + 1);
@@ -90,6 +91,8 @@ load_file (const gmk_floc *flocp, const 
   int r;
   load_func_t symp;
 
+  *dlp = NULL;
+
   /* Break the input into an object file name and a symbol name.  If no symbol
      name was provided, compute one from the object file name.  */
   fp = strchr (*ldname, '(');
@@ -165,7 +168,7 @@ load_file (const gmk_floc *flocp, const 
   DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), symname, *ldname));
 
   /* Load it!  */
-  symp = load_object(flocp, noerror, *ldname, symname);
+  symp = load_object(flocp, noerror, *ldname, symname, dlp);
   if (! symp)
     return 0;
 


--- loadapi.c~1 2013-04-28 06:41:57.000000000 +0300
+++ loadapi.c   2013-04-30 08:35:33.423280500 +0300
@@ -14,8 +14,6 @@ A PARTICULAR PURPOSE.  See the GNU Gener
 You should have received a copy of the GNU General Public License along with
 this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#include "gnumake.h"
-
 #include "makeint.h"
 
 #include "filedef.h"


--- makeint.h~1 2013-04-29 06:47:10.000000000 +0300
+++ makeint.h   2013-04-30 12:01:21.405874500 +0300
@@ -48,8 +48,12 @@ char *alloca ();
 #endif
 
 /* Include the externally-visible content.
-   Be sure to use the local one, and not one installed on the system.  */
+   Be sure to use the local one, and not one installed on the system.
+   Define MAIN for proper selection of dllexport/dllimport declarations
+   for MS-Windows.  */
+#define MAIN
 #include "gnumake.h"
+#undef MAIN
 
 #ifdef  CRAY
 /* This must happen before #include <signal.h> so
@@ -476,7 +480,8 @@ int guile_gmake_setup (const gmk_floc *f
 
 /* Loadable object support.  Sets to the strcached name of the loaded file.  */
 typedef int (*load_func_t)(const gmk_floc *flocp);
-int load_file (const gmk_floc *flocp, const char **filename, int noerror);
+int load_file (const gmk_floc *flocp, const char **filename, int noerror,
+              void **dlp);
 
 #ifdef  HAVE_VFORK_H
 # include <vfork.h>


--- read.c~2    2013-04-28 06:41:57.000000000 +0300
+++ read.c      2013-04-30 12:10:07.718054200 +0300
@@ -937,11 +937,12 @@ eval (struct ebuffer *ebuf, int set_defa
               struct nameseq *next = files->next;
               const char *name = files->name;
               struct dep *deps;
+              void *dlp;
 
               free_ns (files);
               files = next;
 
-              if (! load_file (&ebuf->floc, &name, noerror) && ! noerror)
+              if (! load_file (&ebuf->floc, &name, noerror, &dlp) && ! noerror)
                 fatal (&ebuf->floc, _("%s: failed to load"), name);
 
               deps = alloc_dep ();
@@ -950,6 +951,7 @@ eval (struct ebuffer *ebuf, int set_defa
               deps->file = lookup_file (name);
               if (deps->file == 0)
                 deps->file = enter_file (name);
+              deps->file->dlopen_ptr = dlp;
             }
 
           continue;


--- tests/scripts/features/load~2       2013-04-28 06:41:58.000000000 +0300
+++ tests/scripts/features/load 2013-04-30 12:12:27.409354500 +0300
@@ -21,14 +21,14 @@ print $F <<'EOF' ;
 #include "gnumake.h"
 
 int
-testload_gmk_setup ()
+testload_gmk_setup (gmk_floc *pos)
 {
     gmk_eval ("TESTLOAD = implicit", 0);
     return 1;
 }
 
 int
-explicit_setup ()
+explicit_setup (gmk_floc *pos)
 {
     gmk_eval ("TESTLOAD = explicit", 0);
     return 1;
--- w32/compat/posixfcn.c~2     2013-04-30 11:39:33.497473300 +0300
+++ w32/compat/posixfcn.c       2013-04-30 11:39:44.058741000 +0300
@@ -338,6 +338,17 @@ dlsym (void *handle, const char *name)
   return (void *)addr;
 }
 
+int
+dlclose (void *handle)
+{
+  if (!handle || handle == INVALID_HANDLE_VALUE)
+    return -1;
+  if (!FreeLibrary (handle))
+    return -1;
+
+  return 0;
+}
+
 
 #endif /* MAKE_LOAD */
 


--- w32/include/dlfcn.h~2       2013-04-29 15:48:02.793614200 +0300
+++ w32/include/dlfcn.h 2013-04-30 11:38:27.309442800 +0300
@@ -9,5 +9,6 @@
 extern void *dlopen (const char *, int);
 extern void *dlsym (void *, const char *);
 extern char *dlerror (void);
+extern int   dlclose (void *);
 
 #endif /* DLFCN_H */


--- build_w32.bat~2     2013-04-28 06:41:56.000000000 +0300
+++ build_w32.bat       2013-04-30 10:02:24.098783700 +0300
@@ -291,7 +291,7 @@
 gcc -mthreads -Wall -gdwarf-2 -g3 %OPT% %GUILECFLAGS% -I. -I./glob 
-I./w32/include -DWINDOWS32 -DHAVE_CONFIG_H -c guile.c
 :LinkGCC
 @echo on
-gcc -mthreads -gdwarf-2 -g3 -o gnumake.exe variable.o rule.o remote-stub.o 
commands.o file.o getloadavg.o default.o signame.o expand.o dir.o main.o 
getopt1.o %GUILEOBJ% job.o read.o version.o getopt.o arscan.o remake.o misc.o 
hash.o strcache.o ar.o function.o vpath.o implicit.o loadapi.o load.o glob.o 
fnmatch.o pathstuff.o posixfcn.o w32_misc.o sub_proc.o w32err.o %GUILELIBS% 
-lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 
-loleaut32 -luuid -lodbc32 -lodbccp32
+gcc -mthreads -gdwarf-2 -g3 -o gnumake.exe variable.o rule.o remote-stub.o 
commands.o file.o getloadavg.o default.o signame.o expand.o dir.o main.o 
getopt1.o %GUILEOBJ% job.o read.o version.o getopt.o arscan.o remake.o misc.o 
hash.o strcache.o ar.o function.o vpath.o implicit.o loadapi.o load.o glob.o 
fnmatch.o pathstuff.o posixfcn.o w32_misc.o sub_proc.o w32err.o %GUILELIBS% 
-lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 
-loleaut32 -luuid -lodbc32 -lodbccp32 -Wl,--out-implib=libgnumake.dll.a
 @GoTo BuildEnd
 :Usage
 echo Usage: %0 [options] [gcc]



reply via email to

[Prev in Thread] Current Thread [Next in Thread]