Skip to content

Commit 83c6fb9

Browse files
authored
Merge pull request #1596 from elicn/mmio-unmap-fix
MMIO unmap fix
2 parents bbf8d7a + b6f085c commit 83c6fb9

2 files changed

Lines changed: 48 additions & 49 deletions

File tree

qiling/hw/hw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,6 @@ def restore(self, saved_state):
182182

183183
# a dirty hack to rehydrate non-pickleable hwman
184184
# 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():
185+
for *_, ph in self.ql.mem.map_info:
186186
if isinstance(ph, QlPripheralHandler):
187187
setattr(ph, '_hwman', self)

qiling/os/memory.py

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
from qiling import Qiling
1414
from qiling.exception import *
1515

16-
# tuple: range start, range end, permissions mask, range label, is mmio?
17-
MapInfoEntry = Tuple[int, int, int, str, bool]
18-
19-
MmioReadCallback = Callable[[Qiling, int, int], int]
20-
MmioWriteCallback = Callable[[Qiling, int, int, int], None]
21-
2216

2317
class QlMmioHandler(Protocol):
2418
"""A simple MMIO handler boilerplate that can be used to implement memory mapped devices.
@@ -36,6 +30,13 @@ def write(self, ql: Qiling, offset: int, size: int, value: int) -> None:
3630
...
3731

3832

33+
# tuple: range start, range end, permissions mask, range label, mmio hander object (if mmio range)
34+
MapInfoEntry = Tuple[int, int, int, str, Optional[QlMmioHandler]]
35+
36+
MmioReadCallback = Callable[[Qiling, int, int], int]
37+
MmioWriteCallback = Callable[[Qiling, int, int, int], None]
38+
39+
3940
class QlMemoryManager:
4041
"""
4142
some ideas and code from:
@@ -45,7 +46,6 @@ class QlMemoryManager:
4546
def __init__(self, ql: Qiling, pagesize: int = 0x1000):
4647
self.ql = ql
4748
self.map_info: List[MapInfoEntry] = []
48-
self.mmio_cbs: Dict[Tuple[int, int], QlMmioHandler] = {}
4949

5050
bit_stuff = {
5151
64: (1 << 64) - 1,
@@ -121,18 +121,18 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]:
121121

122122
self.__write_string(addr, value, encoding)
123123

124-
def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio: bool = False):
124+
def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, mmio_ctx: Optional[QlMmioHandler] = None):
125125
"""Add a new memory range to map.
126126
127127
Args:
128128
mem_s: memory range start
129129
mem_e: memory range end
130130
mem_p: permissions mask
131131
mem_info: map entry label
132-
is_mmio: memory range is mmio
132+
mmio_ctx: mmio handler object; if specified the range will be treated as mmio
133133
"""
134134

135-
bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, is_mmio))
135+
bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, mmio_ctx))
136136

137137
def del_mapinfo(self, mem_s: int, mem_e: int):
138138
"""Subtract a memory range from map.
@@ -146,13 +146,13 @@ def del_mapinfo(self, mem_s: int, mem_e: int):
146146

147147
def __split_overlaps():
148148
for idx in overlap_ranges:
149-
lbound, ubound, perms, label, is_mmio = self.map_info[idx]
149+
lbound, ubound, perms, label, mmio_ctx = self.map_info[idx]
150150

151151
if lbound < mem_s:
152-
yield (lbound, mem_s, perms, label, is_mmio)
152+
yield (lbound, mem_s, perms, label, mmio_ctx)
153153

154154
if mem_e < ubound:
155-
yield (mem_e, ubound, perms, label, is_mmio)
155+
yield (mem_e, ubound, perms, label, mmio_ctx)
156156

157157
# indices of first and last overlapping ranges. since map info is always
158158
# sorted, we know that all overlapping rages are consecutive, so i1 > i0
@@ -170,27 +170,30 @@ def __split_overlaps():
170170
for entry in new_entries:
171171
bisect.insort(self.map_info, entry)
172172

173-
def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None):
174-
tmp_map_info: Optional[MapInfoEntry] = None
175-
info_idx: int = -1
176-
177-
for idx, map_info in enumerate(self.map_info):
178-
if mem_s >= map_info[0] and mem_e <= map_info[1]:
179-
tmp_map_info = map_info
180-
info_idx = idx
181-
break
173+
def change_mapinfo(self, mem_s: int, mem_e: int, *, new_perms: Optional[int] = None, new_info: Optional[str] = None) -> None:
174+
if new_perms is None and new_info is None:
175+
# nothing to do
176+
return
182177

183-
if tmp_map_info is None:
178+
try:
179+
# locate the map info entry to change
180+
entry = next(entry for entry in self.map_info if mem_s >= entry[0] and mem_e <= entry[1])
181+
except StopIteration:
184182
self.ql.log.error(f'Cannot change mapinfo at {mem_s:#08x}-{mem_e:#08x}')
185183
return
186184

187-
if mem_p is not None:
188-
self.del_mapinfo(mem_s, mem_e)
189-
self.add_mapinfo(mem_s, mem_e, mem_p, mem_info if mem_info else tmp_map_info[3])
190-
return
185+
_, _, perms, info, mmio_ctx = entry
186+
187+
# caller wants to change perms?
188+
if new_perms is not None:
189+
perms = new_perms
191190

192-
if mem_info is not None:
193-
self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4])
191+
# caller wants to change info?
192+
if new_info is not None:
193+
info = new_info
194+
195+
self.del_mapinfo(mem_s, mem_e)
196+
self.add_mapinfo(mem_s, mem_e, perms, info, mmio_ctx)
194197

195198
def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]:
196199
"""Get memory map info.
@@ -209,18 +212,18 @@ def __perms_mapping(ps: int) -> str:
209212

210213
return ''.join(val if idx & ps else '-' for idx, val in perms_d.items())
211214

212-
def __process(lbound: int, ubound: int, perms: int, label: str, is_mmio: bool) -> Tuple[int, int, str, str, str]:
213-
perms_str = __perms_mapping(perms)
215+
def __process(entry: MapInfoEntry) -> Tuple[int, int, str, str, str]:
216+
lbound, ubound, perms, label, mmio_ctx = entry
214217

215218
if hasattr(self.ql, 'loader'):
216219
image = self.ql.loader.find_containing_image(lbound)
217-
container = image.path if image and not is_mmio else ''
220+
container = image.path if image and mmio_ctx is None else ''
218221
else:
219222
container = ''
220223

221-
return (lbound, ubound, perms_str, label, container)
224+
return (lbound, ubound, __perms_mapping(perms), label, container)
222225

223-
return tuple(__process(*entry) for entry in self.map_info)
226+
return tuple(__process(entry) for entry in self.map_info)
224227

225228
def get_formatted_mapinfo(self) -> Sequence[str]:
226229
"""Get memory map info in a nicely formatted table.
@@ -311,12 +314,13 @@ def save(self):
311314
"mmio" : []
312315
}
313316

314-
for lbound, ubound, perm, label, is_mmio in self.map_info:
315-
if is_mmio:
316-
mem_dict['mmio'].append((lbound, ubound, perm, label, self.mmio_cbs[(lbound, ubound)]))
317+
for lbound, ubound, perm, label, mmio_ctx in self.map_info:
318+
if mmio_ctx is None:
319+
key, data = 'ram', bytes(self.read(lbound, ubound - lbound))
317320
else:
318-
data = self.read(lbound, ubound - lbound)
319-
mem_dict['ram'].append((lbound, ubound, perm, label, bytes(data)))
321+
key, data = 'mmio', mmio_ctx
322+
323+
mem_dict[key].append((lbound, ubound, perm, label, data))
320324

321325
return mem_dict
322326

@@ -429,7 +433,7 @@ def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = No
429433
assert begin < end, 'search arguments do not make sense'
430434

431435
# narrow the search down to relevant ranges; mmio ranges are excluded due to potential read side effects
432-
ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, is_mmio in self.map_info if not (end < lbound or ubound < begin or is_mmio)]
436+
ranges = [(max(begin, lbound), min(ubound, end)) for lbound, ubound, _, _, mmio_ctx in self.map_info if not (end < lbound or ubound < begin or mmio_ctx is not None)]
433437
results = []
434438

435439
# if needle is a bytes sequence use it verbatim, not as a pattern
@@ -455,9 +459,6 @@ def unmap(self, addr: int, size: int) -> None:
455459
self.del_mapinfo(addr, addr + size)
456460
self.ql.uc.mem_unmap(addr, size)
457461

458-
if (addr, addr + size) in self.mmio_cbs:
459-
del self.mmio_cbs[(addr, addr+size)]
460-
461462
def unmap_between(self, mem_s: int, mem_e: int) -> None:
462463
"""Reclaim any allocated memory region within the specified range.
463464
@@ -616,7 +617,7 @@ def protect(self, addr: int, size: int, perms):
616617
aligned_size = self.align_up((addr & (self.pagesize - 1)) + size)
617618

618619
self.ql.uc.mem_protect(aligned_address, aligned_size, perms)
619-
self.change_mapinfo(aligned_address, aligned_address + aligned_size, perms)
620+
self.change_mapinfo(aligned_address, aligned_address + aligned_size, new_perms=perms)
620621

621622
def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None):
622623
"""Map a new memory range.
@@ -638,7 +639,7 @@ def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str
638639
raise QlMemoryMappedError('Requested memory is unavailable')
639640

640641
self.ql.uc.mem_map(addr, size, perms)
641-
self.add_mapinfo(addr, addr + size, perms, info or '[mapped]', is_mmio=False)
642+
self.add_mapinfo(addr, addr + size, perms, info or '[mapped]', None)
642643

643644
def map_mmio(self, addr: int, size: int, handler: QlMmioHandler, info: str = '[mmio]'):
644645
# TODO: mmio memory overlap with ram? Is that possible?
@@ -664,9 +665,7 @@ def __mmio_write(uc, offset: int, size: int, value: int, user_data: MmioWriteCal
664665
cb(self.ql, offset, size, value)
665666

666667
self.ql.uc.mmio_map(addr, size, __mmio_read, handler.read, __mmio_write, handler.write)
667-
self.add_mapinfo(addr, addr + size, prot, info, is_mmio=True)
668-
669-
self.mmio_cbs[(addr, addr + size)] = handler
668+
self.add_mapinfo(addr, addr + size, prot, info, handler)
670669

671670

672671
class Chunk:

0 commit comments

Comments
 (0)