33# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44#
55
6- import ctypes
6+ from functools import cached_property
7+ from typing import Any , Dict , List , Optional , Tuple
78
8- from qiling . core import Qiling
9+ from qiling import Qiling
910from qiling .hw .peripheral import QlPeripheral
1011from qiling .utils import ql_get_module_function
1112from qiling .exception import QlErrorModuleFunctionNotFound
1213
1314
15+ # should adhere to the QlMmioHandler interface, but not extend it directly to
16+ # avoid potential pickling issues
17+ class QlPripheralHandler :
18+ def __init__ (self , hwman : "QlHwManager" , base : int , size : int , label : str ) -> None :
19+ self ._hwman = hwman
20+ self ._base = base
21+ self ._size = size
22+ self ._label = label
23+
24+ def __getstate__ (self ):
25+ state = self .__dict__ .copy ()
26+ del state ['_hwman' ] # remove non-pickleable reference
27+
28+ return state
29+
30+ @cached_property
31+ def _mmio (self ) -> bytearray :
32+ """Get memory buffer used to back non-mapped hardware mmio regions.
33+ """
34+
35+ return bytearray (self ._size )
36+
37+ def read (self , ql : Qiling , offset : int , size : int ) -> int :
38+ address = self ._base + offset
39+ hardware = self ._hwman .find (address )
40+
41+ if hardware :
42+ return hardware .read (address - hardware .base , size )
43+
44+ else :
45+ ql .log .debug ('[%s] read non-mapped hardware [%#010x]' , self ._label , address )
46+ return int .from_bytes (self ._mmio [offset :offset + size ], byteorder = 'little' )
47+
48+ def write (self , ql : Qiling , offset : int , size : int , value : int ) -> None :
49+ address = self ._base + offset
50+ hardware = self ._hwman .find (address )
51+
52+ if hardware :
53+ hardware .write (address - hardware .base , size , value )
54+
55+ else :
56+ ql .log .debug ('[%s] write non-mapped hardware [%#010x] = %#010x' , self ._label , address , value )
57+ self ._mmio [offset :offset + size ] = value .to_bytes (size , 'little' )
58+
59+
1460class QlHwManager :
1561 def __init__ (self , ql : Qiling ):
1662 self .ql = ql
1763
18- self .entity = {}
19- self .region = {}
20-
21- self .stepable = {}
64+ self .entity : Dict [str , QlPeripheral ] = {}
65+ self .region : Dict [str , List [Tuple [int , int ]]] = {}
2266
23- def create (self , label : str , struct : str = None , base : int = None , kwargs : dict = {} ) -> " QlPeripheral" :
67+ def create (self , label : str , struct : Optional [ str ] = None , base : Optional [ int ] = None , kwargs : Optional [ Dict [ str , Any ]] = None ) -> QlPeripheral :
2468 """ Create the peripheral accroding the label and envs.
2569
2670 struct: Structure of the peripheral. Use defualt ql structure if not provide.
@@ -30,88 +74,76 @@ def create(self, label: str, struct: str=None, base: int=None, kwargs: dict={})
3074 if struct is None :
3175 struct , base , kwargs = self .load_env (label .upper ())
3276
77+ if kwargs is None :
78+ kwargs = {}
79+
3380 try :
34-
3581 entity = ql_get_module_function ('qiling.hw' , struct )(self .ql , label , ** kwargs )
36-
37- self .entity [label ] = entity
38- if hasattr (entity , 'step' ):
39- self .stepable [label ] = entity
4082
41- self .region [label ] = [(lbound + base , rbound + base ) for (lbound , rbound ) in entity .region ]
83+ except QlErrorModuleFunctionNotFound :
84+ self .ql .log .warning (f'could not create { struct } ({ label } ): implementation not found' )
4285
86+ else :
87+ assert isinstance (entity , QlPeripheral )
88+ assert isinstance (base , int )
89+
90+ self .entity [label ] = entity
91+ self .region [label ] = [(lbound + base , rbound + base ) for (lbound , rbound ) in entity .region ]
4392
4493 return entity
45- except QlErrorModuleFunctionNotFound :
46- self .ql .log .debug (f'The { struct } ({ label } ) has not been implemented' )
4794
48- def delete (self , label : str ):
95+ # FIXME: what should we do if struct is not implemented? is it OK to return None , or we fail?
96+
97+ def delete (self , label : str ) -> None :
4998 """ Remove the peripheral
5099 """
100+
51101 if label in self .entity :
52- self .entity . pop ( label )
53- self . region . pop ( label )
54- if label in self .stepable :
55- self .stepable . pop ( label )
102+ del self .entity [ label ]
103+
104+ if label in self .region :
105+ del self .region [ label ]
56106
57- def load_env (self , label : str ):
107+ def load_env (self , label : str ) -> Tuple [ str , int , Dict [ str , Any ]] :
58108 """ Get peripheral information (structure, base address, initialization list) from env.
59109
60110 Args:
61111 label (str): Peripheral Label
62-
112+
63113 """
64114 args = self .ql .env [label ]
65-
115+
66116 return args ['struct' ], args ['base' ], args .get ("kwargs" , {})
67117
68118 def load_all (self ):
69119 for label , args in self .ql .env .items ():
70120 if args ['type' ] == 'peripheral' :
71121 self .create (label .lower (), args ['struct' ], args ['base' ], args .get ("kwargs" , {}))
72122
73- def find (self , address : int ):
123+ # TODO: this is wasteful. device mapping is known at creation time. at least we could cache lru entries
124+ def find (self , address : int ) -> Optional [QlPeripheral ]:
74125 """ Find the peripheral at `address`
75126 """
76-
127+
77128 for label in self .entity .keys ():
78129 for lbound , rbound in self .region [label ]:
79130 if lbound <= address < rbound :
80131 return self .entity [label ]
81132
133+ return None
134+
82135 def step (self ):
83- """ Update all peripheral's state
136+ """ Update all peripheral's state
84137 """
85- for entity in self .stepable .values ():
86- entity .step ()
87-
88- def setup_mmio (self , begin , size , info = "" ):
89- mmio = ctypes .create_string_buffer (size )
90-
91- def mmio_read_cb (ql , offset , size ):
92- address = begin + offset
93- hardware = self .find (address )
94-
95- if hardware :
96- return hardware .read (address - hardware .base , size )
97- else :
98- ql .log .debug ('%s Read non-mapped hardware [0x%08x]' % (info , address ))
99-
100- buf = ctypes .create_string_buffer (size )
101- ctypes .memmove (buf , ctypes .addressof (mmio ) + offset , size )
102- return int .from_bytes (buf .raw , byteorder = 'little' )
103-
104- def mmio_write_cb (ql , offset , size , value ):
105- address = begin + offset
106- hardware = self .find (address )
107-
108- if hardware :
109- hardware .write (address - hardware .base , size , value )
110- else :
111- ql .log .debug ('%s Write non-mapped hardware [0x%08x] = 0x%08x' % (info , address , value ))
112- ctypes .memmove (ctypes .addressof (mmio ) + offset , (value ).to_bytes (size , 'little' ), size )
113-
114- self .ql .mem .map_mmio (begin , size , mmio_read_cb , mmio_write_cb , info = info )
138+
139+ for ent in self .entity .values ():
140+ if hasattr (ent , 'step' ):
141+ ent .step ()
142+
143+ def setup_mmio (self , begin : int , size : int , info : str ) -> None :
144+ dev = QlPripheralHandler (self , begin , size , info )
145+
146+ self .ql .mem .map_mmio (begin , size , dev , info )
115147
116148 def show_info (self ):
117149 self .ql .log .info (f'{ "Start" :8s} { "End" :8s} { "Label" :8s} { "Class" } ' )
@@ -131,8 +163,25 @@ def __getattr__(self, key):
131163 return self .entity .get (key )
132164
133165 def save (self ):
134- return {label : entity .save () for label , entity in self .entity .items ()}
166+ return {
167+ 'entity' : {label : entity .save () for label , entity in self .entity .items ()},
168+ 'region' : self .region
169+ }
135170
136171 def restore (self , saved_state ):
137- for label , data in saved_state .items ():
172+ entity = saved_state ['entity' ]
173+ assert isinstance (entity , dict )
174+
175+ region = saved_state ['region' ]
176+ assert isinstance (region , dict )
177+
178+ for label , data in entity .items ():
138179 self .entity [label ].restore (data )
180+
181+ self .region = region
182+
183+ # a dirty hack to rehydrate non-pickleable hwman
184+ # 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 ():
186+ if isinstance (ph , QlPripheralHandler ):
187+ setattr (ph , '_hwman' , self )
0 commit comments