Skip to content

Commit f4464b9

Browse files
authored
Merge pull request #1549 from elicn/refactor-qdb
Refactor qdb
2 parents d4ca103 + 135e713 commit f4464b9

39 files changed

+2266
-2407
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ keywords = [
2828

2929
[tool.poetry.dependencies]
3030
python = "^3.8"
31-
capstone = "^4"
31+
capstone = "^5"
3232
unicorn = "2.1.3"
3333
pefile = ">=2022.5.30"
3434
python-registry = "^1.3.1"

qiling/debugger/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
from .debugger import QlDebugger
2-
# from .disassember import QlDisassember
3-
# from .utils import QlReadELF

qiling/debugger/disassember.py

Lines changed: 0 additions & 55 deletions
This file was deleted.

qiling/debugger/qdb/arch/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6-
from .arch_x86 import ArchX86
7-
from .arch_mips import ArchMIPS
86
from .arch_arm import ArchARM, ArchCORTEX_M
9-
from .arch_x8664 import ArchX8664
7+
from .arch_intel import ArchIntel, ArchX86, ArchX64
8+
from .arch_mips import ArchMIPS

qiling/debugger/qdb/arch/arch.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,81 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6+
from typing import Collection, Dict, Mapping, Optional, TypeVar
67

7-
from qiling.const import QL_ARCH
8-
from unicorn import UC_ERR_READ_UNMAPPED
9-
import unicorn
8+
T = TypeVar('T')
109

1110

1211
class Arch:
12+
"""Arch base class.
1313
"""
14-
base class for arch
15-
"""
1614

17-
def __init__(self):
18-
pass
15+
def __init__(self, regs: Collection[str], swaps: Mapping[str, str], asize: int, isize: int) -> None:
16+
"""Initialize architecture instance.
17+
18+
Args:
19+
regs : collection of registers names to include in context
20+
asize : native address size in bytes
21+
isize : instruction size in bytes
22+
swaps : readable register names alternatives, may be empty
23+
"""
24+
25+
self._regs = regs
26+
self._swaps = swaps
27+
self._asize = asize
28+
self._isize = isize
1929

2030
@property
21-
def arch_insn_size(self):
22-
return 4
31+
def regs(self) -> Collection[str]:
32+
"""Collection of registers names.
33+
"""
34+
35+
return self._regs
2336

2437
@property
25-
def archbit(self):
26-
return 4
38+
def isize(self) -> int:
39+
"""Native instruction size.
40+
"""
41+
42+
return self._isize
43+
44+
@property
45+
def asize(self) -> int:
46+
"""Native pointer size.
47+
"""
48+
49+
return self._asize
50+
51+
def swap_regs(self, mapping: Mapping[str, T]) -> Dict[str, T]:
52+
"""Swap default register names with their aliases.
53+
54+
Args:
55+
mapping: regsiters names mapped to their values
56+
57+
Returns: a new dictionary where all swappable names were swapped with their aliases
58+
"""
59+
60+
return {self._swaps.get(k, k): v for k, v in mapping.items()}
61+
62+
def unalias(self, name: str) -> str:
63+
"""Get original register name for the specified alias.
64+
65+
Args:
66+
name: aliaes register name
67+
68+
Returns: original name of aliased register, or same name if not an alias
69+
"""
70+
71+
# perform a reversed lookup in swaps to find the original name for given alias
72+
return next((org for org, alt in self._swaps.items() if name == alt), name)
73+
74+
def read_insn(self, address: int) -> Optional[bytearray]:
75+
"""Read a single instruction from given address.
76+
77+
Args:
78+
address: memory address to read from
2779
28-
def read_insn(self, address: int):
29-
try:
30-
result = self.read_mem(address, self.arch_insn_size)
31-
except unicorn.unicorn.UcError as err:
32-
result = None
80+
Returns: instruction bytes, or None if memory could not be read
81+
"""
3382

34-
return result
83+
return self.try_read_mem(address, self.isize)

qiling/debugger/qdb/arch/arch_arm.py

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,105 +3,141 @@
33
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
44
#
55

6-
from typing import Mapping
6+
from typing import Dict, Optional
77

88
from .arch import Arch
99

10+
1011
class ArchARM(Arch):
11-
def __init__(self):
12-
super().__init__()
13-
self._regs = (
14-
"r0", "r1", "r2", "r3",
15-
"r4", "r5", "r6", "r7",
16-
"r8", "r9", "r10", "r11",
17-
"r12", "sp", "lr", "pc",
18-
)
12+
def __init__(self) -> None:
13+
regs = (
14+
'r0', 'r1', 'r2', 'r3',
15+
'r4', 'r5', 'r6', 'r7',
16+
'r8', 'r9', 'r10', 'r11',
17+
'r12', 'sp', 'lr', 'pc'
18+
)
19+
20+
aliases = {
21+
'r9' : 'sb',
22+
'r10': 'sl',
23+
'r12': 'ip',
24+
'r11': 'fp'
25+
}
26+
27+
asize = 4
28+
isize = 4
29+
30+
super().__init__(regs, aliases, asize, isize)
31+
32+
@staticmethod
33+
def get_flags(bits: int) -> Dict[str, bool]:
34+
return {
35+
'thumb': bits & (0b1 << 5) != 0,
36+
'fiq': bits & (0b1 << 6) != 0,
37+
'irq': bits & (0b1 << 7) != 0,
38+
'overflow': bits & (0b1 << 28) != 0,
39+
'carry': bits & (0b1 << 29) != 0,
40+
'zero': bits & (0b1 << 30) != 0,
41+
'neg': bits & (0b1 << 31) != 0
42+
}
43+
44+
@staticmethod
45+
def get_mode(bits: int) -> str:
46+
modes = {
47+
0b10000: 'User',
48+
0b10001: 'FIQ',
49+
0b10010: 'IRQ',
50+
0b10011: 'Supervisor',
51+
0b10110: 'Monitor',
52+
0b10111: 'Abort',
53+
0b11010: 'Hypervisor',
54+
0b11011: 'Undefined',
55+
0b11111: 'System'
56+
}
57+
58+
return modes.get(bits & 0b11111, '?')
1959

2060
@property
21-
def regs(self):
22-
return self._regs
61+
def is_thumb(self) -> bool:
62+
"""Query whether the processor is currently in thumb mode.
63+
"""
2364

24-
@regs.setter
25-
def regs(self, regs):
26-
self._regs += regs
65+
return self.ql.arch.is_thumb
2766

2867
@property
29-
def regs_need_swapped(self):
30-
return {
31-
"sl": "r10",
32-
"ip": "r12",
33-
"fp": "r11",
34-
}
68+
def isize(self) -> int:
69+
return 2 if self.is_thumb else self._isize
3570

3671
@staticmethod
37-
def get_flags(bits: int) -> Mapping[str, bool]:
38-
"""
39-
get flags for ARM
72+
def __is_wide_insn(data: bytes) -> bool:
73+
"""Determine whether a sequence of bytes respresents a wide thumb instruction.
4074
"""
4175

42-
def get_mode(bits: int) -> int:
43-
"""
44-
get operating mode for ARM
45-
"""
46-
return {
47-
0b10000: "User",
48-
0b10001: "FIQ",
49-
0b10010: "IRQ",
50-
0b10011: "Supervisor",
51-
0b10110: "Monitor",
52-
0b10111: "Abort",
53-
0b11010: "Hypervisor",
54-
0b11011: "Undefined",
55-
0b11111: "System",
56-
}.get(bits & 0x00001f)
76+
assert len(data) in (2, 4), f'unexpected instruction length: {len(data)}'
5777

58-
return {
59-
"mode": get_mode(bits),
60-
"thumb": bits & 0x00000020 != 0,
61-
"fiq": bits & 0x00000040 != 0,
62-
"irq": bits & 0x00000080 != 0,
63-
"neg": bits & 0x80000000 != 0,
64-
"zero": bits & 0x40000000 != 0,
65-
"carry": bits & 0x20000000 != 0,
66-
"overflow": bits & 0x10000000 != 0,
67-
}
78+
# determine whether this is a wide instruction by inspecting the 5 most
79+
# significant bits in the first half-word
80+
return (data[1] >> 3) & 0b11111 in (0b11101, 0b11110, 0b11111)
6881

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

75-
return self.ql.arch.is_thumb
88+
lo_half = self.try_read_mem(address, 2)
7689

90+
if lo_half is None:
91+
return None
7792

78-
def read_insn(self, address: int) -> bytes:
79-
"""
80-
read instruction depending on current operating mode
93+
data = lo_half
94+
95+
if ArchARM.__is_wide_insn(data):
96+
hi_half = self.try_read_mem(address + 2, 2)
97+
98+
# fail if higher half-word was required but could not be read
99+
if hi_half is None:
100+
return None
101+
102+
data.extend(hi_half)
103+
104+
return data
105+
106+
def __read_thumb_insn(self, address: int) -> Optional[bytearray]:
107+
"""Read one instruction in thumb mode.
108+
109+
Thumb instructions may be either 2 or 4 bytes long, depending on encoding of
110+
the first word. However, reading two chunks of two bytes each is slower. For
111+
most cases reading all four bytes in advance will be safe and quicker.
81112
"""
82113

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

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

88-
# to judge it's thumb mode or not
89-
if any([
90-
first_two & 0xf000 == 0xf000,
91-
first_two & 0xf800 == 0xf800,
92-
first_two & 0xe800 == 0xe800,
93-
]):
121+
if ArchARM.__is_wide_insn(data):
122+
return data
94123

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

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

100-
return super().read_insn(address) if not self.thumb_mode else thumb_read(address)
130+
if self.is_thumb:
131+
return self.__read_thumb_insn(address)
101132

133+
return super().read_insn(address)
102134

103135

104136
class ArchCORTEX_M(ArchARM):
105137
def __init__(self):
106138
super().__init__()
107-
self.regs += ("xpsr", "control", "primask", "basepri", "faultmask")
139+
140+
self._regs += (
141+
'xpsr', 'control', 'primask',
142+
'basepri', 'faultmask'
143+
)

0 commit comments

Comments
 (0)