source: rtems/cpukit/libdl/rtl.c @ 89c59be

Last change on this file since 89c59be was 89c59be, checked in by Chris Johns <chrisj@…>, on Dec 17, 2018 at 5:36:48 AM

libdl: Add symbol searching and loading from archives.

  • Load archive symbol tables to support searching of archives for symbols.
  • Search archive symbols and load the object file that contains the symbol.
  • Search the global and archives until all remaining unresolved symbols are not found. Group the loaded object files in the pending queue.
  • Run the object file and loaded dependents as a group before adding to the main object list.
  • Remove orphaned object files after references are removed.

Updates #3686

  • Property mode set to 100644
File size: 18.5 KB
Line 
1/*
2 *  COPYRIGHT (c) 2012, 2018 Chris Johns <chrisj@rtems.org>
3 *
4 *  The license and distribution terms for this file may be
5 *  found in the file LICENSE in this distribution or at
6 *  http://www.rtems.org/license/LICENSE.
7 */
8/**
9 * @file
10 *
11 * @ingroup rtems_rtld
12 *
13 * @brief RTEMS Run-Time Link Editor
14 *
15 * This is the RTL implementation.
16 */
17
18#if HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include <stdlib.h>
23#include <stdio.h>
24#include <string.h>
25
26#include <rtems/libio_.h>
27
28#include <rtems/rtl/rtl.h>
29#include <rtems/rtl/rtl-allocator.h>
30#include <rtems/rtl/rtl-trace.h>
31#include "rtl-chain-iterator.h"
32#include "rtl-error.h"
33#include "rtl-string.h"
34
35/**
36 * Symbol table cache size. They can be big so the cache needs space to work.
37 */
38#define RTEMS_RTL_ELF_SYMBOL_CACHE (2048)
39
40/**
41 * String table cache size.
42 */
43#define RTEMS_RTL_ELF_STRING_CACHE (2048)
44
45/**
46 * Relocations table cache size.
47 */
48#define RTEMS_RTL_ELF_RELOC_CACHE (2048)
49
50/**
51 * Decompression output buffer.
52 */
53#define RTEMS_RTL_COMP_OUTPUT (2048)
54
55/**
56 * Static RTL data is returned to the user when the linker is locked.
57 */
58static rtems_rtl_data* rtl;
59static bool            rtl_data_init;
60
61/**
62 * Define a default base global symbol loader function that is weak
63 * so a real table can be linked in when the user wants one.
64 */
65void rtems_rtl_base_global_syms_init (void) __attribute__ ((weak));
66void
67rtems_rtl_base_global_syms_init (void)
68{
69  /*
70   * Do nothing.
71   */
72}
73
74static bool
75rtems_rtl_data_init (void)
76{
77  /*
78   * Lock the RTL. We only create a lock if a call is made. First we test if a
79   * lock is present. If one is present we lock it. If not the libio lock is
80   * locked and we then test the lock again. If not present we create the lock
81   * then release libio lock.
82   */
83  if (!rtl)
84  {
85    rtems_libio_lock ();
86
87    if (!rtl)
88    {
89      /*
90       * We cannot set an error in this code because there is no RTL data to
91       * hold it.
92       */
93
94      if (rtl_data_init)
95      {
96        rtems_libio_unlock ();
97        return false;
98      }
99
100      rtl_data_init = true;
101
102      /*
103       * Always in the heap.
104       */
105      rtl = malloc (sizeof (rtems_rtl_data));
106      if (!rtl)
107      {
108        rtems_libio_unlock ();
109        errno = ENOMEM;
110        return false;
111      }
112
113      *rtl = (rtems_rtl_data) { 0 };
114
115      /*
116       * The initialise the allocator data.
117       */
118      rtems_rtl_alloc_initialise (&rtl->allocator);
119
120      /*
121       * Create the RTL lock.
122       */
123      rtems_recursive_mutex_init (&rtl->lock, "Run-Time Linker");
124      rtems_recursive_mutex_lock (&rtl->lock);
125
126      /*
127       * Initialise the objects and pending list.
128       */
129      rtems_chain_initialize_empty (&rtl->objects);
130      rtems_chain_initialize_empty (&rtl->pending);
131
132      /*
133       * Open the global symbol table.
134       */
135      if (!rtems_rtl_symbol_table_open (&rtl->globals,
136                                        RTEMS_RTL_SYMS_GLOBAL_BUCKETS))
137      {
138        rtems_recursive_mutex_destroy (&rtl->lock);
139        free (rtl);
140        rtems_libio_unlock ();
141        return false;
142      }
143
144      /*
145       * Open the archives.
146       */
147      rtems_rtl_archives_open (&rtl->archives, "/etc/rtl-libs.conf");
148
149      /*
150       * Open the unresolved table.
151       */
152      if (!rtems_rtl_unresolved_table_open (&rtl->unresolved,
153                                            RTEMS_RTL_UNRESOLVED_BLOCK_SIZE))
154      {
155        rtems_rtl_symbol_table_close (&rtl->globals);
156        rtems_recursive_mutex_destroy (&rtl->lock);
157        free (rtl);
158        rtems_libio_unlock ();
159        return false;
160      }
161
162      if (!rtems_rtl_obj_cache_open (&rtl->symbols,
163                                     RTEMS_RTL_ELF_SYMBOL_CACHE))
164      {
165        rtems_rtl_symbol_table_close (&rtl->globals);
166        rtems_rtl_unresolved_table_close (&rtl->unresolved);
167        rtems_recursive_mutex_destroy (&rtl->lock);
168        free (rtl);
169        rtems_libio_unlock ();
170        return false;
171      }
172
173      if (!rtems_rtl_obj_cache_open (&rtl->strings,
174                                     RTEMS_RTL_ELF_STRING_CACHE))
175      {
176        rtems_rtl_obj_cache_close (&rtl->symbols);
177        rtems_rtl_unresolved_table_close (&rtl->unresolved);
178        rtems_rtl_symbol_table_close (&rtl->globals);
179        rtems_recursive_mutex_destroy (&rtl->lock);
180        free (rtl);
181        rtems_libio_unlock ();
182        return false;
183      }
184
185      if (!rtems_rtl_obj_cache_open (&rtl->relocs,
186                                     RTEMS_RTL_ELF_RELOC_CACHE))
187      {
188        rtems_rtl_obj_cache_close (&rtl->strings);
189        rtems_rtl_obj_cache_close (&rtl->symbols);
190        rtems_rtl_unresolved_table_close (&rtl->unresolved);
191        rtems_rtl_symbol_table_close (&rtl->globals);
192        rtems_recursive_mutex_destroy (&rtl->lock);
193        free (rtl);
194        rtems_libio_unlock ();
195        return false;
196      }
197
198      if (!rtems_rtl_obj_comp_open (&rtl->decomp,
199                                    RTEMS_RTL_COMP_OUTPUT))
200      {
201        rtems_rtl_obj_cache_close (&rtl->relocs);
202        rtems_rtl_obj_cache_close (&rtl->strings);
203        rtems_rtl_obj_cache_close (&rtl->symbols);
204        rtems_rtl_unresolved_table_close (&rtl->unresolved);
205        rtems_rtl_symbol_table_close (&rtl->globals);
206        rtems_recursive_mutex_destroy (&rtl->lock);
207        free (rtl);
208        rtems_libio_unlock ();
209        return false;
210      }
211
212      rtl->base = rtems_rtl_obj_alloc ();
213      if (!rtl->base)
214      {
215        rtems_rtl_obj_comp_close (&rtl->decomp);
216        rtems_rtl_obj_cache_close (&rtl->relocs);
217        rtems_rtl_obj_cache_close (&rtl->strings);
218        rtems_rtl_obj_cache_close (&rtl->symbols);
219        rtems_rtl_unresolved_table_close (&rtl->unresolved);
220        rtems_rtl_symbol_table_close (&rtl->globals);
221        rtems_recursive_mutex_destroy (&rtl->lock);
222        free (rtl);
223        rtems_libio_unlock ();
224        return false;
225      }
226
227      /*
228       * Need to malloc the memory so the free does not complain.
229       */
230      rtl->base->oname = rtems_rtl_strdup ("rtems-kernel");
231
232      /*
233       * Lock the base image and flag it as the base image.
234       */
235      rtl->base->flags |= RTEMS_RTL_OBJ_LOCKED | RTEMS_RTL_OBJ_BASE;
236
237      rtems_chain_append (&rtl->objects, &rtl->base->link);
238    }
239
240    rtems_libio_unlock ();
241
242    rtems_rtl_path_append (".");
243
244    rtems_rtl_base_global_syms_init ();
245
246    rtems_rtl_unlock ();
247  }
248  return true;
249}
250
251rtems_rtl_data*
252rtems_rtl_data_unprotected (void)
253{
254  return rtl;
255}
256
257rtems_rtl_symbols*
258rtems_rtl_global_symbols (void)
259{
260  if (!rtl)
261  {
262    rtems_rtl_set_error (ENOENT, "no rtl");
263    return NULL;
264  }
265  return &rtl->globals;
266}
267
268rtems_chain_control*
269rtems_rtl_objects_unprotected (void)
270{
271  if (!rtl)
272  {
273    rtems_rtl_set_error (ENOENT, "no rtl");
274    return NULL;
275  }
276  return &rtl->objects;
277}
278
279rtems_chain_control*
280rtems_rtl_pending_unprotected (void)
281{
282  if (!rtl)
283  {
284    rtems_rtl_set_error (ENOENT, "no rtl");
285    return NULL;
286  }
287  return &rtl->pending;
288}
289
290rtems_rtl_unresolved*
291rtems_rtl_unresolved_unprotected (void)
292{
293  if (!rtl)
294  {
295    rtems_rtl_set_error (ENOENT, "no rtl");
296    return NULL;
297  }
298  return &rtl->unresolved;
299}
300
301rtems_rtl_archives*
302rtems_rtl_archives_unprotected (void)
303{
304  if (!rtl)
305  {
306    rtems_rtl_set_error (ENOENT, "no rtl");
307    return NULL;
308  }
309  return &rtl->archives;
310}
311
312void
313rtems_rtl_obj_caches (rtems_rtl_obj_cache** symbols,
314                      rtems_rtl_obj_cache** strings,
315                      rtems_rtl_obj_cache** relocs)
316{
317  if (!rtl)
318  {
319    if (symbols)
320       *symbols = NULL;
321    if (strings)
322      *strings = NULL;
323    if (relocs)
324      *relocs = NULL;
325  }
326  else
327  {
328    if (symbols)
329      *symbols = &rtl->symbols;
330    if (strings)
331      *strings = &rtl->strings;
332    if (relocs)
333      *relocs = &rtl->relocs;
334  }
335}
336
337void
338rtems_rtl_obj_caches_flush (void)
339{
340  if (rtl)
341  {
342    rtems_rtl_obj_cache_flush (&rtl->symbols);
343    rtems_rtl_obj_cache_flush (&rtl->strings);
344    rtems_rtl_obj_cache_flush (&rtl->relocs);
345  }
346}
347
348void
349rtems_rtl_obj_decompress (rtems_rtl_obj_comp** decomp,
350                          rtems_rtl_obj_cache* cache,
351                          int                  fd,
352                          int                  compression,
353                          off_t                offset)
354{
355  if (!rtl)
356  {
357    *decomp = NULL;
358  }
359  else
360  {
361    *decomp = &rtl->decomp;
362    rtems_rtl_obj_comp_set (*decomp, cache, fd, compression, offset);
363  }
364}
365
366typedef struct rtems_rtl_obj_flags_data
367{
368  uint32_t clear;   /**< Flags to clear, do not invert. */
369  uint32_t set;     /**< Flags to set, applied after clearing. */
370} rtems_rtl_obj_flags_data;
371
372static bool
373rtems_rtl_obj_flags_iterator (rtems_chain_node* node, void* data)
374{
375  rtems_rtl_obj* obj              = (rtems_rtl_obj*) node;
376  rtems_rtl_obj_flags_data* flags = (rtems_rtl_obj_flags_data*) data;
377  if (flags->clear != 0)
378    obj->flags &= ~flags->clear;
379  if (flags->set != 0)
380    obj->flags |= flags->set;
381  return true;
382}
383
384void
385rtems_rtl_obj_update_flags (uint32_t clear, uint32_t set)
386{
387  rtems_rtl_obj_flags_data flags = {
388    .clear = clear,
389    .set   = set
390  };
391  rtems_rtl_chain_iterate (&rtl->objects,
392                           rtems_rtl_obj_flags_iterator,
393                           &flags);
394}
395
396rtems_rtl_data*
397rtems_rtl_lock (void)
398{
399  if (!rtems_rtl_data_init ())
400    return NULL;
401
402  rtems_recursive_mutex_lock (&rtl->lock);
403
404  return rtl;
405}
406
407void
408rtems_rtl_unlock (void)
409{
410  rtems_recursive_mutex_unlock (&rtl->lock);
411}
412
413rtems_rtl_obj*
414rtems_rtl_check_handle (void* handle)
415{
416  rtems_rtl_obj*    obj;
417  rtems_chain_node* node;
418
419  obj = handle;
420  node = rtems_chain_first (&rtl->objects);
421
422  while (!rtems_chain_is_tail (&rtl->objects, node))
423  {
424    rtems_rtl_obj* check = (rtems_rtl_obj*) node;
425    if (check == obj)
426      return obj;
427    node = rtems_chain_next (node);
428  }
429
430  return NULL;
431}
432
433rtems_rtl_obj*
434rtems_rtl_find_obj (const char* name)
435{
436  rtems_chain_node* node;
437  rtems_rtl_obj*    found = NULL;
438  const char*       aname = NULL;
439  const char*       oname = NULL;
440  off_t             ooffset;
441
442  if (!rtems_rtl_parse_name (name, &aname, &oname, &ooffset))
443    return NULL;
444
445  node = rtems_chain_first (&rtl->objects);
446
447  while (!rtems_chain_is_tail (&rtl->objects, node))
448  {
449    rtems_rtl_obj* obj = (rtems_rtl_obj*) node;
450    if ((aname == NULL && strcmp (obj->oname, oname) == 0) ||
451        (aname != NULL &&
452         strcmp (obj->aname, aname) == 0 && strcmp (obj->oname, oname) == 0))
453    {
454      found = obj;
455      break;
456    }
457    node = rtems_chain_next (node);
458  }
459
460  if (aname != NULL)
461    rtems_rtl_alloc_del(RTEMS_RTL_ALLOC_OBJECT, (void*) aname);
462
463  if (oname != NULL)
464    rtems_rtl_alloc_del(RTEMS_RTL_ALLOC_OBJECT, (void*) oname);
465
466  return found;
467}
468
469rtems_rtl_obj*
470rtems_rtl_find_obj_with_symbol (const rtems_rtl_obj_sym* sym)
471{
472  rtems_chain_node* node = rtems_chain_first (&rtl->objects);
473  while (!rtems_chain_is_tail (&rtl->objects, node))
474  {
475    rtems_rtl_obj* obj = (rtems_rtl_obj*) node;
476    if (rtems_rtl_obj_has_symbol (obj, sym))
477      return obj;
478    node = rtems_chain_next (node);
479  }
480  node = rtems_chain_first (&rtl->pending);
481  while (!rtems_chain_is_tail (&rtl->pending, node))
482  {
483    rtems_rtl_obj* obj = (rtems_rtl_obj*) node;
484    if (rtems_rtl_obj_has_symbol (obj, sym))
485      return obj;
486    node = rtems_chain_next (node);
487  }
488  return NULL;
489}
490
491rtems_rtl_obj*
492rtems_rtl_load_object (const char* name, int mode)
493{
494  rtems_rtl_obj* obj;
495
496  if (rtems_rtl_trace (RTEMS_RTL_TRACE_LOAD))
497    printf ("rtl: loading '%s'\n", name);
498
499  /*
500   * See if the object module has already been loaded.
501   */
502  obj = rtems_rtl_find_obj (name);
503  if (!obj)
504  {
505    /*
506     * Allocate a new object file descriptor and attempt to load it.
507     */
508    obj = rtems_rtl_obj_alloc ();
509    if (obj == NULL)
510    {
511      rtems_rtl_set_error (ENOMEM, "no memory for object descriptor");
512      return NULL;
513    }
514
515    /*
516     * Find the file in the file system using the search path. The fname field
517     * will point to a valid file name if found.
518     */
519    if (!rtems_rtl_obj_find_file (obj, name))
520    {
521      rtems_rtl_obj_free (obj);
522      rtems_rtl_obj_caches_flush ();
523      return NULL;
524    }
525
526    rtems_chain_append (&rtl->pending, &obj->link);
527
528    if (!rtems_rtl_obj_load (obj))
529    {
530      rtems_chain_extract (&obj->link);
531      rtems_rtl_obj_free (obj);
532      rtems_rtl_obj_caches_flush ();
533      return NULL;
534    }
535
536    rtems_rtl_obj_caches_flush ();
537  }
538
539  /*
540   * Increase the number of users.
541   */
542  ++obj->users;
543
544  return obj;
545}
546
547rtems_rtl_obj*
548rtems_rtl_load (const char* name, int mode)
549{
550  rtems_rtl_obj* obj;
551
552  /*
553   * Refesh the archives.
554   */
555  rtems_rtl_archives_refresh (&rtl->archives);
556
557  /*
558   * Collect the loaded object files.
559   */
560  rtems_chain_initialize_empty (&rtl->pending);
561
562  obj = rtems_rtl_load_object (name, mode);
563  if (obj != NULL)
564  {
565    rtems_chain_node* node;
566
567    rtems_rtl_unresolved_resolve ();
568
569    /*
570     * Iterator over the pending list of object files that have been loaded.
571     */
572    node = rtems_chain_first (&rtl->pending);
573    while (!rtems_chain_is_tail (&rtl->pending, node))
574    {
575      rtems_rtl_obj* obj = (rtems_rtl_obj*) node;
576
577      /*
578       * Move to the next pending object file and place this object file on the
579       * RTL's objects list.
580       */
581      node = rtems_chain_next (&obj->link);
582      rtems_chain_extract (&obj->link);
583      rtems_chain_append (&rtl->objects, &obj->link);
584
585      /*
586       * Make sure the object file and cache is synchronized.
587       */
588      rtems_rtl_obj_synchronize_cache (obj);
589
590      /*
591       * Run any local constructors if this is the first user because the
592       * object file will have just been loaded. Unlock the linker to avoid any
593       * dead locks if the object file needs to load files or update the symbol
594       * table. We also do not want a constructor to unload this object file.
595       */
596      if (obj->users == 1)
597      {
598        obj->flags |= RTEMS_RTL_OBJ_LOCKED;
599        rtems_rtl_unlock ();
600        rtems_rtl_obj_run_ctors (obj);
601        rtems_rtl_lock ();
602        obj->flags &= ~RTEMS_RTL_OBJ_LOCKED;
603      }
604    }
605  }
606
607  return obj;
608}
609
610bool
611rtems_rtl_unload_object (rtems_rtl_obj* obj)
612{
613  if (rtems_rtl_trace (RTEMS_RTL_TRACE_UNLOAD))
614    printf ("rtl: unload object '%s'\n", rtems_rtl_obj_fname (obj));
615
616  /*
617   * If the object is locked it cannot be unloaded and the unload fails.
618   */
619  if ((obj->flags & RTEMS_RTL_OBJ_LOCKED) == RTEMS_RTL_OBJ_LOCKED)
620  {
621    rtems_rtl_set_error (EINVAL, "object file is locked");
622    return false;
623  }
624
625  /*
626   * Move the object file from the objects list to the pending list if unloaded.
627   */
628  if (rtems_rtl_obj_get_reference (obj) > 0)
629  {
630    rtems_rtl_set_error (EINVAL, "object file has references to it");
631    return false;
632  }
633
634  /*
635   * Check the number of users in a safe manner. If this is the last user unload
636   * the object file from memory.
637   */
638  if (obj->users > 0)
639    --obj->users;
640
641  return true;
642}
643
644bool
645rtems_rtl_unload (rtems_rtl_obj* obj)
646{
647  bool ok = rtems_rtl_unload_object (obj);
648  if (ok && obj->users == 0)
649  {
650    rtems_chain_control unloading;
651    rtems_chain_node*   node;
652
653    /*
654     * Remove the orphaned object files from the objects list. This makes the
655     * list private and any changes in any desctructors will effect the run.
656     */
657    rtems_chain_initialize_empty (&unloading);
658
659    node = rtems_chain_first (&rtl->objects);
660    while (!rtems_chain_is_tail (&rtl->objects, node))
661    {
662      rtems_chain_node* next_node = rtems_chain_next (node);
663      rtems_rtl_obj* uobj = (rtems_rtl_obj*) node;
664      if (rtems_rtl_trace (RTEMS_RTL_TRACE_UNLOAD))
665        printf ("rtl: unload object: %s: %s\n",
666                rtems_rtl_obj_oname (obj),
667                rtems_rtl_obj_orphaned (uobj) ? "orphaned" : "inuse");
668      if (rtems_rtl_obj_orphaned (uobj))
669      {
670        rtems_rtl_obj_remove_dependencies (uobj);
671        rtems_chain_extract (&uobj->link);
672        rtems_chain_append (&unloading, &uobj->link);
673        uobj->flags |= RTEMS_RTL_OBJ_LOCKED;
674      }
675      node = next_node;
676    }
677
678    /*
679     * Call the desctructors unlocked. An RTL call will not deadlock.
680     */
681    rtems_rtl_unlock ();
682
683    node = rtems_chain_first (&unloading);
684    while (!rtems_chain_is_tail (&unloading, node))
685    {
686      rtems_rtl_obj* uobj = (rtems_rtl_obj*) node;
687      rtems_rtl_obj_run_dtors (uobj);
688      node = rtems_chain_next (node);
689    }
690
691    rtems_rtl_lock ();
692
693    /*
694     * Unload the object files.
695     */
696    node = rtems_chain_first (&unloading);
697    while (!rtems_chain_is_tail (&unloading, node))
698    {
699      rtems_chain_node* next_node = rtems_chain_next (node);
700      rtems_rtl_obj*    uobj = (rtems_rtl_obj*) node;
701      if (rtems_rtl_trace (RTEMS_RTL_TRACE_UNLOAD))
702        printf ("rtl: unloading '%s'\n", rtems_rtl_obj_oname (uobj));
703      uobj->flags &= ~RTEMS_RTL_OBJ_LOCKED;
704      if (!rtems_rtl_obj_unload (uobj))
705        ok = false;
706      rtems_rtl_obj_free (uobj);
707      rtems_rtl_obj_caches_flush ();
708      node = next_node;
709    }
710  }
711  return ok;
712}
713
714static bool
715rtems_rtl_path_update (bool prepend, const char* path)
716{
717  char*       paths;
718  const char* src = NULL;
719  char*       dst;
720  int         len;
721
722  if (!rtems_rtl_lock ())
723    return false;
724
725  len = strlen (path);
726
727  if (rtl->paths)
728    len += strlen (rtl->paths) + 1;
729  else
730    prepend = true;
731
732  paths = rtems_rtl_alloc_new (RTEMS_RTL_ALLOC_OBJECT, len + 1, false);
733
734  if (!paths)
735  {
736    rtems_rtl_unlock ();
737    return false;
738  }
739
740  dst = paths;
741
742  if (prepend)
743  {
744    len = strlen (path);
745    src = path;
746  }
747  else if (rtl->paths)
748  {
749    len = strlen (rtl->paths);
750    src = rtl->paths;
751  }
752
753  memcpy (dst, src, len);
754
755  dst += len;
756
757  if (rtl->paths)
758  {
759    *dst = ':';
760    ++dst;
761  }
762
763  if (prepend)
764  {
765    src = rtl->paths;
766    if (src)
767      len = strlen (src);
768  }
769  else
770  {
771    len = strlen (path);
772    src = path;
773  }
774
775  if (src)
776  {
777    memcpy (dst, src, len);
778    dst += len;
779  }
780
781  *dst = '\0';
782
783  rtl->paths = paths;
784
785  rtems_rtl_unlock ();
786  return false;
787}
788
789bool
790rtems_rtl_path_append (const char* path)
791{
792  return rtems_rtl_path_update (false, path);
793}
794
795bool
796rtems_rtl_path_prepend (const char* path)
797{
798  return rtems_rtl_path_update (true, path);
799}
800
801void
802rtems_rtl_base_sym_global_add (const unsigned char* esyms,
803                               unsigned int         size)
804{
805  if (rtems_rtl_trace (RTEMS_RTL_TRACE_GLOBAL_SYM))
806    printf ("rtl: adding global symbols, table size %u\n", size);
807
808  if (!rtems_rtl_lock ())
809  {
810    rtems_rtl_set_error (EINVAL, "global add cannot lock rtl");
811    return;
812  }
813
814  rtems_rtl_symbol_global_add (rtl->base, esyms, size);
815
816  rtems_rtl_unlock ();
817}
818
819rtems_rtl_obj*
820rtems_rtl_baseimage (void)
821{
822  rtems_rtl_obj* base = NULL;
823  if (rtems_rtl_lock ())
824  {
825    base = rtl->base;
826    rtems_rtl_unlock ();
827  }
828  return base;
829}
Note: See TracBrowser for help on using the repository browser.