stage2.c
author Mathieu Lacage <mathieu.lacage@alcmeon.com>
Sat, 23 Feb 2013 20:59:51 +0100
changeset 656 e4817d48962f
parent 654 bc53596f57de
permissions -rw-r--r--
basic test for tls

#include "system.h"
#include "vdl.h"
#include "vdl-utils.h"
#include "vdl-alloc.h"
#include "vdl-log.h"
#include "vdl-list.h"
#include "vdl-reloc.h"
#include "vdl-dl.h"
#include "glibc.h"
#include "valgrind.h"
#include "gdb.h"
#include "machine.h"
#include "stage2.h"
#include "futex.h"
#include "vdl-gc.h"
#include "vdl-tls.h"
#include "vdl-sort.h"
#include "vdl-context.h"
#include "vdl-linkmap.h"
#include "vdl-map.h"
#include "vdl-file.h"
#include "vdl-unmap.h"
#include "vdl-init.h"
#include "vdl-fini.h"


static unsigned long 
get_entry_point (unsigned long load_base,
		 unsigned long phnum,
		 ElfW(Phdr) *phdr)
{
  ElfW(Phdr) *pt_load = vdl_utils_search_phdr (phdr, phnum, PT_LOAD);
  if (pt_load == 0)
    {
      // should never happen: there should always be a PT_LOAD entry
      return 0;
    }
  if (pt_load->p_offset > 0 || pt_load->p_filesz < sizeof(ElfW(Ehdr)))
    {
      // should never happen: the elf header should always be mapped
      // within the first PT_LOAD entry.
      return 0;
    }
  ElfW(Ehdr) *header = (ElfW(Ehdr)*)(pt_load->p_vaddr + load_base);
  return header->e_entry + load_base;
}

static char *
get_pt_interp (unsigned long main_load_base,
	       unsigned long phnum,
	       ElfW(Phdr) *phdr)
{
  // will not work when main exec is loader itself
  ElfW(Phdr) *pt_interp = vdl_utils_search_phdr (phdr, phnum, PT_INTERP);
  if (pt_interp == 0)
    {
      return 0;
    }
  return (char*)(main_load_base + pt_interp->p_vaddr);
}


static struct VdlMapResult
interpreter_map (unsigned long load_base, 
		 const char *pt_interp,
		 struct VdlContext *context)
{
  /* We make many assumptions here:
   *   - The loader is an ET_DYN
   *   - The loader has been compile-time linked at base address 0
   *   - The first PT_LOAD map of the interpreter contains the elf header 
   *     and program headers.
   *   
   * Consequently, we can infer that the load_base in fact points to
   * the first PT_LOAD map of the interpreter which means that load_base
   * in fact points to the elf header itself.
   */
  ElfW(Ehdr) *header = (ElfW(Ehdr) *)load_base;
  ElfW(Phdr) *phdr = (ElfW(Phdr) *) (header->e_phoff + load_base);
  struct VdlMapResult result;
  // It's important to initialize the filename of the interpreter
  // entry in the linkmap to the PT_INTERP of the main binary for
  // gdb. gdb initializes its first linkmap with an entry which describes
  // the loader with a filename equal to PT_INTERP so, if we don't use
  // the same value, gdb will incorrectly believe that the loader entry
  // has been removed which can lead to certain bad things to happen
  // in the first call to r_debug_state.
  result = vdl_map_from_memory (load_base, header->e_phnum, phdr,
				pt_interp, LDSO_SONAME, context);
  if (result.requested == 0)
    {
      goto error;
    }
  // the interpreter has already been reloced during stage1, so, 
  // we must be careful to not relocate it twice.
  result.requested->reloced = 1;
 error:
  return result;
}

struct VdlList *
ld_preload_list_new (struct VdlContext *context, const char **envp)
{
  // add the LD_PRELOAD binary if it is specified somewhere.
  // We must do this _before_ adding the dependencies of the main 
  // binary to the link map to ensure that the symbol scope of 
  // the main binary is correct, that is, that symbols are 
  // resolved first within the LD_PRELOAD binary, before every
  // other library, but after the main binary itself.
  struct VdlList *retval = vdl_list_new ();
  const char *ld_preload = vdl_utils_getenv (envp, "LD_PRELOAD");
  struct VdlList *list = vdl_utils_strsplit (ld_preload, ':');
  void **cur;
  for (cur = vdl_list_begin (list); 
       cur != vdl_list_end (list); 
       cur = vdl_list_next (cur))
    {
      char *filename = *cur;
      struct VdlMapResult result = vdl_map_from_filename (context, filename);
      if (result.requested == 0)
	{
	  VDL_LOG_ERROR ("Could not map LD_PRELOAD %s: %s\n", filename, result.error_string);
	  goto error;
	}
      result.requested->count++;
      vdl_list_insert_range (retval, vdl_list_end (retval),
			     vdl_list_begin (result.newly_mapped),
			     vdl_list_end (result.newly_mapped));
      vdl_list_delete (result.newly_mapped);
    }
 error:
  vdl_utils_str_list_delete (list);
  return retval;
}

static void
setup_env_vars (const char **envp)
{
  // populate search_dirs from LD_LIBRARY_PATH
  const char *ld_lib_path = vdl_utils_getenv (envp, "LD_LIBRARY_PATH");
  struct VdlList *list = vdl_utils_splitpath (ld_lib_path);
  vdl_list_insert_range (g_vdl.search_dirs,
			 vdl_list_begin (g_vdl.search_dirs),
			 vdl_list_begin (list),
			 vdl_list_end (list));
  vdl_list_delete (list);

  // setup logging from LD_LOG
  const char *ld_log = vdl_utils_getenv (envp, "LD_LOG");
  vdl_log_set (ld_log);

  // setup bind_now from LD_BIND_NOW
  const char *bind_now = vdl_utils_getenv (envp, "LD_BIND_NOW");
  if (bind_now != 0)
    {
      g_vdl.bind_now = 1;
    }
}

struct Stage2Output
stage2_initialize (struct Stage2Input input)
{
  struct Stage2Output output;

  setup_env_vars ((const char**)input.program_envp);

  // The load base of the main program is easy to calculate as the difference
  // between the PT_PHDR vaddr and its real address in memory.
  unsigned long main_load_base = ((unsigned long)input.program_phdr) - input.program_phdr->p_vaddr;

  struct VdlContext *context = vdl_context_new (input.program_argc,
						input.program_argv,
						input.program_envp);

  // First, let's make sure we have an entry for the loader
  const char *pt_interp = get_pt_interp (main_load_base, 
					 input.program_phnum,
					 input.program_phdr);
  struct VdlMapResult interp_result;
  interp_result = interpreter_map (input.interpreter_load_base,
				   pt_interp,
				   context);
  struct VdlFile *interp = interp_result.requested;
  VDL_LOG_ASSERT (interp != 0,
		  "Could not map loader %s: %s", pt_interp, 
		  interp_result.error_string);
  vdl_list_delete (interp_result.newly_mapped); // there are no deps
  interp->count++;
  g_vdl.ldso = interp;

  // let's make sure that LD_PRELOAD binaries and their dependencies are loaded.
  struct VdlList *ld_preload;
  ld_preload = ld_preload_list_new (context, (const char **)input.program_envp);

  // Now, Let's do the main binary.
  struct VdlMapResult main_result;
  main_result = vdl_map_from_memory (main_load_base, 
				     input.program_phnum, 
				     input.program_phdr,
				     // the filename for the main exec is "" for gdb.
				     "",
				     input.program_argv[0],
				     context);
  VDL_LOG_ASSERT (main_result.requested != 0,
		  "unable to map main binary (%s) and dependencies: %s\n",
		  input.program_argv[0],
		  main_result.error_string);
  struct VdlFile *main_file = main_result.requested;
  main_file->count++;
  main_file->is_executable = 1;


  // Now, we setup our public linkmap.
  // We need to be careful to insert first the main file,
  // then, the interpreter, then, the ld preload entries,
  // then, the dependencies from the main file
  vdl_linkmap_append (main_file);
  vdl_linkmap_append (interp);
  vdl_linkmap_append_range (vdl_list_begin (ld_preload),
			    vdl_list_end (ld_preload));
  vdl_linkmap_append_range (vdl_list_begin (main_result.newly_mapped),
			    vdl_list_end (main_result.newly_mapped));
  vdl_list_delete (main_result.newly_mapped);
  main_result.newly_mapped = 0;

  // Now, prepare the global scope of the main context
  // The global scope is the same as the public linkmap except that
  // it does not contain the interpreter (unless, of course, it
  // is a dependency of the main binary or one of the ld_preloaded
  // binaries.
  vdl_list_push_back (context->global_scope, main_file);
  // of course, the ld_preload binaries must be in there if needed.
  vdl_list_insert_range (context->global_scope,
			 vdl_list_end (context->global_scope),
			 vdl_list_begin (ld_preload),
			 vdl_list_end (ld_preload));
  struct VdlList *all_deps = vdl_sort_deps_breadth_first (main_file);
  vdl_list_insert_range (context->global_scope,
			 vdl_list_end (context->global_scope),
			 vdl_list_begin (all_deps),
			 vdl_list_end (all_deps));
  vdl_list_delete (all_deps);
  vdl_list_unicize (context->global_scope);

  vdl_list_delete (ld_preload);


  gdb_initialize (main_file);

  // We need to do this before relocation because the TLS-type relocations 
  // need tls information.
  vdl_tls_file_initialize_main (context->loaded);

  // We either setup the GOT for lazy symbol resolution
  // or we perform binding for all symbols now if LD_BIND_NOW is set
  vdl_reloc (context->loaded, g_vdl.bind_now);

  // Once relocations are done, we can initialize the tls blocks
  // and the dtv. We need to wait post-reloc because the tls
  // template area used to initialize the tls blocks is likely 
  // to be modified during relocation processing.
  unsigned long tcb = vdl_tls_tcb_allocate ();
  vdl_tls_tcb_initialize (tcb, input.sysinfo);
  vdl_tls_dtv_allocate (tcb);
  vdl_tls_dtv_initialize (tcb);
  // configure the current thread to use this TCB as a thread pointer
  machine_thread_pointer_set (tcb);

  // Note that we must invoke this method to notify gdb that we have
  // a valid linkmap only _after_ relocations have been done (if you do
  // it before, gdb gets confused) and _before_ the initializers are 
  // run (to allow the user to debug the initializers).
  gdb_notify ();

  // patch glibc functions which need to be overriden.
  // This is really a hack I am not very proud of.
  glibc_patch (context->loaded);

  // glibc-specific crap to avoid segfault in initializer
  glibc_initialize ();

  valgrind_initialize ();

  // Finally, call init functions
  struct VdlList *call_init = vdl_sort_call_init (context->loaded);
  vdl_init_call (call_init);
  vdl_list_delete (call_init);

  unsigned long entry = get_entry_point (main_load_base,
					 input.program_phnum, 
					 input.program_phdr);
  if (entry == 0)
    {
      VDL_LOG_ERROR ("Zero entry point: nothing to do in %s\n", main_file->name);
      goto error;
    }
  glibc_startup_finished ();

  output.entry_point = entry;
  return output;
error:
  system_exit (-6);
  return output; // quiet compiler
}

void stage2_freeres (void)
{
  VDL_LOG_FUNCTION ("");
  // We know, that we will _not_ be called again after we return
  // from this function so, we can cleanup everything _except_ for the
  // code/data memory mappings because the caller code segment would be 
  // unmapped and that would trigger interesting crashes upon return
  // from this function. When we return, the caller is going to call
  // the exit_group syscall.

  struct VdlList *link_map = vdl_linkmap_copy ();      
  vdl_unmap (link_map, false);
  vdl_list_delete (link_map);

  unsigned long tcb = machine_thread_pointer_get ();
  vdl_tls_dtv_deallocate (tcb);
  vdl_tls_tcb_deallocate (tcb);
}

inline static void file_list_print(struct VdlList *l)
{
  void **cur;
  for (cur = vdl_list_begin(l); cur != vdl_list_end(l); cur = vdl_list_next(cur))
    {
      struct VdlFile *file = *cur;
      VDL_LOG_DEBUG("file=%p/\"%s\"\n", file, file->filename);
    }
}

void
stage2_finalize (void)
{
  // Our job here is to invoke the destructors of all still-loaded
  // objects. This is tricky since:
  //   - must handle all namespaces
  //   - must handle still-running code in other threads
  futex_lock (g_vdl.futex);
  struct VdlList *link_map = vdl_linkmap_copy ();
  struct VdlList *call_fini = vdl_sort_call_fini (link_map);
  struct VdlList *locked = vdl_fini_lock(call_fini);
  vdl_list_delete (call_fini);
  vdl_list_delete (link_map);

  futex_unlock (g_vdl.futex);
  vdl_fini_call (locked);
  futex_lock (g_vdl.futex);

  vdl_list_delete (locked);
  futex_unlock (g_vdl.futex);

}