Skip to content

Commit 34d49c5

Browse files
Add x86_64 debug support
1 parent 5d7a8f0 commit 34d49c5

File tree

9 files changed

+273
-4
lines changed

9 files changed

+273
-4
lines changed

qiling/debugger/qdb/arch/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .arch_x86 import ArchX86
77
from .arch_mips import ArchMIPS
88
from .arch_arm import ArchARM, ArchCORTEX_M
9+
from .arch_x8664 import ArchX8664
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
4+
#
5+
6+
from typing import Mapping
7+
8+
from .arch import Arch
9+
10+
class ArchX8664(Arch):
11+
'''
12+
This is currently mostly just a copy of x86 - other than the size of archbits. Some of this may be wrong.
13+
'''
14+
15+
def __init__(self):
16+
super().__init__()
17+
18+
@property
19+
def arch_insn_size(self):
20+
'''
21+
Architecture maximum instruction size. x86_64 instructions are a maximum size of 15 bytes.
22+
23+
@returns bytes
24+
'''
25+
26+
return 15
27+
28+
@property
29+
def regs(self):
30+
return (
31+
"rax", "rbx", "rcx", "rdx",
32+
"rsp", "rbp", "rsi", "rdi",
33+
"rip", "r8", "r9", "r10",
34+
"r11", "r12", "r13", "r14",
35+
"r15", "ss", "cs", "ds", "es",
36+
"fs", "gs", "eflags"
37+
)
38+
39+
@property
40+
def archbit(self):
41+
'''
42+
Architecture maximum register size. x86 is a maximum of 4 bytes.
43+
44+
@returns bytes
45+
'''
46+
47+
return 8
48+
49+
def read_insn(self, address: int) -> bytes:
50+
# Due to the variadicc length of x86 instructions
51+
# always assume the maximum size for disassembler to tell
52+
# what it is.
53+
54+
return self.read_mem(address, self.arch_insn_size)
55+
56+
@staticmethod
57+
def get_flags(bits: int) -> Mapping[str, bool]:
58+
59+
return {
60+
"CF" : bits & 0x0001 != 0, # CF, carry flag
61+
"PF" : bits & 0x0004 != 0, # PF, parity flag
62+
"AF" : bits & 0x0010 != 0, # AF, adjust flag
63+
"ZF" : bits & 0x0040 != 0, # ZF, zero flag
64+
"SF" : bits & 0x0080 != 0, # SF, sign flag
65+
"OF" : bits & 0x0800 != 0, # OF, overflow flag
66+
}

qiling/debugger/qdb/branch_predictor/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .branch_predictor_x86 import BranchPredictorX86
77
from .branch_predictor_mips import BranchPredictorMIPS
88
from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M
9+
from .branch_predictor_x8664 import BranchPredictorX8664
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
4+
#
5+
6+
7+
8+
import ast, re
9+
10+
from .branch_predictor import *
11+
from ..arch import ArchX8664
12+
from ..misc import check_and_eval
13+
14+
class BranchPredictorX8664(BranchPredictor, ArchX8664):
15+
"""
16+
predictor for X86
17+
"""
18+
19+
class ParseError(Exception):
20+
"""
21+
indicate parser error
22+
"""
23+
pass
24+
25+
def __init__(self, ql):
26+
super().__init__(ql)
27+
ArchX8664.__init__(self)
28+
29+
def predict(self):
30+
prophecy = self.Prophecy()
31+
line = self.disasm(self.cur_addr)
32+
33+
jump_table = {
34+
# conditional jump
35+
36+
"jo" : (lambda C, P, A, Z, S, O: O == 1),
37+
"jno" : (lambda C, P, A, Z, S, O: O == 0),
38+
39+
"js" : (lambda C, P, A, Z, S, O: S == 1),
40+
"jns" : (lambda C, P, A, Z, S, O: S == 0),
41+
42+
"je" : (lambda C, P, A, Z, S, O: Z == 1),
43+
"jz" : (lambda C, P, A, Z, S, O: Z == 1),
44+
45+
"jne" : (lambda C, P, A, Z, S, O: Z == 0),
46+
"jnz" : (lambda C, P, A, Z, S, O: Z == 0),
47+
48+
"jb" : (lambda C, P, A, Z, S, O: C == 1),
49+
"jc" : (lambda C, P, A, Z, S, O: C == 1),
50+
"jnae" : (lambda C, P, A, Z, S, O: C == 1),
51+
52+
"jnb" : (lambda C, P, A, Z, S, O: C == 0),
53+
"jnc" : (lambda C, P, A, Z, S, O: C == 0),
54+
"jae" : (lambda C, P, A, Z, S, O: C == 0),
55+
56+
"jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
57+
"jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1),
58+
59+
"ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
60+
"jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0),
61+
62+
"jl" : (lambda C, P, A, Z, S, O: S != O),
63+
"jnge" : (lambda C, P, A, Z, S, O: S != O),
64+
65+
"jge" : (lambda C, P, A, Z, S, O: S == O),
66+
"jnl" : (lambda C, P, A, Z, S, O: S == O),
67+
68+
"jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
69+
"jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O),
70+
71+
"jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
72+
"jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O),
73+
74+
"jp" : (lambda C, P, A, Z, S, O: P == 1),
75+
"jpe" : (lambda C, P, A, Z, S, O: P == 1),
76+
77+
"jnp" : (lambda C, P, A, Z, S, O: P == 0),
78+
"jpo" : (lambda C, P, A, Z, S, O: P == 0),
79+
80+
# unconditional jump
81+
82+
"call" : (lambda *_: True),
83+
"jmp" : (lambda *_: True),
84+
85+
}
86+
87+
jump_reg_table = {
88+
"jcxz" : (lambda cx: cx == 0),
89+
"jecxz" : (lambda ecx: ecx == 0),
90+
"jrcxz" : (lambda rcx: rcx == 0),
91+
}
92+
93+
if line.mnemonic in jump_table:
94+
eflags = self.get_flags(self.ql.arch.regs.eflags).values()
95+
prophecy.going = jump_table.get(line.mnemonic)(*eflags)
96+
97+
elif line.mnemonic in jump_reg_table:
98+
prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx)
99+
100+
if prophecy.going:
101+
takeaway_list = ["ptr", "dword", "[", "]"]
102+
103+
if len(line.op_str.split()) > 1:
104+
new_line = line.op_str.replace(":", "+")
105+
for each in takeaway_list:
106+
new_line = new_line.replace(each, " ")
107+
108+
new_line = " ".join(new_line.split())
109+
for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()):
110+
if each_reg in new_line:
111+
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
112+
113+
for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()):
114+
if each_reg in new_line:
115+
new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line)
116+
117+
118+
prophecy.where = check_and_eval(new_line)
119+
120+
elif line.op_str in self.ql.arch.regs.register_mapping:
121+
prophecy.where = self.ql.arch.regs.read(line.op_str)
122+
123+
else:
124+
prophecy.where = read_int(line.op_str)
125+
else:
126+
prophecy.where = self.cur_addr + line.size
127+
128+
return prophecy

qiling/debugger/qdb/memory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from qiling.const import QL_ARCH
77

88
from .context import Context
9-
from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86
9+
from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86, ArchX8664
1010
from .misc import check_and_eval
1111
import re, math
1212

@@ -16,6 +16,7 @@ def setup_memory_Manager(ql):
1616

1717
arch_type = {
1818
QL_ARCH.X86: ArchX86,
19+
QL_ARCH.X8664: ArchX8664,
1920
QL_ARCH.MIPS: ArchMIPS,
2021
QL_ARCH.ARM: ArchARM,
2122
QL_ARCH.CORTEX_M: ArchCORTEX_M,

qiling/debugger/qdb/render/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .render_x86 import ContextRenderX86
77
from .render_mips import ContextRenderMIPS
88
from .render_arm import ContextRenderARM, ContextRenderCORTEX_M
9+
from .render_x8664 import ContextRenderX8664

qiling/debugger/qdb/render/render.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,18 @@ def render_stack_dump(self, arch_sp: int) -> None:
8989
helper function for redering stack dump
9090
"""
9191

92+
# Loops over stack range (last 10 addresses)
9293
for idx in range(self.stack_num):
9394
addr = arch_sp + idx * self.pointersize
94-
if (val := self.try_read_pointer(addr)[0]):
95-
print(f"$sp+0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="")
95+
96+
'''
97+
@NOTE: Implemented new class arch_x8664 in order to bugfix issue with only dereferencing 32-bit pointers
98+
on 64-bit emulation passes.
99+
'''
100+
if (val := self.try_read_pointer(addr)[0]): # defined to be try_read_pointer(addr)[0] - dereferneces pointer
101+
102+
# @TODO: Bug here where the values on the stack are being displayed in 32-bit format
103+
print(f"RSP + 0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="")
96104

97105
# try to dereference wether it's a pointer
98106
if (buf := self.try_read_pointer(addr))[0] is not None:
@@ -180,8 +188,9 @@ def context_stack(self) -> None:
180188
display context stack dump
181189
"""
182190

191+
print(f"{self.ql.arch.regs.arch_sp:x}")
183192
self.render_stack_dump(self.ql.arch.regs.arch_sp)
184-
193+
185194
@Render.divider_printer("[ REGISTERS ]")
186195
def context_reg(self, saved_states: Mapping["str", int]) -> None:
187196
"""
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
4+
#
5+
6+
7+
8+
from .render import *
9+
from ..arch import ArchX8664
10+
11+
class ContextRenderX8664(ContextRender, ArchX8664):
12+
"""
13+
Context render for X86_64
14+
"""
15+
16+
def __init__(self, ql, predictor):
17+
super().__init__(ql, predictor)
18+
ArchX8664.__init__(self)
19+
20+
@Render.divider_printer("[ REGISTERS ]")
21+
def context_reg(self, saved_reg_dump):
22+
cur_regs = self.dump_regs()
23+
diff_reg = self.reg_diff(cur_regs, saved_reg_dump)
24+
self.render_regs_dump(cur_regs, diff_reg=diff_reg)
25+
print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.eflags)), color.END, sep="")
26+
27+
@Render.divider_printer("[ DISASM ]")
28+
def context_asm(self):
29+
lines = {}
30+
past_list = []
31+
32+
cur_addr = self.cur_addr
33+
while len(past_list) < 10:
34+
line = self.disasm(cur_addr)
35+
past_list.append(line)
36+
cur_addr += line.size
37+
38+
fd_list = []
39+
cur_insn = None
40+
for each in past_list:
41+
if each.address > self.cur_addr:
42+
fd_list.append(each)
43+
44+
elif each.address == self.cur_addr:
45+
cur_insn = each
46+
47+
"""
48+
only forward and current instruction will be printed,
49+
because we don't have a solid method to disasm backward instructions,
50+
since it's x86 instruction length is variadic
51+
"""
52+
53+
lines.update({
54+
"current": cur_insn,
55+
"forward": fd_list,
56+
})
57+
58+
self.render_assembly(lines)

qiling/debugger/qdb/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
from .render import (
1818
ContextRenderX86,
19+
ContextRenderX8664,
1920
ContextRenderARM,
2021
ContextRenderCORTEX_M,
2122
ContextRenderMIPS
2223
)
2324

2425
from .branch_predictor import (
2526
BranchPredictorX86,
27+
BranchPredictorX8664,
2628
BranchPredictorARM,
2729
BranchPredictorCORTEX_M,
2830
BranchPredictorMIPS,
@@ -64,6 +66,7 @@ def setup_branch_predictor(ql):
6466

6567
return {
6668
QL_ARCH.X86: BranchPredictorX86,
69+
QL_ARCH.X8664: BranchPredictorX8664,
6770
QL_ARCH.ARM: BranchPredictorARM,
6871
QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M,
6972
QL_ARCH.MIPS: BranchPredictorMIPS,
@@ -76,6 +79,7 @@ def setup_context_render(ql, predictor):
7679

7780
return {
7881
QL_ARCH.X86: ContextRenderX86,
82+
QL_ARCH.X8664: ContextRenderX8664,
7983
QL_ARCH.ARM: ContextRenderARM,
8084
QL_ARCH.CORTEX_M: ContextRenderCORTEX_M,
8185
QL_ARCH.MIPS: ContextRenderMIPS,

0 commit comments

Comments
 (0)