From 96f3025e5b52818f483bc0c017091e66cfd74004 Mon Sep 17 00:00:00 2001 From: HyperSine Date: Sun, 29 Oct 2023 16:26:09 +0800 Subject: [PATCH] enhance del_mapinfo/change_mapinfo Signed-off-by: HyperSine --- qiling/os/memory.py | 133 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 22 deletions(-) diff --git a/qiling/os/memory.py b/qiling/os/memory.py index b5be6d91d..93b1ab549 100644 --- a/qiling/os/memory.py +++ b/qiling/os/memory.py @@ -4,6 +4,7 @@ # import bisect +import itertools import os import re from typing import Any, Callable, Iterator, List, Mapping, Optional, Pattern, Sequence, Tuple, Union @@ -81,7 +82,8 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]: self.__write_string(addr, value, encoding) def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio: bool = False): - """Add a new memory range to map. + """Add a new memory range to map. Caller must guarantee that + the map info to be added DOES NOT overlap with any existing map infos. Args: mem_s: memory range start @@ -93,6 +95,36 @@ def add_mapinfo(self, mem_s: int, mem_e: int, mem_p: int, mem_info: str, is_mmio bisect.insort(self.map_info, (mem_s, mem_e, mem_p, mem_info, is_mmio)) + def find_mapinfo(self, mem_s: int, mem_e: int) -> List[int]: + """find map infos that overlap with a memory range + + Args: + mem_s: memory range start + mem_e: memory range end + + Returns: indices of map infos that overlap with the memory range [mem_s, mem_e) + """ + + retval = [] + + # index of first (lbound, ubound, ...) that is >= (mem_s,) + # i.e., lbound >= mem_s + idx = bisect.bisect_left(self.map_info, (mem_s,)) + + # check the previous one, its lbound is bound to be < mem_s + # so, check its ubound only + if idx > 0 and mem_s < self.map_info[idx - 1][1]: + retval.append(idx - 1) + + for i in range(idx, len(self.map_info)): + if self.map_info[i][0] < mem_e: + retval.append(i) + else: + # the lbounds of map infos left are all bound to be >= mem_e, so skip check + break + + return retval + def del_mapinfo(self, mem_s: int, mem_e: int): """Subtract a memory range from map. @@ -101,7 +133,9 @@ def del_mapinfo(self, mem_s: int, mem_e: int): mem_e: memory range end """ - overlap_ranges = [idx for idx, (lbound, ubound, _, _, _) in enumerate(self.map_info) if (mem_s < ubound) and (mem_e > lbound)] + overlap_ranges = self.find_mapinfo(mem_s, mem_e) + if len(overlap_ranges) == 0: + return def __split_overlaps(): for idx in overlap_ranges: @@ -114,7 +148,7 @@ def __split_overlaps(): yield (mem_e, ubound, perms, label, is_mmio) # indices of first and last overlapping ranges. since map info is always - # sorted, we know that all overlapping rages are consecutive, so i1 > i0 + # sorted, we know that all overlapping rages are consecutive, so i1 >= i0 i0 = overlap_ranges[0] i1 = overlap_ranges[-1] @@ -126,30 +160,85 @@ def __split_overlaps(): del self.map_info[i0:i1 + 1] # add new ones - for entry in new_entries: - bisect.insort(self.map_info, entry) + for i, entry in enumerate(new_entries): + self.map_info.insert(i0 + i, entry) def change_mapinfo(self, mem_s: int, mem_e: int, mem_p: Optional[int] = None, mem_info: Optional[str] = None): - tmp_map_info: Optional[MapInfoEntry] = None - info_idx: int = -1 - - for idx, map_info in enumerate(self.map_info): - if mem_s >= map_info[0] and mem_e <= map_info[1]: - tmp_map_info = map_info - info_idx = idx - break + """Change permissions/labels of a memory range from map. + + Args: + mem_s: memory range start + mem_e: memory range end + mem_p: permissions mask, or remain unchanged if None + mem_info: map entry label, or remain unchanged if None + """ + if mem_p is None and mem_info is None: + return # nothing to change, just return - if tmp_map_info is None: - self.ql.log.error(f'Cannot change mapinfo at {mem_s:#08x}-{mem_e:#08x}') + overlap_ranges = self.find_mapinfo(mem_s, mem_e) + if len(overlap_ranges) == 0: return - if mem_p is not None: - self.del_mapinfo(mem_s, mem_e) - self.add_mapinfo(mem_s, mem_e, mem_p, mem_info if mem_info else tmp_map_info[3]) - return + def __split_overlaps(): + for idx in overlap_ranges: + lbound, ubound, perms, label, is_mmio = self.map_info[idx] + + if (mem_p is None or perms == mem_p) and (mem_info is None or label == mem_info): + # when nothing changed, emit as it is + yield (lbound, ubound, perms, label, is_mmio) + else: + if lbound < mem_s: + yield (lbound, mem_s, perms, label, is_mmio) + + new_perms = perms if mem_p is None else mem_p + new_label = label if mem_info is None else mem_info + yield (max(lbound, mem_s), min(ubound, mem_e), new_perms, new_label, is_mmio) + + if mem_e < ubound: + yield (mem_e, ubound, perms, label, is_mmio) + + def __merge_adjacency(map_infos: Iterator[MapInfoEntry]): + prev: Optional[MapInfoEntry] = None + for current in map_infos: + if prev is None: + prev = current + else: + if prev[1] == current[0] and prev[2:] == current[2:]: + # when prev ubound == current lbound and perms/label/is_mmio are the same, merge current to prev + prev = (prev[0], current[1], prev[2], prev[3], prev[4]) + else: + yield prev + prev = current + if prev is not None: + yield prev + + i0 = overlap_ranges[0] + i1 = overlap_ranges[-1] - if mem_info is not None: - self.map_info[info_idx] = (tmp_map_info[0], tmp_map_info[1], tmp_map_info[2], mem_info, tmp_map_info[4]) + # check if the map info just before overlap_ranges is adjacency to the memory range [mem_s, mem_e) + if 0 < i0 and self.map_info[i0 - 1][1] == mem_s: + i0 -= 1 + left_to_merge = [self.map_info[i0]] + else: + left_to_merge = [] + + # check if the map info just after overlap_ranges is adjacency to the memory range [mem_s, mem_e) + if i1 < len(self.map_info) - 1 and mem_e == self.map_info[i1 + 1][0]: + i1 += 1 + right_to_merge = [self.map_info[i1]] + else: + right_to_merge = [] + + # create new entries by splitting overlapping ranges and merging. + # this has to be done before removing visited entries + new_entries = list(__merge_adjacency(itertools.chain(left_to_merge, __split_overlaps(), right_to_merge))) + + # remove visited entries + del self.map_info[i0:i1 + 1] + + # add new ones + for i, entry in enumerate(new_entries): + self.map_info.insert(i0 + i, entry) def get_mapinfo(self) -> Sequence[Tuple[int, int, str, str, str]]: """Get memory map info. @@ -419,8 +508,8 @@ def unmap(self, addr: int, size: int) -> None: size: range size (in bytes) """ - self.del_mapinfo(addr, addr + size) self.ql.uc.mem_unmap(addr, size) + self.del_mapinfo(addr, addr + size) if (addr, addr + size) in self.mmio_cbs: del self.mmio_cbs[(addr, addr+size)]