Skip to content

Commit d81051c

Browse files
authored
Merge pull request #1584 from elicn/dev-mmio-cbs
2 parents 2f604f4 + 7b98c01 commit d81051c

File tree

3 files changed

+166
-97
lines changed

3 files changed

+166
-97
lines changed

qiling/hw/hw.py

Lines changed: 108 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,70 @@
11
#!/usr/bin/env python3
2-
#
2+
#
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6-
import ctypes
6+
from functools import cached_property
7+
from typing import Any, Dict, List, Optional, Tuple
78

8-
from qiling.core import Qiling
9+
from qiling import Qiling
910
from qiling.hw.peripheral import QlPeripheral
1011
from qiling.utils import ql_get_module_function
1112
from qiling.exception import QlErrorModuleFunctionNotFound
1213

1314

15+
# should adhere to the QlMmioHandler interface, but not extend it directly to
16+
# avoid potential pickling issues
17+
class QlPripheralHandler:
18+
def __init__(self, hwman: "QlHwManager", base: int, size: int, label: str) -> None:
19+
self._hwman = hwman
20+
self._base = base
21+
self._size = size
22+
self._label = label
23+
24+
def __getstate__(self):
25+
state = self.__dict__.copy()
26+
del state['_hwman'] # remove non-pickleable reference
27+
28+
return state
29+
30+
@cached_property
31+
def _mmio(self) -> bytearray:
32+
"""Get memory buffer used to back non-mapped hardware mmio regions.
33+
"""
34+
35+
return bytearray(self._size)
36+
37+
def read(self, ql: Qiling, offset: int, size: int) -> int:
38+
address = self._base + offset
39+
hardware = self._hwman.find(address)
40+
41+
if hardware:
42+
return hardware.read(address - hardware.base, size)
43+
44+
else:
45+
ql.log.debug('[%s] read non-mapped hardware [%#010x]', self._label, address)
46+
return int.from_bytes(self._mmio[offset:offset + size], byteorder='little')
47+
48+
def write(self, ql: Qiling, offset: int, size: int, value: int) -> None:
49+
address = self._base + offset
50+
hardware = self._hwman.find(address)
51+
52+
if hardware:
53+
hardware.write(address - hardware.base, size, value)
54+
55+
else:
56+
ql.log.debug('[%s] write non-mapped hardware [%#010x] = %#010x', self._label, address, value)
57+
self._mmio[offset:offset + size] = value.to_bytes(size, 'little')
58+
59+
1460
class QlHwManager:
1561
def __init__(self, ql: Qiling):
1662
self.ql = ql
1763

18-
self.entity = {}
19-
self.region = {}
20-
21-
self.stepable = {}
64+
self.entity: Dict[str, QlPeripheral] = {}
65+
self.region: Dict[str, List[Tuple[int, int]]] = {}
2266

23-
def create(self, label: str, struct: str=None, base: int=None, kwargs: dict={}) -> "QlPeripheral":
67+
def create(self, label: str, struct: Optional[str] = None, base: Optional[int] = None, kwargs: Optional[Dict[str, Any]] = None) -> QlPeripheral:
2468
""" Create the peripheral accroding the label and envs.
2569
2670
struct: Structure of the peripheral. Use defualt ql structure if not provide.
@@ -30,88 +74,76 @@ def create(self, label: str, struct: str=None, base: int=None, kwargs: dict={})
3074
if struct is None:
3175
struct, base, kwargs = self.load_env(label.upper())
3276

77+
if kwargs is None:
78+
kwargs = {}
79+
3380
try:
34-
3581
entity = ql_get_module_function('qiling.hw', struct)(self.ql, label, **kwargs)
36-
37-
self.entity[label] = entity
38-
if hasattr(entity, 'step'):
39-
self.stepable[label] = entity
4082

41-
self.region[label] = [(lbound + base, rbound + base) for (lbound, rbound) in entity.region]
83+
except QlErrorModuleFunctionNotFound:
84+
self.ql.log.warning(f'could not create {struct}({label}): implementation not found')
4285

86+
else:
87+
assert isinstance(entity, QlPeripheral)
88+
assert isinstance(base, int)
89+
90+
self.entity[label] = entity
91+
self.region[label] = [(lbound + base, rbound + base) for (lbound, rbound) in entity.region]
4392

4493
return entity
45-
except QlErrorModuleFunctionNotFound:
46-
self.ql.log.debug(f'The {struct}({label}) has not been implemented')
4794

48-
def delete(self, label: str):
95+
# FIXME: what should we do if struct is not implemented? is it OK to return None , or we fail?
96+
97+
def delete(self, label: str) -> None:
4998
""" Remove the peripheral
5099
"""
100+
51101
if label in self.entity:
52-
self.entity.pop(label)
53-
self.region.pop(label)
54-
if label in self.stepable:
55-
self.stepable.pop(label)
102+
del self.entity[label]
103+
104+
if label in self.region:
105+
del self.region[label]
56106

57-
def load_env(self, label: str):
107+
def load_env(self, label: str) -> Tuple[str, int, Dict[str, Any]]:
58108
""" Get peripheral information (structure, base address, initialization list) from env.
59109
60110
Args:
61111
label (str): Peripheral Label
62-
112+
63113
"""
64114
args = self.ql.env[label]
65-
115+
66116
return args['struct'], args['base'], args.get("kwargs", {})
67117

68118
def load_all(self):
69119
for label, args in self.ql.env.items():
70120
if args['type'] == 'peripheral':
71121
self.create(label.lower(), args['struct'], args['base'], args.get("kwargs", {}))
72122

73-
def find(self, address: int):
123+
# TODO: this is wasteful. device mapping is known at creation time. at least we could cache lru entries
124+
def find(self, address: int) -> Optional[QlPeripheral]:
74125
""" Find the peripheral at `address`
75126
"""
76-
127+
77128
for label in self.entity.keys():
78129
for lbound, rbound in self.region[label]:
79130
if lbound <= address < rbound:
80131
return self.entity[label]
81132

133+
return None
134+
82135
def step(self):
83-
""" Update all peripheral's state
136+
""" Update all peripheral's state
84137
"""
85-
for entity in self.stepable.values():
86-
entity.step()
87-
88-
def setup_mmio(self, begin, size, info=""):
89-
mmio = ctypes.create_string_buffer(size)
90-
91-
def mmio_read_cb(ql, offset, size):
92-
address = begin + offset
93-
hardware = self.find(address)
94-
95-
if hardware:
96-
return hardware.read(address - hardware.base, size)
97-
else:
98-
ql.log.debug('%s Read non-mapped hardware [0x%08x]' % (info, address))
99-
100-
buf = ctypes.create_string_buffer(size)
101-
ctypes.memmove(buf, ctypes.addressof(mmio) + offset, size)
102-
return int.from_bytes(buf.raw, byteorder='little')
103-
104-
def mmio_write_cb(ql, offset, size, value):
105-
address = begin + offset
106-
hardware = self.find(address)
107-
108-
if hardware:
109-
hardware.write(address - hardware.base, size, value)
110-
else:
111-
ql.log.debug('%s Write non-mapped hardware [0x%08x] = 0x%08x' % (info, address, value))
112-
ctypes.memmove(ctypes.addressof(mmio) + offset, (value).to_bytes(size, 'little'), size)
113-
114-
self.ql.mem.map_mmio(begin, size, mmio_read_cb, mmio_write_cb, info=info)
138+
139+
for ent in self.entity.values():
140+
if hasattr(ent, 'step'):
141+
ent.step()
142+
143+
def setup_mmio(self, begin: int, size: int, info: str) -> None:
144+
dev = QlPripheralHandler(self, begin, size, info)
145+
146+
self.ql.mem.map_mmio(begin, size, dev, info)
115147

116148
def show_info(self):
117149
self.ql.log.info(f'{"Start":8s} {"End":8s} {"Label":8s} {"Class"}')
@@ -131,8 +163,25 @@ def __getattr__(self, key):
131163
return self.entity.get(key)
132164

133165
def save(self):
134-
return {label : entity.save() for label, entity in self.entity.items()}
166+
return {
167+
'entity': {label: entity.save() for label, entity in self.entity.items()},
168+
'region': self.region
169+
}
135170

136171
def restore(self, saved_state):
137-
for label, data in saved_state.items():
172+
entity = saved_state['entity']
173+
assert isinstance(entity, dict)
174+
175+
region = saved_state['region']
176+
assert isinstance(region, dict)
177+
178+
for label, data in entity.items():
138179
self.entity[label].restore(data)
180+
181+
self.region = region
182+
183+
# a dirty hack to rehydrate non-pickleable hwman
184+
# a proper fix would require a deeper refactoring to how peripherals are created and managed
185+
for ph in self.ql.mem.mmio_cbs.values():
186+
if isinstance(ph, QlPripheralHandler):
187+
setattr(ph, '_hwman', self)

qiling/loader/mcu.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
2-
#
2+
#
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
4-
# Built on top of Unicorn emulator (www.unicorn-engine.org)
4+
# Built on top of Unicorn emulator (www.unicorn-engine.org)
55

66

77
import io
@@ -27,7 +27,7 @@ def __init__(self, path):
2727
if addr != begin + len(stream):
2828
self.segments.append((begin, stream))
2929
begin, stream = addr, data
30-
30+
3131
else:
3232
stream += data
3333

@@ -36,13 +36,13 @@ def __init__(self, path):
3636
def parse_line(self, line):
3737
if len(line) < 9:
3838
return
39-
39+
4040
desc = line[7: 9]
41-
size = int(line[1: 3], 16)
42-
41+
size = int(line[1: 3], 16)
42+
4343
addr = bytes.fromhex(line[3: 7])
44-
data = bytes.fromhex(line[9: 9 + size * 2])
45-
44+
data = bytes.fromhex(line[9: 9 + size * 2])
45+
4646
if desc == '00': # Data
4747
offset = int.from_bytes(addr, byteorder='big')
4848
self.mem.append((self.base + offset, data))
@@ -52,20 +52,20 @@ def parse_line(self, line):
5252

5353
elif desc == '04': # Extended Linear Address
5454
self.base = int.from_bytes(data, byteorder='big') * 0x10000
55-
55+
5656

5757
class QlLoaderMCU(QlLoader):
5858
def __init__(self, ql:Qiling):
59-
super().__init__(ql)
60-
59+
super().__init__(ql)
60+
6161
self.entry_point = 0
6262
self.load_address = 0
6363
self.filetype = self.guess_filetype()
64-
64+
6565
if self.filetype == 'elf':
6666
with open(self.ql.path, 'rb') as infile:
6767
self.elf = ELFFile(io.BytesIO(infile.read()))
68-
68+
6969
elif self.filetype == 'bin':
7070
self.map_address = self.argv[1]
7171

@@ -74,16 +74,16 @@ def __init__(self, ql:Qiling):
7474

7575
def guess_filetype(self):
7676
if self.ql.path.endswith('.elf'):
77-
return 'elf'
78-
77+
return 'elf'
78+
7979
if self.ql.path.endswith('.bin'):
8080
return 'bin'
8181

8282
if self.ql.path.endswith('.hex'):
8383
return 'hex'
8484

8585
return 'elf'
86-
86+
8787
def reset(self):
8888
if self.filetype == 'elf':
8989
for segment in self.elf.iter_segments(type='PT_LOAD'):
@@ -99,7 +99,7 @@ def reset(self):
9999
for begin, data in self.ihex.segments:
100100
self.ql.mem.write(begin, data)
101101

102-
102+
103103
self.ql.arch.init_context()
104104
self.entry_point = self.ql.arch.regs.read('pc')
105105

@@ -109,30 +109,34 @@ def load_profile(self):
109109
def load_env(self):
110110
for name, args in self.env.items():
111111
memtype = args['type']
112+
112113
if memtype == 'memory':
113114
size = args['size']
114115
base = args['base']
115116
self.ql.mem.map(base, size, info=f'[{name}]')
116-
117-
if memtype == 'remap':
118-
size = args['size']
119-
base = args['base']
120-
alias = args['alias']
121-
self.ql.hw.setup_remap(alias, base, size, info=f'[{name}]')
122117

123-
if memtype == 'mmio':
118+
# elif memtype == 'remap':
119+
# size = args['size']
120+
# base = args['base']
121+
# alias = args['alias']
122+
# self.ql.hw.setup_remap(alias, base, size, info=f'[{name}]')
123+
124+
elif memtype == 'mmio':
124125
size = args['size']
125126
base = args['base']
126-
self.ql.hw.setup_mmio(base, size, info=f'[{name}]')
127+
self.ql.hw.setup_mmio(base, size, name)
127128

128-
if memtype == 'core':
129+
elif memtype == 'core':
129130
self.ql.hw.create(name.lower())
130131

132+
else:
133+
self.ql.log.debug(f'ignoring unknown memory type "{memtype}" for {name}')
134+
131135
def run(self):
132136
self.load_profile()
133137
self.load_env()
134-
138+
135139
## Handle interrupt from instruction execution
136140
self.ql.hook_intr(self.ql.arch.unicorn_exception_handler)
137-
141+
138142
self.reset()

0 commit comments

Comments
 (0)