|
6 | 6 | from __future__ import annotations |
7 | 7 |
|
8 | 8 | from ctypes import Structure, c_uint32, c_uint16 |
9 | | -from typing import TYPE_CHECKING, BinaryIO |
| 9 | +from functools import lru_cache |
| 10 | +from typing import TYPE_CHECKING, BinaryIO, Dict, Tuple |
10 | 11 |
|
11 | 12 | from .base import QlBaseCoverage |
12 | 13 |
|
13 | 14 |
|
14 | 15 | if TYPE_CHECKING: |
15 | 16 | from qiling import Qiling |
| 17 | + from qiling.loader.loader import QlLoader |
16 | 18 |
|
17 | 19 |
|
18 | 20 | # Adapted from https://www.ayrx.me/drcov-file-format |
@@ -40,9 +42,35 @@ def __init__(self, ql: Qiling): |
40 | 42 |
|
41 | 43 | self.drcov_version = 2 |
42 | 44 | self.drcov_flavor = 'drcov' |
43 | | - self.basic_blocks = [] |
| 45 | + self.basic_blocks: Dict[int, bb_entry] = {} |
44 | 46 | self.bb_callback = None |
45 | 47 |
|
| 48 | + @lru_cache(maxsize=64) |
| 49 | + def _get_img_base(self, loader: QlLoader, address: int) -> Tuple[int, int]: |
| 50 | + """Retrieve the containing image of a given address. |
| 51 | +
|
| 52 | + Addresses are expected to be aligned to page boundary, and cached for faster retrieval. |
| 53 | + """ |
| 54 | + |
| 55 | + return next((i, img.base) for i, img in enumerate(loader.images) if img.base <= address < img.end) |
| 56 | + |
| 57 | + def block_callback(self, ql: Qiling, address: int, size: int): |
| 58 | + if address not in self.basic_blocks: |
| 59 | + try: |
| 60 | + # we rely on the fact that images are allocated on page size boundary and |
| 61 | + # use it to speed up image retrieval. we align the basic block address to |
| 62 | + # page boundary, knowing basic blocks within the same page belong to the |
| 63 | + # same image. then we use the aligned address to retreive the containing |
| 64 | + # image. returned values are cached so subsequent retrievals for basic |
| 65 | + # blocks within the same page will return the cached value instead of |
| 66 | + # going through the retreival process again (up to maxsize cached pages) |
| 67 | + |
| 68 | + i, img_base = self._get_img_base(ql.loader, address & ~(0x1000 - 1)) |
| 69 | + except StopIteration: |
| 70 | + pass |
| 71 | + else: |
| 72 | + self.basic_blocks[address] = bb_entry(address - img_base, size, i) |
| 73 | + |
46 | 74 | def activate(self) -> None: |
47 | 75 | self.bb_callback = self.ql.hook_block(self.block_callback) |
48 | 76 |
|
|
0 commit comments