Exemple #1
0
 def __init__(self,
              snapshot,
              ctl_parser,
              config=None,
              final=False,
              defb_size=8,
              defb_mod=1,
              zfill=False,
              defm_width=66,
              asm_hex=False,
              asm_lower=False):
     ctl_parser.apply_asm_data_directives(snapshot)
     self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill,
                                      defm_width, asm_hex, asm_lower)
     self.ctl_parser = ctl_parser
     if asm_hex:
         if asm_lower:
             self.address_fmt = '{0:04x}'
         else:
             self.address_fmt = '{0:04X}'
     else:
         self.address_fmt = '{0}'
     self.entry_map = {}
     self.config = config or {}
     self.build(final)
Exemple #2
0
def _get_code_blocks(snapshot, start, end, fname):
    if os.path.isdir(fname):
        raise SkoolKitError('{0} is a directory'.format(fname))
    try:
        size = os.path.getsize(fname)
    except OSError as e:
        if e.errno == 2:
            raise SkoolKitError('{0}: file not found'.format(fname))
        raise SkoolKitError('Failed to get size of {}: {}'.format(
            fname, e.strerror))

    if size == 8192:
        # Assume this is a Z80 map file
        sys.stderr.write('Reading {0}'.format(fname))
        sys.stderr.flush()
        addresses = []
        data = read_bin_file(fname)
        address = start & 65528
        for b in data[start // 8:end // 8 + 1]:
            for i in range(8):
                if b & 1 and start <= address < end:
                    addresses.append(address)
                b >>= 1
                address += 1
    elif size == 65536:
        # Assume this is a SpecEmu map file
        sys.stderr.write('Reading {}'.format(fname))
        sys.stderr.flush()
        addresses = []
        data = read_bin_file(fname)
        for address in range(start, end):
            if data[address] & 1:
                addresses.append(address)
    else:
        sys.stderr.write('Reading {0}: '.format(fname))
        sys.stderr.flush()
        with open_file(fname) as f:
            addresses = _get_addresses(f, fname, size, start, end)
    sys.stderr.write('\n')

    code_blocks = []
    disassembler = Disassembler(snapshot)
    for address in addresses:
        size = disassembler.disassemble(address, address + 1)[0].size()
        if code_blocks and address <= sum(code_blocks[-1]):
            if address == sum(code_blocks[-1]):
                code_blocks[-1][1] += size
        else:
            code_blocks.append([address, size])

    return code_blocks
Exemple #3
0
def _get_code_blocks(snapshot, start, end, fname):
    if os.path.isdir(fname):
        raise SkoolKitError('{0} is a directory'.format(fname))
    try:
        size = os.path.getsize(fname)
    except OSError as e:
        if e.errno == 2:
            raise SkoolKitError('{0}: file not found'.format(fname))
        raise # pragma: no cover

    if size == 8192:
        # Assume this is a Z80 map file
        sys.stderr.write('Reading {0}'.format(fname))
        sys.stderr.flush()
        addresses = []
        data = read_bin_file(fname)
        address = start & 65528
        for b in data[start // 8:end // 8 + 1]:
            for i in range(8):
                if b & 1 and start <= address < end:
                    addresses.append(address)
                b >>= 1
                address += 1
    elif size == 65536:
        # Assume this is a SpecEmu map file
        sys.stderr.write('Reading {}'.format(fname))
        sys.stderr.flush()
        addresses = []
        data = read_bin_file(fname)
        for address in range(start, end):
            if data[address] & 1:
                addresses.append(address)
    else:
        sys.stderr.write('Reading {0}: '.format(fname))
        sys.stderr.flush()
        with open_file(fname) as f:
            addresses = _get_addresses(f, fname, size, start, end)
    sys.stderr.write('\n')

    code_blocks = []
    disassembler = Disassembler(snapshot)
    for address in addresses:
        size = disassembler.disassemble(address, address + 1)[0].size()
        if code_blocks and address <= sum(code_blocks[-1]):
            if address == sum(code_blocks[-1]):
                code_blocks[-1][1] += size
        else:
            code_blocks.append([address, size])

    return code_blocks
Exemple #4
0
 def __init__(self,
              snapshot,
              sftfile,
              zfill=False,
              asm_hex=False,
              asm_lower=False):
     self.snapshot = snapshot
     self.disassembler = Disassembler(snapshot,
                                      zfill=zfill,
                                      asm_hex=asm_hex,
                                      asm_lower=asm_lower)
     self.sftfile = sftfile
     self.address_fmt = get_address_format(asm_hex, asm_lower)
     self.stack = []
     self.disassemble = True
Exemple #5
0
 def __init__(self, snapshot, sftfile, zfill=False, asm_hex=False, asm_lower=False):
     self.snapshot = snapshot
     self.disassembler = Disassembler(snapshot, zfill=zfill, asm_hex=asm_hex, asm_lower=asm_lower)
     self.sftfile = sftfile
     self.address_fmt = get_address_format(asm_hex, asm_lower)
     self.stack = []
     self.disassemble = True
Exemple #6
0
 def __init__(self, snapshot, ctl_parser, final=False, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False):
     self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower)
     self.ctl_parser = ctl_parser
     if asm_hex:
         if asm_lower:
             self.address_fmt = '{0:04x}'
         else:
             self.address_fmt = '{0:04X}'
     else:
         self.address_fmt = '{0}'
     self.entry_map = {}
     self.build(final)
Exemple #7
0
 def __init__(self, snapshot, ctl_parser, config=None, final=False, defb_size=8, defb_mod=1,
              zfill=False, defm_width=66, asm_hex=False, asm_lower=False):
     ctl_parser.apply_asm_data_directives(snapshot)
     self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower)
     self.ctl_parser = ctl_parser
     if asm_hex:
         if asm_lower:
             self.address_fmt = '{0:04x}'
         else:
             self.address_fmt = '{0:04X}'
     else:
         self.address_fmt = '{0}'
     self.entry_map = {}
     self.config = config or {}
     self.build(final)
Exemple #8
0
class SftParser:
    def __init__(self,
                 snapshot,
                 sftfile,
                 zfill=False,
                 asm_hex=False,
                 asm_lower=False):
        self.snapshot = snapshot
        self.disassembler = Disassembler(snapshot,
                                         zfill=zfill,
                                         asm_hex=asm_hex,
                                         asm_lower=asm_lower)
        self.sftfile = sftfile
        self.address_fmt = get_address_format(asm_hex, asm_lower)
        self.stack = []
        self.disassemble = True

    def _parse_instruction(self, line):
        ctl = line[0]
        lengths = []
        if line[1] == ';':
            inst_ctl = None
            start = None
            i = line.find(' ', 2)
            if i < 0:
                i = len(line.rstrip())
            comment_index = get_int_param(line[2:i])
        else:
            inst_ctl = line[1]
            if inst_ctl == 'I':
                i = find_unquoted(line.rstrip(), ' ', 2)
                address_end = j = find_unquoted(line, ';', 2, i)
            else:
                address_end = line.index(',', 2)
                i = find_unquoted(line.rstrip(), ' ', address_end + 1)
                j = find_unquoted(line, ';', address_end + 1, i)
            start = get_int_param(line[2:address_end])
            if j == i:
                comment_index = -1
            else:
                comment_index = get_int_param(line[j + 1:i])
            if j > address_end + 1:
                params = split_unquoted(line[address_end + 1:j], ',')
                lengths = parse_params(inst_ctl, params, 0)
            elif inst_ctl != 'I':
                raise ValueError

        comment = line[i:].strip()
        return ctl, inst_ctl, start, lengths, comment_index, comment

    def _parse_asm_directive(self, directive):
        if parse_asm_block_directive(directive, self.stack):
            self.disassemble = True
            for _p, i in self.stack:
                if i != '-':
                    self.disassemble = False
                    break

    def _set_bytes(self, line):
        address = parse_int(line[1:6])
        if address is not None:
            comment_index = find_unquoted(line, ';')
            operation = line[7:comment_index].strip()
            set_bytes(self.snapshot, address, operation)

    def _parse_sft(self, min_address, max_address):
        start_index = -1
        lines = []
        v_block_ctl = None
        f = open_file(self.sftfile)
        for line in f:
            if line.startswith('#'):
                # This line is a skool file template comment
                continue

            if not line.strip():
                # This line is blank
                lines.append(VerbatimLine(line))
                v_block_ctl = None
                continue

            if line.startswith(';'):
                # This line is an entry-level comment
                lines.append(VerbatimLine(line))
                continue

            if line.startswith('@'):
                lines.append(VerbatimLine(line))
                self._parse_asm_directive(line[1:].rstrip())
                continue

            if not self.disassemble:
                # This line is inside a '+' block, so include it as is
                lines.append(VerbatimLine(line))
                continue

            # Check whether we're in a block that should be restored verbatim
            if v_block_ctl is None and line.startswith(VERBATIM_BLOCKS):
                v_block_ctl = line[0]
            if v_block_ctl:
                if v_block_ctl == 'd':
                    self._set_bytes(line)
                lines.append(VerbatimLine(line))
                continue

            # Check whether the line starts with a valid character
            if line[0] not in VALID_CTLS:
                lines.append(VerbatimLine(line))
                continue

            try:
                ctl, inst_ctl, start, lengths, comment_index, comment = self._parse_instruction(
                    line)
            except (IndexError, ValueError):
                raise SftParsingError("Invalid line: {0}".format(
                    line.split()[0]))
            if start is not None:
                # This line contains a control directive
                if start >= min_address > 0 and start_index < 0:
                    start_index = len(lines)
                instructions = []
                for length, sublengths in lengths:
                    end = start + length
                    if inst_ctl == 'C':
                        base = sublengths[0][1]
                        instructions += self.disassembler.disassemble(
                            start, end, base)
                    elif inst_ctl == 'W':
                        instructions += self.disassembler.defw_range(
                            start, end, sublengths)
                    elif inst_ctl == 'T':
                        instructions += self.disassembler.defm_range(
                            start, end, sublengths)
                    elif inst_ctl == 'S':
                        instructions.append(
                            self.disassembler.defs(start, end, sublengths))
                    else:
                        instructions += self.disassembler.defb_range(
                            start, end, sublengths)
                    start += length
                if instructions:
                    done = False
                    for instruction in instructions:
                        if instruction.address >= max_address:
                            while lines and lines[-1].is_trimmable():
                                lines.pop()
                            done = True
                            break
                        address = self.address_fmt.format(instruction.address)
                        lines.append(
                            InstructionLine(ctl, address,
                                            instruction.operation,
                                            comment_index, comment))
                        ctl = ' '
                    if done:
                        break
                else:
                    lines.append(
                        InstructionLine(ctl, start, '', comment_index,
                                        comment))
            else:
                # This line is an instruction-level comment continuation line
                lines.append(
                    InstructionLine(comment_index=comment_index,
                                    comment=comment))
        f.close()

        if start_index < 0:
            return lines
        if start_index < len(lines):
            if str(lines[start_index])[0] in DIRECTIVES:
                while start_index > 0 and not lines[start_index].is_blank():
                    start_index -= 1
            else:
                while start_index < len(
                        lines) and not lines[start_index].is_blank():
                    start_index += 1
            return lines[start_index + 1:]
        return []

    def write_skool(self, min_address=0, max_address=65536):
        for line in self._parse_sft(min_address, max_address):
            write_line(str(line))
Exemple #9
0
class Disassembly:
    def __init__(self,
                 snapshot,
                 ctl_parser,
                 config=None,
                 final=False,
                 defb_size=8,
                 defb_mod=1,
                 zfill=False,
                 defm_width=66,
                 asm_hex=False,
                 asm_lower=False):
        ctl_parser.apply_asm_data_directives(snapshot)
        self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill,
                                         defm_width, asm_hex, asm_lower)
        self.ctl_parser = ctl_parser
        if asm_hex:
            if asm_lower:
                self.address_fmt = '{0:04x}'
            else:
                self.address_fmt = '{0:04X}'
        else:
            self.address_fmt = '{0}'
        self.entry_map = {}
        self.config = config or {}
        self.build(final)

    def build(self, final=False):
        self.instructions = {}
        self.entries = []
        self._create_entries()
        if self.entries:
            self.org = self.entries[0].address
        else:
            self.org = None
        if final:
            self._calculate_references()

    def _create_entries(self):
        for block in self.ctl_parser.get_blocks():
            if block.start in self.entry_map:
                entry = self.entry_map[block.start]
                self.entries.append(entry)
                for instruction in entry.instructions:
                    self.instructions[instruction.address] = instruction
                continue
            title = block.title
            if not title:
                ctl = block.ctl
                if ctl != 'i' or block.description or block.registers or block.blocks[
                        0].header:
                    name = 'Title-' + ctl
                    title = format_template(self.config.get(name, ''),
                                            name,
                                            address=self._address_str(
                                                block.start))
            for sub_block in block.blocks:
                address = sub_block.start
                if sub_block.ctl in 'cBT':
                    base = sub_block.sublengths[0][1]
                    instructions = self.disassembler.disassemble(
                        sub_block.start, sub_block.end, base)
                elif sub_block.ctl in 'bgstuw':
                    sublengths = sub_block.sublengths
                    if sublengths[0][0]:
                        if sub_block.ctl == 's':
                            length = sublengths[0][0]
                        else:
                            length = sum([s[0] for s in sublengths])
                    else:
                        length = sub_block.end - sub_block.start
                    instructions = []
                    while address < sub_block.end:
                        end = min(address + length, sub_block.end)
                        if sub_block.ctl == 't':
                            instructions += self.disassembler.defm_range(
                                address, end, sublengths)
                        elif sub_block.ctl == 'w':
                            instructions += self.disassembler.defw_range(
                                address, end, sublengths)
                        elif sub_block.ctl == 's':
                            instructions.append(
                                self.disassembler.defs(address, end,
                                                       sublengths))
                        else:
                            instructions += self.disassembler.defb_range(
                                address, end, sublengths)
                        address += length
                else:
                    instructions = self.disassembler.ignore(
                        sub_block.start, sub_block.end)
                sub_block.instructions = instructions
                for instruction in instructions:
                    self.instructions[instruction.address] = instruction
                    instruction.asm_directives = sub_block.asm_directives.get(
                        instruction.address, ())

            sub_blocks = []
            i = 0
            while i < len(block.blocks):
                sub_block = block.blocks[i]
                i += 1
                sub_blocks.append(sub_block)
                if sub_block.multiline_comment is not None:
                    end, sub_block.comment = sub_block.multiline_comment
                    while i < len(
                            block.blocks) and block.blocks[i].start < end:
                        next_sub_block = block.blocks[i]
                        sub_block.instructions += next_sub_block.instructions
                        sub_block.end = next_sub_block.end
                        i += 1

            entry = Entry(block.header, title, block.description, block.ctl,
                          sub_blocks, block.registers, block.end_comment,
                          block.footer, block.asm_directives,
                          block.ignoreua_directives)
            self.entry_map[entry.address] = entry
            self.entries.append(entry)
        for i, entry in enumerate(self.entries[1:]):
            self.entries[i].next = entry

    def remove_entry(self, address):
        if address in self.entry_map:
            del self.entry_map[address]

    def _calculate_references(self):
        for entry in self.entries:
            for instruction in entry.instructions:
                instruction.referrers = []
        for entry in self.entries:
            for instruction in entry.instructions:
                operation = instruction.operation
                if operation.upper().startswith(
                    ('DJ', 'JR', 'JP', 'CA', 'RS')):
                    addr_str = get_address(operation)
                    if addr_str:
                        callee = self.instructions.get(parse_int(addr_str))
                        if callee:
                            callee.add_referrer(entry)

    def _address_str(self, address):
        return self.address_fmt.format(address)
Exemple #10
0
def _generate_ctls_with_code_map(snapshot, start, end, code_map):
    # (1) Use the code map to create an initial set of 'c' ctls, and mark all
    #     unexecuted blocks as 'U' (unknown)
    # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the
    #     next RET/JP/JR in the following 'U' blocks, or up to the next 'c'
    #     block
    # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c'
    #     blocks with 'c'
    # (4) Split 'c' blocks on RET/JP/JR
    # (5) Scan the disassembly for pairs of adjacent blocks where the start
    #     address of the second block is JRed or JPed to from the first block,
    #     and join such pairs
    # (6) Examine the remaining 'U' blocks for text
    # (7) Mark data blocks of all zeroes with 's'

    # (1) Mark all executed blocks as 'c' and unexecuted blocks as 'U'
    # (unknown)
    ctls = {start: 'U', end: 'i'}
    for address, length in _get_code_blocks(snapshot, start, end, code_map):
        ctls[address] = 'c'
        if address + length < end:
            ctls[address + length] = 'U'

    # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the
    # next RET/JP/JR in the following 'U' blocks, or up to the next 'c' block
    disassembler = Disassembler(snapshot)
    while 1:
        done = True
        for ctl, b_start, b_end in _get_blocks(ctls):
            if ctl == 'c':
                if _is_terminal_instruction(
                        disassembler.disassemble(b_start, b_end)[-1]):
                    continue
                if _find_terminal_instruction(disassembler, ctls, b_end,
                                              end) < end:
                    done = False
                    break
        if done:
            break

    # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c'
    # blocks with 'c'
    ctl_parser = CtlParser(ctls)
    disassembly = Disassembly(snapshot, ctl_parser)
    while 1:
        disassembly.build(True)
        done = True
        for entry in disassembly.entries:
            if entry.ctl == 'U':
                for instruction in entry.instructions:
                    for referrer in instruction.referrers:
                        if ctls[referrer.address] == 'c':
                            ctls[instruction.address] = 'c'
                            if entry.next:
                                e_end = entry.next.address
                            else:
                                e_end = 65536
                            _find_terminal_instruction(disassembler, ctls,
                                                       instruction.address,
                                                       e_end, entry.ctl)
                            disassembly.remove_entry(entry.address)
                            done = False
                            break
                    if not done:
                        break
                if not done:
                    break
        if done:
            break

    # (4) Split 'c' blocks on RET/JP/JR
    for ctl, b_address, b_end in _get_blocks(ctls):
        if ctl == 'c':
            next_address = _find_terminal_instruction(disassembler, ctls,
                                                      b_address, b_end, 'c')
            if next_address < b_end:
                disassembly.remove_entry(b_address)
                while next_address < b_end:
                    next_address = _find_terminal_instruction(
                        disassembler, ctls, next_address, b_end, 'c')

    # (5) Scan the disassembly for pairs of adjacent blocks where the start
    # address of the second block is JRed or JPed to from the first block, and
    # join such pairs
    while 1:
        disassembly.build()
        done = True
        for entry in disassembly.entries[:-1]:
            if entry.ctl == 'c':
                for instruction in entry.instructions:
                    operation = instruction.operation
                    if operation[:2] in ('JR', 'JP') and operation[-5:] == str(
                            entry.next.address):
                        del ctls[entry.next.address]
                        disassembly.remove_entry(entry.address)
                        disassembly.remove_entry(entry.next.address)
                        done = False
                        break
        if done:
            break

    # (6) Examine the 'U' blocks for text/data
    for ctl, b_start, b_end in _get_blocks(ctls):
        if ctl == 'U':
            ctls[b_start] = 'b'
            for t_start, t_end in _get_text_blocks(snapshot, b_start, b_end):
                ctls[t_start] = 't'
                if t_end < b_end:
                    ctls[t_end] = 'b'

    # (7) Mark data blocks of all zeroes with 's'
    for ctl, b_start, b_end in _get_blocks(ctls):
        if ctl == 'b':
            z_end = b_start
            while z_end < b_end and snapshot[z_end] == 0:
                z_end += 1
            if z_end > b_start:
                ctls[b_start] = 's'
                if z_end < b_end:
                    ctls[z_end] = 'b'

    return ctls
Exemple #11
0
class SftParser:
    def __init__(self, snapshot, sftfile, zfill=False, asm_hex=False, asm_lower=False):
        self.snapshot = snapshot
        self.disassembler = Disassembler(snapshot, zfill=zfill, asm_hex=asm_hex, asm_lower=asm_lower)
        self.sftfile = sftfile
        self.address_fmt = get_address_format(asm_hex, asm_lower)
        self.stack = []
        self.disassemble = True

    def _parse_instruction(self, line):
        ctl = line[0]
        lengths = []
        if line[1] == ';':
            inst_ctl = None
            start = None
            i = line.find(' ', 2)
            if i < 0:
                i = len(line.rstrip())
            comment_index = get_int_param(line[2:i])
        else:
            inst_ctl = line[1]
            if inst_ctl == 'I':
                i = find_unquoted(line.rstrip(), ' ', 2)
                address_end = j = find_unquoted(line, ';', 2, i)
            else:
                address_end = line.index(',', 2)
                i = find_unquoted(line.rstrip(), ' ', address_end + 1)
                j = find_unquoted(line, ';', address_end + 1, i)
            start = get_int_param(line[2:address_end])
            if j == i:
                comment_index = -1
            else:
                comment_index = get_int_param(line[j + 1:i])
            if j > address_end + 1:
                params = split_unquoted(line[address_end + 1:j], ',')
                lengths = parse_params(inst_ctl, params, 0)
            elif inst_ctl != 'I':
                raise ValueError

        comment = line[i:].strip()
        return ctl, inst_ctl, start, lengths, comment_index, comment

    def _parse_asm_directive(self, directive):
        if parse_asm_block_directive(directive, self.stack):
            self.disassemble = True
            for _p, i in self.stack:
                if i != '-':
                    self.disassemble = False
                    break

    def _set_bytes(self, line):
        address = parse_int(line[1:6])
        if address is not None:
            comment_index = find_unquoted(line, ';')
            operation = line[7:comment_index].strip()
            set_bytes(self.snapshot, address, operation)

    def _parse_sft(self, min_address, max_address):
        start_index = -1
        lines = []
        v_block_ctl = None
        f = open_file(self.sftfile)
        for line in f:
            if line.startswith('#'):
                # This line is a skool file template comment
                continue

            if not line.strip():
                # This line is blank
                lines.append(VerbatimLine(line))
                v_block_ctl = None
                continue

            if line.startswith(';'):
                # This line is an entry-level comment
                lines.append(VerbatimLine(line))
                continue

            if line.startswith('@'):
                lines.append(VerbatimLine(line))
                self._parse_asm_directive(line[1:].rstrip())
                continue

            if not self.disassemble:
                # This line is inside a '+' block, so include it as is
                lines.append(VerbatimLine(line))
                continue

            # Check whether we're in a block that should be restored verbatim
            if v_block_ctl is None and line[0] in 'dr' or (line[0] == 'i' and line[1] in '$0123456789'):
                v_block_ctl = line[0]
            if v_block_ctl:
                if v_block_ctl == 'd':
                    self._set_bytes(line)
                lines.append(VerbatimLine(line))
                continue

            # Check whether the line starts with a valid character
            if line[0] not in VALID_CTLS:
                lines.append(VerbatimLine(line))
                continue

            try:
                ctl, inst_ctl, start, lengths, comment_index, comment = self._parse_instruction(line)
            except (IndexError, ValueError):
                raise SftParsingError("Invalid line: {0}".format(line.split()[0]))
            if start is not None:
                # This line contains a control directive
                if start >= min_address > 0 and start_index < 0:
                    start_index = len(lines)
                instructions = []
                for length, sublengths in lengths:
                    end = start + length
                    if inst_ctl == 'C':
                        base = sublengths[0][1]
                        instructions += self.disassembler.disassemble(start, end, base)
                    elif inst_ctl == 'W':
                        instructions += self.disassembler.defw_range(start, end, sublengths)
                    elif inst_ctl == 'T':
                        instructions += self.disassembler.defm_range(start, end, sublengths)
                    elif inst_ctl == 'S':
                        instructions.append(self.disassembler.defs(start, end, sublengths))
                    else:
                        instructions += self.disassembler.defb_range(start, end, sublengths)
                    start += length
                if instructions:
                    done = False
                    for instruction in instructions:
                        if instruction.address >= max_address:
                            while lines and lines[-1].is_trimmable():
                                lines.pop()
                            done = True
                            break
                        address = self.address_fmt.format(instruction.address)
                        lines.append(InstructionLine(ctl, address, instruction.operation, comment_index, comment))
                        ctl = ' '
                    if done:
                        break
                else:
                    lines.append(InstructionLine(ctl, start, '', comment_index, comment))
            else:
                # This line is an instruction-level comment continuation line
                lines.append(InstructionLine(comment_index=comment_index, comment=comment))
        f.close()

        if start_index < 0:
            return lines
        if start_index < len(lines):
            if str(lines[start_index])[0] in DIRECTIVES:
                while start_index > 0 and not lines[start_index].is_blank():
                    start_index -= 1
            else:
                while start_index < len(lines) and not lines[start_index].is_blank():
                    start_index += 1
            return lines[start_index + 1:]
        return []

    def write_skool(self, min_address=0, max_address=65536):
        for line in self._parse_sft(min_address, max_address):
            write_line(str(line))
Exemple #12
0
class Disassembly:
    def __init__(self, snapshot, ctl_parser, final=False, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False):
        self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower)
        self.ctl_parser = ctl_parser
        if asm_hex:
            if asm_lower:
                self.address_fmt = '{0:04x}'
            else:
                self.address_fmt = '{0:04X}'
        else:
            self.address_fmt = '{0}'
        self.entry_map = {}
        self.build(final)

    def build(self, final=False):
        self.instructions = {}
        self.entries = []
        self._create_entries()
        self.org = self.entries[0].address
        if final:
            self._calculate_references()

    def _create_entries(self):
        for block in self.ctl_parser.get_blocks():
            if block.start in self.entry_map:
                entry = self.entry_map[block.start]
                self.entries.append(entry)
                for instruction in entry.instructions:
                    self.instructions[instruction.address] = instruction
                continue
            title = block.title
            if block.ctl == 'c':
                title = title or 'Routine at {}'.format(self._address_str(block.start))
            elif block.ctl in 'bw':
                title = title or 'Data block at {}'.format(self._address_str(block.start))
            elif block.ctl == 't':
                title = title or 'Message at {}'.format(self._address_str(block.start))
            elif block.ctl == 'g':
                title = title or 'Game status buffer entry at {}'.format(self._address_str(block.start))
            elif block.ctl in 'us':
                title = title or 'Unused'
            elif block.ctl == 'i' and (block.description or block.registers or block.blocks[0].header):
                title = title or 'Ignored'
            for sub_block in block.blocks:
                address = sub_block.start
                if sub_block.ctl in 'cBT':
                    base = sub_block.sublengths[0][1]
                    instructions = self.disassembler.disassemble(sub_block.start, sub_block.end, base)
                elif sub_block.ctl in 'bgstuw':
                    sublengths = sub_block.sublengths
                    if sublengths[0][0]:
                        if sub_block.ctl == 's':
                            length = sublengths[0][0]
                        else:
                            length = sum([s[0] for s in sublengths])
                    else:
                        length = sub_block.end - sub_block.start
                    instructions = []
                    while address < sub_block.end:
                        end = min(address + length, sub_block.end)
                        if sub_block.ctl == 't':
                            instructions += self.disassembler.defm_range(address, end, sublengths)
                        elif sub_block.ctl == 'w':
                            instructions += self.disassembler.defw_range(address, end, sublengths)
                        elif sub_block.ctl == 's':
                            instructions.append(self.disassembler.defs(address, end, sublengths))
                        else:
                            instructions += self.disassembler.defb_range(address, end, sublengths)
                        address += length
                else:
                    instructions = self.disassembler.ignore(sub_block.start, sub_block.end)
                sub_block.instructions = instructions
                for instruction in instructions:
                    self.instructions[instruction.address] = instruction
                    instruction.asm_directives = sub_block.asm_directives.get(instruction.address, ())

            sub_blocks = []
            i = 0
            while i < len(block.blocks):
                sub_block = block.blocks[i]
                i += 1
                sub_blocks.append(sub_block)
                if sub_block.multiline_comment is not None:
                    end, sub_block.comment = sub_block.multiline_comment
                    while i < len(block.blocks) and block.blocks[i].start < end:
                        next_sub_block = block.blocks[i]
                        sub_block.instructions += next_sub_block.instructions
                        sub_block.end = next_sub_block.end
                        i += 1

            entry = Entry(title, block.description, block.ctl, sub_blocks,
                          block.registers, block.end_comment, block.asm_directives,
                          block.ignoreua_directives)
            self.entry_map[entry.address] = entry
            self.entries.append(entry)
        for i, entry in enumerate(self.entries[1:]):
            self.entries[i].next = entry

    def remove_entry(self, address):
        if address in self.entry_map:
            del self.entry_map[address]

    def contains_entry_asm_directive(self, asm_dir):
        for entry in self.entries:
            for directive, value in entry.asm_directives:
                if directive == asm_dir:
                    return True

    def _calculate_references(self):
        for entry in self.entries:
            for instruction in entry.instructions:
                instruction.referrers = []
        for entry in self.entries:
            for instruction in entry.instructions:
                operation = instruction.operation
                if operation.upper().startswith(('DJ', 'JR', 'JP', 'CA', 'RS')):
                    addr_str = get_address(operation)
                    if addr_str:
                        callee = self.instructions.get(parse_int(addr_str))
                        if callee:
                            callee.add_referrer(entry)

    def _address_str(self, address):
        return self.address_fmt.format(address)
Exemple #13
0
def _generate_ctls_with_code_map(snapshot, start, end, code_map):
    # (1) Use the code map to create an initial set of 'c' ctls, and mark all
    #     unexecuted blocks as 'U' (unknown)
    # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the
    #     next RET/JP/JR in the following 'U' blocks, or up to the next 'c'
    #     block
    # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c'
    #     blocks with 'c'
    # (4) Split 'c' blocks on RET/JP/JR
    # (5) Scan the disassembly for pairs of adjacent blocks where the start
    #     address of the second block is JRed or JPed to from the first block,
    #     and join such pairs
    # (6) Examine the remaining 'U' blocks for text
    # (7) Mark data blocks of all zeroes with 's'

    # (1) Mark all executed blocks as 'c' and unexecuted blocks as 'U'
    # (unknown)
    ctls = {start: 'U', end: 'i'}
    for address, length in _get_code_blocks(snapshot, start, end, code_map):
        ctls[address] = 'c'
        if address + length < end:
            ctls[address + length] = 'U'

    # (2) Where a 'c' block doesn't end with a RET/JP/JR, extend it up to the
    # next RET/JP/JR in the following 'U' blocks, or up to the next 'c' block
    disassembler = Disassembler(snapshot)
    while 1:
        done = True
        for ctl, b_start, b_end in _get_blocks(ctls):
            if ctl == 'c':
                if _is_terminal_instruction(disassembler.disassemble(b_start, b_end)[-1]):
                    continue
                if _find_terminal_instruction(disassembler, ctls, b_end, end) < end:
                    done = False
                    break
        if done:
            break

    # (3) Mark entry points in 'U' blocks that are CALLed or JPed to from 'c'
    # blocks with 'c'
    ctl_parser = CtlParser(ctls)
    disassembly = Disassembly(snapshot, ctl_parser)
    while 1:
        disassembly.build(True)
        done = True
        for entry in disassembly.entries:
            if entry.ctl == 'U':
                for instruction in entry.instructions:
                    for referrer in instruction.referrers:
                        if ctls[referrer.address] == 'c':
                            ctls[instruction.address] = 'c'
                            if entry.next:
                                e_end = entry.next.address
                            else:
                                e_end = 65536
                            _find_terminal_instruction(disassembler, ctls, instruction.address, e_end, entry.ctl)
                            disassembly.remove_entry(entry.address)
                            done = False
                            break
                    if not done:
                        break
                if not done:
                    break
        if done:
            break

    # (4) Split 'c' blocks on RET/JP/JR
    for ctl, b_address, b_end in _get_blocks(ctls):
        if ctl == 'c':
            next_address = _find_terminal_instruction(disassembler, ctls, b_address, b_end, 'c')
            if next_address < b_end:
                disassembly.remove_entry(b_address)
                while next_address < b_end:
                    next_address = _find_terminal_instruction(disassembler, ctls, next_address, b_end, 'c')

    # (5) Scan the disassembly for pairs of adjacent blocks where the start
    # address of the second block is JRed or JPed to from the first block, and
    # join such pairs
    while 1:
        disassembly.build()
        done = True
        for entry in disassembly.entries[:-1]:
            if entry.ctl == 'c':
                for instruction in entry.instructions:
                    operation = instruction.operation
                    if operation[:2] in ('JR', 'JP') and operation[-5:] == str(entry.next.address):
                        del ctls[entry.next.address]
                        disassembly.remove_entry(entry.address)
                        disassembly.remove_entry(entry.next.address)
                        done = False
                        break
        if done:
            break

    # (6) Examine the 'U' blocks for text/data
    for ctl, b_start, b_end in _get_blocks(ctls):
        if ctl == 'U':
            ctls[b_start] = 'b'
            for t_start, t_end in _get_text_blocks(snapshot, b_start, b_end):
                ctls[t_start] = 't'
                if t_end < b_end:
                    ctls[t_end] = 'b'

    # (7) Mark data blocks of all zeroes with 's'
    for ctl, b_start, b_end in _get_blocks(ctls):
        if ctl == 'b':
            z_end = b_start
            while z_end < b_end and snapshot[z_end] == 0:
                z_end += 1
            if z_end > b_start:
                ctls[b_start] = 's'
                if z_end < b_end:
                    ctls[z_end] = 'b'

    return ctls
Exemple #14
0
class Disassembly:
    def __init__(self, snapshot, ctl_parser, config=None, final=False, defb_size=8, defb_mod=1,
                 zfill=False, defm_width=66, asm_hex=False, asm_lower=False):
        ctl_parser.apply_asm_data_directives(snapshot)
        self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower)
        self.ctl_parser = ctl_parser
        if asm_hex:
            if asm_lower:
                self.address_fmt = '{0:04x}'
            else:
                self.address_fmt = '{0:04X}'
        else:
            self.address_fmt = '{0}'
        self.entry_map = {}
        self.config = config or {}
        self.build(final)

    def build(self, final=False):
        self.instructions = {}
        self.entries = []
        self._create_entries()
        if self.entries:
            self.org = self.entries[0].address
        else:
            self.org = None
        if final:
            self._calculate_references()

    def _create_entries(self):
        for block in self.ctl_parser.get_blocks():
            if block.start in self.entry_map:
                entry = self.entry_map[block.start]
                self.entries.append(entry)
                for instruction in entry.instructions:
                    self.instructions[instruction.address] = instruction
                continue
            title = block.title
            if not any(title):
                ctl = block.ctl
                if ctl != 'i' or block.description or block.registers or block.blocks[0].header:
                    name = 'Title-' + ctl
                    title = [format_template(self.config.get(name, ''), name, address=self._address_str(block.start))]
            for sub_block in block.blocks:
                address = sub_block.start
                if sub_block.ctl in 'cBT':
                    base = sub_block.sublengths[0][1]
                    instructions = self.disassembler.disassemble(sub_block.start, sub_block.end, base)
                elif sub_block.ctl in 'bgstuw':
                    sublengths = sub_block.sublengths
                    if sublengths[0][0]:
                        if sub_block.ctl == 's':
                            length = sublengths[0][0]
                        else:
                            length = sum([s[0] for s in sublengths])
                    else:
                        length = sub_block.end - sub_block.start
                    instructions = []
                    while address < sub_block.end:
                        end = min(address + length, sub_block.end)
                        if sub_block.ctl == 't':
                            instructions += self.disassembler.defm_range(address, end, sublengths)
                        elif sub_block.ctl == 'w':
                            instructions += self.disassembler.defw_range(address, end, sublengths)
                        elif sub_block.ctl == 's':
                            instructions += self.disassembler.defs(address, end, sublengths)
                        else:
                            instructions += self.disassembler.defb_range(address, end, sublengths)
                        address += length
                else:
                    instructions = self.disassembler.ignore(sub_block.start, sub_block.end)
                self._add_instructions(sub_block, instructions)

            sub_blocks = []
            i = 0
            while i < len(block.blocks):
                sub_block = block.blocks[i]
                i += 1
                sub_blocks.append(sub_block)
                if sub_block.multiline_comment is not None:
                    end, sub_block.comment = sub_block.multiline_comment
                    while i < len(block.blocks) and block.blocks[i].start < end:
                        next_sub_block = block.blocks[i]
                        sub_block.instructions += next_sub_block.instructions
                        sub_block.end = next_sub_block.end
                        i += 1

            entry = Entry(block.header, title, block.description, block.ctl, sub_blocks,
                          block.registers, block.end_comment, block.footer, block.asm_directives,
                          block.ignoreua_directives)
            self.entry_map[entry.address] = entry
            self.entries.append(entry)
        for i, entry in enumerate(self.entries[1:]):
            self.entries[i].next = entry

    def remove_entry(self, address):
        if address in self.entry_map:
            del self.entry_map[address]

    def _add_instructions(self, sub_block, instructions):
        sub_block.instructions = instructions
        for instruction in instructions:
            self.instructions[instruction.address] = instruction
            instruction.asm_directives = sub_block.asm_directives.get(instruction.address, ())
            instruction.label = None
            for asm_dir in instruction.asm_directives:
                if asm_dir.startswith(AD_LABEL + '='):
                    instruction.label = asm_dir[6:]
                    if instruction.label.startswith('*'):
                        instruction.ctl = '*'
                    break

    def _calculate_references(self):
        for entry in self.entries:
            for instruction in entry.instructions:
                instruction.referrers = []
        for entry in self.entries:
            for instruction in entry.instructions:
                operation = instruction.operation
                if operation.upper().startswith(('DJ', 'JR', 'JP', 'CA', 'RS')):
                    addr_str = get_address(operation)
                    if addr_str:
                        callee = self.instructions.get(parse_int(addr_str))
                        if callee and (entry.ctl != 'u' or callee.entry == entry) and callee.label != '':
                            callee.add_referrer(entry)

    def _address_str(self, address):
        return self.address_fmt.format(address)