33import sys
44import traceback
55from enum import Enum
6- from typing import List , Union , NamedTuple
6+ from typing import List , Union , NamedTuple , Callable
77import inspect
88from collections import OrderedDict
99from dataclasses import dataclass , field
@@ -39,22 +39,41 @@ class ExceptionType(Enum):
3939 ContextSwitch = 3
4040 Terminate = 4
4141
42+ class MemoryViolation (Enum ):
43+ Unknown = 0
44+ ReadUnmapped = 1
45+ WriteUnmapped = 2
46+ ExecuteUnmapped = 3
47+ ReadProtect = 4
48+ WriteProtect = 5
49+ ExecuteProtect = 6
50+ ReadUnaligned = 7
51+ WriteUnaligned = 8
52+ ExecuteUnaligned = 9
53+
4254@dataclass
4355class ExceptionInfo :
4456 type : ExceptionType = ExceptionType .NoException
45- memory_access : int = 0 # refers to UC_MEM_* values
57+ # type == ExceptionType.Memory
58+ memory_violation : MemoryViolation = MemoryViolation .Unknown
4659 memory_address : int = 0
4760 memory_size : int = 0
4861 memory_value : int = 0
62+ # type == ExceptionType.Interrupt
4963 interrupt_number : int = 0
64+
65+ # Internal state
66+ _handling : bool = False
67+
68+ @dataclass
69+ class UnicornExceptionInfo (ExceptionInfo ):
70+ final : bool = False
5071 code_hook_h : Optional [int ] = None # represents a `unicorn.uc_hook_h` value (from uc.hook_add)
5172 context : Optional [unicorn .UcContext ] = None
5273 tb_start : int = 0
5374 tb_size : int = 0
5475 tb_icount : int = 0
5576 step_count : int = 0
56- final : bool = False
57- handling : bool = False
5877
5978 def __str__ (self ):
6079 return f"{ self .type } , ({ hex (self .tb_start )} , { hex (self .tb_size )} , { self .tb_icount } )"
@@ -305,8 +324,9 @@ def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, d
305324 self .kill_me = None
306325 self .exit_code = None
307326 self .exports = self ._all_exports ()
308- self .exception = ExceptionInfo ()
309- self .last_exception : Optional [ExceptionInfo ] = None
327+ self ._exception = UnicornExceptionInfo ()
328+ self ._last_exception : Optional [UnicornExceptionInfo ] = None
329+ self ._exception_hook : Optional [Callable [[ExceptionInfo ], Optional [int ]]] = None
310330 if not self ._quiet :
311331 print ("Memory map:" )
312332 self .print_memory ()
@@ -896,15 +916,28 @@ def allocate(self, size, page_align=False):
896916 self .memory .commit (self .memory .align_page (ptr ), self .memory .align_page (size ))
897917 return ptr
898918
899- def handle_exception (self ):
900- assert not self .exception .handling
901- self .exception .handling = True
919+ def set_exception_hook (self , exception_hook : Optional [Callable [[ExceptionInfo ], Optional [int ]]]):
920+ previous_hook = self ._exception_hook
921+ self ._exception_hook = exception_hook
922+ return previous_hook
902923
903- if self .exception .type == ExceptionType .ContextSwitch :
924+ def handle_exception (self ):
925+ assert not self ._exception ._handling
926+ self ._exception ._handling = True
927+
928+ if self ._exception_hook is not None :
929+ hook_result = self ._exception_hook (self ._exception )
930+ if hook_result is not None :
931+ # Clear the pending exception
932+ self ._last_exception = self ._exception
933+ self ._exception = UnicornExceptionInfo ()
934+ return hook_result
935+
936+ if self ._exception .type == ExceptionType .ContextSwitch :
904937 self .info (f"context switch, cip: { hex (self .regs .cip )} " )
905938 # Clear the pending exception
906- self .last_exception = self .exception
907- self .exception = ExceptionInfo ()
939+ self ._last_exception = self ._exception
940+ self ._exception = UnicornExceptionInfo ()
908941 # NOTE: the context has already been restored using context_restore in the caller
909942 return self .regs .cip
910943
@@ -961,22 +994,23 @@ def handle_exception(self):
961994 context_ex .XState .Offset = 0xF0 if self ._x64 else 0x20
962995 context_ex .XState .Length = 0x160 if self ._x64 else 0x140
963996 record = record_type ()
964- if self .exception .type == ExceptionType .Memory :
965- record .ExceptionCode = 0xC0000005
997+ alignment_violations = [MemoryViolation .ReadUnaligned , MemoryViolation .WriteUnaligned , MemoryViolation .ExecuteUnaligned ]
998+ if self ._exception .type == ExceptionType .Memory and self ._exception .memory_violation not in alignment_violations :
999+ record .ExceptionCode = STATUS_ACCESS_VIOLATION
9661000 record .ExceptionFlags = 0
9671001 record .ExceptionAddress = self .regs .cip
9681002 record .NumberParameters = 2
9691003 types = {
970- UC_MEM_READ_UNMAPPED : EXCEPTION_READ_FAULT ,
971- UC_MEM_WRITE_UNMAPPED : EXCEPTION_WRITE_FAULT ,
972- UC_MEM_FETCH_UNMAPPED : EXCEPTION_READ_FAULT ,
973- UC_MEM_READ_PROT : EXCEPTION_READ_FAULT ,
974- UC_MEM_WRITE_PROT : EXCEPTION_WRITE_FAULT ,
975- UC_MEM_FETCH_PROT : EXCEPTION_EXECUTE_FAULT ,
1004+ MemoryViolation . ReadUnmapped : EXCEPTION_READ_FAULT ,
1005+ MemoryViolation . WriteUnmapped : EXCEPTION_WRITE_FAULT ,
1006+ MemoryViolation . ExecuteUnmapped : EXCEPTION_READ_FAULT ,
1007+ MemoryViolation . ReadProtect : EXCEPTION_READ_FAULT ,
1008+ MemoryViolation . WriteProtect : EXCEPTION_WRITE_FAULT ,
1009+ MemoryViolation . ExecuteProtect : EXCEPTION_EXECUTE_FAULT ,
9761010 }
977- record .ExceptionInformation [0 ] = types [self .exception . memory_access ]
978- record .ExceptionInformation [1 ] = self .exception .memory_address
979- elif self .exception .type == ExceptionType .Interrupt and self .exception .interrupt_number == 3 :
1011+ record .ExceptionInformation [0 ] = types [self ._exception . memory_violation ]
1012+ record .ExceptionInformation [1 ] = self ._exception .memory_address
1013+ elif self ._exception .type == ExceptionType .Interrupt and self ._exception .interrupt_number == 3 :
9801014 if self ._x64 :
9811015 context .Rip -= 1 # TODO: long int3 and prefixes
9821016 record .ExceptionCode = 0x80000003
@@ -990,11 +1024,11 @@ def handle_exception(self):
9901024 record .ExceptionAddress = context .Eip
9911025 record .NumberParameters = 1
9921026 else :
993- raise NotImplementedError (f"{ self .exception } " ) # TODO: implement
1027+ raise NotImplementedError (f"{ self ._exception } " ) # TODO: implement
9941028
9951029 # Clear the pending exception
996- self .last_exception = self .exception
997- self .exception = ExceptionInfo ()
1030+ self ._last_exception = self ._exception
1031+ self ._exception = UnicornExceptionInfo ()
9981032
9991033 def write_stack (cur_ptr : int , data : bytes ):
10001034 self .write (cur_ptr , data )
@@ -1024,19 +1058,19 @@ def write_stack(cur_ptr: int, data: bytes):
10241058
10251059 def start (self , begin , end = 0xffffffffffffffff , count = 0 ) -> None :
10261060 # Clear exceptions before starting
1027- self .exception = ExceptionInfo ()
1061+ self ._exception = UnicornExceptionInfo ()
10281062 emu_begin = begin
10291063 emu_until = end
10301064 emu_count = count
10311065 while True :
10321066 try :
1033- if self .exception .type != ExceptionType .NoException :
1034- if self .exception .final :
1067+ if self ._exception .type != ExceptionType .NoException :
1068+ if self ._exception .final :
10351069 # Restore the context (unicorn might mess with it before stopping)
1036- if self .exception .context is not None :
1037- self ._uc .context_restore (self .exception .context )
1070+ if self ._exception .context is not None :
1071+ self ._uc .context_restore (self ._exception .context )
10381072
1039- if self .exception .type == ExceptionType .Terminate :
1073+ if self ._exception .type == ExceptionType .Terminate :
10401074 if self .exit_code is not None :
10411075 self .info (f"exit code: { hex (self .exit_code )} " )
10421076 break
@@ -1051,20 +1085,20 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
10511085 emu_count = 0
10521086 else :
10531087 # If this happens there was an error restarting simulation
1054- assert self .exception .step_count == 0
1088+ assert self ._exception .step_count == 0
10551089
10561090 # Hook should be installed at this point
1057- assert self .exception .code_hook_h is not None
1091+ assert self ._exception .code_hook_h is not None
10581092
10591093 # Restore the context (unicorn might mess with it before stopping)
1060- assert self .exception .context is not None
1061- self ._uc .context_restore (self .exception .context )
1094+ assert self ._exception .context is not None
1095+ self ._uc .context_restore (self ._exception .context )
10621096
10631097 # Restart emulation
10641098 self .info (f"restarting emulation to handle exception..." )
10651099 emu_begin = self .regs .cip
10661100 emu_until = 0xffffffffffffffff
1067- emu_count = self .exception .tb_icount + 1
1101+ emu_count = self ._exception .tb_icount + 1
10681102
10691103 self .info (f"emu_start({ hex (emu_begin )} , { hex (emu_until )} , { emu_count } )" )
10701104 self .kill_me = None
@@ -1076,7 +1110,7 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
10761110 except UcError as err :
10771111 if self .kill_me is not None and type (self .kill_me ) is not UcError :
10781112 raise self .kill_me
1079- if self .exception .type != ExceptionType .NoException :
1113+ if self ._exception .type != ExceptionType .NoException :
10801114 # Handle the exception outside of the except handler
10811115 continue
10821116 else :
@@ -1232,7 +1266,7 @@ def load_dll(self, file_name: str, file_data: bytes):
12321266def _hook_code_exception (uc : Uc , address , size , dp : Dumpulator ):
12331267 try :
12341268 dp .info (f"exception step: { hex (address )} [{ size } ]" )
1235- ex = dp .exception
1269+ ex = dp ._exception
12361270 ex .step_count += 1
12371271 if ex .step_count >= ex .tb_icount :
12381272 raise Exception ("Stepped past the basic block without reaching exception" )
@@ -1246,18 +1280,27 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
12461280 return True
12471281
12481282 fetch_accesses = [UC_MEM_FETCH , UC_MEM_FETCH_PROT , UC_MEM_FETCH_UNMAPPED ]
1249- if access == UC_MEM_FETCH_UNMAPPED and address >= FORCE_KILL_ADDR - 0x10 and address <= FORCE_KILL_ADDR + 0x10 and dp .kill_me is not None :
1283+ if access == UC_MEM_FETCH_UNMAPPED and FORCE_KILL_ADDR - 0x10 <= address <= FORCE_KILL_ADDR + 0x10 and dp .kill_me is not None :
12501284 dp .error (f"forced exit memory operation { access } of { hex (address )} [{ hex (size )} ] = { hex (value )} " )
12511285 return False
1252- if dp .exception .final and access in fetch_accesses :
1286+ if dp ._exception .final and access in fetch_accesses :
12531287 dp .info (f"fetch from { hex (address )} [{ size } ] already reported" )
12541288 return False
12551289 # TODO: figure out why when you start executing at 0 this callback is triggered more than once
12561290 try :
1291+ violation = {
1292+ UC_MEM_READ_UNMAPPED : MemoryViolation .ReadUnmapped ,
1293+ UC_MEM_WRITE_UNMAPPED : MemoryViolation .WriteUnmapped ,
1294+ UC_MEM_FETCH_UNMAPPED : MemoryViolation .ExecuteUnmapped ,
1295+ UC_MEM_READ_PROT : MemoryViolation .ReadProtect ,
1296+ UC_MEM_WRITE_PROT : MemoryViolation .WriteProtect ,
1297+ UC_MEM_FETCH_PROT : MemoryViolation .ExecuteProtect ,
1298+ }.get (access , MemoryViolation .Unknown )
1299+ assert violation != MemoryViolation .Unknown , f"Unexpected memory access { access } "
12571300 # Extract exception information
1258- exception = ExceptionInfo ()
1301+ exception = UnicornExceptionInfo ()
12591302 exception .type = ExceptionType .Memory
1260- exception .memory_access = access
1303+ exception .memory_violation = violation
12611304 exception .memory_address = address
12621305 exception .memory_size = size
12631306 exception .memory_value = value
@@ -1269,7 +1312,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
12691312 exception .tb_icount = tb .icount
12701313
12711314 # Print exception info
1272- final = dp .trace or dp .exception .code_hook_h is not None
1315+ final = dp .trace or dp ._exception .code_hook_h is not None
12731316 info = "final" if final else "initial"
12741317 if access == UC_MEM_READ_UNMAPPED :
12751318 dp .error (f"{ info } unmapped read from { hex (address )} [{ hex (size )} ], cip = { hex (dp .regs .cip )} , exception: { exception } " )
@@ -1295,25 +1338,25 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
12951338 if final :
12961339 # Make sure this is the same exception we expect
12971340 if not dp .trace :
1298- assert access == dp .exception . memory_access
1299- assert address == dp .exception .memory_address
1300- assert size == dp .exception .memory_size
1301- assert value == dp .exception .memory_value
1341+ assert violation == dp ._exception . memory_violation
1342+ assert address == dp ._exception .memory_address
1343+ assert size == dp ._exception .memory_size
1344+ assert value == dp ._exception .memory_value
13021345
13031346 # Delete the code hook
1304- uc .hook_del (dp .exception .code_hook_h )
1305- dp .exception .code_hook_h = None
1347+ uc .hook_del (dp ._exception .code_hook_h )
1348+ dp ._exception .code_hook_h = None
13061349
13071350 # At this point we know for sure the context is correct so we can report the exception
1308- dp .exception = exception
1309- dp .exception .final = True
1351+ dp ._exception = exception
1352+ dp ._exception .final = True
13101353
13111354 # Stop emulation (we resume it on KiUserExceptionDispatcher later)
13121355 dp .stop ()
13131356 return False
13141357
13151358 # There should not be an exception active
1316- assert dp .exception .type == ExceptionType .NoException
1359+ assert dp ._exception .type == ExceptionType .NoException
13171360
13181361 # Remove the translation block cache for this block
13191362 # Without doing this single stepping the block won't work
@@ -1325,7 +1368,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
13251368 exception .code_hook_h = uc .hook_add (UC_HOOK_CODE , _hook_code_exception , user_data = dp )
13261369
13271370 # Store the exception info
1328- dp .exception = exception
1371+ dp ._exception = exception
13291372
13301373 # Stop emulation (we resume execution later)
13311374 dp .stop ()
@@ -1452,7 +1495,7 @@ def _arg_type_string(arg):
14521495def _hook_interrupt (uc : Uc , number , dp : Dumpulator ):
14531496 try :
14541497 # Extract exception information
1455- exception = ExceptionInfo ()
1498+ exception = UnicornExceptionInfo ()
14561499 exception .type = ExceptionType .Interrupt
14571500 exception .interrupt_number = number
14581501 exception .context = uc .context_save ()
@@ -1470,11 +1513,11 @@ def _hook_interrupt(uc: Uc, number, dp: Dumpulator):
14701513 dp .error (f"interrupt { number } ({ description } ), cip = { hex (dp .regs .cip )} , cs = { hex (dp .regs .cs )} " )
14711514
14721515 # There should not be an exception active
1473- assert dp .exception .type == ExceptionType .NoException
1516+ assert dp ._exception .type == ExceptionType .NoException
14741517
14751518 # At this point we know for sure the context is correct so we can report the exception
1476- dp .exception = exception
1477- dp .exception .final = True
1519+ dp ._exception = exception
1520+ dp ._exception .final = True
14781521 except AssertionError as err :
14791522 traceback .print_exc ()
14801523 raise err
@@ -1560,7 +1603,7 @@ def syscall_arg(index):
15601603 status = syscall_impl (dp , * args )
15611604 if isinstance (status , ExceptionInfo ):
15621605 print ("context switch, stopping emulation" )
1563- dp .exception = status
1606+ dp ._exception = status
15641607 raise dp .raise_kill (UcError (UC_ERR_EXCEPTION )) from None
15651608 else :
15661609 dp .info (f"status = { hex (status )} " )
@@ -1610,11 +1653,11 @@ def _hook_invalid(uc: Uc, dp: Dumpulator):
16101653 instr = next (dp .cs .disasm (code , address , 1 ))
16111654 if _emulate_unsupported_instruction (dp , instr ):
16121655 # Resume execution with a context switch
1613- assert dp .exception .type == ExceptionType .NoException
1614- exception = ExceptionInfo ()
1656+ assert dp ._exception .type == ExceptionType .NoException
1657+ exception = UnicornExceptionInfo ()
16151658 exception .type = ExceptionType .ContextSwitch
16161659 exception .final = True
1617- dp .exception = exception
1660+ dp ._exception = exception
16181661 return False # NOTE: returning True would stop emulation
16191662 except StopIteration :
16201663 pass # Unsupported instruction
0 commit comments