Subversion Repositories Kolibri OS

Rev

Rev 6767 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
6767 clevermous 1
; Module management, non-PE-specific code.
2
; Works in conjuction with peloader.inc for PE-specific code.
3
 
4
;  void* dlopen(const char* filename, int mode)
5
;  Opens the module named filename and maps it in; returns a handle that can be
6
;  passed to dlsym to get symbol values from it.
7
;
8
;  If filename starts with '/', it is treated as an absolute file name.
9
;  Otherwise, dlopen searches for filename in predefined locations:
8088 dunkaist 10
;  /sys/lib, /kolibrios/lib, directory of the executable file.
6767 clevermous 11
;  The current directory is *not* searched.
12
;
13
;  If the same module is loaded again with dlopen(), the same
14
;  handle is returned.  The loader maintains reference
15
;  counts for loaded modules, so a dynamically loaded module is
16
;  not deallocated until dlclose() has been called on it as many times
17
;  as dlopen() has succeeded on it.  Any initialization functions
18
;  are called just once.
19
;
20
;  If dlopen() fails for any reason, it returns NULL.
21
;
22
;  mode is reserved and should be zero.
23
proc dlopen stdcall uses esi edi, file, mode
24
; find_module_by_name and load_module do all the work.
25
; We just need to acquire/release the mutex and adjust input/output.
26
        cmp     [mode], 0
27
        jnz     .invalid_mode
28
        mutex_lock modules_mutex
29
        mov     edi, [file]
30
        call    find_module_by_name
31
        test    esi, esi
32
        jnz     .inc_refcount
33
        call    load_module
34
        xor     edi, edi
35
        test    eax, eax
36
        jz      .unlock_return
37
; The handle returned on success is module base address.
38
; Unlike pointer to MODULE struct, it can be actually useful
39
; for the caller as is.
40
        mov     edi, [eax+MODULE.base]
41
        jmp     .unlock_return
42
.inc_refcount:
43
        inc     [esi+MODULE.refcount]
44
        mov     edi, [esi+MODULE.base]
45
.unlock_return:
46
        mutex_unlock modules_mutex
47
        mov     eax, edi
48
        ret
49
.invalid_mode:
50
        xor     eax, eax
51
        ret
52
endp
53
 
54
;  int dlclose(void* handle)
55
;  Decrements the reference count on the dynamically loaded module
56
;  referred to by handle. If the reference count drops to zero,
57
;  then the module is unloaded. All modules that were automatically loaded
58
;  when dlopen() was invoked on the module referred to by handle are
59
;  recursively closed in the same manner.
60
;
61
;  A successful return from dlclose() does not guarantee that the
62
;  module has been actually removed from the caller's address space.
63
;  In addition to references resulting from explicit dlopen() calls,
64
;  a module may have been implicitly loaded (and reference counted)
65
;  because of dependencies in other shared objects.
66
;  Only when all references have been released can the module be removed
67
;  from the address space.
68
;  On success, dlclose() returns 0; on error, it returns a nonzero value.
69
proc dlclose stdcall uses esi, handle
70
; This function uses two worker functions:
71
; find_module_by_addr to map handle -> MODULE,
72
; dereference_module for the main work.
73
; Aside of calling these, we should only acquire/release the mutex.
74
        mutex_lock modules_mutex
75
        mov     ecx, [handle]
76
        call    find_module_by_addr
77
        test    esi, esi
78
        jz      .invalid_handle
79
        call    dereference_module
80
        mutex_unlock modules_mutex
81
        xor     eax, eax
82
        ret
83
.invalid_handle:
84
        mutex_unlock modules_mutex
85
        xor     eax, eax
86
        inc     eax
87
        ret
88
endp
89
 
90
;  void* dlsym(void* handle, const char* symbol)
91
;  Obtains address of a symbol in a module.
92
;  On failure, returns NULL.
93
;
94
;  symbol can also be a number between 0 and 0xFFFF;
95
;  it is interpreted as an ordinal of a symbol.
96
;  Low 64K of address space are blocked for the allocation,
97
;  so a valid pointer cannot be less than 0x10000.
98
;
99
;  handle is not validated. Passing an invalid handle can result in a crash.
100
proc dlsym stdcall, handle, symbol
101
locals
102
export_base             dd      ?
103
export_ptr              dd      ?
104
export_size             dd      ?
105
import_module           dd      0
106
endl
107
; Again, helper functions do all the work.
108
; We don't need to browse list of MODULEs,
109
; so we don't need to acquire/release the mutex.
110
; Unless the function is forwarded or module name is required for error message,
111
; but this should be processed by get_exported_function_*.
112
        mov     eax, [handle]
113
        call    prepare_import_from_module
114
        mov     ecx, [symbol]
115
        cmp     ecx, 0x10000
116
        jb      .ordinal
117
        mov     edx, -1 ; no hint for lookup in name table
118
        call    get_exported_function_by_name
119
        ret
120
.ordinal:
121
        call    get_exported_function_by_ordinal
122
        ret
123
endp
124
 
125
; Errors happen.
126
; Some errors should be reported to the user. Some errors are normal.
127
; After the process has been initialized, we don't know what an error
128
; should mean - is the failed DLL absolutely required or unimportant enhancement?
129
; So we report an error to the caller and let it decide how to handle it.
130
; However, when the process is initializing, there is no one to report to,
131
; so we must inform the user ourselves.
132
; In any case, write to the debug board - it is *debug* board, after all.
133
;
134
; This function is called whenever an error occurs in the loader.
135
; Except errors in malloc/realloc - they shouldn't happen anyway,
136
; and if they happened after all, we are screwed and likely will fail anyway,
137
; so don't bother.
138
; Variable number of arguments: strings to be concatenated, end with NULL.
139
proc loader_say_error c uses ebx esi, first_msg, ...
140
; 1. Concatenate all given strings to the final error message.
141
; 1a. Calculate the total length.
142
        xor     ebx, ebx
143
        lea     edx, [first_msg]
144
.get_length:
145
        mov     ecx, [edx]
146
        test    ecx, ecx
147
        jz      .length_done
148
@@:
149
        inc     ebx
150
        inc     ecx
151
        cmp     byte [ecx-1], 0
152
        jnz     @b
153
        dec     ebx
154
        add     edx, 4
155
        jmp     .get_length
156
.length_done:
157
        inc     ebx ; terminating zero
158
; 1b. Allocate memory. Exit if failed.
159
        stdcall malloc, ebx
160
        test    eax, eax
161
        jz      .nothing
162
        mov     esi, eax
163
; 1c. Copy data.
164
        lea     edx, [first_msg]
165
.copy_data:
166
        mov     ecx, [edx]
167
        test    ecx, ecx
168
        jz      .data_done
169
@@:
170
        mov     bl, [ecx]
171
        test    bl, bl
172
        jz      @f
173
        mov     [eax], bl
174
        inc     ecx
175
        inc     eax
176
        jmp     @b
177
@@:
178
        add     edx, 4
179
        jmp     .copy_data
180
.data_done:
181
        mov     byte [eax], 0 ; terminating zero
182
; 2. Print to the debug board.
183
        mov     ecx, loader_debugboard_prefix
184
        call    sys_msg_board_str
185
        mov     ecx, esi
186
        call    sys_msg_board_str
187
        mov     ecx, msg_newline
188
        call    sys_msg_board_str
189
; 3. If the initialization is in process, report to the user.
190
        xor     eax, eax
191
        cmp     [process_initialized], al
192
        jnz     .no_report
193
; Use @notify. Create structure for function 70.7 on the stack.
194
        push    eax ; to be rewritten with part of path
195
        push    eax ; to be rewritten with part of path
196
        push    eax ; reserved
197
        push    eax ; reserved
198
        push    esi ; command line
199
        push    eax ; flags: none
200
        push    7
201
        mov     eax, 70
202
        mov     ebx, esp
203
        mov     dword [ebx+21], notify_program
204
        call    FS_SYSCALL_PTR
205
        add     esp, 28
206
; Ignore any errors. We can't do anything with them anyway.
207
.no_report:
208
        stdcall free, esi
209
.nothing:
210
        ret
211
endp
212
 
213
; When the loader is initializing the process, errors can happen.
214
; They should be reported to the user.
215
; The main executable cannot do this, it is not initialized yet.
216
; So we should do it ourselves.
217
; However, after the process has been initialized, the main
218
;
219
; Helper function that is called whenever an error is occured.
220
 
221
; For now, we don't expect many modules in one process.
222
; So, all modules are linked into a single list,
223
; and lookup functions simply walk the entire list.
224
; This should be revisited if dozens of modules would be typical.
225
 
226
; This structure describes one loaded PE module.
227
; malloc'd from the default heap,
228
; includes variable-sized module path in the end.
229
struct MODULE
230
; All modules are linked in the global list with head at modules_list.
231
next            dd      ?
232
prev            dd      ?
233
base            dd      ?       ; base address
234
size            dd      ?       ; size in memory
235
refcount        dd      ?       ; reference counter
236
timestamp       dd      ?       ; for bound imports
237
basedelta       dd      ?       ; base address - preferred address, for bound imports
238
num_imports     dd      ?       ; size of imports array
239
imports         dd      ?
240
; Pointer to array of pointers to MODULEs containing imported functions.
241
; Used to unload all dependencies when the module is unloaded.
242
; Contains all modules referenced by import table;
243
; if the module forwards some export to another module,
244
; then forward target is added to this array when forward source is requested.
245
filename        dd      ?       ; pointer inside path array after dirname
246
filenamelen     dd      ?       ; strlen(filename) + 1
247
path            rb      0
248
ends
249
 
250
; Fills some fields in a new MODULE struct based on given PE image.
251
; Assumes that MODULE.path has been filled during the allocation,
252
; does not insert the structure in the common list, fills everything else.
253
; in: eax -> MODULE
254
; in: esi = module base
255
proc init_module_struct
256
; Straightforward initialization of all non-PE-specific fields.
257
        lea     edx, [eax+MODULE.path]
258
        mov     [eax+MODULE.filename], edx
259
@@:
260
        inc     edx
261
        cmp     byte [edx-1], 0
262
        jz      @f
263
        cmp     byte [edx-1], '/'
264
        jnz     @b
265
        mov     [eax+MODULE.filename], edx
266
        jmp     @b
267
@@:
268
        sub     edx, [eax+MODULE.filename]
269
        mov     [eax+MODULE.filenamelen], edx
270
        xor     edx, edx
271
        mov     [eax+MODULE.base], esi
272
        mov     [eax+MODULE.refcount], 1
273
        mov     [eax+MODULE.num_imports], edx
274
        mov     [eax+MODULE.imports], edx
275
; Let the PE-specific part do its job.
276
        init_module_struct_pe_specific
277
endp
278
 
279
; Helper function for dlclose and resolving forwarded exports from dlsym.
280
; in: ecx = module base address
281
; out: esi -> MODULE or esi = NULL
282
; modules_mutex should be locked
283
proc find_module_by_addr
284
; Simple linear lookup in the list.
285
        mov     esi, [modules_list + MODULE.next]
286
.scan:
287
        cmp     esi, modules_list
288
        jz      .notfound
289
        cmp     ecx, [esi+MODULE.base]
290
        jz      .found
291
        mov     esi, [esi+MODULE.next]
292
        jmp     .scan
293
.notfound:
294
        xor     esi, esi
295
.found:
296
        ret
297
endp
298
 
299
; Helper function for whenever we have a module name
300
; and want to check whether it is already loaded.
301
; in: edi -> name with or without a path
302
; out: esi -> MODULE or esi = NULL
303
; modules_mutex should be locked
304
proc find_module_by_name uses edi
305
; 1. Skip the path, if it is present.
306
; eax = current pointer,
307
; edi is updated whenever the previous character is '/'
308
        mov     eax, edi
309
.find_basename:
310
        cmp     byte [eax], 0
311
        jz      .found_basename
312
        inc     eax
313
        cmp     byte [eax-1], '/'
314
        jnz     .find_basename
315
        mov     edi, eax
316
        jmp     .find_basename
317
.found_basename:
318
; 2. Simple linear lookup in the list.
319
        mov     eax, [modules_list + MODULE.next]
320
.scan:
321
        cmp     eax, modules_list
322
        jz      .notfound
323
; For every module, compare base names ignoring paths.
324
        push    edi
325
        mov     esi, [eax+MODULE.filename]
326
        mov     ecx, [eax+MODULE.filenamelen]
327
        repz cmpsb
328
        pop     edi
329
        jz      .found
330
        mov     eax, [eax+MODULE.next]
331
        jmp     .scan
332
.found:
333
        mov     esi, eax
334
        ret
335
.notfound:
336
        xor     esi, esi
337
        ret
338
endp
339
 
340
; Called when some module is implicitly loaded by another module,
341
; either due to a record in import table,
342
; or because some exported function forwards to another module.
343
; Checks whether the target module has already been referenced
344
; by the source module. The first reference is passed down
345
; to load_module increasing refcount of the target and possibly
346
; loading it if not yet, subsequent references just return
347
; without modifying refcount.
348
; We don't actually need to deduplicate DLLs from import table
349
; as long as we decrement refcount on unload the same number of times
350
; that we have incremented it on load.
351
; However, we need to keep track of references to forward targets,
352
; and we don't want to scan the entire export table and load all forward
353
; targets just in case some of those would be useful,
354
; so load them on-demand first time and ignore subsequential references.
355
; To be consistent, do the same for import table too.
356
;
357
; in: esi -> source MODULE struct
358
; in: edi -> target module name
359
; out: eax -> imported MODULE, 0 on error
360
; modules_mutex should be locked
361
proc load_imported_module uses edi
362
; 1. Find the target module in the loaded modules list.
363
; If not found, go to 5.
364
        push    esi
365
        call    find_module_by_name
366
        test    esi, esi
367
        mov     eax, esi
368
        pop     esi
369
        jz      .load
370
; 2. The module has been already loaded.
371
; Now check whether it is already stored in imports array.
372
; If yes, just return without doing anything.
373
        mov     edi, [esi+MODULE.imports]
374
        mov     ecx, [esi+MODULE.num_imports]
375
        test    ecx, ecx
376
        jz      .newref
377
        repnz scasd
378
        jz      .nothing
379
.newref:
380
; The module is loaded, but not by us.
381
; 3. Increment the reference counter of the target.
382
        inc     [eax+MODULE.refcount]
383
.add_to_imports:
384
; 4. Add the new pointer to the imports array.
385
; 4a. Check whether there is place in the array.
386
; If so, go to 4c.
387
; We don't want to reallocate too often, since reallocation
388
; may involve copying our data to a new place.
389
; We always reserve space that is a power of two; in this way,
390
; the wasted space is never greater than the used space,
391
; and total time of copying the data is O(number of modules).
392
; The last fact is not really important right now,
393
; since the current implementation of step 2 makes everything
394
; quadratic and the number of modules is very small anyway,
395
; but since this enhancement costs only a few instructions, why not?
396
        mov     edi, eax
397
; X is a power of two or zero if and only if (X and (X - 1)) is zero
398
        mov     ecx, [esi+MODULE.num_imports]
399
        lea     edx, [ecx-1]
400
        test    ecx, edx
401
        jnz     .has_space
402
; 4b. Reallocate the imports array:
403
; if the current size is zero, allocate 1 item,
404
; otherwise double number of items.
405
; Item size is 4 bytes.
406
        lea     ecx, [ecx*8]
407
        test    ecx, ecx
408
        jnz     @f
409
        mov     ecx, 4
410
@@:
411
        stdcall realloc, [esi+MODULE.imports], ecx
412
        test    eax, eax
413
        jz      .realloc_failed
414
        mov     [esi+MODULE.imports], eax
415
        mov     ecx, [esi+MODULE.num_imports]
416
.has_space:
417
; 4c. Append pointer to the target MODULE to imports array.
418
        mov     eax, [esi+MODULE.imports]
419
        mov     [eax+ecx*4], edi
420
        inc     [esi+MODULE.num_imports]
421
        mov     eax, edi
422
.nothing:
423
        ret
424
.load:
425
; 5. This is a totally new module. Load it.
426
        call    load_module
427
; On error, return it to the caller. On success, go to 4.
428
        test    eax, eax
429
        jz      .nothing
430
        jmp     .add_to_imports
431
.realloc_failed:
432
; Out of memory for a couple of dwords? Should not happen.
433
; Dereference the target referenced by step 3 or 5
434
; and return error to the caller.
435
        push    esi
436
        mov     esi, edi
437
        call    dereference_module
438
        pop     esi
439
        xor     eax, eax
440
        ret
441
endp
442
 
443
; Helper procedure for load_module.
444
; Allocates MODULE structure for (given path) + (module name),
445
; calls the kernel to map it,
446
; on success, fills the MODULE structure.
447
; in: edi -> module name
448
; in: ebx = strlen(filename) + 1
449
proc try_map_module uses ebx esi, path_ptr, path_len
450
; 1. Allocate MODULE structure.
451
        mov     eax, [path_len]
452
        lea     eax, [eax+ebx+MODULE.path]
453
        stdcall malloc, eax
454
        test    eax, eax
455
        jz      .nothing
456
; 2. Create the full name of module in MODULE structure:
457
; concatenate module path, if given, and module name.
458
        mov     ecx, [path_len]
459
        mov     esi, [path_ptr]
460
        push    edi
461
        lea     edi, [eax+MODULE.path]
462
        rep movsb
463
        mov     ecx, ebx
464
        mov     esi, [esp]
465
        rep movsb
466
        pop     edi
467
        mov     esi, eax
468
; 3. Call the kernel to map the module.
469
        lea     ecx, [eax+MODULE.path]
470
        mov     eax, 68
471
        mov     ebx, 28
472
        call    FS_SYSCALL_PTR
473
        cmp     eax, -0x1000
474
        ja      .failed
475
; 4. On success, fill the rest of MODULE structure and return it.
476
        xchg    eax, esi
477
        call    init_module_struct
478
        ret
479
.failed:
480
; On failure, undo allocation at step 1 and return zero.
481
        stdcall free, esi
482
        xor     eax, eax
483
.nothing:
484
        ret
485
endp
486
 
487
; Worker procedure for loading a new module.
488
; Does not check whether the module has been already loaded;
489
; find_module_by_name should be called beforehand.
490
; in: edi -> filename
491
; out: eax -> MODULE or 0
492
; modules_mutex should be locked
493
proc load_module uses ebx esi ebp
494
; 1. Map the module.
495
; 1a. Prepare for try_map_module: calculate length of the name.
496
        mov     ebx, edi
497
@@:
498
        inc     ebx
499
        cmp     byte [ebx-1], 0
500
        jnz     @b
501
        sub     ebx, edi
502
; 1b. Check whether the given path is absolute.
503
; If so, proceed to 1c. If not, go to 1d.
504
        cmp     byte [edi], '/'
505
        jnz     .relative
506
; 1c. The given path is absolute. Use it as is. Don't try any other paths.
507
        stdcall try_map_module, 0, 0
508
        test    eax, eax
509
        jnz     .loaded_ok
510
        ccall   loader_say_error, msg_cannot_open, edi, 0
511
        jmp     .load_failed
512
.relative:
513
; 1d. The given path is relative.
8088 dunkaist 514
; Try /sys/lib/, /kolibrios/lib/ and path to executable
6767 clevermous 515
; in this order.
516
        stdcall try_map_module, module_path1, module_path1.size
517
        test    eax, eax
518
        jnz     .loaded_ok
519
        stdcall try_map_module, module_path2, module_path2.size
520
        test    eax, eax
521
        jnz     .loaded_ok
522
; Note: we assume that the executable is always the first module in the list.
523
        mov     eax, [modules_list + MODULE.next]
524
        mov     ecx, [eax+MODULE.filename]
525
        add     eax, MODULE.path
526
        mov     esi, eax
527
        sub     ecx, eax
528
        stdcall try_map_module, eax, ecx
529
        test    eax, eax
530
        jnz     .loaded_ok
531
        mov     ebx, dword [esi+MODULE.filename-MODULE.path]
532
        movzx   eax, byte [ebx]
533
        mov     byte [ebx], 0
534
        push    eax
535
        ccall   loader_say_error, msg_cannot_open, edi, msg_paths_begin, esi, 0
536
        pop     eax
537
        mov     byte [ebx], al
538
.load_failed:
539
        xor     eax, eax
540
        ret
541
.loaded_ok:
542
; Module has been mapped.
543
; MODULE structure has been initialized, but not yet inserted in the common list.
544
; 2. Insert the MODULE structure in the end of the common list.
545
        mov     esi, eax
546
        mov     eax, [modules_list+MODULE.prev]
547
        mov     [eax+MODULE.next], esi
548
        mov     [esi+MODULE.prev], eax
549
        mov     [modules_list+MODULE.prev], esi
550
        mov     [esi+MODULE.next], modules_list
551
; 3. Call PE-specific code to initialize the mapped module.
552
        push    esi
553
        push    edi ; for messages in fixup_pe_relocations
554
        mov     esi, [esi+MODULE.base]
555
        call    fixup_pe_relocations
556
        pop     ecx
557
        pop     esi
558
        jc      .fail_unload
559
        call    resolve_pe_imports
560
        test    eax, eax
561
        jnz     .fail_unload
562
        mov     eax, esi
563
        ret
564
.fail_unload:
565
        call    dereference_module
566
        xor     eax, eax
567
        ret
568
endp
569
 
570
; Worker procedure for unloading a module.
571
; Drops one reference to the module; if it was the last one,
572
; unloads the module and all referenced modules recursively.
573
; in: esi -> MODULE struct
574
; modules_mutex should be locked
575
proc dereference_module
576
; 1. Decrement reference counter.
577
; If the decremented value is nonzero, exit.
578
        dec     [esi+MODULE.refcount]
579
        jnz     .nothing
580
; 2. Remove the module from the common list.
581
        mov     eax, [esi+MODULE.prev]
582
        mov     edx, [esi+MODULE.next]
583
        mov     [eax+MODULE.next], edx
584
        mov     [edx+MODULE.prev], eax
585
; 3. Recursively unload dependencies.
586
        cmp     [esi+MODULE.num_imports], 0
587
        jz      .import_deref_done
588
.import_deref_loop:
589
        mov     eax, [esi+MODULE.num_imports]
590
        push    esi
591
        mov     esi, [esi+MODULE.imports]
592
        mov     esi, [esi+(eax-1)*4]
593
        call    dereference_module
594
        pop     esi
595
        dec     [esi+MODULE.num_imports]
596
        jnz     .import_deref_loop
597
.import_deref_done:
598
        stdcall free, [esi+MODULE.imports] ; free(NULL) is ok
599
; 4. Unmap the module.
600
        push    ebx
601
        mov     eax, 68
602
        mov     ebx, 29
603
        mov     ecx, [esi+MODULE.base]
604
        call    FS_SYSCALL_PTR
605
        pop     ebx
606
; 5. Free the MODULE struct.
607
        stdcall free, esi
608
.nothing:
609
        ret
610
endp