Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor qdb #1549

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
Draft
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
5 changes: 2 additions & 3 deletions qiling/debugger/qdb/arch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from .arch_x86 import ArchX86
from .arch_mips import ArchMIPS
from .arch_arm import ArchARM, ArchCORTEX_M
from .arch_x8664 import ArchX8664
from .arch_intel import ArchIntel, ArchX86, ArchX64
from .arch_mips import ArchMIPS
83 changes: 66 additions & 17 deletions qiling/debugger/qdb/arch/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,81 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from typing import Collection, Dict, Mapping, Optional, TypeVar

from qiling.const import QL_ARCH
from unicorn import UC_ERR_READ_UNMAPPED
import unicorn
T = TypeVar('T')


class Arch:
"""Arch base class.
"""
base class for arch
"""

def __init__(self):
pass
def __init__(self, regs: Collection[str], swaps: Mapping[str, str], asize: int, isize: int) -> None:
"""Initialize architecture instance.

Args:
regs : collection of registers names to include in context
asize : native address size in bytes
isize : instruction size in bytes
swaps : readable register names alternatives, may be empty
"""

self._regs = regs
self._swaps = swaps
self._asize = asize
self._isize = isize

@property
def arch_insn_size(self):
return 4
def regs(self) -> Collection[str]:
"""Collection of registers names.
"""

return self._regs

@property
def archbit(self):
return 4
def isize(self) -> int:
"""Native instruction size.
"""

return self._isize

@property
def asize(self) -> int:
"""Native pointer size.
"""

return self._asize

def swap_regs(self, mapping: Mapping[str, T]) -> Dict[str, T]:
"""Swap default register names with their aliases.

Args:
mapping: regsiters names mapped to their values

Returns: a new dictionary where all swappable names were swapped with their aliases
"""

return {self._swaps.get(k, k): v for k, v in mapping.items()}

def unalias(self, name: str) -> str:
"""Get original register name for the specified alias.

Args:
name: aliaes register name

Returns: original name of aliased register, or same name if not an alias
"""

# perform a reversed lookup in swaps to find the original name for given alias
return next((org for org, alt in self._swaps.items() if name == alt), name)

def read_insn(self, address: int) -> Optional[bytearray]:
"""Read a single instruction from given address.

Args:
address: memory address to read from

def read_insn(self, address: int):
try:
result = self.read_mem(address, self.arch_insn_size)
except unicorn.unicorn.UcError as err:
result = None
Returns: instruction bytes, or None if memory could not be read
"""

return result
return self.try_read_mem(address, self.isize)
176 changes: 106 additions & 70 deletions qiling/debugger/qdb/arch/arch_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,105 +3,141 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from typing import Mapping
from typing import Dict, Optional

from .arch import Arch


class ArchARM(Arch):
def __init__(self):
super().__init__()
self._regs = (
"r0", "r1", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11",
"r12", "sp", "lr", "pc",
)
def __init__(self) -> None:
regs = (
'r0', 'r1', 'r2', 'r3',
'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'r11',
'r12', 'sp', 'lr', 'pc'
)

aliases = {
'r9' : 'sb',
'r10': 'sl',
'r12': 'ip',
'r11': 'fp'
}

asize = 4
isize = 4

super().__init__(regs, aliases, asize, isize)

@staticmethod
def get_flags(bits: int) -> Dict[str, bool]:
return {
'thumb': bits & (0b1 << 5) != 0,
'fiq': bits & (0b1 << 6) != 0,
'irq': bits & (0b1 << 7) != 0,
'overflow': bits & (0b1 << 28) != 0,
'carry': bits & (0b1 << 29) != 0,
'zero': bits & (0b1 << 30) != 0,
'neg': bits & (0b1 << 31) != 0
}

@staticmethod
def get_mode(bits: int) -> str:
modes = {
0b10000: 'User',
0b10001: 'FIQ',
0b10010: 'IRQ',
0b10011: 'Supervisor',
0b10110: 'Monitor',
0b10111: 'Abort',
0b11010: 'Hypervisor',
0b11011: 'Undefined',
0b11111: 'System'
}

return modes.get(bits & 0b11111, '?')

@property
def regs(self):
return self._regs
def is_thumb(self) -> bool:
"""Query whether the processor is currently in thumb mode.
"""

@regs.setter
def regs(self, regs):
self._regs += regs
return self.ql.arch.is_thumb

@property
def regs_need_swapped(self):
return {
"sl": "r10",
"ip": "r12",
"fp": "r11",
}
def isize(self) -> int:
return 2 if self.is_thumb else self._isize

@staticmethod
def get_flags(bits: int) -> Mapping[str, bool]:
"""
get flags for ARM
def __is_wide_insn(data: bytes) -> bool:
"""Determine whether a sequence of bytes respresents a wide thumb instruction.
"""

def get_mode(bits: int) -> int:
"""
get operating mode for ARM
"""
return {
0b10000: "User",
0b10001: "FIQ",
0b10010: "IRQ",
0b10011: "Supervisor",
0b10110: "Monitor",
0b10111: "Abort",
0b11010: "Hypervisor",
0b11011: "Undefined",
0b11111: "System",
}.get(bits & 0x00001f)
assert len(data) in (2, 4), f'unexpected instruction length: {len(data)}'

return {
"mode": get_mode(bits),
"thumb": bits & 0x00000020 != 0,
"fiq": bits & 0x00000040 != 0,
"irq": bits & 0x00000080 != 0,
"neg": bits & 0x80000000 != 0,
"zero": bits & 0x40000000 != 0,
"carry": bits & 0x20000000 != 0,
"overflow": bits & 0x10000000 != 0,
}
# determine whether this is a wide instruction by inspecting the 5 most
# significant bits in the first half-word
return (data[1] >> 3) & 0b11111 in (0b11101, 0b11110, 0b11111)

@property
def thumb_mode(self) -> bool:
"""
helper function for checking thumb mode
def __read_thumb_insn_fail(self, address: int) -> Optional[bytearray]:
"""A failsafe method for reading thumb instructions. This method is needed for
rare cases in which a narrow instruction is on a page boundary where the next
page is unavailable.
"""

return self.ql.arch.is_thumb
lo_half = self.try_read_mem(address, 2)

if lo_half is None:
return None

def read_insn(self, address: int) -> bytes:
"""
read instruction depending on current operating mode
data = lo_half

if ArchARM.__is_wide_insn(data):
hi_half = self.try_read_mem(address + 2, 2)

# fail if higher half-word was required but could not be read
if hi_half is None:
return None

data.extend(hi_half)

return data

def __read_thumb_insn(self, address: int) -> Optional[bytearray]:
"""Read one instruction in thumb mode.

Thumb instructions may be either 2 or 4 bytes long, depending on encoding of
the first word. However, reading two chunks of two bytes each is slower. For
most cases reading all four bytes in advance will be safe and quicker.
"""

def thumb_read(address: int) -> bytes:
data = self.try_read_mem(address, 4)

first_two = self.ql.mem.read_ptr(address, 2)
result = self.ql.pack16(first_two)
if data is None:
# there is a slight chance we could not read 4 bytes because only 2
# are available. try the failsafe method to find out
return self.__read_thumb_insn_fail(address)

# to judge it's thumb mode or not
if any([
first_two & 0xf000 == 0xf000,
first_two & 0xf800 == 0xf800,
first_two & 0xe800 == 0xe800,
]):
if ArchARM.__is_wide_insn(data):
return data

latter_two = self.ql.mem.read_ptr(address+2, 2)
result += self.ql.pack16(latter_two)
return data[:2]

return result
def read_insn(self, address: int) -> Optional[bytearray]:
"""Read one instruction worth of bytes.
"""

return super().read_insn(address) if not self.thumb_mode else thumb_read(address)
if self.is_thumb:
return self.__read_thumb_insn(address)

return super().read_insn(address)


class ArchCORTEX_M(ArchARM):
def __init__(self):
super().__init__()
self.regs += ("xpsr", "control", "primask", "basepri", "faultmask")

self._regs += (
'xpsr', 'control', 'primask',
'basepri', 'faultmask'
)
59 changes: 59 additions & 0 deletions qiling/debugger/qdb/arch/arch_intel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from typing import Collection, Dict

from .arch import Arch


class ArchIntel(Arch):
"""Arch base class for Intel architecture.
"""

def __init__(self, regs: Collection[str], asize: int) -> None:
super().__init__(regs, {}, asize, 15)

@staticmethod
def get_flags(bits: int) -> Dict[str, bool]:
return {
'CF' : bits & (0b1 << 0) != 0, # carry
'PF' : bits & (0b1 << 2) != 0, # parity
'AF' : bits & (0b1 << 4) != 0, # adjust
'ZF' : bits & (0b1 << 6) != 0, # zero
'SF' : bits & (0b1 << 7) != 0, # sign
'IF' : bits & (0b1 << 9) != 0, # interrupt enable
'DF' : bits & (0b1 << 10) != 0, # direction
'OF' : bits & (0b1 << 11) != 0 # overflow
}

@staticmethod
def get_iopl(bits: int) -> int:
return bits & (0b11 << 12)


class ArchX86(ArchIntel):
def __init__(self) -> None:
regs = (
'eax', 'ebx', 'ecx', 'edx',
'ebp', 'esp', 'esi', 'edi',
'eip', 'eflags' ,'ss', 'cs',
'ds', 'es', 'fs', 'gs'
)

super().__init__(regs, 4)


class ArchX64(ArchIntel):
def __init__(self) -> None:
regs = (
'rax', 'rbx', 'rcx', 'rdx',
'rbp', 'rsp', 'rsi', 'rdi',
'r8', 'r9', 'r10', 'r11',
'r12', 'r13', 'r14', 'r15',
'rip', 'eflags', 'ss', 'cs',
'ds', 'es', 'fs', 'gs'
)

super().__init__(regs, 8)
Loading
Loading