Goto sanos source index

//
// moddb.c
//
// Module loader
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os.h>
#include <os/pe.h>
#include <string.h>
#include <stdarg.h>
#include <inifile.h>

#include <moddb.h>

#ifdef KERNEL
#include <os/krnl.h>
#endif

int vsprintf(char *buf, const char *fmt, va_list args);

struct stab {
  unsigned long n_strx;           // Index into string table of name
  unsigned char n_type;           // Type of symbol
  unsigned char n_other;          // Misc info (usually empty)
  unsigned short n_desc;          // Description field
  unsigned long n_value;          // Value of symbol
};

#define N_SO    0x64              // Name of main source file
#define N_BINCL 0x82              // Beginning of an include file
#define N_EINCL 0xa2              // End of an include file
#define N_SLINE 0x44              // Line number in text segment
#define N_FUN   0x24              // Function name or text-segment variable for C

static void logmsg(struct moddb *db, const char *fmt, ...) {
  va_list args;
  char buffer[1024];

  if (db->log) {
    va_start(args, fmt);
    vsprintf(buffer, fmt, args);
    va_end(args);
    db->log(buffer);
  }
}

static char *get_basename(char *filename, char *buffer, int size) {
  char *basename = filename;
  char *bufend = buffer + size - 1;
  char *p = filename;

  while (*p && buffer < bufend) {
    *buffer = *p;
    if (*buffer == PS1 || *buffer == PS2) basename = buffer + 1;
    buffer++;
    p++;
  }
  *buffer = 0;

  return basename;
}

static char *get_extension(char *filename) {
  char *p = filename;
  char *ext = NULL;
  while (*p) {
    if (*p == PS1 || *p == PS2) {
      ext = NULL;
    } else if (*p == '.') {
      ext = p;
    }
    p++;
  }

  return ext;
}

static int is_pe_filename(char *filename) {
  char *ext = get_extension(filename);
  return ext && (stricmp(ext, ".dll") == 0 || stricmp(ext, ".exe") == 0);
}

static void insert_before(struct module *m, struct module *n) {
  n->next = m;
  n->prev = m->prev;
  m->prev->next = n;
  m->prev = n;
}

static void insert_after(struct module *m, struct module *n) {
  n->next = m->next;
  n->prev = m;
  m->next->prev = n;
  m->next = n;
}

static void remove(struct module *m) {
  m->next->prev = m->prev;
  m->prev->next = m->next;
}

struct image_header *get_image_header(hmodule_t hmod) {
  struct dos_header *doshdr = (struct dos_header *) hmod;

  if (doshdr == NULL) return NULL;
  return (struct image_header *) RVA(hmod, doshdr->e_lfanew);
}

static void *get_image_directory(hmodule_t hmod, int dir) {
  unsigned long addr = get_image_header(hmod)->optional.data_directory[dir].virtual_address;
  return (void *) addr ? RVA(hmod, addr) : 0;
}

static unsigned long get_image_directory_size(hmodule_t hmod, int dir) {
  return get_image_header(hmod)->optional.data_directory[dir].size;
}

struct module *get_module_for_handle(struct moddb *db, hmodule_t hmod) {
  struct module *m;

  // Try to find module in module list
  m = db->modules;
  while (1) {
    if (hmod == m->hmod) return m;
    m = m->next;
    if (m == db->modules) break;
  }
 
  return NULL;
}

static struct module *get_module_for_address(struct moddb *db, void *addr) {
  struct module *m;

  m = db->modules;
  while (1) {
    struct image_header *imghdr = get_image_header(m->hmod);
    if (imghdr) {
      void *end = (char *) m->hmod + imghdr->optional.size_of_image;
      if (addr >= m->hmod && addr < end) return m;
    }
    m = m->next;
    if (m == db->modules) break;
  }

  return NULL;
}

static char *get_module_section(struct module *mod, const char *name, int *size) {
  int i;
  struct image_header *imghdr = get_image_header(mod->hmod);
  for (i = 0; i < imghdr->header.number_of_sections; i++) {
    if (strncmp(name, imghdr->sections[i].name, IMAGE_SIZEOF_SHORT_NAME) == 0) {
      if (size) *size = imghdr->sections[i].size_of_raw_data;
      return RVA(mod->hmod, imghdr->sections[i].virtual_address);
    }
  }
  return NULL;
}

static char *find_in_modpaths(struct moddb *db, char *name, char *path, int type) {
  int i;
  char *p;
  char *s;
  char *basename;
  char *dot;
  char *pathend = path + MAXPATH - 1;
  int len;
  struct modalias *ma;

  for (i = 0; i < db->nmodpaths; i++) {
    // Build path name
    len = strlen(db->modpaths[i]);
    if (len > MAXPATH - 2) continue;
    p = path;
    memcpy(p, db->modpaths[i], len);
    p += len;

    *p++ = PS1;

    basename = p;
    dot = NULL;
    s = name;
    while (*s && p < pathend) {
      if (*s == '.') {
        dot = s;
        *p++ = '.';
      } else if (*s >= 'A' && *s <= 'Z') {
        *p++ = *s + ('a' - 'A');
      } else {
        *p++ = *s;
      }

      s++;
    }

    if (!dot && (type & (MODTYPE_EXE | MODTYPE_DLL)) && p + 4 < pathend) {
      memcpy(p, type & MODTYPE_EXE ? ".exe" : ".dll", 4);
      p += 4;
    }

    *p = 0;

    // Check for alias
    ma = db->aliases;
    while (ma) {
      if (strcmp(basename, ma->name) == 0 && basename + strlen(ma->name) < pathend) {
        strcpy(basename, ma->alias);
        break;
      }

      ma = ma->next;
    }

    // Return path if file exists
    if (access(path, X_OK) >= 0) return basename;
  }

  return NULL;
}

static char *get_module_name(struct moddb *db, char *name, char *path, int type) {
  char *dot;
  char *basename;
  char *s;

  // Check for file path in module name
  s = name;
  while (*s != 0 && *s != PS1 && *s != PS2) s++;

  if (*s) {
    // Get full path name for module
    basename = get_basename(name, path, MAXPATH);

    // Convert base name to lower case
    dot = NULL;
    s = basename;
    while (*s) {
      if (*s == '.') {
        dot = s;
      } else if (*s >= 'A' && *s <= 'Z') {
        *s = *s + ('a' - 'A');
      }

      s++;
    }

    // Add .dll to name if no extension
    if (!dot && (type & (MODTYPE_EXE | MODTYPE_DLL))) {
      memcpy(s, type & MODTYPE_EXE ? ".exe" : ".dll", 4);
      s += 4;
    }

    *s = 0;
    
    // Check that file exists and is executable.
    if (access(path, X_OK) < 0) return NULL;
  } else {
    // Search for file in library paths
    basename = find_in_modpaths(db, name, path, type);
  }

  return basename;
}

static struct module *get_module(struct moddb *db, char *name, int type) {
  char buffer[MAXPATH];
  char *basename;
  char *dot;
  char *p;
  char *s;
  struct module *m;
  struct modalias *ma;

  // Get canonical module name
  basename = name;
  while (*name) {
    if (*name == PS1 || *name == PS2) basename = name + 1;
    name++;
  }

  p = buffer;
  s = basename;
  dot = NULL;
  while (*s) {
    if (p - buffer == MAXPATH - 1) break;

    if (*s == '.') {
      dot = s;
      *p++ = '.';
    } else if (*s >= 'A' && *s <= 'Z') {
      *p++ = *s + ('a' - 'A');
    } else {
      *p++ = *s;
    }

    s++;
  }

  if (!dot && (type & (MODTYPE_EXE | MODTYPE_DLL)) && p - buffer < MAXPATH - 5) {
    memcpy(p, type &  MODTYPE_EXE ? ".exe" : ".dll", 4);
    p += 4;
  }

  *p = 0;

  // Check for alias
  ma = db->aliases;
  while (ma) {
    if (strcmp(buffer, ma->name) == 0) return get_module(db, ma->alias, type);
    ma = ma->next;
  }

  // Try to find module in module list
  m = db->modules;
  while (1) {
    if (strcmp(buffer, m->name) == 0) return m;
    m = m->next;
    if (m == db->modules) break;
  }

  return NULL;
}

static void *get_proc_by_name(hmodule_t hmod, int hint, char *procname) {
  struct image_export_directory *exp;
  unsigned int *names;
  unsigned int i;

  exp = (struct image_export_directory *) get_image_directory(hmod, IMAGE_DIRECTORY_ENTRY_EXPORT);
  if (!exp) return NULL;

  names = (unsigned int *) RVA(hmod, exp->address_of_names);

  if (hint >= 0 && hint < (int) exp->number_of_names && strcmp(procname, RVA(hmod, names[hint])) == 0) {
    unsigned short idx;

    idx = *((unsigned short *) RVA(hmod, exp->address_of_name_ordinals) + hint);
    return RVA(hmod, *((unsigned long *) RVA(hmod, exp->address_of_functions) + idx));
  }

  for (i = 0; i < exp->number_of_names; i++) {
    char *name = RVA(hmod,  *names);
    if (strcmp(name, procname) == 0) {
      unsigned short idx;
      
      idx = *((unsigned short *) RVA(hmod, exp->address_of_name_ordinals) + i);
      return RVA(hmod, *((unsigned long *) RVA(hmod, exp->address_of_functions) + idx));
    }

    names++;
  }

  return NULL;
}

static void *get_proc_by_ordinal(hmodule_t hmod, unsigned int ordinal) {
  struct image_export_directory *exp;

  exp = (struct image_export_directory *) get_image_directory(hmod, IMAGE_DIRECTORY_ENTRY_EXPORT);
  if (!exp) return NULL;

  if (ordinal < exp->base || ordinal >= exp->number_of_functions + exp->base) panic("invalid ordinal");
  return RVA(hmod, *((unsigned long *) RVA(hmod, exp->address_of_functions) + (ordinal - exp->base)));
}

static struct module *resolve_imports(struct module *mod) {
  struct image_import_descriptor *imp;
  struct module *modlist = mod;
  char path[MAXPATH];

  // Find import directory in image
  imp = (struct image_import_descriptor *) get_image_directory(mod->hmod, IMAGE_DIRECTORY_ENTRY_IMPORT);
  if (!imp) return mod;

  // Load each dependent module
  while (imp->characteristics != 0) {
    char *name = RVA(mod->hmod, imp->name);
    struct module *newmod = get_module(mod->db, name, MODTYPE_DLL);

    if (newmod == NULL) {
      char *imgbase;

      name = get_module_name(mod->db, name, path, MODTYPE_DLL);
      if (!name) {
        logmsg(mod->db, "module %s not found", RVA(mod->hmod, imp->name));
        return NULL;
      }

      imgbase = mod->db->load_image(path);
      if (imgbase == NULL) {
        logmsg(mod->db, "unable to load module %s", path);
        return NULL;
      }

      newmod = (struct module *) malloc(sizeof(struct module));
      newmod->hmod = imgbase;
      newmod->db = mod->db;
      newmod->name = strdup(name);
      newmod->path = strdup(path);
      newmod->refcnt = 0;
      newmod->flags = MODULE_LOADED;
      insert_before(mod, newmod);

      newmod = resolve_imports(newmod);
      if (newmod == NULL) return NULL;

      if (modlist == mod) modlist = newmod;
    }
    
    imp++;
  }

  return modlist;
}

static int bind_imports(struct module *mod) {
  struct image_import_descriptor *imp;
  int errs = 0;

  // Find import directory in image
  imp = (struct image_import_descriptor *) get_image_directory(mod->hmod, IMAGE_DIRECTORY_ENTRY_IMPORT);
  if (!imp) return 0;
  
  if (imp->forwarder_chain != 0 && imp->forwarder_chain != 0xFFFFFFFF) {
    logmsg(mod->db, "import forwarder chains not supported (%s)", mod->name);
    return -ENOSYS;
  }

  // Update Import Address Table (IAT)
  while (imp->characteristics != 0) {
    unsigned long *thunks;
    unsigned long *origthunks;
    struct image_import_by_name *ibn;
    char *name;
    struct module *expmod;

    name = RVA(mod->hmod, imp->name);
    expmod = get_module(mod->db, name, MODTYPE_DLL);
    
    if (!expmod) {
      logmsg(mod->db, "module %s no longer loaded", name);
      return -ENOEXEC;
    }
    
    thunks = (unsigned long *) RVA(mod->hmod, imp->first_thunk);
    origthunks = (unsigned long *) RVA(mod->hmod, imp->original_first_thunk);
    while (*thunks) {
      if (*origthunks & IMAGE_ORDINAL_FLAG) {
        // Import by ordinal
        unsigned long ordinal = *origthunks & ~IMAGE_ORDINAL_FLAG;
        *thunks = (unsigned long) get_proc_by_ordinal(expmod->hmod, ordinal);
        if (*thunks == 0) {
          logmsg(mod->db, "unable to resolve %s:#%d in %s", expmod->name, ordinal, mod->name);
          errs++;
        }
      } else {
        // Import by name (and hint)
        ibn = (struct image_import_by_name *) RVA(mod->hmod, *origthunks);
        *thunks = (unsigned long) get_proc_by_name(expmod->hmod, ibn->hint, ibn->name);
        if (*thunks == 0) {
          logmsg(mod->db, "unable to resolve %s:%s in %s", expmod->name, ibn->name, mod->name);
          errs++;
        }
      }

      thunks++;
      origthunks++;
    }

    imp++;
  }

  if (errs) return -ENOEXEC;

  mod->flags |= MODULE_BOUND;
  return 0;
}

static int relocate_module(struct module *mod) {
  unsigned long offset;
  char *pagestart;
  unsigned short *fixup;
  int i;
  struct image_base_relocation *reloc;
  struct image_base_relocation *end;
  int nrelocs;

  offset = (unsigned long) mod->hmod - get_image_header(mod->hmod)->optional.image_base;
  if (offset == 0) return 0;

  if (get_image_header(mod->hmod)->header.characteristics & IMAGE_FILE_RELOCS_STRIPPED) {
    logmsg(mod->db, "relocation info missing for %s", mod->name);
    return -ENOEXEC;
  }

  reloc = (struct image_base_relocation *) get_image_directory(mod->hmod, IMAGE_DIRECTORY_ENTRY_BASERELOC);
  if (!reloc) return 0;
  end = (struct image_base_relocation *) ((char *) reloc + get_image_directory_size(mod->hmod, IMAGE_DIRECTORY_ENTRY_BASERELOC));

  while (reloc < end && (reloc->virtual_address != 0 || reloc->size_of_block != 0)) {
    pagestart = RVA(mod->hmod, reloc->virtual_address);
    nrelocs = (reloc->size_of_block - sizeof(struct image_base_relocation)) / 2;
    fixup = (unsigned short *) (reloc + 1);
    for (i = 0; i < nrelocs; i++, fixup++) {
      unsigned short type = *fixup >> 12;
      unsigned short pos = *fixup & 0xfff;

      if (type == IMAGE_REL_BASED_HIGHLOW) {
        *(unsigned long *) (pagestart + pos) += offset;
      } else if (type != IMAGE_REL_BASED_ABSOLUTE) {
        logmsg(mod->db, "unsupported relocation type %d in %s", type, mod->name);
        return -ENOEXEC;
      }
    }

    reloc = (struct image_base_relocation *) fixup;
  }

  mod->flags |= MODULE_RELOCATED;
  return 0;
}

static int protect_module(struct module *mod) {
  struct image_header *imghdr = get_image_header(mod->hmod);
  int i;

  if (!mod->db->protect_region) return 0;

  // Set page protect for each section
  for (i = 0; i < imghdr->header.number_of_sections; i++) {
    int protect;
    unsigned long scn = imghdr->sections[i].characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE);

    if (scn == (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ)) {
      protect = PAGE_EXECUTE_READ;
    } else if (scn == (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) {
      protect = PAGE_READWRITE;
    } else if (scn == IMAGE_SCN_MEM_READ) {
      protect = PAGE_READONLY;
    } else {
      protect = PAGE_EXECUTE_READWRITE;
    }

    mod->db->protect_region(RVA(mod->hmod, imghdr->sections[i].virtual_address), imghdr->sections[i].size_of_raw_data, protect);
  }

  mod->flags |= MODULE_PROTECTED;
  return 0;
}

static void update_refcount(struct module *mod) {
  struct image_import_descriptor *imp;

  // If imports already ref counted just skip
  if (mod->flags & MODULE_IMPORTS_REFED) return;

  // Find import directory in image
  imp = (struct image_import_descriptor *) get_image_directory(mod->hmod, IMAGE_DIRECTORY_ENTRY_IMPORT);
  if (!imp) return;

  // Get or load each dependent module
  while (imp->characteristics != 0) {
    char *name = RVA(mod->hmod, imp->name);
    struct module *depmod = get_module(mod->db, name, MODTYPE_DLL);

    if (depmod != NULL) depmod->refcnt++;
    
    imp++;
  }

  mod->flags |= MODULE_IMPORTS_REFED;
}

static int remove_module(struct module *mod) {
  struct image_header *imghdr;
  struct image_import_descriptor *imp;

  imghdr = get_image_header(mod->hmod);

  if (mod->flags & MODULE_INITIALIZED) {
    // Notify DLL
    if (imghdr->header.characteristics & IMAGE_FILE_DLL) {
      ((int (__stdcall *)(hmodule_t, int, void *)) get_entrypoint(mod->hmod))(mod->hmod, DLL_PROCESS_DETACH, NULL);
    }
    mod->flags &= ~MODULE_INITIALIZED;
  }

  if (mod->flags & MODULE_IMPORTS_REFED) {
    // Decrement reference count on all dependent modules 
    imp = (struct image_import_descriptor *) get_image_directory(mod->hmod, IMAGE_DIRECTORY_ENTRY_IMPORT);
    if (imp) {
      while (imp->characteristics != 0) {
        char *name = RVA(mod->hmod, imp->name);
        struct module *depmod = get_module(mod->db, name, MODTYPE_DLL);

        if (depmod && --depmod->refcnt == 0) remove_module(depmod); 
        imp++;
      }
    }

    mod->flags &= ~MODULE_IMPORTS_REFED;
  }

  // Release memory for module
  if (mod->flags & MODULE_LOADED) {
    mod->db->unload_image(mod->hmod, imghdr->optional.size_of_image);
    mod->flags &= ~MODULE_LOADED;
  }

  // Notify
  if (mod->db->notify_unload) mod->db->notify_unload(mod->hmod);

  // Remove module from module list
  remove(mod);
  free(mod->name);
  free(mod->path);
  free(mod);

  return 0;
}

static void free_unused_modules(struct moddb *db) {
  struct module *mod;
  
  mod = db->modules;
  while (1) {
    if (mod->refcnt == 0) {
      remove_module(mod);
      mod = db->modules;
    } else {
      mod = mod->next;
      if (mod == db->modules) break;
    }
  }
}

static int sharable_module(struct module *mod) {
  struct image_header *imghdr = get_image_header(mod->hmod);
  return (imghdr->header.characteristics & IMAGE_FILE_UP_SYSTEM_ONLY) == 0;
}

void *get_proc_address(hmodule_t hmod, char *procname) {
  return get_proc_by_name(hmod, -1, procname);
}

hmodule_t get_module_handle(struct moddb *db, char *name) {
  struct module *mod;
  
  if (!name || strlen(name) > MAXPATH - 1) return NULL;
  mod = get_module(db, name, MODTYPE_DLL);
  if (mod == NULL) mod = get_module(db, name, MODTYPE_EXE);
  if (mod == NULL) return NULL;
  return mod->hmod;
}

int get_module_filename(struct moddb *db, hmodule_t hmod, char *buffer, int size) {
  struct module *mod;
  
  mod = get_module_for_handle(db, hmod);
  if (mod == NULL) return -EINVAL;
  strncpy(buffer, mod->path, size);
  return strlen(mod->path);
}

void *get_entrypoint(hmodule_t hmod) {
  return RVA(hmod, get_image_header(hmod)->optional.address_of_entry_point);
}

hmodule_t load_module(struct moddb *db, char *name, int flags) {
  char buffer[MAXPATH];
  char *basename;
  char *imgbase;
  struct module *mod;
  struct module *modlist;
  struct module *m;
  int rc;

  // Check module name
  if (!name || strlen(name) > MAXPATH - 1) return NULL;
  
  // Check if module is already loaded
  if ((flags & MODLOAD_NOSHARE) == 0) {
    mod = get_module(db, name, MODTYPE_DLL);
    if (mod == NULL) mod = get_module(db, name, MODTYPE_EXE);
    if (mod != NULL && sharable_module(mod)) {
      mod->refcnt++;
      return mod->hmod;
    }
  }

  // Try to read hashbang from script
  basename = NULL;
  if ((flags & MODLOAD_SCRIPT) && db->read_magic && !is_pe_filename(name)) {
    basename = get_module_name(db, name, buffer, MODTYPE_SCRIPT);
    if (basename != NULL) {
      char interp[MAXPATH];
      int len = db->read_magic(buffer, interp, MAXPATH - 1);
      if (len > 2 && interp[0] == '#' && interp[1] == '!') {
        // Get interpreter name.
        int end = 2;
        while (end < len) {
          int ch = interp[end];
          if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') break;
          end++;
        }
        interp[end] = 0;

        // Get module name for interpreter
        basename = get_module_name(db, interp + 2, buffer, MODTYPE_EXE);
      }
    }
  }

  // Get module name
  if (!basename) {
    basename = get_module_name(db, name, buffer, flags & MODLOAD_EXE ? MODTYPE_EXE : MODTYPE_DLL);
    if (!basename) return NULL;
  }

  // Load image into memory
  imgbase = db->load_image(buffer);
  if (imgbase == NULL) return NULL;

  // Create new module
  mod = (struct module *) malloc(sizeof(struct module));
  mod->hmod = imgbase;
  mod->db = db;
  mod->name = strdup(basename);
  mod->path = strdup(buffer);
  mod->refcnt = 0;
  mod->flags = MODULE_LOADED;
  insert_before(db->modules, mod);

  // Resolve module dependencies
  modlist = resolve_imports(mod);
  if (modlist == NULL) {
    free_unused_modules(db);
    return NULL;
  }

  // Relocate, bind imports and protect new modules
  m = modlist;
  while (1) {
    rc = relocate_module(m);
    if (rc < 0) {
      free_unused_modules(db);
      return NULL;
    }

    rc = bind_imports(m);
    if (rc < 0) {
      free_unused_modules(db);
      return NULL;
    }

    rc = protect_module(m);
    if (rc < 0) {
      free_unused_modules(db);
      return NULL;
    }

    if (m == mod) break;
    m = m->next;
  }

  // Initialize and notify new modules
  m = modlist;
  while (1) {
    if (get_image_header(m->hmod)->header.characteristics & IMAGE_FILE_DLL) {
      if ((flags & MODLOAD_NOINIT) == 0 || m != mod) {
        int ok;

        //logmsg(db, "initializing module %s", m->name);
        ok = ((int (__stdcall *)(hmodule_t, int, void *)) get_entrypoint(m->hmod))(m->hmod, DLL_PROCESS_ATTACH, NULL);
        //logmsg(db, "module %s initialized%s", m->name, ok ? "" : ", init failed");
        if (!ok) {
          free_unused_modules(db);
          return NULL;
        }

        m->flags |= MODULE_INITIALIZED;
      }
    }

    // Notify
    if (db->notify_load) db->notify_load(m->hmod, &m->name);

    if (m == mod) break;
    m = m->next;
  }

  // Update ref counts on module dependencies
  mod->refcnt++;
  m = modlist;
  while (1) {
    update_refcount(m);
    if (m == mod) break;
    m = m->next;
  }

  return mod->hmod;
}

int unload_module(struct moddb *db, hmodule_t hmod) {
  struct module *mod;

  // Find module
  mod = get_module_for_handle(db, hmod);
  if (mod == NULL) return -EINVAL;

  // Decrement reference count, return if not zero
  if (--mod->refcnt > 0) return 0;

  // Remove module
  return remove_module(mod);
}

static struct image_resource_directory_entry *find_resource(char *resbase, struct image_resource_directory *dir, char *id) {
  struct image_resource_directory_entry *direntry;
  struct image_resource_directory_string *entname;
  int i;

  direntry = (struct image_resource_directory_entry *) (dir + 1);
  if ((unsigned long) id < 0x10000) {
    // Lookup by ID, first skip named entries
    direntry += dir->number_of_named_entries;

    for (i = 0; i < dir->number_of_id_entries; i++) {
      if (direntry->id == (unsigned long) id) return direntry;
      direntry++;
    }
  } else {
    // Lookup by name
    for (i = 0; i < dir->number_of_named_entries; i++) {
      unsigned short *p1;
      unsigned char *p2;
      int left;
      unsigned short ch1;
      unsigned short ch2;

      entname = (struct image_resource_directory_string *) RVA(resbase, direntry->name_offset);
      p1 = (unsigned short *) entname->name_string;
      p2 = (unsigned char *) id;
      left = entname->length;
      while (left > 0 && *p2 != 0) {
        if (((ch1 = *p1++) >= 'a') && (ch1 <= 'z')) ch1 += 'A' - 'a';
        if (((ch2 = *p2++) >= 'a') && (ch2 <= 'z')) ch2 += 'A' - 'a';

        if (ch1 != ch2) break;

        left--;
      }
      
      if (left == 0 && *p2 == 0) return direntry;
      direntry++;
    }
  }

  return NULL;
}

int get_resource_data(hmodule_t hmod, char *id1, char *id2, char *id3, void **data) {
  char *resbase;
  struct image_resource_directory *dir;
  struct image_resource_directory_entry *direntry;
  struct image_resource_data_entry *dataentry;

  // Find resource root directory
  resbase = get_image_directory(hmod, IMAGE_DIRECTORY_ENTRY_RESOURCE);
  if (!resbase) return -ENOENT;
  dir = (struct image_resource_directory *) resbase;

  // Find first level entry
  direntry = find_resource(resbase, dir, id1);
  if (!direntry) return -ENOENT;
  if (!direntry->data_is_directory) return -EINVAL;
  dir = (struct image_resource_directory *) RVA(resbase, direntry->offset_to_directory);

  // Find second level entry
  direntry = find_resource(resbase, dir, id2);
  if (!direntry) return -ENOENT;
  if (!direntry->data_is_directory) return -EINVAL;
  dir = (struct image_resource_directory *) RVA(resbase, direntry->offset_to_directory);

  // Find third level entry
  direntry = find_resource(resbase, dir, id3);
  if (!direntry) return -ENOENT;
  if (direntry->data_is_directory) return -EINVAL;

  dataentry = (struct image_resource_data_entry *) RVA(resbase, direntry->offset_to_data);
  *data = RVA(hmod, dataentry->offset_to_data);
  return dataentry->size;
}

int get_stack_trace(struct moddb *db, struct context *ctxt,
                    void *stktop, void *stklimit, 
                    struct stackframe *frames, int depth) {
  struct module *mod;
  struct stab *stab, *stab_end;
  int stab_size;
  char *stabstr;
  void *eip = (void *) ctxt->eip;
  void *ebp = (void *) ctxt->ebp;
  int i = 0;
  memset(frames, 0, sizeof(struct stackframe) * depth);
  while (i < depth && ebp > stklimit && ebp < stktop) {
    frames[i].eip = eip;
    frames[i].ebp = ebp;
    frames[i].line = -1;
    
    mod = get_module_for_address(db, eip);
    if (mod) {
      frames[i].hmod = mod->hmod;
      frames[i].modname = mod->name;
      stab = (struct stab *) get_module_section(mod, ".stab", &stab_size);
      stabstr = get_module_section(mod, ".stabstr", NULL);
      if (stab && stabstr) {
        int found = 0;
        struct stab *source = NULL;
        struct stab *func = NULL;
        struct stab *line = NULL;
        stab_end = (struct stab *) ((char *) stab + stab_size);
        while (!found && stab < stab_end) {
          switch (stab->n_type) {
            case N_SLINE:
              if (func && eip > (void *) (func->n_value + stab->n_value)) line = stab;
              break;
              
            case N_FUN:
              if (func) {
                if (stab->n_strx == 0 && eip < (void *) (func->n_value + stab->n_value)) {
                  found = 1;
                } else {
                  func = NULL;
                }
              } else if (eip > (void *) stab->n_value) {
                func = stab;
              }
              break;

            case N_SO:
              source = stab;
              break;
          }
          stab++;
        }
        if (found) {
          if (func) {
            frames[i].func = stabstr + func->n_strx;
            frames[i].offset = (unsigned long) eip - func->n_value;
            if (line) frames[i].line = line->n_desc;
          }
          if (source) frames[i].file = stabstr + source->n_strx;
        }
      }
    }

    eip = *((void **) ebp + 1);
    ebp = *(void **) ebp;
    i++;
  }

  return i;
}

int init_module_database(struct moddb *db, char *name, hmodule_t hmod, char *libpath, struct section *aliassect, int flags) {
  char *basename;
  char *p;
  struct property *prop;

  // Set flags
  db->flags = flags;

  // Set library paths
  if (libpath) {
    int n;
    char *p;
    char *q;
    char *path;

    p = libpath;
    n = 1;
    while (*p) {
      if (*p == ';') n++;
      p++;
    }

    db->nmodpaths = n;
    db->modpaths = (char **) malloc(n * sizeof(char *));

    p = libpath;
    n = 0;
    while (*p) {
      q = p;
      while (*q && *q != ';') q++;

      db->modpaths[n++] = path = (char *) malloc(q - p + 1);
      memcpy(path, p, q - p);
      path[q - p] = 0;
      
      if (*q) {
        p = q + 1;
      } else {
        p = q;
      }
    }
  } else {
    db->modpaths = NULL;
    db->nmodpaths = 0;
  }

  // Setup module aliases
  db->aliases = NULL;
  if (aliassect) {
    prop = aliassect->properties;
    while (prop) {
      struct modalias *ma;

      ma = (struct modalias *) malloc(sizeof(struct modalias));
      if (!ma) return -ENOMEM;

      ma->name = strdup(prop->name);
      ma->alias = strdup(prop->value);
      ma->next = db->aliases;

      db->aliases = ma;

      prop = prop->next;
    }
  }

  // Setup module database with initial module
  basename = name;
  p = name;
  while (*p) {
    if (*p == PS1 || *p == PS2) basename = p + 1;
    p++;
  }

  db->modules = (struct module *) malloc(sizeof(struct module));
  if (!db->modules) return -ENOMEM;

  db->modules->hmod = hmod;
  db->modules->db = db;
  db->modules->name = strdup(basename);
  db->modules->path = strdup(name);
  db->modules->next = db->modules;
  db->modules->prev = db->modules;
  db->modules->refcnt = 1;
  db->modules->flags =  MODULE_LOADED | MODULE_IMPORTS_REFED | MODULE_RESOLVED | MODULE_RELOCATED | MODULE_BOUND | MODULE_PROTECTED | MODULE_INITIALIZED;

  // Protect initial module
  if (db->protect_region) protect_module(db->modules);

  return 0;
}