1313from qiling import Qiling
1414from qiling .exception import *
1515
16- # tuple: range start, range end, permissions mask, range label, is mmio?
17- MapInfoEntry = Tuple [int , int , int , str , bool ]
18-
19- MmioReadCallback = Callable [[Qiling , int , int ], int ]
20- MmioWriteCallback = Callable [[Qiling , int , int , int ], None ]
21-
2216
2317class QlMmioHandler (Protocol ):
2418 """A simple MMIO handler boilerplate that can be used to implement memory mapped devices.
@@ -36,6 +30,13 @@ def write(self, ql: Qiling, offset: int, size: int, value: int) -> None:
3630 ...
3731
3832
33+ # tuple: range start, range end, permissions mask, range label, mmio hander object (if mmio range)
34+ MapInfoEntry = Tuple [int , int , int , str , Optional [QlMmioHandler ]]
35+
36+ MmioReadCallback = Callable [[Qiling , int , int ], int ]
37+ MmioWriteCallback = Callable [[Qiling , int , int , int ], None ]
38+
39+
3940class QlMemoryManager :
4041 """
4142 some ideas and code from:
@@ -45,7 +46,6 @@ class QlMemoryManager:
4546 def __init__ (self , ql : Qiling , pagesize : int = 0x1000 ):
4647 self .ql = ql
4748 self .map_info : List [MapInfoEntry ] = []
48- self .mmio_cbs : Dict [Tuple [int , int ], QlMmioHandler ] = {}
4949
5050 bit_stuff = {
5151 64 : (1 << 64 ) - 1 ,
@@ -121,18 +121,18 @@ def string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]:
121121
122122 self .__write_string (addr , value , encoding )
123123
124- def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , is_mmio : bool = False ):
124+ def add_mapinfo (self , mem_s : int , mem_e : int , mem_p : int , mem_info : str , mmio_ctx : Optional [ QlMmioHandler ] = None ):
125125 """Add a new memory range to map.
126126
127127 Args:
128128 mem_s: memory range start
129129 mem_e: memory range end
130130 mem_p: permissions mask
131131 mem_info: map entry label
132- is_mmio: memory range is mmio
132+ mmio_ctx: mmio handler object; if specified the range will be treated as mmio
133133 """
134134
135- bisect .insort (self .map_info , (mem_s , mem_e , mem_p , mem_info , is_mmio ))
135+ bisect .insort (self .map_info , (mem_s , mem_e , mem_p , mem_info , mmio_ctx ))
136136
137137 def del_mapinfo (self , mem_s : int , mem_e : int ):
138138 """Subtract a memory range from map.
@@ -146,13 +146,13 @@ def del_mapinfo(self, mem_s: int, mem_e: int):
146146
147147 def __split_overlaps ():
148148 for idx in overlap_ranges :
149- lbound , ubound , perms , label , is_mmio = self .map_info [idx ]
149+ lbound , ubound , perms , label , mmio_ctx = self .map_info [idx ]
150150
151151 if lbound < mem_s :
152- yield (lbound , mem_s , perms , label , is_mmio )
152+ yield (lbound , mem_s , perms , label , mmio_ctx )
153153
154154 if mem_e < ubound :
155- yield (mem_e , ubound , perms , label , is_mmio )
155+ yield (mem_e , ubound , perms , label , mmio_ctx )
156156
157157 # indices of first and last overlapping ranges. since map info is always
158158 # sorted, we know that all overlapping rages are consecutive, so i1 > i0
@@ -170,27 +170,30 @@ def __split_overlaps():
170170 for entry in new_entries :
171171 bisect .insort (self .map_info , entry )
172172
173- def change_mapinfo (self , mem_s : int , mem_e : int , mem_p : Optional [int ] = None , mem_info : Optional [str ] = None ):
174- tmp_map_info : Optional [MapInfoEntry ] = None
175- info_idx : int = - 1
176-
177- for idx , map_info in enumerate (self .map_info ):
178- if mem_s >= map_info [0 ] and mem_e <= map_info [1 ]:
179- tmp_map_info = map_info
180- info_idx = idx
181- break
173+ def change_mapinfo (self , mem_s : int , mem_e : int , * , new_perms : Optional [int ] = None , new_info : Optional [str ] = None ) -> None :
174+ if new_perms is None and new_info is None :
175+ # nothing to do
176+ return
182177
183- if tmp_map_info is None :
178+ try :
179+ # locate the map info entry to change
180+ entry = next (entry for entry in self .map_info if mem_s >= entry [0 ] and mem_e <= entry [1 ])
181+ except StopIteration :
184182 self .ql .log .error (f'Cannot change mapinfo at { mem_s :#08x} -{ mem_e :#08x} ' )
185183 return
186184
187- if mem_p is not None :
188- self .del_mapinfo (mem_s , mem_e )
189- self .add_mapinfo (mem_s , mem_e , mem_p , mem_info if mem_info else tmp_map_info [3 ])
190- return
185+ _ , _ , perms , info , mmio_ctx = entry
186+
187+ # caller wants to change perms?
188+ if new_perms is not None :
189+ perms = new_perms
191190
192- if mem_info is not None :
193- self .map_info [info_idx ] = (tmp_map_info [0 ], tmp_map_info [1 ], tmp_map_info [2 ], mem_info , tmp_map_info [4 ])
191+ # caller wants to change info?
192+ if new_info is not None :
193+ info = new_info
194+
195+ self .del_mapinfo (mem_s , mem_e )
196+ self .add_mapinfo (mem_s , mem_e , perms , info , mmio_ctx )
194197
195198 def get_mapinfo (self ) -> Sequence [Tuple [int , int , str , str , str ]]:
196199 """Get memory map info.
@@ -209,18 +212,18 @@ def __perms_mapping(ps: int) -> str:
209212
210213 return '' .join (val if idx & ps else '-' for idx , val in perms_d .items ())
211214
212- def __process (lbound : int , ubound : int , perms : int , label : str , is_mmio : bool ) -> Tuple [int , int , str , str , str ]:
213- perms_str = __perms_mapping ( perms )
215+ def __process (entry : MapInfoEntry ) -> Tuple [int , int , str , str , str ]:
216+ lbound , ubound , perms , label , mmio_ctx = entry
214217
215218 if hasattr (self .ql , 'loader' ):
216219 image = self .ql .loader .find_containing_image (lbound )
217- container = image .path if image and not is_mmio else ''
220+ container = image .path if image and mmio_ctx is None else ''
218221 else :
219222 container = ''
220223
221- return (lbound , ubound , perms_str , label , container )
224+ return (lbound , ubound , __perms_mapping ( perms ) , label , container )
222225
223- return tuple (__process (* entry ) for entry in self .map_info )
226+ return tuple (__process (entry ) for entry in self .map_info )
224227
225228 def get_formatted_mapinfo (self ) -> Sequence [str ]:
226229 """Get memory map info in a nicely formatted table.
@@ -311,12 +314,13 @@ def save(self):
311314 "mmio" : []
312315 }
313316
314- for lbound , ubound , perm , label , is_mmio in self .map_info :
315- if is_mmio :
316- mem_dict [ 'mmio' ]. append (( lbound , ubound , perm , label , self .mmio_cbs [ (lbound , ubound )] ))
317+ for lbound , ubound , perm , label , mmio_ctx in self .map_info :
318+ if mmio_ctx is None :
319+ key , data = 'ram' , bytes ( self .read (lbound , ubound - lbound ))
317320 else :
318- data = self .read (lbound , ubound - lbound )
319- mem_dict ['ram' ].append ((lbound , ubound , perm , label , bytes (data )))
321+ key , data = 'mmio' , mmio_ctx
322+
323+ mem_dict [key ].append ((lbound , ubound , perm , label , data ))
320324
321325 return mem_dict
322326
@@ -429,7 +433,7 @@ def search(self, needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = No
429433 assert begin < end , 'search arguments do not make sense'
430434
431435 # narrow the search down to relevant ranges; mmio ranges are excluded due to potential read side effects
432- ranges = [(max (begin , lbound ), min (ubound , end )) for lbound , ubound , _ , _ , is_mmio in self .map_info if not (end < lbound or ubound < begin or is_mmio )]
436+ ranges = [(max (begin , lbound ), min (ubound , end )) for lbound , ubound , _ , _ , mmio_ctx in self .map_info if not (end < lbound or ubound < begin or mmio_ctx is not None )]
433437 results = []
434438
435439 # if needle is a bytes sequence use it verbatim, not as a pattern
@@ -455,9 +459,6 @@ def unmap(self, addr: int, size: int) -> None:
455459 self .del_mapinfo (addr , addr + size )
456460 self .ql .uc .mem_unmap (addr , size )
457461
458- if (addr , addr + size ) in self .mmio_cbs :
459- del self .mmio_cbs [(addr , addr + size )]
460-
461462 def unmap_between (self , mem_s : int , mem_e : int ) -> None :
462463 """Reclaim any allocated memory region within the specified range.
463464
@@ -616,7 +617,7 @@ def protect(self, addr: int, size: int, perms):
616617 aligned_size = self .align_up ((addr & (self .pagesize - 1 )) + size )
617618
618619 self .ql .uc .mem_protect (aligned_address , aligned_size , perms )
619- self .change_mapinfo (aligned_address , aligned_address + aligned_size , perms )
620+ self .change_mapinfo (aligned_address , aligned_address + aligned_size , new_perms = perms )
620621
621622 def map (self , addr : int , size : int , perms : int = UC_PROT_ALL , info : Optional [str ] = None ):
622623 """Map a new memory range.
@@ -638,7 +639,7 @@ def map(self, addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str
638639 raise QlMemoryMappedError ('Requested memory is unavailable' )
639640
640641 self .ql .uc .mem_map (addr , size , perms )
641- self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , is_mmio = False )
642+ self .add_mapinfo (addr , addr + size , perms , info or '[mapped]' , None )
642643
643644 def map_mmio (self , addr : int , size : int , handler : QlMmioHandler , info : str = '[mmio]' ):
644645 # TODO: mmio memory overlap with ram? Is that possible?
@@ -664,9 +665,7 @@ def __mmio_write(uc, offset: int, size: int, value: int, user_data: MmioWriteCal
664665 cb (self .ql , offset , size , value )
665666
666667 self .ql .uc .mmio_map (addr , size , __mmio_read , handler .read , __mmio_write , handler .write )
667- self .add_mapinfo (addr , addr + size , prot , info , is_mmio = True )
668-
669- self .mmio_cbs [(addr , addr + size )] = handler
668+ self .add_mapinfo (addr , addr + size , prot , info , handler )
670669
671670
672671class Chunk :
0 commit comments