|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# |
| 3 | +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework |
| 4 | +# Built on top of Unicorn emulator (www.unicorn-engine.org) |
| 5 | + |
| 6 | + |
| 7 | +import struct |
| 8 | +from elftools.elf.elffile import ELFFile |
| 9 | + |
| 10 | +from qiling.const import * |
| 11 | +from qiling.core import Qiling |
| 12 | + |
| 13 | +from .loader import QlLoader |
| 14 | + |
| 15 | + |
| 16 | +class IhexParser: |
| 17 | + def __init__(self, path): |
| 18 | + self.pc = None |
| 19 | + self.base = None |
| 20 | + self.mem = [] |
| 21 | + self.segments = [] |
| 22 | + |
| 23 | + with open(path, 'r') as f: |
| 24 | + for line in f.read().splitlines(): |
| 25 | + self.parse_line(line.strip()) |
| 26 | + |
| 27 | + begin, end, bindata = 0, 0, b'' |
| 28 | + for addr, data in self.mem: |
| 29 | + if addr != end: |
| 30 | + self.add_segment(begin, end, bindata) |
| 31 | + begin, end, bindata = addr, addr + len(data), data |
| 32 | + else: |
| 33 | + bindata += data |
| 34 | + end += len(data) |
| 35 | + self.add_segment(begin, end, bindata) |
| 36 | + |
| 37 | + def add_segment(self, begin, end, bindata): |
| 38 | + if len(bindata) > 0 and end - begin == len(bindata): |
| 39 | + self.segments.append((begin, end, bindata)) |
| 40 | + |
| 41 | + def parse_line(self, line): |
| 42 | + if len(line) < 9: |
| 43 | + return |
| 44 | + |
| 45 | + size = int(line[1: 3], 16) |
| 46 | + type = line[7: 9] |
| 47 | + |
| 48 | + addr = bytes.fromhex(line[3: 7]) |
| 49 | + data = bytes.fromhex(line[9: 9 + size * 2]) |
| 50 | + |
| 51 | + if type == '04': |
| 52 | + self.base = struct.unpack('>I', data + b'\x00\x00')[0] |
| 53 | + elif type == '05': |
| 54 | + self.pc = struct.unpack('>I', data)[0] |
| 55 | + elif type == '00': |
| 56 | + offset = struct.unpack('>I', b'\x00\x00' + addr)[0] |
| 57 | + self.mem.append((self.base + offset, data)) |
| 58 | + |
| 59 | +class QlLoaderMCU(QlLoader): |
| 60 | + def __init__(self, ql:Qiling): |
| 61 | + super(QlLoaderMCU, self).__init__(ql) |
| 62 | + |
| 63 | + self.load_address = 0 |
| 64 | + self.path = self.argv[0] |
| 65 | + self.filetype = self.guess_filetype() |
| 66 | + |
| 67 | + if self.filetype == 'elf': |
| 68 | + self.elf = ELFFile(open(self.path, 'rb')) |
| 69 | + |
| 70 | + elif self.filetype == 'bin': |
| 71 | + self.map_address = self.argv[1] |
| 72 | + |
| 73 | + else: # self.filetype == 'hex': |
| 74 | + self.ihex = IhexParser(self.path) |
| 75 | + |
| 76 | + def guess_filetype(self): |
| 77 | + if self.path.endswith('.elf'): |
| 78 | + return 'elf' |
| 79 | + |
| 80 | + if self.path.endswith('.bin'): |
| 81 | + return 'bin' |
| 82 | + |
| 83 | + if self.path.endswith('.hex'): |
| 84 | + return 'hex' |
| 85 | + |
| 86 | + return 'elf' |
| 87 | + |
| 88 | + def reset(self): |
| 89 | + if self.filetype == 'elf': |
| 90 | + for segment in self.elf.iter_segments(): |
| 91 | + if segment['p_type'] != 'PT_LOAD': |
| 92 | + continue |
| 93 | + |
| 94 | + for section in self.elf.iter_sections(): |
| 95 | + if segment.section_in_segment(section): |
| 96 | + self.ql.mem.write(section.header['sh_addr'], section.data()) |
| 97 | + |
| 98 | + # TODO: load symbol table |
| 99 | + |
| 100 | + elif self.filetype == 'bin': |
| 101 | + with open(self.path, 'rb') as f: |
| 102 | + self.ql.mem.write(self.map_address, f.read()) |
| 103 | + |
| 104 | + else: # self.filetype == 'hex': |
| 105 | + for begin, _, data in self.ihex.segments: |
| 106 | + self.ql.mem.write(begin, data) |
| 107 | + |
| 108 | + self.ql.reg.write('lr', 0xffffffff) |
| 109 | + self.ql.reg.write('msp', self.ql.mem.read_ptr(0x0)) |
| 110 | + self.ql.reg.write('pc' , self.entry_point) |
| 111 | + |
| 112 | + def run(self): |
| 113 | + ## Load memory / mmio / peripheral from profile |
| 114 | + for section_name in self.ql.profile.sections(): |
| 115 | + section = self.ql.profile[section_name] |
| 116 | + if section['type'] == 'memory': |
| 117 | + size = eval(section['size']) |
| 118 | + base = eval(section['base']) |
| 119 | + self.ql.mem.map(base, size, info=f'[{section_name}]') |
| 120 | + if section_name == 'FLASH': |
| 121 | + self.ql.hw.setup_remap(0, base, size, info=f'[CODE]') |
| 122 | + |
| 123 | + if section['type'] == 'bitband': |
| 124 | + size = eval(section['size']) * 32 |
| 125 | + base = eval(section['base']) |
| 126 | + alias = eval(section['alias']) |
| 127 | + self.ql.hw.setup_bitband(base, alias, size, info=f'[{section_name}]') |
| 128 | + |
| 129 | + if section['type'] == 'mmio': |
| 130 | + size = eval(section['size']) |
| 131 | + base = eval(section['base']) |
| 132 | + self.ql.hw.setup_mmio(base, size, info=f'[{section_name}]') |
| 133 | + |
| 134 | + if section['type'] == 'core periperal': |
| 135 | + self.ql.hw.create(section_name.lower()) |
| 136 | + |
| 137 | + ## Handle interrupt from instruction execution |
| 138 | + self.ql.hook_intr(self.ql.arch.intr_cb) |
| 139 | + |
| 140 | + self.reset() |
| 141 | + |
| 142 | + @property |
| 143 | + def entry_point(self): |
| 144 | + return self.ql.mem.read_ptr(0x4) |
0 commit comments