1+ ##############################################################################
2+ # This example is meant to demonstrate the modifications necessary
3+ # to enable code coverage when emulating small code snippets or bare-metal
4+ # code.
5+ ##############################################################################
6+ from qiling import Qiling
7+ from qiling .const import QL_ARCH , QL_OS , QL_VERBOSE
8+ from qiling .extensions .coverage import utils as cov_utils
9+ from qiling .loader .loader import Image
10+ import os
11+
12+ BASE_ADDRESS = 0x10000000
13+ CHECKSUM_FUNC_ADDR = BASE_ADDRESS + 0x8
14+ END_ADDRESS = 0x100000ba
15+ DATA_ADDR = 0xa0000000 # Arbitrary address for data
16+ STACK_ADDR = 0xb0000000 # Arbitrary address for stack
17+
18+ # Python implementation of the checksum function being emulated
19+ # This checksum function is intended to have different code paths based on the input
20+ # which is useful for observing code coverage
21+ def checksum_function (input_data_buffer : bytes ):
22+ expected_checksum_python = 0
23+ input_data_len = len (input_data_buffer )
24+ if input_data_len >= 1 and input_data_buffer [0 ] == 0xDE : # MAGIC_VALUE_1
25+ for i in range (min (input_data_len , 4 )):
26+ expected_checksum_python += input_data_buffer [i ]
27+ expected_checksum_python += 0x10
28+ elif input_data_len >= 2 and input_data_buffer [1 ] == 0xAD : # MAGIC_VALUE_2
29+ for i in range (input_data_len ):
30+ expected_checksum_python ^= input_data_buffer [i ]
31+ expected_checksum_python += 0x20
32+ else :
33+ for i in range (input_data_len ):
34+ expected_checksum_python += input_data_buffer [i ]
35+ expected_checksum_python &= 0xFF # Ensure it's a single byte
36+ return expected_checksum_python
37+
38+ def unmapped_handler (ql : Qiling , type : int , addr : int , size : int , value : int ) -> None :
39+ print (f"Unmapped Memory R/W, trying to access { size :d} bytes at { addr :#010x} from { ql .arch .regs .pc :#010x} " )
40+
41+ def emulate_checksum_function (input_data_buffer : bytes ) -> None :
42+ print (f"\n --- Testing with input: { input_data_buffer .hex ()} ---" )
43+
44+ test_file = "rootfs/blob/example_raw.bin"
45+
46+ with open (test_file , "rb" ) as f :
47+ raw_code : bytes = f .read ()
48+
49+ ql : Qiling = Qiling (
50+ code = raw_code ,
51+ archtype = QL_ARCH .ARM ,
52+ ostype = QL_OS .BLOB ,
53+ profile = "blob_raw.ql" ,
54+ verbose = QL_VERBOSE .DEBUG ,
55+ thumb = True
56+ )
57+
58+ ''' monkeypatch - Correcting the loader image name, used for coverage collection
59+ removing all images with name 'blob_code' that were created by the blob loader.
60+ This is necessary because some code coverage visualization tools require the
61+ module name to match that of the input file '''
62+ ql .loader .images = [img for img in ql .loader .images if img .path != 'blob_code' ]
63+ ql .loader .images .append (Image (ql .loader .load_address , ql .loader .load_address + ql .os .code_ram_size , os .path .basename (test_file )))
64+
65+ input_data_len : int = len (input_data_buffer )
66+
67+ # Map memory for the data and stack
68+ ql .mem .map (STACK_ADDR , 0x2000 )
69+ ql .mem .map (DATA_ADDR , ql .mem .align_up (input_data_len + 0x100 )) # Map enough space for data
70+
71+ # Write input data
72+ ql .mem .write (DATA_ADDR , input_data_buffer )
73+
74+ # Set up the stack pointer
75+ ql .arch .regs .sp = STACK_ADDR + 0x2000 - 4
76+ # Set up argument registers
77+ ql .arch .regs .r0 = DATA_ADDR
78+ ql .arch .regs .r1 = input_data_len
79+
80+ # Set the program counter to the function's entry point
81+ ql .arch .regs .pc = CHECKSUM_FUNC_ADDR
82+
83+ # Set the return address (LR) to a dummy address.
84+ ql .arch .regs .lr = 0xbebebebe
85+
86+ ql .hook_mem_unmapped (unmapped_handler )
87+ #ql.debugger="gdb:127.0.0.1:9999"
88+
89+ # Start emulation
90+ print (f"Starting emulation at PC: { hex (ql .arch .regs .pc )} " )
91+ try :
92+ with cov_utils .collect_coverage (ql , 'drcov' , 'output.cov' ):
93+ ql .run (begin = CHECKSUM_FUNC_ADDR , end = END_ADDRESS )
94+ except Exception as e :
95+ print (f"Emulation error: { e } " )
96+
97+ print (f"Emulated checksum: { hex (ql .arch .regs .r0 )} " )
98+
99+ if __name__ == "__main__" :
100+ data = b"\x01 \x02 \x03 \x04 \x05 " # Example input data
101+ emulate_checksum_function (data )
0 commit comments