Subversion Repositories Kolibri OS

Rev

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

  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:
  10. ;  /sys/lib, /kolibrios/lib, directory of the executable file.
  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.
  514. ; Try /sys/lib/, /kolibrios/lib/ and path to executable
  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
  611.