33# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44#
55
6- from ctypes import Structure
7- from ctypes import c_uint32 , c_uint16
6+ from __future__ import annotations
7+
8+ from ctypes import Structure , c_uint32 , c_uint16
9+ from functools import lru_cache
10+ from typing import TYPE_CHECKING , BinaryIO , Dict , Tuple
811
912from .base import QlBaseCoverage
1013
1114
15+ if TYPE_CHECKING :
16+ from qiling import Qiling
17+ from qiling .loader .loader import QlLoader
18+
19+
1220# Adapted from https://www.ayrx.me/drcov-file-format
1321class bb_entry (Structure ):
1422 _fields_ = [
@@ -29,36 +37,61 @@ class QlDrCoverage(QlBaseCoverage):
2937
3038 FORMAT_NAME = "drcov"
3139
32- def __init__ (self , ql ):
40+ def __init__ (self , ql : Qiling ):
3341 super ().__init__ (ql )
3442
3543 self .drcov_version = 2
3644 self .drcov_flavor = 'drcov'
37- self .basic_blocks = []
45+ self .basic_blocks : Dict [ int , bb_entry ] = {}
3846 self .bb_callback = None
3947
40- @staticmethod
41- def block_callback (ql , address , size , self ):
42- for mod_id , mod in enumerate (ql .loader .images ):
43- if mod .base <= address <= mod .end :
44- ent = bb_entry (address - mod .base , size , mod_id )
45- self .basic_blocks .append (ent )
46- break
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)
4767
48- def activate (self ):
49- self .bb_callback = self .ql .hook_block (self .block_callback , user_data = self )
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 )
5073
51- def deactivate (self ):
52- self .ql .hook_del (self .bb_callback )
74+ def activate (self ) -> None :
75+ self .bb_callback = self .ql .hook_block (self .block_callback )
76+
77+ def deactivate (self ) -> None :
78+ if self .bb_callback :
79+ self .ql .hook_del (self .bb_callback )
80+
81+ def dump_coverage (self , coverage_file : str ) -> None :
82+ def __write_line (bio : BinaryIO , line : str ) -> None :
83+ bio .write (f'{ line } \n ' .encode ())
5384
54- def dump_coverage (self , coverage_file ):
5585 with open (coverage_file , "wb" ) as cov :
56- cov .write (f"DRCOV VERSION: { self .drcov_version } \n " .encode ())
57- cov .write (f"DRCOV FLAVOR: { self .drcov_flavor } \n " .encode ())
58- cov .write (f"Module Table: version { self .drcov_version } , count { len (self .ql .loader .images )} \n " .encode ())
59- cov .write ("Columns: id, base, end, entry, checksum, timestamp, path\n " .encode ())
86+ __write_line (cov , f"DRCOV VERSION: { self .drcov_version } " )
87+ __write_line (cov , f"DRCOV FLAVOR: { self .drcov_flavor } " )
88+ __write_line (cov , f"Module Table: version { self .drcov_version } , count { len (self .ql .loader .images )} " )
89+ __write_line (cov , "Columns: id, base, end, entry, checksum, timestamp, path" )
90+
6091 for mod_id , mod in enumerate (self . ql .loader .images ):
61- cov .write (f"{ mod_id } , { mod .base } , { mod .end } , 0, 0, 0, { mod .path } \n " .encode ())
62- cov .write (f"BB Table: { len (self .basic_blocks )} bbs\n " .encode ())
63- for bb in self .basic_blocks :
92+ __write_line (cov , f"{ mod_id } , { mod .base } , { mod .end } , 0, 0, 0, { mod .path } " )
93+
94+ __write_line (cov , f"BB Table: { len (self .basic_blocks )} bbs" )
95+
96+ for bb in self .basic_blocks .values ():
6497 cov .write (bytes (bb ))
0 commit comments