Exemplo n.º 1
0
 def map_struct(self, name, addr, struct):
     size = struct.get_size()
     access = AccessStruct(self.mem, struct, addr)
     label = self.label_mgr.get_label(addr)
     mem = Memory(addr, size, label, access)
     log_mem_alloc.info("map struct: %s", mem)
     return mem
Exemplo n.º 2
0
    def __init__(self,
                 name,
                 version,
                 addr,
                 num_vectors,
                 pos_size,
                 mem,
                 label_mgr,
                 struct=LibraryDef,
                 lib_class=None):
        self.name = name
        self.version = version
        self.lib_class = lib_class
        self.struct = struct

        self.num_vectors = num_vectors
        self.pos_size = pos_size
        self.neg_size = num_vectors * 6
        self.size = self.pos_size + self.neg_size

        self.lib_begin = addr
        self.lib_base = addr + self.neg_size
        self.lib_end = self.lib_base + self.pos_size
        self.real_lib_base = self.lib_base

        self.ref_cnt = 0
        self.lib_id = None

        self.access = AccessStruct(mem, struct, self.lib_base)
        self.label = None

        # native lib only
        self.seg_list = None
        self.vectors = None
Exemplo n.º 3
0
 def alloc_lib_base(self, ctx):
     """alloc memory for the library base"""
     # alloc memory - use an Exec AllocMemory() compatible scheme here
     # as the lib might get free'd by library code and calling FreeMem()
     lib_size = self.mem_neg_size + self.mem_pos_size
     tag = "LibBase(%s)" % self.name
     self.mem_obj = ctx.alloc.alloc_memory(tag, lib_size, add_label=False)
     self.addr_begin = self.mem_obj.addr
     self.addr_base = self.addr_begin + self.mem_neg_size
     self.addr_end = self.addr_base + self.mem_pos_size
     # create memory label
     self.label = LabelLib(self.name, self.addr_begin, lib_size,
                           self.addr_base, self.struct, self)
     ctx.label_mgr.add_label(self.label)
     # create access
     self.access = AccessStruct(ctx.mem, self.struct, self.addr_base)
     self.lib_access = AccessStruct(ctx.mem, LibraryDef, self.addr_base)
Exemplo n.º 4
0
 def alloc_struct(self, name, struct):
     size = struct.get_size()
     addr = self.alloc_mem(size)
     label = LabelStruct(name, addr, struct)
     self.label_mgr.add_label(label)
     access = AccessStruct(self.mem, struct, addr)
     mem = Memory(addr, size, label, access)
     log_mem_alloc.info("alloc struct: %s", mem)
     self.mem_objs[addr] = mem
     return mem
Exemplo n.º 5
0
 def alloc_lib_base(self, ctx):
   """alloc memory for the library base"""
   # alloc memory - use an Exec AllocMemory() compatible scheme here
   # as the lib might get free'd by library code and calling FreeMem()
   lib_size = self.mem_neg_size + self.mem_pos_size
   tag = "LibBase(%s)" % self.name
   self.mem_obj = ctx.alloc.alloc_memory(tag, lib_size, add_label=False)
   self.addr_begin = self.mem_obj.addr
   self.addr_base = self.addr_begin + self.mem_neg_size
   self.addr_end = self.addr_base + self.mem_pos_size
   self.addr_base_open = self.addr_base
   # create memory label
   self.label = LabelLib(self.name, self.addr_begin, lib_size, self.addr_base, self.struct, self)
   ctx.label_mgr.add_label(self.label)
   # create access
   self.access = AccessStruct(ctx.mem, self.struct, self.addr_base)
   self.lib_access = AccessStruct(ctx.mem, LibraryDef, self.addr_base)
Exemplo n.º 6
0
 def examine_lock(self, lock, fib_mem):
     # name
     name_addr = fib_mem.s_get_addr('fib_FileName')
     fib_mem.w_cstr(name_addr, lock.name)
     # dummy key
     fib_mem.w_s('fib_DiskKey', 0xcafebabe)
     # type
     if os.path.isdir(lock.sys_path):
         dirEntryType = 0x2  # dir
     else:
         dirEntryType = 0xfffffffd  # file
     fib_mem.w_s('fib_DirEntryType', dirEntryType)
     # protection
     prot = DosProtection(0)
     try:
         os_stat = os.stat(lock.sys_path)
         mode = os_stat.st_mode
         if mode & stat.S_IXUSR == 0:
             prot.clr(DosProtection.FIBF_EXECUTE)
         if mode & stat.S_IRUSR == 0:
             prot.clr(DosProtection.FIBF_READ)
         if mode & stat.S_IWUSR == 0:
             prot.clr(DosProtection.FIBF_WRITE)
         log_lock.debug("examine lock: '%s' mode=%03o: prot=%s", lock, mode,
                        prot)
     except OSError:
         return ERROR_OBJECT_IN_USE
     fib_mem.w_s('fib_Protection', prot.mask)
     # size
     if os.path.isfile(lock.sys_path):
         size = os.path.getsize(lock.sys_path)
         fib_mem.w_s('fib_Size', size)
     # date (use mtime here)
     date_addr = fib_mem.s_get_addr('fib_Date')
     date = AccessStruct(fib_mem.mem, DateStampDef, date_addr)
     t = os.path.getmtime(lock.sys_path)
     at = sys_to_ami_time(t)
     date.w_s('ds_Days', at.tday)
     date.w_s('ds_Minute', at.tmin)
     date.w_s('ds_Tick', at.tick)
     return NO_ERROR
Exemplo n.º 7
0
  def examine_lock(self, lock, fib_mem):
    # name
    name_addr = fib_mem.s_get_addr('fib_FileName')
    fib_mem.w_cstr(name_addr, lock.name)
    # dummy key
    fib_mem.w_s('fib_DiskKey',0xcafebabe)
    # type
    if os.path.isdir(lock.sys_path):
      dirEntryType = 0x2 # dir
    else:
      dirEntryType = 0xfffffffd # file
    fib_mem.w_s('fib_DirEntryType', dirEntryType )
    # protection
    prot = DosProtection(0)
    try:
      os_stat = os.stat(lock.sys_path)
      mode = os_stat.st_mode
      if mode & stat.S_IXUSR == 0:
        prot.clr(DosProtection.FIBF_EXECUTE)
      if mode & stat.S_IRUSR == 0:
        prot.clr(DosProtection.FIBF_READ)
      if mode & stat.S_IWUSR == 0:
        prot.clr(DosProtection.FIBF_WRITE)
      log_lock.debug("examine lock: '%s' mode=%03o: prot=%s", lock, mode, prot)
    except OSError:
      return ERROR_OBJECT_IN_USE
    fib_mem.w_s('fib_Protection', prot.mask)
    # size
    if os.path.isfile(lock.sys_path):
      size = os.path.getsize(lock.sys_path)
      fib_mem.w_s('fib_Size', size)
    # date (use mtime here)
    date_addr = fib_mem.s_get_addr('fib_Date')
    date = AccessStruct(fib_mem.mem, DateStampDef, date_addr)
    t = os.path.getmtime(lock.sys_path)
    at = sys_to_ami_time(t)
    date.w_s('ds_Days', at.tday)
    date.w_s('ds_Minute', at.tmin)
    date.w_s('ds_Tick', at.tick)
    return NO_ERROR

    
    
Exemplo n.º 8
0
class AmigaLibrary:

    op_rts = 0x4e75
    op_reset = 0x4e70

    def __init__(self, name, struct, config):
        self.name = name
        self.struct = struct
        self.config = config
        self.version = config.version

        # will be set by lib manager
        self.lib_mgr = None

        # lib flags
        self.is_native = False

        # stub generation flags
        self.profile = config.profile
        self.log_call = False
        self.log_dummy_call = False
        self.benchmark = False
        self.catch_ex = True

        # proposal of size
        self.pos_size = self.struct.get_size()
        self.neg_size = 0

        # (optional) fd describing lib functions
        self.fd = None
        # (optional) native segment list loaded for lib
        self.seg_list = None

        # --- setup state in memory ---
        # set memory version
        self.mem_version = 0
        # memory pos size and neg size
        # (might differ if native lib was loaded)
        self.mem_pos_size = 0
        self.mem_neg_size = 0
        # if lib is setup then a base address is assigned
        self.mem_obj = None
        self.addr_base = 0
        self.addr_begin = 0
        self.addr_end = 0
        self.addr_base_open = 0  # address returned by open as lib base
        self.label = None
        self.access = None
        self.lib_access = None
        # number of users opening the lib
        self.ref_cnt = 0
        # also an access object for the lib struct members are allocated

    def calc_neg_size_from_fd(self):
        """calc the neg size from the fd bias"""
        if self.fd == None:
            return
        max_bias = self.fd.get_max_bias()
        self.neg_size = max_bias + 6

    def use_sizes(self):
        self.mem_pos_size = self.pos_size
        self.mem_neg_size = self.neg_size

    def _get_class_methods(self):
        """return a map with method name to bound method mapping of this class"""
        members = inspect.getmembers(self, predicate=inspect.ismethod)
        result = {}
        for member in members:
            name = member[0]
            method = member[1]
            result[name] = method
        return result

    def get_callee_pc(self, ctx):
        """a call stub log helper to extract the callee's pc"""
        sp = ctx.cpu.r_reg(REG_A7)
        return ctx.mem.access.r32(sp)

    def _gen_arg_dump(self, args, ctx):
        """a call stub helper to dump the registers of a call"""
        if args == None or len(args) == 0:
            return ""
        result = []
        for a in args:
            name = a[0]
            reg = a[1]
            reg_num = int(reg[1])
            if reg[0] == 'a':
                reg_num += 8
            val = ctx.cpu.r_reg(reg_num)
            result.append("%s[%s]=%08x" % (name, reg, val))
        return ", ".join(result)

    def _generate_dummy_call_stub(self, ctx, bias, name, args=None):
        """a call stub for unsupported/empty functions.
       only trace the call and return D0=0
    """
        if self.log_dummy_call:

            def call_stub(op, pc):
                callee_pc = self.get_callee_pc(ctx)
                call_name = "%4d %s( %s ) from PC=%06x" % (
                    bias, name, self._gen_arg_dump(args, ctx), callee_pc)
                self.log("? CALL: %s -> d0=0 (default)" % call_name,
                         level=logging.WARN)
                ctx.cpu.w_reg(REG_D0, 0)
        else:

            def call_stub(op, pc):
                ctx.cpu.w_reg(REG_D0, 0)

        return call_stub

    def _generate_fast_call_stub(self, ctx, method):
        """generate a fast call stub without any processing"""
        def call_stub(op, pc):
            """the generic call stub: call python bound method and
         if return value exists then set it in CPU's D0 register"""
            d0 = method(ctx)
            if d0 != None:
                if d0.__class__.__name__ == 'list':
                    ctx.cpu.w_reg(REG_D0, d0[0] & 0xffffffff)
                    ctx.cpu.w_reg(REG_D1, d0[1] & 0xffffffff)
                else:
                    ctx.cpu.w_reg(REG_D0, d0 & 0xffffffff)

        return call_stub

    def _generate_call_stub(self, ctx, bias, name, method=None, args=None):
        """generate a call stub for this given bound method
       this will construct a small wrapper around the library call to be set as a trap
       function.
       For unknown methods it can generate a default/dummy stub that only set d0=0
    """
        # if no method is available then generate a dummy stub
        if method == None:
            return self._generate_dummy_call_stub(ctx, bias, name, args)

        # if extra processing is disabled then create a compact call stub
        if not self.log_call and not self.benchmark and not self.profile and not self.catch_ex:
            return self._generate_fast_call_stub(ctx, method)

        # ... otherwise processing is enabled and we need to synthesise a
        # suitable call stub
        need_timing = self.benchmark or self.profile
        code = ["def call_stub(op, pc):"]

        # logging (begin)
        if self.log_call:
            code.append('  callee_pc = self.get_callee_pc(ctx)')
            code.append(
                '  call_name = "%4d %s( %s ) from PC=%06x" % (bias, name, self._gen_arg_dump(args, ctx), callee_pc)'
            )
            code.append(
                '  self.log("{ CALL: %s" % call_name, level=logging.INFO)')

        # timing code
        if need_timing:
            code.append('  start = time.clock()')

        # main call: call method and evaluate result
        code.append('  d0 = method(ctx)')
        code.append('  if d0 != None:')
        code.append('    ctx.cpu.w_reg(REG_D0, d0)')

        # timing code
        if need_timing:
            code.append('  end = time.clock()')
            code.append('  delta = end - start')
        if self.profile:
            code.append('  self._account_profile_data(name, delta)')
        if self.benchmark:
            code.append('  self._account_benchmark_data(delta)')

        # logging (end)
        if self.log_call:
            code.append(
                '  self.log("} END CALL: d0=%s (default)" % (d0), level=logging.INFO)'
            )

        # wrap exception handler
        if self.catch_ex:
            c = [code[0]]
            c.append("  try:")
            for l in code[1:]:
                c.append("  " + l)
            c.append("  except:")
            c.append("    self._handle_exc()")
            code = c

        # generate code
        l = {}
        l.update(globals())
        l.update(locals())
        exec "\n".join(code) in l
        return l['call_stub']

    def trap_lib_entry(self, ctx, bias, name, method=None, args=None):
        """generate a trap in the library's jump table.
       returns True if trap was applied or False if no trap could be setup
    """
        # get call stub
        call_stub = self._generate_call_stub(ctx, bias, name, method, args)
        # allocate a trap
        tid = ctx.traps.setup(call_stub, auto_rts=True)
        if tid < 0:
            self.log("patch $%04x: '%s' -> NO TRAP AVAILABLE" % (bias, name),
                     level=logging.ERROR)
            return False
        # generate opcode
        op = 0xa000 | tid
        # patch the lib in memory
        addr = self.addr_base - bias
        ctx.mem.access.w16(addr, op)
        self.log("patch $%04x: op=$%04x '%s' [%s]" % (bias, op, name, method),
                 level=logging.DEBUG)
        return True

    def trap_class_entries(self, ctx, add_private=False):
        """look up all names (from fd) and if members of this class match then trap the function.

       return (number of methods patched, number of dummies patched)
    """
        # this works only with fd file!
        if self.fd == None:
            return None

        # build a map: bias -> func name
        bias_map = {}
        for f in self.fd.get_funcs():
            # add public and optionally private API calls
            if add_private or not f.is_private():
                bias_map[f.get_bias()] = (f.get_name(), f.get_args())

        # get all implemented class methods
        method_map = self._get_class_methods()

        # loop over all biases
        bias = 6
        addr = self.addr_base - bias
        num_dummy = 0
        num_method = 0
        while bias < self.neg_size:
            # bias is found in FD
            if bias in bias_map:
                # get function name from FD
                info = bias_map[bias]
                name = info[0]
                args = info[1]
                # is a method implemented in this class?
                if name in method_map:
                    method = method_map[name]
                    num_method += 1
                else:
                    method = None
                    num_dummy += 1

                # now trap entry
                self.trap_lib_entry(ctx, bias, name, method, args)

            addr -= 6
            bias += 6

        return (num_method, num_dummy)

    def create_empty_jump_table(self, ctx):
        """create a table full of RESET opcodes for all function entries"""
        bias = 6
        addr = self.addr_base
        while bias < self.neg_size:
            ctx.mem.access.w16(addr, self.op_reset)
            bias += 6
            addr -= 6

    def __str__(self):
        return "[Lib %s V%d {+%d -%d} mem: V%d {+%d -%d} <%08x, %08x, %08x> open_base=%08x ref_cnt=%d]" % \
          (self.name, self.version,
           self.pos_size, self.neg_size,
           self.mem_version,
           self.mem_pos_size, self.mem_neg_size,
           self.addr_begin, self.addr_base, self.addr_end,
           self.addr_base_open, self.ref_cnt)

    def alloc_lib_base(self, ctx):
        """alloc memory for the library base"""
        # alloc memory - use an Exec AllocMemory() compatible scheme here
        # as the lib might get free'd by library code and calling FreeMem()
        lib_size = self.mem_neg_size + self.mem_pos_size
        tag = "LibBase(%s)" % self.name
        self.mem_obj = ctx.alloc.alloc_memory(tag, lib_size, add_label=False)
        self.addr_begin = self.mem_obj.addr
        self.addr_base = self.addr_begin + self.mem_neg_size
        self.addr_end = self.addr_base + self.mem_pos_size
        self.addr_base_open = self.addr_base
        # create memory label
        self.label = LabelLib(self.name, self.addr_begin, lib_size,
                              self.addr_base, self.struct, self)
        ctx.label_mgr.add_label(self.label)
        # create access
        self.access = AccessStruct(ctx.mem, self.struct, self.addr_base)
        self.lib_access = AccessStruct(ctx.mem, LibraryDef, self.addr_base)

    def free_lib_base(self, ctx, free_alloc=True):
        """free memory for the library base"""
        # free memory
        if free_alloc:
            ctx.alloc.free_memory(self.mem_obj)
        ctx.label_mgr.remove_label(self.label)
        # clean up
        self.mem_obj = None
        self.addr_begin = 0
        self.addr_base = 0
        self.addr_end = 0
        self.addr_base_open = 0
        self.label = None
        self.access = None
        self.lib_access = None

    def fill_lib_struct(self):
        # now we can fill the library structure with some sane values
        self.lib_access.w_s("lib_Version", self.mem_version)
        self.lib_access.w_s("lib_PosSize", self.mem_pos_size)
        self.lib_access.w_s("lib_NegSize", self.mem_neg_size)
        self.lib_access.w_s("lib_OpenCnt", 0)

    def setup_lib(self, ctx):
        """the lib is now used in memory at the given base address"""
        self.ref_cnt = 0
        # enable profiling
        if self.profile:
            self.profile_map = {}

    def finish_lib(self, ctx):
        """the lib is no longer used in memory"""
        if self.ref_cnt != 0:
            self.log("lib ref count != 0: %d" % self.ref_cnt,
                     level=logging.ERROR)

        # dump profile
        if self.profile:
            self.dump_profile()

    def inc_usage(self):
        """increment usage counter"""
        self.ref_cnt += 1
        self.lib_access.w_s("lib_OpenCnt", self.ref_cnt)

    def dec_usage(self):
        """decrement usage counter"""
        self.ref_cnt -= 1
        self.lib_access.w_s("lib_OpenCnt", self.ref_cnt)

    def get_num_vectors(self):
        return self.num_jumps

    def get_struct(self):
        return self.struct

    def get_name(self):
        return self.name

    def get_version(self):
        return self.version

    def get_total_size(self):
        return self.neg_size + self.pos_size

    def get_neg_size(self):
        return self.neg_size

    def get_pos_size(self):
        return self.pos_size

    def log(self, text, level=logging.INFO):
        log_lib.log(level, "[%16s]  %s", self.name, text)

    def dump_profile(self):
        log_prof.info("'%s' Function Call Profile", self.name)
        funcs = sorted(self.profile_map.keys())
        sum_total = 0.0
        for f in funcs:
            entry = self.profile_map[f]
            cnt = entry[1]
            total = entry[0]
            per_call = total / cnt
            log_prof.info("  %20s: #%8d  total=%10.4f  per call=%10.4f", f,
                          cnt, total, per_call)
            sum_total += total
        log_prof.info("sum total=%.4f", sum_total)

    def _account_profile_data(self, func, delta):
        """account profiling data for the current function"""
        if self.profile_map.has_key(func):
            entry = self.profile_map[func]
            entry[0] += delta
            entry[1] += 1
        else:
            entry = [delta, 1]
            self.profile_map[func] = entry

    def _account_benchmark_data(self, delta):
        self.lib_mgr.bench_total += delta

    def _handle_exc(self):
        """handle an exception that occurred inside the call stub's python code"""
        sys.stderr.write("\n**** Unexpected exception in library stub: %s " %
                         sys.exc_info()[1])
        traceback.print_tb(sys.exc_info()[2], file=sys.stderr)
        sys.stderr.write("\n")
        raise
Exemplo n.º 9
0
class AmigaLibrary:

  op_rts = 0x4e75
  op_reset = 0x4e70

  def __init__(self, name, struct, config):
    self.name = name
    self.struct = struct
    self.config = config
    self.version = config.version

    # will be set by lib manager
    self.lib_mgr = None

    # lib flags
    self.is_native = False

    # stub generation flags
    self.profile = config.profile
    self.log_call = False
    self.log_dummy_call = False
    self.benchmark = False
    self.catch_ex = True

    # proposal of size
    self.pos_size = self.struct.get_size()
    self.neg_size = 0

    # (optional) fd describing lib functions
    self.fd = None
    # (optional) native segment list loaded for lib
    self.seg_list = None

    # --- setup state in memory ---
    # set memory version
    self.mem_version = 0
    # memory pos size and neg size
    # (might differ if native lib was loaded)
    self.mem_pos_size = 0
    self.mem_neg_size = 0
    # if lib is setup then a base address is assigned
    self.mem_obj = None
    self.addr_base = 0
    self.addr_begin = 0
    self.addr_end = 0
    self.addr_base_open = 0 # address returned by open as lib base
    self.label = None
    self.access = None
    self.lib_access = None
    # number of users opening the lib
    self.ref_cnt = 0
    self.lock_in_memory = False
    self.traps = []
    # also an access object for the lib struct members are allocated

  def calc_neg_size_from_fd(self):
    """calc the neg size from the fd bias"""
    if self.fd == None:
      return
    max_bias = self.fd.get_max_bias()
    self.neg_size = max_bias + 6

  def use_sizes(self):
    self.mem_pos_size = self.pos_size
    self.mem_neg_size = self.neg_size

  def _get_class_methods(self):
    """return a map with method name to bound method mapping of this class"""
    members = inspect.getmembers(self, predicate=inspect.ismethod)
    result = {}
    for member in members:
      name = member[0]
      method = member[1]
      result[name] = method
    return result

  def get_callee_pc(self,ctx):
    """a call stub log helper to extract the callee's pc"""
    sp = ctx.cpu.r_reg(REG_A7)
    return ctx.mem.access.r32(sp)

  def _gen_arg_dump(self,args,ctx):
    """a call stub helper to dump the registers of a call"""
    if args == None or len(args) == 0:
      return ""
    result = []
    for a in args:
      name = a[0]
      reg = a[1]
      reg_num = int(reg[1])
      if reg[0] == 'a':
        reg_num += 8
      val = ctx.cpu.r_reg(reg_num)
      result.append("%s[%s]=%08x" % (name,reg,val))
    return ", ".join(result)

  def _generate_dummy_call_stub(self, ctx, bias, name, args=None):
    """a call stub for unsupported/empty functions.
       only trace the call and return D0=0
    """
    if self.log_dummy_call:
      def call_stub(op, pc):
        callee_pc = self.get_callee_pc(ctx)
        call_name = "%4d %s( %s ) from PC=%06x" % (bias, name, self._gen_arg_dump(args, ctx), callee_pc)
        self.log("? CALL: %s -> d0=0 (default)" % call_name, level=logging.WARN)
        ctx.cpu.w_reg(REG_D0, 0)
    else:
      def call_stub(op, pc):
        ctx.cpu.w_reg(REG_D0, 0)
    return call_stub

  def _generate_fast_call_stub(self, ctx, method):
    """generate a fast call stub without any processing"""
    def call_stub(op, pc):
      """the generic call stub: call python bound method and
         if return value exists then set it in CPU's D0 register"""
      d0 = method(ctx)
      if d0 != None:
        if d0.__class__.__name__ == 'list':
          ctx.cpu.w_reg(REG_D0, d0[0] & 0xffffffff)
          ctx.cpu.w_reg(REG_D1, d0[1] & 0xffffffff)
        else:
          ctx.cpu.w_reg(REG_D0, d0 & 0xffffffff)
    return call_stub

  def _generate_call_stub(self, ctx, bias, name, method=None, args=None):
    """generate a call stub for this given bound method
       this will construct a small wrapper around the library call to be set as a trap
       function.
       For unknown methods it can generate a default/dummy stub that only set d0=0
    """
    # if no method is available then generate a dummy stub
    if method == None:
      return self._generate_dummy_call_stub(ctx, bias, name, args)

    # if extra processing is disabled then create a compact call stub
    if not self.log_call and not self.benchmark and not self.profile and not self.catch_ex:
      return self._generate_fast_call_stub(ctx, method)

    # ... otherwise processing is enabled and we need to synthesise a
    # suitable call stub
    need_timing = self.benchmark or self.profile
    code = ["def call_stub(op, pc):"]

    # logging (begin)
    if self.log_call:
      code.append('  callee_pc = self.get_callee_pc(ctx)')
      code.append('  call_name = "%4d %s( %s ) from PC=%06x" % (bias, name, self._gen_arg_dump(args, ctx), callee_pc)')
      code.append('  self.log("{ CALL: %s" % call_name, level=logging.INFO)')

    # timing code
    if need_timing:
      code.append('  start = time.clock()')

    # main call: call method and evaluate result
    code.append('  d0 = method(ctx)')
    code.append('  if d0 != None:')
    code.append('    ctx.cpu.w_reg(REG_D0, d0)')

    # timing code
    if need_timing:
      code.append('  end = time.clock()')
      code.append('  delta = end - start')
    if self.profile:
      code.append('  self._account_profile_data(name, delta)')
    if self.benchmark:
      code.append('  self._account_benchmark_data(delta)')

    # logging (end)
    if self.log_call:
      code.append('  self.log("} END CALL: d0=%s (default)" % (d0), level=logging.INFO)')

    # wrap exception handler
    if self.catch_ex:
      c = [code[0]]
      c.append("  try:")
      for l in code[1:]:
        c.append("  " + l)
      c.append("  except:")
      c.append("    self._handle_exc()")
      code = c

    # generate code
    l = {}
    l.update(globals())
    l.update(locals())
    exec "\n".join(code) in l
    return l['call_stub']

  def trap_lib_entry(self, ctx, bias, name, method=None, args=None):
    """generate a trap in the library's jump table.
       returns True if trap was applied or False if no trap could be setup
    """
    # get call stub
    call_stub = self._generate_call_stub(ctx, bias, name, method, args)
    # allocate a trap
    tid = ctx.traps.setup(call_stub, auto_rts=True)
    if tid < 0:
      self.log("patch $%04x: '%s' -> NO TRAP AVAILABLE" % (bias, name), level=logging.ERROR)
      return False
    # generate opcode
    op = 0xa000 | tid
    # patch the lib in memory
    addr = self.addr_base - bias
    self.traps.append(tid)
    ctx.mem.access.w16(addr,op)
    self.log("patch $%04x: op=$%04x '%s' [%s]" % (bias, op, name, method), level=logging.DEBUG)
    return True

  def trap_class_entries(self, ctx, add_private=False):
    """look up all names (from fd) and if members of this class match then trap the function.

       return (number of methods patched, number of dummies patched)
    """
    # this works only with fd file!
    if self.fd == None:
      return None

    # build a map: bias -> func name
    bias_map = {}
    for f in self.fd.get_funcs():
      # add public and optionally private API calls
      if add_private or not f.is_private():
        bias_map[f.get_bias()] = (f.get_name(), f.get_args())

    # get all implemented class methods
    method_map = self._get_class_methods()

    # loop over all biases
    bias = 6
    addr = self.addr_base - bias
    num_dummy = 0
    num_method = 0
    while bias < self.neg_size:
      # bias is found in FD
      if bias in bias_map:
        # get function name from FD
        info = bias_map[bias]
        name = info[0]
        args = info[1]
        # is a method implemented in this class?
        if name in method_map:
          method = method_map[name]
          num_method += 1
        else:
          method = None
          num_dummy += 1

        # now trap entry
        self.trap_lib_entry(ctx, bias, name, method, args)

      addr -= 6
      bias += 6

    return (num_method, num_dummy)

  def create_empty_jump_table(self, ctx):
    """create a table full of RESET opcodes for all function entries"""
    bias = 6
    addr = self.addr_base
    while bias < self.neg_size:
      ctx.mem.access.w16(addr,self.op_reset)
      bias += 6
      addr -= 6

  def __str__(self):
    return "[Lib %s V%d {+%d -%d} mem: V%d {+%d -%d} <%08x, %08x, %08x> open_base=%08x ref_cnt=%d]" % \
      (self.name, self.version,
       self.pos_size, self.neg_size,
       self.mem_version,
       self.mem_pos_size, self.mem_neg_size,
       self.addr_begin, self.addr_base, self.addr_end,
       self.addr_base_open, self.ref_cnt)

  def alloc_lib_base(self, ctx):
    """alloc memory for the library base"""
    # alloc memory - use an Exec AllocMemory() compatible scheme here
    # as the lib might get free'd by library code and calling FreeMem()
    lib_size = self.mem_neg_size + self.mem_pos_size
    tag = "LibBase(%s)" % self.name
    self.mem_obj = ctx.alloc.alloc_memory(tag, lib_size, add_label=False)
    self.addr_begin = self.mem_obj.addr
    self.addr_base = self.addr_begin + self.mem_neg_size
    self.addr_end = self.addr_base + self.mem_pos_size
    self.addr_base_open = self.addr_base
    # create memory label
    self.label = LabelLib(self.name, self.addr_begin, lib_size, self.addr_base, self.struct, self)
    ctx.label_mgr.add_label(self.label)
    # create access
    self.access = AccessStruct(ctx.mem, self.struct, self.addr_base)
    self.lib_access = AccessStruct(ctx.mem, LibraryDef, self.addr_base)

  def free_lib_base(self, ctx, free_alloc=True):
    """free memory for the library base"""
    # free memory
    if free_alloc:
      ctx.alloc.free_memory(self.mem_obj)
    ctx.label_mgr.remove_label(self.label)
    # clean up
    self.mem_obj = None
    self.addr_begin = 0
    self.addr_base = 0
    self.addr_end = 0
    self.addr_base_open = 0
    self.label = None
    self.access = None
    self.lib_access = None
    # release all the traps so they can be recycled
    for tid in self.traps:
      ctx.traps.free(tid)
    self.traps = []

  def fill_lib_struct(self):
    # now we can fill the library structure with some sane values
    self.lib_access.w_s("lib_Version", self.mem_version)
    self.lib_access.w_s("lib_PosSize", self.mem_pos_size)
    self.lib_access.w_s("lib_NegSize", self.mem_neg_size)
    self.lib_access.w_s("lib_OpenCnt", 0)

  def setup_lib(self, ctx, lock_in_memory = False):
    """the lib is now used in memory at the given base address"""
    self.ref_cnt = 0
    # enable profiling
    if self.profile:
      self.profile_map = {}
    # If the library is a system library, better lock it in memory
    # to ensure that the traps are kept.
    self.lock_in_memory = lock_in_memory

  def finish_lib(self, ctx):
    """the lib is no longer used in memory"""
    if self.ref_cnt != 0:
      self.log("lib ref count != 0: %d" % self.ref_cnt, level=logging.ERROR)

    # dump profile
    if self.profile:
      self.dump_profile()

  def inc_usage(self):
    """increment usage counter"""
    self.ref_cnt += 1
    self.lib_access.w_s("lib_OpenCnt", self.ref_cnt)

  def dec_usage(self):
    """decrement usage counter"""
    if not self.lock_in_memory:
      self.ref_cnt -= 1
      self.lib_access.w_s("lib_OpenCnt", self.ref_cnt)

  def get_num_vectors(self):
    return self.num_jumps

  def get_struct(self):
    return self.struct

  def get_name(self):
    return self.name

  def get_version(self):
    return self.version

  def get_total_size(self):
    return self.neg_size + self.pos_size

  def get_neg_size(self):
    return self.neg_size

  def get_pos_size(self):
    return self.pos_size

  def log(self, text, level=logging.INFO):
    log_lib.log(level, "[%16s]  %s", self.name, text)

  def dump_profile(self):
    log_prof.info("'%s' Function Call Profile", self.name)
    funcs = sorted(self.profile_map.keys())
    sum_total = 0.0
    for f in funcs:
      entry = self.profile_map[f]
      cnt = entry[1]
      total = entry[0]
      per_call = total / cnt
      log_prof.info("  %20s: #%8d  total=%10.4f  per call=%10.4f", f, cnt, total, per_call)
      sum_total += total
    log_prof.info("sum total=%.4f",sum_total)

  def _account_profile_data(self, func, delta):
    """account profiling data for the current function"""
    if self.profile_map.has_key(func):
      entry = self.profile_map[func]
      entry[0] += delta
      entry[1] += 1
    else:
      entry = [delta, 1]
      self.profile_map[func] = entry

  def _account_benchmark_data(self, delta):
    self.lib_mgr.bench_total += delta

  def _handle_exc(self):
    """handle an exception that occurred inside the call stub's python code"""
    sys.stderr.write("\n**** Unexpected exception in library stub: %s " % sys.exc_info()[1])
    traceback.print_tb(sys.exc_info()[2],file=sys.stderr)
    sys.stderr.write("\n")
    raise