Commit 278c782a3205dbe49de4233b3bd1e68687a9998b

Silvio Traversaro 2019-02-14T14:57:29

Merge pull request #44 from pali/master Fix resolving global symbols and implement RTLD_DEFAULT and RTLD_NEXT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 48945b2..07addbb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,6 +63,11 @@ if (BUILD_TESTS)
   enable_testing()
   add_library(testdll SHARED testdll.c)
   set_target_properties(testdll PROPERTIES PREFIX "")
+  add_library(testdll2 SHARED testdll2.c)
+  set_target_properties(testdll2 PROPERTIES PREFIX "")
+  target_link_libraries(testdll2 dl)
+  add_library(testdll3 SHARED testdll3.c)
+  set_target_properties(testdll3 PROPERTIES PREFIX "")
   add_executable(t_dlfcn test.c)
   target_link_libraries(t_dlfcn dl)
   add_test (NAME t_dlfcn COMMAND t_dlfcn)
diff --git a/Makefile b/Makefile
index c02dce0..efac5af 100644
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,13 @@ test.exe: test.o $(TARGETS)
 testdll.dll: testdll.c
 	$(CC) -shared -o $@ $^
 
-test: $(TARGETS) test.exe testdll.dll
+testdll2.dll: testdll2.c $(TARGETS)
+	$(CC) -shared -o $@ $< -L. -ldl $(LIBS)
+
+testdll3.dll: testdll3.c
+	$(CC) -shared -o $@ $^
+
+test: $(TARGETS) test.exe testdll.dll testdll2.dll testdll3.dll
 	$(WINE) test.exe
 
 clean::
@@ -74,7 +80,7 @@ clean::
 		dlfcn.o \
 		libdl.dll libdl.a libdl.def libdl.dll.a libdl.lib libdl.exp \
 		tmptest.c tmptest.dll \
-		test.exe testdll.dll
+		test.exe testdll.dll testdll2.dll testdll3.dll
 
 distclean: clean
 	rm -f config.mak
diff --git a/cmake-test/CMakeLists.txt b/cmake-test/CMakeLists.txt
index 532f2b8..659a79a 100644
--- a/cmake-test/CMakeLists.txt
+++ b/cmake-test/CMakeLists.txt
@@ -8,6 +8,11 @@ find_package(dlfcn-win32 REQUIRED)
 
 add_library(testdll SHARED ../testdll.c)
 set_target_properties(testdll PROPERTIES PREFIX "")
+add_library(testdll2 SHARED ../testdll2.c)
+set_target_properties(testdll2 PROPERTIES PREFIX "")
+target_link_libraries(testdll2 dlfcn-win32::dl)
+add_library(testdll3 SHARED ../testdll3.c)
+set_target_properties(testdll3 PROPERTIES PREFIX "")
 add_executable(t_dlfcn ../test.c)
 target_link_libraries(t_dlfcn dlfcn-win32::dl)
 enable_testing()
diff --git a/dlfcn.c b/dlfcn.c
index b356558..8d3c793 100644
--- a/dlfcn.c
+++ b/dlfcn.c
@@ -2,6 +2,7 @@
  * dlfcn-win32
  * Copyright (c) 2007 Ramiro Polla
  * Copyright (c) 2015 Tiancheng "Timothy" Gu
+ * Copyright (c) 2019 Pali Rohár <pali.rohar@gmail.com>
  *
  * dlfcn-win32 is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,6 +30,17 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#ifdef _MSC_VER
+/* https://docs.microsoft.com/en-us/cpp/intrinsics/returnaddress */
+#include <intrin.h>
+#pragma intrinsic(_ReturnAddress)
+#else
+/* https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html */
+#ifndef _ReturnAddress
+#define _ReturnAddress() (__builtin_extract_return_addr(__builtin_return_address(0)))
+#endif
+#endif
+
 #ifdef SHARED
 #define DLFCN_WIN32_EXPORTS
 #endif
@@ -52,57 +64,48 @@
  * any kind of thread safety.
  */
 
-typedef struct global_object {
+typedef struct local_object {
     HMODULE hModule;
-    struct global_object *previous;
-    struct global_object *next;
-} global_object;
+    struct local_object *previous;
+    struct local_object *next;
+} local_object;
 
-static global_object first_object;
-static global_object first_automatic_object;
-static int auto_ref_count = 0;
+static local_object first_object;
 
-/* These functions implement a double linked list for the global objects. */
-static global_object *global_search( global_object *start, HMODULE hModule )
+/* These functions implement a double linked list for the local objects. */
+static local_object *local_search( HMODULE hModule )
 {
-    global_object *pobject;
+    local_object *pobject;
 
     if( hModule == NULL )
         return NULL;
 
-    for( pobject = start; pobject; pobject = pobject->next )
+    for( pobject = &first_object; pobject; pobject = pobject->next )
         if( pobject->hModule == hModule )
             return pobject;
 
     return NULL;
 }
 
-static void global_add( global_object *start, HMODULE hModule )
+static void local_add( HMODULE hModule )
 {
-    global_object *pobject;
-    global_object *nobject;
+    local_object *pobject;
+    local_object *nobject;
 
     if( hModule == NULL )
         return;
 
-    pobject = global_search( start, hModule );
+    pobject = local_search( hModule );
 
     /* Do not add object again if it's already on the list */
     if( pobject )
         return;
 
-    if( start == &first_automatic_object )
-    {
-        pobject = global_search( &first_object, hModule );
-        if( pobject )
-            return;
-    }
-
-    for( pobject = start; pobject->next; pobject = pobject->next );
+    for( pobject = &first_object; pobject->next; pobject = pobject->next );
 
-    nobject = (global_object*) malloc( sizeof( global_object ) );
+    nobject = (local_object*) malloc( sizeof( local_object ) );
 
-    /* Should this be enough to fail global_add, and therefore also fail
+    /* Should this be enough to fail local_add, and therefore also fail
      * dlopen?
      */
     if( !nobject )
@@ -114,14 +117,14 @@ static void global_add( global_object *start, HMODULE hModule )
     nobject->hModule = hModule;
 }
 
-static void global_rem( global_object *start, HMODULE hModule )
+static void local_rem( HMODULE hModule )
 {
-    global_object *pobject;
+    local_object *pobject;
 
     if( hModule == NULL )
         return;
 
-    pobject = global_search( start, hModule );
+    pobject = local_search( hModule );
 
     if( !pobject )
         return;
@@ -224,10 +227,6 @@ void *dlopen( const char *file, int mode )
 
     if( file == 0 )
     {
-        HMODULE hAddtnlMods[1024]; // Already loaded modules
-        HANDLE hCurrentProc = GetCurrentProcess( );
-        DWORD cbNeeded;
-
         /* POSIX says that if the value of file is 0, a handle on a global
          * symbol object must be provided. That object must be able to access
          * all symbols from the original program file, and any objects loaded
@@ -242,27 +241,13 @@ void *dlopen( const char *file, int mode )
 
         if( !hModule )
             save_err_ptr_str( file );
-
-
-        /* GetModuleHandle( NULL ) only returns the current program file. So
-         * if we want to get ALL loaded module including those in linked DLLs,
-         * we have to use EnumProcessModules( ).
-         */
-        if( EnumProcessModules( hCurrentProc, hAddtnlMods,
-            sizeof( hAddtnlMods ), &cbNeeded ) != 0 )
-        {
-            DWORD i;
-            for( i = 0; i < cbNeeded / sizeof( HMODULE ); i++ )
-            {
-                global_add( &first_automatic_object, hAddtnlMods[i] );
-            }
-        }
-        auto_ref_count++;
     }
     else
     {
+        HANDLE hCurrentProc;
+        DWORD dwProcModsBefore, dwProcModsAfter;
         CHAR lpFileName[MAX_PATH];
-        int i;
+        size_t i;
 
         /* MSDN says backslashes *must* be used instead of forward slashes. */
         for( i = 0 ; i < sizeof(lpFileName) - 1 ; i ++ )
@@ -276,6 +261,11 @@ void *dlopen( const char *file, int mode )
         }
         lpFileName[i] = '\0';
 
+        hCurrentProc = GetCurrentProcess( );
+
+        if( EnumProcessModules( hCurrentProc, NULL, 0, &dwProcModsBefore ) == 0 )
+            dwProcModsBefore = 0;
+
         /* POSIX says the search path is implementation-defined.
          * LOAD_WITH_ALTERED_SEARCH_PATH is used to make it behave more closely
          * to UNIX's search paths (start with system folders instead of current
@@ -284,17 +274,24 @@ void *dlopen( const char *file, int mode )
         hModule = LoadLibraryEx(lpFileName, NULL, 
                                 LOAD_WITH_ALTERED_SEARCH_PATH );
 
-        /* If the object was loaded with RTLD_GLOBAL, add it to list of global
-         * objects, so that its symbols may be retrieved even if the handle for
+        if( EnumProcessModules( hCurrentProc, NULL, 0, &dwProcModsAfter ) == 0 )
+            dwProcModsAfter = 0;
+
+        /* If the object was loaded with RTLD_LOCAL, add it to list of local
+         * objects, so that its symbols cannot be retrieved even if the handle for
          * the original program file is passed. POSIX says that if the same
          * file is specified in multiple invocations, and any of them are
          * RTLD_GLOBAL, even if any further invocations use RTLD_LOCAL, the
-         * symbols will remain global.
+         * symbols will remain global. If number of loaded modules was not
+         * changed after calling LoadLibraryEx(), it means that library was
+         * already loaded.
          */
         if( !hModule )
             save_err_str( lpFileName );
-        else if( (mode & RTLD_GLOBAL) )
-            global_add( &first_object, hModule );
+        else if( (mode & RTLD_LOCAL) && dwProcModsBefore != dwProcModsAfter )
+            local_add( hModule );
+        else if( !(mode & RTLD_LOCAL) && dwProcModsBefore == dwProcModsAfter )
+            local_rem( hModule );
     }
 
     /* Return to previous state of the error-mode bit flags. */
@@ -303,21 +300,6 @@ void *dlopen( const char *file, int mode )
     return (void *) hModule;
 }
 
-static void free_auto( )
-{
-    global_object *pobject = first_automatic_object.next;
-    if( pobject )
-    {
-        global_object *next;
-        for ( ; pobject; pobject = next )
-        {
-            next = pobject->next;
-            free( pobject );
-        }
-        first_automatic_object.next = NULL;
-    }
-}
-
 int dlclose( void *handle )
 {
     HMODULE hModule = (HMODULE) handle;
@@ -327,22 +309,11 @@ int dlclose( void *handle )
 
     ret = FreeLibrary( hModule );
 
-    /* If the object was loaded with RTLD_GLOBAL, remove it from list of global
+    /* If the object was loaded with RTLD_LOCAL, remove it from list of local
      * objects.
      */
     if( ret )
-    {
-        HMODULE cur = GetModuleHandle( NULL );
-        global_rem( &first_object, hModule );
-        if( hModule == cur )
-        {
-            auto_ref_count--;
-            if( auto_ref_count < 0 )
-                auto_ref_count = 0;
-            if( !auto_ref_count )
-                free_auto( );
-        }
-    }
+        local_rem( hModule );
     else
         save_err_ptr_str( handle );
 
@@ -352,10 +323,13 @@ int dlclose( void *handle )
     return (int) ret;
 }
 
+__declspec(noinline) /* Needed for _ReturnAddress() */
 void *dlsym( void *handle, const char *name )
 {
     FARPROC symbol;
+    HMODULE hCaller;
     HMODULE hModule;
+    HANDLE hCurrentProc;
 
 #ifdef UNICODE
     wchar_t namew[MAX_PATH];
@@ -363,39 +337,88 @@ void *dlsym( void *handle, const char *name )
 #endif
 
     current_error = NULL;
+    symbol = NULL;
+    hCaller = NULL;
+    hModule = GetModuleHandle( NULL );
+    hCurrentProc = GetCurrentProcess( );
 
-    symbol = GetProcAddress( (HMODULE) handle, name );
+    if( handle == RTLD_DEFAULT )
+    {
+        /* The symbol lookup happens in the normal global scope; that is,
+         * a search for a symbol using this handle would find the same
+         * definition as a direct use of this symbol in the program code.
+         * So use same lookup procedure as when filename is NULL.
+         */
+        handle = hModule;
+    }
+    else if( handle == RTLD_NEXT )
+    {
+        /* Specifies the next object after this one that defines name.
+         * This one refers to the object containing the invocation of dlsym().
+         * The next object is the one found upon the application of a load
+         * order symbol resolution algorithm. To get caller function of dlsym()
+         * use _ReturnAddress() intrinsic. To get HMODULE of caller function
+         * use undocumented hack from https://stackoverflow.com/a/2396380
+         * The HMODULE of a DLL is the same value as the module's base address.
+         */
+        MEMORY_BASIC_INFORMATION info;
+        size_t sLen;
+        sLen = VirtualQueryEx( hCurrentProc, _ReturnAddress(), &info, sizeof( info ) );
+        if( sLen != sizeof( info ) )
+            goto end;
+        hCaller = (HMODULE) info.AllocationBase;
+        if(!hCaller)
+            goto end;
+    }
 
-    if( symbol != NULL )
-        goto end;
+    if( handle != RTLD_NEXT )
+    {
+        symbol = GetProcAddress( (HMODULE) handle, name );
+
+        if( symbol != NULL )
+            goto end;
+    }
 
     /* If the handle for the original program file is passed, also search
      * in all globally loaded objects.
      */
 
-    hModule = GetModuleHandle( NULL );
-
-    if( hModule == handle )
+    if( hModule == handle || handle == RTLD_NEXT )
     {
-        global_object *pobject;
-
-        for( pobject = &first_object; pobject; pobject = pobject->next )
-        {
-            if( pobject->hModule )
-            {
-                symbol = GetProcAddress( pobject->hModule, name );
-                if( symbol != NULL )
-                    goto end;
-            }
-        }
+        HMODULE *modules;
+        DWORD cbNeeded;
+        DWORD dwSize;
+        size_t i;
 
-        for( pobject = &first_automatic_object; pobject; pobject = pobject->next )
+        /* GetModuleHandle( NULL ) only returns the current program file. So
+         * if we want to get ALL loaded module including those in linked DLLs,
+         * we have to use EnumProcessModules( ).
+         */
+        if( EnumProcessModules( hCurrentProc, NULL, 0, &dwSize ) != 0 )
         {
-            if( pobject->hModule )
+            modules = malloc( dwSize );
+            if( modules )
             {
-                symbol = GetProcAddress( pobject->hModule, name );
-                if( symbol != NULL )
-                    goto end;
+                if( EnumProcessModules( hCurrentProc, modules, dwSize, &cbNeeded ) != 0 && dwSize == cbNeeded )
+                {
+                    for( i = 0; i < dwSize / sizeof( HMODULE ); i++ )
+                    {
+                        if( handle == RTLD_NEXT && hCaller )
+                        {
+                            /* Next modules can be used for RTLD_NEXT */
+                            if( hCaller == modules[i] )
+                                hCaller = NULL;
+                            continue;
+                        }
+                        if( local_search( modules[i] ) )
+                            continue;
+                        symbol = GetProcAddress( modules[i], name );
+                        if( symbol != NULL )
+                            goto end;
+                    }
+
+                }
+                free( modules );
             }
         }
     }
@@ -472,18 +495,8 @@ char *dlerror( void )
 BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
 {
     (void) hinstDLL;
-    /*
-     * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx 
-     *
-     *     When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap
-     *     memory only if the DLL is being unloaded dynamically (the lpReserved
-     *     parameter is NULL).
-     */
-    if( fdwReason == DLL_PROCESS_DETACH && !lpvReserved )
-    {
-        auto_ref_count = 0;
-        free_auto( );
-    }
+    (void) fdwReason;
+    (void) lpvReserved;
     return TRUE;
 }
 #endif
diff --git a/dlfcn.h b/dlfcn.h
index 711e431..c0d7777 100644
--- a/dlfcn.h
+++ b/dlfcn.h
@@ -44,8 +44,8 @@ extern "C" {
  * Note: All other RTLD_* flags in any dlfcn.h are not standard compliant.
  */
 
-#define RTLD_DEFAULT    0
-#define RTLD_NEXT       0
+#define RTLD_DEFAULT    ((void *)0)
+#define RTLD_NEXT       ((void *)-1)
 
 DLFCN_EXPORT void *dlopen ( const char *file, int mode );
 DLFCN_EXPORT int   dlclose(void *handle);
diff --git a/test.c b/test.c
index ba440da..814aca1 100644
--- a/test.c
+++ b/test.c
@@ -2,6 +2,7 @@
  * dlfcn-win32
  * Copyright (c) 2007-2009 Ramiro Polla
  * Copyright (c) 2014      Tiancheng "Timothy" Gu
+ * Copyright (c) 2019      Pali Rohár <pali.rohar@gmail.com>
  *
  * dlfcn-win32 is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -25,6 +26,7 @@
 #endif
 #include <stdio.h>
 #include <string.h>
+#include <windows.h>
 #include "dlfcn.h"
 
 /* If these dlclose's fails, we don't care as the handles are going to be
@@ -71,12 +73,16 @@
 int main()
 {
     void *global;
+    void *library2;
     void *library;
     char *error;
     int (*function)( void );
+    int (*function2_from_library2)( void );
     size_t (*fwrite_local) ( const void *, size_t, size_t, FILE * );
+    size_t (*fputs_default) ( const char *, FILE * );
     int (*nonexistentfunction)( void );
     int ret;
+    HMODULE library3;
 
 #ifdef _DEBUG
     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
@@ -87,6 +93,16 @@ int main()
     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);
 #endif
 
+    library2 = dlopen( "testdll2.dll", RTLD_GLOBAL );
+    if( !library2 )
+    {
+        error = dlerror( );
+        printf( "ERROR\tCould not open library2 globally: %s\n", error ? error : "" );
+        RETURN_ERROR; 
+    }
+    else
+        printf( "SUCCESS\tOpened library2 globally: %p\n", library2 );
+
     library = dlopen( "testdll.dll", RTLD_GLOBAL );
     if( !library )
     {
@@ -124,6 +140,22 @@ int main()
     fwrite_local(hello_world,sizeof(char),strlen(hello_world),stderr);
     fflush(stderr);
 
+    fputs_default = dlsym(RTLD_DEFAULT, "fputs");
+    if (!fputs_default)
+    {
+        error = dlerror();
+        printf("ERROR\tCould not get symbol from default handle: %s\n",
+            error ? error : "");
+        CLOSE_LIB;
+        CLOSE_GLOBAL;
+        RETURN_ERROR;
+    }
+    else
+        printf("SUCCESS\tGot symbol from default handle: %p\n", fputs_default);
+    char * hello_world_fputs = "Hello world from default fputs!\n";
+    fputs_default(hello_world_fputs, stderr);
+    fflush(stderr);
+
     function = dlsym( library, "function" );
     if( !function )
     {
@@ -139,6 +171,27 @@ int main()
 
     RUNFUNC;
 
+    function2_from_library2 = dlsym( library2, "function2" );
+    if( !function2_from_library2 )
+    {
+        error = dlerror( );
+        printf( "ERROR\tCould not get symbol from library2 handle: %s\n",
+                error ? error : "" );
+        CLOSE_LIB;
+        CLOSE_GLOBAL;
+        RETURN_ERROR;
+    }
+    else
+        printf( "SUCCESS\tGot symbol from library2 handle: %p\n", function2_from_library2 );
+
+    ret = function2_from_library2 ();
+    if( ret != 2 )
+    {
+        CLOSE_LIB;
+        CLOSE_GLOBAL;
+        RETURN_ERROR;
+    }
+
     nonexistentfunction = dlsym( library, "nonexistentfunction" );
     if( nonexistentfunction )
     {
@@ -194,6 +247,16 @@ int main()
     else
         printf( "SUCCESS\tClosed library.\n" );
 
+    ret = dlclose( library2 );
+    if( ret )
+    {
+        error = dlerror( );
+        printf( "ERROR\tCould not close library2: %s\n", error ? error : "" );
+        RETURN_ERROR;
+    }
+    else
+        printf( "SUCCESS\tClosed library2.\n" );
+
     library = dlopen( "testdll.dll", RTLD_LOCAL );
     if( !library )
     {
@@ -335,6 +398,15 @@ int main()
         printf("SUCCESS\tGot symbol from global handle: %p\n", function);
     
 
+    library3 = LoadLibraryA("testdll3.dll");
+    if (!library3)
+    {
+        printf( "ERROR\tCould not open library3 via WINAPI\n" );
+        RETURN_ERROR;
+    }
+    else
+        printf( "SUCCESS\tOpened library3 via WINAPI: %p\n", library3 );
+
     ret = dlclose( library );
     if( ret )
     {
@@ -346,6 +418,21 @@ int main()
     else
         printf( "SUCCESS\tClosed library.\n" );
 
+    function = dlsym(global, "function3");
+    if (!function)
+    {
+        error = dlerror();
+        printf("ERROR\tCould not get symbol from global handle: %s\n",
+            error ? error : "");
+        CLOSE_LIB;
+        CLOSE_GLOBAL;
+        RETURN_ERROR;
+    }
+    else
+        printf("SUCCESS\tGot symbol from global handle: %p\n", function);
+
+    RUNFUNC;
+
     ret = dlclose( global );
     if( ret )
     {
diff --git a/testdll.c b/testdll.c
index ff99f87..3df2234 100644
--- a/testdll.c
+++ b/testdll.c
@@ -30,6 +30,12 @@
 #define EXPORT
 #endif
 
+EXPORT int function2( void )
+{
+    printf( "Hello, world! from original library\n" );
+    return 0;
+}
+
 EXPORT int function( void )
 {
     printf( "Hello, world!\n" );
diff --git a/testdll2.c b/testdll2.c
new file mode 100644
index 0000000..910c820
--- /dev/null
+++ b/testdll2.c
@@ -0,0 +1,55 @@
+/*
+ * dlfcn-win32
+ * Copyright (c) 2007 Ramiro Polla
+ * Copyright (c) 2019 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * dlfcn-win32 is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * dlfcn-win32 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with dlfcn-win32; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifdef _DEBUG
+#define _CRTDBG_MAP_ALLOC
+#include <stdlib.h>
+#include <crtdbg.h>
+#endif
+#include <stdio.h>
+
+#include "dlfcn.h"
+
+#if defined(_WIN32)
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+
+EXPORT int function2( void )
+{
+    char *error;
+    int (*function2_orig)(void);
+    printf( "Hello, world! from wrapper library\n" );
+    function2_orig = dlsym(RTLD_NEXT, "function2");
+    if (!function2_orig)
+    {
+        error = dlerror( );
+        printf( "ERROR\tCould not get symbol from RTLD_NEXT handle: %s\n",
+                error ? error : "" );
+        return 1;
+    }
+    if (function2_orig() != 0)
+    {
+        printf( "ERROR\tOriginal function from RTLD_NEXT handle did not return correct value\n" );
+        return 1;
+    }
+    return 2;
+}
diff --git a/testdll3.c b/testdll3.c
new file mode 100644
index 0000000..3906e48
--- /dev/null
+++ b/testdll3.c
@@ -0,0 +1,38 @@
+/*
+ * dlfcn-win32
+ * Copyright (c) 2007 Ramiro Polla
+ * Copyright (c) 2019 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * dlfcn-win32 is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * dlfcn-win32 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with dlfcn-win32; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifdef _DEBUG
+#define _CRTDBG_MAP_ALLOC
+#include <stdlib.h>
+#include <crtdbg.h>
+#endif
+#include <stdio.h>
+
+#if defined(_WIN32)
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+
+EXPORT int function3( void )
+{
+    printf( "Hello, world!\n" );
+    return 0;
+}