@@ -35,8 +35,22 @@ def __init__(self, instr_to_test, expected_result=None, immediat_accepted=None,
3535 self .expected_result = expected_result
3636 self .must_fail = must_fail
3737 self .debug = debug
38+ self .callargs = None
39+
3840
3941 def __call__ (self , * args ):
42+ assert args is not None
43+ self .callargs = args
44+ return self
45+
46+ def __repr__ (self ):
47+ if self .must_fail :
48+ return "MustFail:{0}{1}" .format (self .instr_to_test .__name__ , self .callargs )
49+ return "{0}{1}" .format (self .instr_to_test .__name__ , self .callargs )
50+
51+ def dotest (self ):
52+ assert self .callargs is not None
53+ args = self .callargs
4054 try :
4155 if self .debug :
4256 import pdb ;pdb .set_trace ()
@@ -70,6 +84,7 @@ def __call__(self, *args):
7084 raise AssertionError ("Not all bytes have been used by the disassembler" )
7185 self .compare_mnemo (capres )
7286 self .compare_args (args , capres )
87+ return True
7388
7489 def compare_mnemo (self , capres ):
7590 expected = self .instr_to_test .__name__ .lower ()
@@ -80,9 +95,15 @@ def compare_mnemo(self, capres):
8095
8196 def compare_args (self , args , capres ):
8297 capres_op = list (capres .operands )
83- if len (args ) != len (capres_op ):
84- raise AssertionError ("Expected {0} operands got {1}" .format (len (args ), len (capres_op )))
85- for op_args , cap_op in zip (args , capres_op ):
98+ # We may have != number of operand as shift are:
99+ # - arguments for simple_arm64
100+ # - atribute of immediat for capstone
101+ if not len (capres_op ) <= len (args ):
102+ raise AssertionError ("Expected at most {0} operands got {1}" .format (len (args ), len (capres_op )))
103+
104+ opargit = iter (args ) # allow manually using next() to get next simple_arm64 arg for shift compare
105+ # capres_op must be first in zip (as its smaller) or last next(opargit) will be consommed by zip
106+ for cap_op , op_args in zip (capres_op , opargit ):
86107 if isinstance (op_args , str ): # Register
87108 if cap_op .type != capstone .arm64 .ARM64_OP_REG :
88109 raise AssertionError ("Expected args {0} operands got {1}" .format (op_args , capres_op ))
@@ -91,9 +112,39 @@ def compare_args(self, args, capres):
91112 elif isinstance (op_args , int_types ):
92113 if (op_args != cap_op .imm ) and not (self .immediat_accepted and self .immediat_accepted == cap_op .imm ):
93114 raise AssertionError ("Expected Immediat <{0}> got {1}" .format (op_args , cap_op .imm ))
115+ cap_shift = cap_op .shift
116+ if not (cap_shift .type == cap_shift .value == 0 ):
117+ self .compare_shift (next (opargit ), cap_shift )
94118 else :
95119 raise ValueError ("Unknow argument {0} of type {1}" .format (op_args , type (op_args )))
96120
121+ # Check that no argument were unused in args
122+ # As args + shift should perfectly match the capres_op
123+ sentinel = object ()
124+ nextarg = next (opargit , sentinel )
125+ if nextarg != sentinel :
126+ # Ignore a leading LSL #0 shift, as it should be authorized but not displayed by disassembler
127+ shift = Shift .parse (nextarg )
128+ if not (shift .type == "LSL" and shift .value == 0 ):
129+ raise ValueError ("Non consomated argument: {0} (probable non-encoded shift)" .format (nextarg ))
130+
131+ SHIFT_TYPE_TO_CAPSTONE = {
132+ "LSL" : capstone .arm64 .ARM64_SFT_LSL ,
133+ "LSR" : capstone .arm64 .ARM64_SFT_LSR ,
134+ "ASR" : capstone .arm64 .ARM64_SFT_ASR ,
135+ "ROR" : capstone .arm64 .ARM64_SFT_ROR ,
136+ # "MSL": apstone.arm64.ARM64_SFT_MSL # Not yet used in PFW
137+ }
138+
139+ def compare_shift (self , shiftstr , cap_shift ):
140+ shift = Shift .parse (shiftstr )
141+ if not self .SHIFT_TYPE_TO_CAPSTONE [shift .type ] == cap_shift .type :
142+ raise ValueError ("Shift type mismatch: expected {0} got {1}" .format (shift .type , cap_shift .type ))
143+ if not shift .value == cap_shift .value :
144+ raise ValueError ("Shift value mismatch: expected {0} got {1}" .format (shift .value , cap_shift .value ))
145+ return True
146+
147+
97148def test_shift_parsing ():
98149 assert Shift .parse ("LSL #0" )
99150 assert Shift .parse ("LSL #12" )
@@ -110,32 +161,36 @@ def test_shift_parsing():
110161 assert not Shift .parse ("LSX ##1" )
111162 assert not Shift .parse ("LSX #" )
112163
113- def test_assembler ():
114- CheckInstr (Add )('W0' , 'W0' , 0 )
115- CheckInstr (Add )('W1' , 'W0' , 0 )
116- CheckInstr (Add )('W30' , 'W12' , 0 )
117- CheckInstr (Add )('W0' , 'W0' , 1 )
118-
119- CheckInstr (Add )('X0' , 'X0' , 0 )
120- CheckInstr (Add )('X30' , 'X12' , 0 )
121- CheckInstr (Add )('X0' , 'X0' , 1 )
122- CheckInstr (Add )('X11' , 'X12' , 0x123 )
123- # CheckInstr(Add)('X11', 'X12', 0x123, "LSL #0")
124- CheckInstr (Add )('X11' , 'X12' , 0x123 , "LSL #12" )
125-
126- # Error test todo
127- # CheckInstr(Add)('X11', 'W12', 0x123)
128- with pytest .raises (ValueError ):
129- CheckInstr (Add )('BADREG' , 'X12' , 0 )
130- with pytest .raises (ValueError ):
131- CheckInstr (Add )('X11' , 'X12' , 0x123 , "LSL #1234" )
132-
133- with pytest .raises (ValueError ):
134- # Immediat too big for encoding
135- CheckInstr (Add )('X11' , 'X12' , 0x12345678 )
136-
137- CheckInstr (Ret )("X0" )
138- CheckInstr (Ret , expected_result = "ret " )("X30" )
139- CheckInstr (Ret )()
140- with pytest .raises (ValueError ):
141- CheckInstr (Ret )("W0" )
164+ @pytest .mark .parametrize ("checkinstr" , [
165+ CheckInstr (Add )('W0' , 'W0' , 0 ),
166+ CheckInstr (Add )('W1' , 'W0' , 0 ),
167+ CheckInstr (Add )('W30' , 'W12' , 0 ),
168+ CheckInstr (Add )('W0' , 'W0' , 1 ),
169+ CheckInstr (Add )('X0' , 'X0' , 0 ),
170+ CheckInstr (Add )('X30' , 'X12' , 0 ),
171+ CheckInstr (Add )('X0' , 'X0' , 1 ),
172+ CheckInstr (Add )('X11' , 'X12' , 0x123 ),
173+ CheckInstr (Add )('X11' , 'X12' , 0x123 , "LSL #0" ),
174+ CheckInstr (Add )('X11' , 'X12' , 0x123 , "LSL #12" ),
175+ CheckInstr (Add , must_fail = True )('X11' , 'W12' , 0x123 ), # Bitness mismatch
176+ CheckInstr (Add , must_fail = True )('BADREG' , 'X12' , 0 ),
177+ CheckInstr (Add , must_fail = True )('X11' , 'X12' , 0x123 , "LSL #1234" ),
178+ CheckInstr (Add , must_fail = True )('X11' , 'X12' , 0x12345678 ),
179+
180+ CheckInstr (Movz )('X0' , 0 ),
181+ CheckInstr (Movz )('X0' , 0 , "LSL #32" ),
182+ CheckInstr (Movz )('X18' , 0 , "LSL #48" ),
183+ CheckInstr (Movz )('W18' , 0 , "LSL #16" ),
184+ CheckInstr (Movz , must_fail = True )('X0' , 0 , "LSL #12" ), # Invalid LSL for MovWideImmediat
185+ CheckInstr (Movz , must_fail = True )('W0' , 0 , "LSL #32" ),
186+ CheckInstr (Movz , must_fail = True )('X0' , 0 , "ROR #32" ),
187+
188+ CheckInstr (Movk )('X0' , 0x1234 , "LSL #32" ),
189+ CheckInstr (Movk )('X18' , 0x5678 , "LSL #48" ),
190+
191+ CheckInstr (Ret )("X0" ),
192+ CheckInstr (Ret , expected_result = "ret " )("X30" ),
193+ CheckInstr (Ret )(),
194+ ], ids = CheckInstr .__repr__ )
195+ def test_instruction_assembling (checkinstr ):
196+ assert checkinstr .dotest ()
0 commit comments