Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lift.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ def lift_pe(module: Module, filename: str, start: int, *, verbose=False) -> Func
print(riscvm_run)
themida = lift_pe(module, "tests/example2-virt.bin", 0x140001000)
print(themida)
x86_ops = lift_pe(module, "tests/x86_ops.exe", 0x140001000)
print(x86_ops)
19 changes: 19 additions & 0 deletions src/striga/x86/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ def add(sem: Semantics):
arith_binop(sem, Opcode.Add, write_add_flags)


@semantic
def adc(sem: Semantics):
dst = sem.op_read(0)
src = sem.resize_int(sem.op_read(1), dst.type)
cf_in = sem.flag_read("cf")
carry = sem.ir.zext(cf_in, dst.type)
src_plus_carry = sem.ir.add(src, carry)
result = sem.ir.add(dst, src_plus_carry)
sem.op_write(0, result)

cf = sem.ir.or_(
sem.ir.icmp(IntPredicate.ULT, result, dst),
sem.ir.and_(sem.ir.icmp(IntPredicate.EQ, result, dst), cf_in),
)
sem.flag_write("cf", cf)
write_common_arith_flags(sem, dst, src, result)
sem.flag_write("of", add_overflow(sem, dst, src, result))


@semantic
def sub(sem: Semantics):
arith_binop(sem, Opcode.Sub, write_sub_flags)
Expand Down
174 changes: 174 additions & 0 deletions src/striga/x86/bitwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,110 @@ def rol(sem: Semantics):
sem.flag_write_if(count_nonzero, "of", of)


@semantic
def ror(sem: Semantics):
dst = sem.op_read(0)
width = dst.type.int_width
count = masked_shift_count(sem, sem.op_read(1), width)
rotate_count = sem.ir.urem(count, count.type.constant(width))
rotate_nonzero = sem.ir.icmp(
IntPredicate.NE, rotate_count, rotate_count.type.constant(0)
)
safe_count = sem.ir.select(rotate_nonzero, rotate_count, count.type.constant(1))

right = sem.ir.lshr(dst, safe_count)
left = sem.ir.shl(dst, sem.ir.sub(count.type.constant(width), safe_count))
rotated = sem.ir.or_(right, left)
result = sem.ir.select(rotate_nonzero, rotated, dst)
sem.op_write(0, result)

count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))
count_one = sem.ir.icmp(IntPredicate.EQ, count, count.type.constant(1))

msb = sem.result_sign_bit(result)
sem.flag_write_if(count_nonzero, "cf", msb)

# SDM: for 1-bit ROR, OF is the XOR of the two most-significant result bits.
next_msb = sem.ir.trunc(
sem.ir.lshr(result, result.type.constant(width - 2)), sem.i1
)
of_for_one = sem.ir.xor(msb, next_msb)
of = sem.ir.select(count_one, of_for_one, sem.flag_undef("of"))
sem.flag_write_if(count_nonzero, "of", of)


def double_shift_flags(sem: Semantics, dst: Value, result: Value, count: Value, cf: Value):
"""Common SHLD/SHRD flag updates (PF/AF/ZF/SF and OF for the 1-bit case)."""
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))
count_one = sem.ir.icmp(IntPredicate.EQ, count, count.type.constant(1))

sem.flag_write_if(count_nonzero, "cf", cf)

of_for_one = sem.ir.xor(sem.result_sign_bit(result), sem.result_sign_bit(dst))
of = sem.ir.select(count_one, of_for_one, sem.flag_undef("of"))
sem.flag_write_if(count_nonzero, "of", of)

sem.flag_write_if(count_nonzero, "pf", sem.result_parity_even(result))
sem.flag_write_undef_if(count_nonzero, "af")
sem.flag_write_if(count_nonzero, "zf", sem.result_is_zero(result))
sem.flag_write_if(count_nonzero, "sf", sem.result_sign_bit(result))


@semantic
def shld(sem: Semantics):
dst = sem.op_read(0)
src = sem.op_read(1)
width = dst.type.int_width
count = masked_shift_count(sem, sem.op_read(2), width)
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))

# Concatenate dst:src in a 2*width-bit value, shift left by count, then take
# the high width bits. This is equivalent to: (dst << count) | (src >> (width - count)).
wide_ty = sem.types.int_n(width * 2)
combined = sem.ir.or_(
sem.ir.shl(sem.ir.zext(dst, wide_ty), wide_ty.constant(width)),
sem.ir.zext(src, wide_ty),
)
wide_count = sem.ir.zext(count, wide_ty)
shifted = sem.ir.lshr(sem.ir.shl(combined, wide_count), wide_ty.constant(width))
result = sem.ir.select(count_nonzero, sem.ir.trunc(shifted, dst.type), dst)
sem.op_write(0, result)

# CF is the last bit shifted out of dst (bit at position width - count).
safe_cf_shift = sem.ir.select(
count_nonzero, sem.ir.sub(count.type.constant(width), count), count.type.constant(0)
)
cf = sem.ir.trunc(sem.ir.lshr(dst, safe_cf_shift), sem.i1)
double_shift_flags(sem, dst, result, count, cf)


@semantic
def shrd(sem: Semantics):
dst = sem.op_read(0)
src = sem.op_read(1)
width = dst.type.int_width
count = masked_shift_count(sem, sem.op_read(2), width)
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))

# Concatenate src:dst in a 2*width-bit value and shift right by count.
wide_ty = sem.types.int_n(width * 2)
combined = sem.ir.or_(
sem.ir.shl(sem.ir.zext(src, wide_ty), wide_ty.constant(width)),
sem.ir.zext(dst, wide_ty),
)
wide_count = sem.ir.zext(count, wide_ty)
shifted = sem.ir.trunc(sem.ir.lshr(combined, wide_count), dst.type)
result = sem.ir.select(count_nonzero, shifted, dst)
sem.op_write(0, result)

# CF is the last bit shifted out of dst (bit at position count - 1).
safe_count = sem.ir.select(count_nonzero, count, count.type.constant(1))
cf = sem.ir.trunc(
sem.ir.lshr(dst, sem.ir.sub(safe_count, count.type.constant(1))), sem.i1
)
double_shift_flags(sem, dst, result, count, cf)


def write_sar_flags(sem: Semantics, lhs: Value, count: Value, result: Value):
width = lhs.type.int_width
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))
Expand Down Expand Up @@ -324,3 +428,73 @@ def bts(sem: Semantics):
sem.op_write(0, result)
else:
sem.mem_write(addr, result)


@semantic
def rcl(sem: Semantics):
dst = sem.op_read(0)
width = dst.type.int_width
count = masked_shift_count(sem, sem.op_read(1), width)
eff_count = sem.ir.urem(count, count.type.constant(width + 1))
eff_nonzero = sem.ir.icmp(IntPredicate.NE, eff_count, count.type.constant(0))
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))
count_one = sem.ir.icmp(IntPredicate.EQ, count, count.type.constant(1))

# Pack CF as the high bit of a (width + 1)-bit value, then rotate left.
wide_ty = sem.types.int_n(width + 1)
cf_in = sem.flag_read("cf")
combined = sem.ir.or_(
sem.ir.shl(sem.ir.zext(cf_in, wide_ty), wide_ty.constant(width)),
sem.ir.zext(dst, wide_ty),
)
safe_count = sem.ir.select(eff_nonzero, eff_count, count.type.constant(1))
wide_count = sem.ir.zext(safe_count, wide_ty)
rotated = sem.ir.or_(
sem.ir.shl(combined, wide_count),
sem.ir.lshr(combined, sem.ir.sub(wide_ty.constant(width + 1), wide_count)),
)
result_wide = sem.ir.select(eff_nonzero, rotated, combined)
result = sem.ir.trunc(result_wide, dst.type)
sem.op_write(0, result)

new_cf = sem.ir.trunc(sem.ir.lshr(result_wide, wide_ty.constant(width)), sem.i1)
sem.flag_write_if(eff_nonzero, "cf", new_cf)

of_for_one = sem.ir.xor(sem.result_sign_bit(result), new_cf)
of = sem.ir.select(count_one, of_for_one, sem.flag_undef("of"))
sem.flag_write_if(count_nonzero, "of", of)


@semantic
def rcr(sem: Semantics):
dst = sem.op_read(0)
width = dst.type.int_width
count = masked_shift_count(sem, sem.op_read(1), width)
eff_count = sem.ir.urem(count, count.type.constant(width + 1))
eff_nonzero = sem.ir.icmp(IntPredicate.NE, eff_count, count.type.constant(0))
count_nonzero = sem.ir.icmp(IntPredicate.NE, count, count.type.constant(0))
count_one = sem.ir.icmp(IntPredicate.EQ, count, count.type.constant(1))

wide_ty = sem.types.int_n(width + 1)
cf_in = sem.flag_read("cf")
combined = sem.ir.or_(
sem.ir.shl(sem.ir.zext(cf_in, wide_ty), wide_ty.constant(width)),
sem.ir.zext(dst, wide_ty),
)
safe_count = sem.ir.select(eff_nonzero, eff_count, count.type.constant(1))
wide_count = sem.ir.zext(safe_count, wide_ty)
rotated = sem.ir.or_(
sem.ir.lshr(combined, wide_count),
sem.ir.shl(combined, sem.ir.sub(wide_ty.constant(width + 1), wide_count)),
)
result_wide = sem.ir.select(eff_nonzero, rotated, combined)
result = sem.ir.trunc(result_wide, dst.type)
sem.op_write(0, result)

new_cf = sem.ir.trunc(sem.ir.lshr(result_wide, wide_ty.constant(width)), sem.i1)
sem.flag_write_if(eff_nonzero, "cf", new_cf)

# SDM: for 1-bit RCR, OF is computed from the operands before the rotation.
of_for_one = sem.ir.xor(sem.result_sign_bit(dst), cf_in)
of = sem.ir.select(count_one, of_for_one, sem.flag_undef("of"))
sem.flag_write_if(count_nonzero, "of", of)
54 changes: 54 additions & 0 deletions tests/x86_ops.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
; Test fixture for the lifter: exercises each opcode added in
; arithmetic.py / bitwise.py (ADC, ROR, SHLD, SHRD, RCL, RCR).
;
; Build (Developer Command Prompt for VS):
; ml64 /c /Fo x86_ops.obj x86_ops.asm
; link /SUBSYSTEM:CONSOLE /ENTRY:test_ops /NODEFAULTLIB ^
; /OUT:x86_ops.exe x86_ops.obj

.code

test_ops PROC
; --- ADC: 64/32/16/8-bit forms ---
mov rax, 1
mov rcx, 2
adc rax, rcx
adc eax, 0FFFFFFFFh
adc ax, cx
adc al, 7Fh

; --- ROR: imm and cl forms across widths ---
ror rax, 1
ror ecx, 17
mov cl, 5
ror rdx, cl
ror ax, 3

; --- SHLD: 64/32/16-bit, imm and cl ---
shld rax, rcx, 13
mov cl, 7
shld edx, esi, cl
shld ax, dx, 4

; --- SHRD: 64/32-bit, imm and cl ---
shrd rax, rcx, 11
mov cl, 9
shrd edx, esi, cl

; --- RCL: the 1-form (special encoding) and cl-form ---
rcl rax, 1
rcl edx, 5
mov cl, 3
rcl rbx, cl

; --- RCR: 1-form and cl-form ---
rcr rax, 1
rcr ecx, 4
mov cl, 2
rcr rdx, cl

xor eax, eax
ret
test_ops ENDP

END
Binary file added tests/x86_ops.exe
Binary file not shown.