99 Instruction ,
1010 InstructionAddRegisterT2 ,
1111 InstructionAddSpPlusRegisterT1 ,
12+ InstructionAdrT1 ,
1213 InstructionBlT1 ,
1314 InstructionBlxRegisterT1 ,
1415 InstructionBT1 ,
1516 InstructionBT2 ,
1617 InstructionBxT1 ,
1718 InstructionLdrLiteralT1 ,
19+ InstructionLdrRegisterT1 ,
1820 InstructionLslImmediateT1 ,
1921 InstructionMovRegisterT1 ,
2022 InstructionPopT1 ,
@@ -85,21 +87,21 @@ def resolve(
8587
8688 match instruction :
8789 case InstructionAddRegisterT2 ():
88- if instruction .dn is Register4 .PC :
89- # PC = PC + register
90- effect_branch = resolve_add_register_t2_pc (cursor_function_region_instructions )
90+ if instruction .dn is not Register4 .PC :
91+ return None
9192
92- return effect_branch
93+ # PC = PC + register
94+ effect_branch = resolve_add_register_t2_pc (cursor_function_region_instructions )
9395
94- return None
96+ return effect_branch
9597 case InstructionAddSpPlusRegisterT1 ():
96- if instruction .dm is Register4 .PC :
97- # PC = SP + PC ???
98-
99- # not seen in the wild
100- raise ResolveUnsupportedInstructionException (instruction , address )
101- else :
98+ if instruction .dm is not Register4 .PC :
10299 return None
100+
101+ # PC = SP + PC ???
102+ # not seen in the wild
103+ raise ResolveUnsupportedInstructionException (instruction , address )
104+
103105 case InstructionBT1 ():
104106 # current position + branch-pc offset (4) + instruction immediate
105107 target_function_offset = cursor_function_region_instructions .function_offset () + 4 + instruction .imm * 2
@@ -139,23 +141,26 @@ def resolve(
139141 # other patterns not seen in the wild
140142 raise ResolveUnsupportedInstructionException (instruction , address )
141143 case InstructionMovRegisterT1 ():
142- if instruction .d is Register4 .PC :
143- # PC = register
144+ if instruction .d is not Register4 .PC :
145+ return None
144146
145- # special case - MOV PC, LR is technically a return
146- if instruction .m is Register4 .LR :
147- return EffectReturn ()
147+ # PC = register
148148
149- # other patterns not seen in the wild
150- raise ResolveUnsupportedInstructionException (instruction , address )
151- else :
152- return None
153- case InstructionPopT1 ():
154- if instruction .pc :
155- # POP PC
149+ # special case - MOV PC, LR is technically a return
150+ if instruction .m is Register4 .LR :
156151 return EffectReturn ()
157152
158- return None
153+ # PC = register (non-LR)
154+ # requires non-trivial handling (eg. jump table via ADR + LDR register)
155+ effect_branch = resolve_mov_register_t1_pc (cursor_function_region_instructions )
156+
157+ return effect_branch
158+ case InstructionPopT1 ():
159+ if not instruction .pc :
160+ return None
161+
162+ # POP PC
163+ return EffectReturn ()
159164 case InstructionUdfT1 ():
160165 return EffectInvalid ()
161166 case InstructionUdfT2 ():
@@ -208,8 +213,8 @@ def resolve_add_register_t2_pc(cursor_function_region_instructions: CursorFuncti
208213
209214 effects_branch = {
210215 effect_branch
211- for effect_branch in ( resolver ( cursor_function_region_instructions ) for resolver in RESOLVERS )
212- if effect_branch is not None
216+ for resolver in RESOLVERS
217+ if ( effect_branch := resolver ( cursor_function_region_instructions )) is not None
213218 }
214219 match list (effects_branch ):
215220 case []:
@@ -365,12 +370,10 @@ def resolve_add_register_t2_pc_offset_table(
365370 # data region must not be empty and we started from the beginning
366371 assert target_instruction_offsets
367372
368- instruction_function_offset = cursor_function_region_instructions .function_offset ()
369-
370373 # convert to function-relative offsets
371374 # use current instruction offset + 4 (pc on next instruction) + offset from current instruction
372375 target_function_offsets = {
373- instruction_function_offset + 4 + target_instruction_offset
376+ cursor_function_region_instructions . function_offset () + 4 + target_instruction_offset
374377 for target_instruction_offset in target_instruction_offsets
375378 }
376379
@@ -404,8 +407,8 @@ def resolve_blx_register_t1(
404407 # they may return the same effect (will be flattened by set comprehensions)
405408 effects_call = {
406409 effect_call
407- for effect_call in ( resolver ( cursor_function_region_instructions ) for resolver in RESOLVERS )
408- if effect_call is not None
410+ for resolver in RESOLVERS
411+ if ( effect_call := resolver ( cursor_function_region_instructions )) is not None
409412 }
410413 match list (effects_call ):
411414 case []:
@@ -494,14 +497,14 @@ def resolve_blx_register_t1_pc_relative_load(
494497 )
495498
496499 # resolve data region containing target address
497- cursor_function_region_data_target = (
498- cursor_function_region_instructions_modifying . cursor_function . region_data ( data_function_offset )
500+ cursor_function_region_data = cursor_function_region_instructions_modifying . cursor_function . region_data (
501+ data_function_offset
499502 )
500- if cursor_function_region_data_target is None :
503+ if cursor_function_region_data is None :
501504 return None
502505
503506 # get the address
504- target_address , _ = cursor_function_region_data_target .read_word_unsigned ()
507+ target_address , _ = cursor_function_region_data .read_word_unsigned ()
505508
506509 # lsb bit must always be set to signify thumb execution
507510 if target_address & 1 != 1 :
@@ -519,3 +522,187 @@ def resolve_blx_register_t1_pc_relative_load(
519522 case _:
520523 # unsupported instruction
521524 return None
525+
526+
527+ def resolve_mov_register_t1_pc (
528+ cursor_function_region_instructions : CursorFunctionRegionInstructions ,
529+ ) -> EffectBranch :
530+ RESOLVERS = [
531+ resolve_mov_register_t1_pc_adr_table ,
532+ ]
533+
534+ effects_branch = {
535+ effect_branch
536+ for resolver in RESOLVERS
537+ if (effect_branch := resolver (cursor_function_region_instructions )) is not None
538+ }
539+ match list (effects_branch ):
540+ case []:
541+ # no resolver resolved anything
542+ address = cursor_function_region_instructions .address ()
543+
544+ raise ResolveMovRegisterT1PcUnknownException (address )
545+ case [effect_branch ]:
546+ # one or many resolver resolved our target effect
547+
548+ return effect_branch
549+ case _:
550+ # multiple resolvers resolved multiple effects
551+ assert False
552+
553+
554+ def resolve_mov_register_t1_pc_adr_table (
555+ cursor_function_region_instructions : CursorFunctionRegionInstructions ,
556+ ) -> EffectBranch | None :
557+ # register contains jump table index [0 - ?]
558+ # lsls register_index, register_source, #0x2 # multiplies jump table index by 4 (word size)
559+ # adr register_base, #imm # loads word-aligned address pointing to the data table
560+ # ldr register_index, [register_base, register_index] # loads absolute address from jump table
561+ # mov pc, register_index # branches to the loaded address
562+ # data region containing absolute addresses (with thumb bit set)
563+ #
564+ # solve this by examining the pattern backwards from mov pc and extracting absolute addresses from data region
565+
566+ # parse the mov pc, register - this should always be true as it was what caused entry to this block
567+ instruction = cursor_function_region_instructions .instruction ()
568+ match instruction :
569+ case InstructionMovRegisterT1 ():
570+ assert instruction .d is Register4 .PC
571+
572+ # extract the register, cast it to Register3 (all other instructions operate on 3-bit only)
573+ register_index = instruction .m .to_register3 ()
574+ if register_index is None :
575+ # is not R0-R7?
576+ return None
577+ case _:
578+ # so who and why called us?
579+ assert False
580+
581+ # parse ldr register_index, [register_base, register_index]
582+ cursor_function_region_instructions_ldr = cursor_function_region_instructions .previous ()
583+ if cursor_function_region_instructions_ldr is None :
584+ # function ended prematurely?
585+ return None
586+ instruction_ldr = cursor_function_region_instructions_ldr .instruction ()
587+ match instruction_ldr :
588+ case InstructionLdrRegisterT1 ():
589+ # we've got LDR register, [register, register]
590+
591+ # target register must be our index register
592+ if instruction_ldr .t is not register_index :
593+ return None
594+ # offset register must be our index register
595+ if instruction_ldr .m is not register_index :
596+ return None
597+
598+ # extract the base register
599+ register_base = instruction_ldr .n
600+ case _:
601+ # other instruction
602+ return None
603+
604+ # parse adr register_base, #imm
605+ cursor_function_region_instructions_adr = cursor_function_region_instructions_ldr .previous ()
606+ if cursor_function_region_instructions_adr is None :
607+ # function ended prematurely?
608+ return None
609+ instruction_adr = cursor_function_region_instructions_adr .instruction ()
610+ match instruction_adr :
611+ case InstructionAdrT1 ():
612+ # we've got ADR
613+
614+ # destination register must be our base register
615+ if instruction_adr .d is not register_base :
616+ return None
617+ case _:
618+ # other instruction
619+ return None
620+
621+ # parse lsls register_index, register_source, #0x2
622+ cursor_function_region_instructions_lsls = cursor_function_region_instructions_adr .previous ()
623+ if cursor_function_region_instructions_lsls is None :
624+ # function ended prematurely?
625+ return None
626+ instruction_lsls = cursor_function_region_instructions_lsls .instruction ()
627+ match instruction_lsls :
628+ case InstructionLslImmediateT1 ():
629+ # we've got LSLS
630+
631+ # destination must be our index register
632+ if instruction_lsls .d is not register_index :
633+ return None
634+ # shift must be 2 (multiply by 4 for word-sized entries)
635+ if instruction_lsls .imm != 2 :
636+ return None
637+ case _:
638+ # other instruction
639+ return None
640+
641+ # instruction pattern matches, now resolve the data table address from ADR
642+ data_function_offset = (
643+ (cursor_function_region_instructions_adr .function_offset () + 4 ) & ~ 0b11
644+ ) + instruction_adr .imm * 4 # Align(PC, 4)
645+
646+ # resolve data region containing the jump table
647+ cursor_function_region_data = cursor_function_region_instructions .cursor_function .region_data (data_function_offset )
648+ if cursor_function_region_data is None :
649+ # region covering this address does not exist?
650+ return None
651+ if cursor_function_region_data .function_region_data_offset != 0 :
652+ # is not at the beginning of the data region?
653+ return None
654+
655+ # extract all branch targets (absolute addresses)
656+ target_addresses = set [Address ]()
657+ cursor_function_region_data_item : CursorFunctionRegionData | None = cursor_function_region_data
658+ while True :
659+ # we reached end of data region
660+ if cursor_function_region_data_item is None :
661+ break
662+
663+ # load absolute address and advance cursor
664+ target_address , cursor_function_region_data_item = cursor_function_region_data_item .read_word_unsigned ()
665+
666+ # skip zero entries (padding / sentinel)
667+ if target_address == 0 :
668+ continue
669+
670+ # thumb bit (LSB) must be set for valid branch targets
671+ if target_address & 1 != 1 :
672+ raise ValueError (
673+ f"Thumb bit not set for jump table entry at { cursor_function_region_instructions } ? "
674+ "This would cause a HardFault..."
675+ )
676+
677+ # clear it to align to the address
678+ target_address &= ~ 1
679+
680+ target_addresses .add (target_address )
681+
682+ # data region must not be empty
683+ assert target_addresses
684+
685+ # convert absolute target addresses to function-relative-addresses
686+ target_function_offsets = {
687+ target_address
688+ - cursor_function_region_instructions .address ()
689+ + cursor_function_region_instructions .function_offset ()
690+ for target_address in target_addresses
691+ }
692+
693+ return EffectBranch (
694+ conditional = False ,
695+ target_function_offsets = frozenset (target_function_offsets ),
696+ )
697+
698+
699+ class ResolveMovRegisterT1PcUnknownException (ResolveException ):
700+ def __init__ (self , address : Address ) -> None :
701+ super ().__init__ (address , self ._format_message (address ))
702+
703+ @classmethod
704+ def _format_message (cls , address : Address ) -> str :
705+ return (
706+ f"Unable to automatically resolve target of MOV PC, Register instruction at 0x{ address :04X} .\n "
707+ "Please open an issue to help us improve this tool, providing as many details as possible."
708+ )
0 commit comments