Skip to content

Commit 91360bb

Browse files
Fix terminal escape symbols on OSX (#56)
* Initial implementation. * Improve pty support for parallel execution of commands. * Issue commands in parallel. Fixes the slow execution of commands caused by the sequential execution of them. * Fix getting state from the different controllers. * [lldb] support `stdin`, `stdout` & `stderr` (#57) * support `stdin`, `stdout` & `stderr` * experimental: use LLDB API for I/O --------- Co-authored-by: Vipul Cariappa <[email protected]>
1 parent 8d74c57 commit 91360bb

File tree

3 files changed

+233
-7
lines changed

3 files changed

+233
-7
lines changed

src/idd/cli.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ async def on_mount(self) -> None:
7676
async def set_command_result(self, version) -> None:
7777
state = Debugger.get_state(version)
7878

79+
await self.set_debugee_console_output(version)
7980
await self.set_pframes_result(state, version)
8081
await self.set_pargs_result(state, version)
8182
await self.set_plocals_result(state, version)
@@ -96,15 +97,27 @@ async def set_common_command_result(self, command_result) -> None:
9697
await self.set_pframes_command_result(state)
9798
await self.set_pargs_command_result(state)
9899
await self.set_plocals_command_result(state)
100+
await self.set_debugee_console_output()
99101
if not self.disable_asm:
100102
await self.set_pasm_command_result(state)
101103
if not self.disable_registers:
102104
await self.set_pregisters_command_result(state)
103105

104106
#calls = Debugger.get_current_calls()
105107

108+
async def set_debugee_console_output(self, target=None):
109+
if target == "base":
110+
result = Debugger.get_console_output(target)
111+
self.diff_area1.append(result)
112+
elif target == "regressed":
113+
result = Debugger.get_console_output(target)
114+
self.diff_area2.append(result)
115+
else:
116+
result = Debugger.get_console_output()
117+
await self.compare_contents(result["base"], result["regressed"])
118+
106119
async def compare_contents(self, raw_base_contents, raw_regression_contents):
107-
if raw_base_contents != '' and raw_regression_contents != '':
120+
if raw_base_contents or raw_regression_contents:
108121
diff1 = self.diff_driver.get_diff(raw_base_contents, raw_regression_contents, "base")
109122
self.diff_area1.append(diff1)
110123

@@ -350,10 +363,10 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
350363
exit(0)
351364

352365
if self.parallel_command_bar.value.startswith("stdin "):
353-
content = self.parallel_command_bar.value[6:]
354-
Debugger.insert_stdin(content + "\n")
355-
self.diff_area1.append([content])
356-
self.diff_area2.append([content])
366+
Debugger.insert_stdin(self.parallel_command_bar.value[6:] + "\n")
367+
self.diff_area1.append([self.parallel_command_bar.value[6:]])
368+
self.diff_area2.append([self.parallel_command_bar.value[6:]])
369+
357370
result = {}
358371

359372
elif self.parallel_command_bar.value != "":
@@ -410,7 +423,7 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
410423
elif event.control.id == 'regressed-command-bar':
411424
if self.regressed_command_bar.value.startswith("stdin "):
412425
Debugger.insert_stdin_single(self.regressed_command_bar.value[6:] + "\n", "regressed")
413-
self.diff_area1.append([self.base_command_bar.value[6:]])
426+
self.diff_area2.append([self.regressed_command_bar.value[6:]])
414427

415428
elif self.regressed_command_bar.value != "":
416429
result = Debugger.run_single_command(self.regressed_command_bar.value, "regressed")
@@ -516,12 +529,13 @@ def main() -> None:
516529

517530
elif comparator == 'lldb':
518531
from idd.debuggers.lldb.lldb_driver import LLDBParallelDebugger, LLDBDebugger
532+
from idd.debuggers.lldb.lldb_new_driver import LLDBNewDriver
519533

520534
if ra == "" and rpid is None:
521535
Debugger = LLDBDebugger(ba, bpid)
522536
base_only = True
523537
else:
524-
Debugger = LLDBParallelDebugger(ba, bpid, ra, rpid)
538+
Debugger = LLDBNewDriver(ba, bpid, ra, rpid)
525539
else:
526540
sys.exit("Invalid comparator set")
527541

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os, sys, pty, tty, select, threading
2+
import platform
3+
import lldb
4+
from lldb import eLaunchFlagStopAtEntry
5+
6+
class IDDLLDBController:
7+
def __init__(self, exe="", pid=None):
8+
self.exe = exe
9+
self.pid = pid
10+
self.debugger = lldb.SBDebugger.Create()
11+
self.debugger.SetAsync(False)
12+
self.debugger.SetUseColor(False)
13+
14+
error = lldb.SBError()
15+
self.target = self.debugger.CreateTarget(exe, platform.machine(), "host", True, error)
16+
if not error.Success():
17+
raise Exception(error.GetCString())
18+
19+
# self.master_fd, self.slave_fd = pty.openpty()
20+
# self.slave_name = os.ttyname(self.slave_fd)
21+
# tty.setraw(self.master_fd)
22+
23+
# self.debuggee_output = []
24+
# self._start_output_stream_thread()
25+
26+
# self.target = self.debugger.CreateTarget(self.exe)
27+
# if not self.target.IsValid():
28+
# raise Exception("Failed to create target")
29+
30+
# self._launch_process()
31+
32+
def _launch_process(self):
33+
launch_info = lldb.SBLaunchInfo([])
34+
# launch_info.SetArguments(["./tmp/main.py"], True)
35+
# launch_info.SetWorkingDirectory(os.getcwd())
36+
launch_info.SetLaunchFlags(eLaunchFlagStopAtEntry)
37+
38+
launch_info.AddOpenFileAction(0, self.slave_name, read=True, write=False) # stdin
39+
launch_info.AddOpenFileAction(1, self.slave_name, read=False, write=True) # stdout
40+
launch_info.AddOpenFileAction(2, self.slave_name, read=False, write=True) # stderr
41+
42+
error = lldb.SBError()
43+
self.process = self.target.Launch(launch_info, error)
44+
45+
if not error.Success():
46+
raise Exception(f"Launch failed: {error.GetCString()}")
47+
48+
def _start_output_stream_thread(self):
49+
def stream_output():
50+
while True:
51+
try:
52+
r, _, _ = select.select([self.master_fd], [], [], 0.1)
53+
if r:
54+
data = os.read(self.master_fd, 1024).decode(errors="replace")
55+
self.debuggee_output.append(data)
56+
except OSError:
57+
break
58+
59+
self.output_thread = threading.Thread(target=stream_output, daemon=True)
60+
self.output_thread.start()
61+
62+
# def write(self, command: str):
63+
# """Send LLDB command as if typed in interactive shell."""
64+
# if not command.endswith("\n"):
65+
# command += "\n"
66+
# os.write(self.master_fd, command.encode())
67+
68+
def run_lldb_command(self, command: str):
69+
result = lldb.SBCommandReturnObject()
70+
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
71+
if result.Succeeded():
72+
return result.GetOutput().splitlines()
73+
return result.GetError().splitlines()
74+
75+
76+
def send_input_to_debuggee(self, text):
77+
"""Send input to the debugged process' stdin."""
78+
# if not text.endswith("\n"):
79+
# text += "\n"
80+
# os.write(self.master_fd, text.encode())
81+
self.target.GetProcess().PutSTDIN(text)
82+
83+
def get_debuggee_output(self):
84+
"""Return all captured debuggee output so far."""
85+
# os.set_blocking(self.master_fd, False)
86+
# try:
87+
# content = os.read(self.master_fd, 1024 * 1024 * 10).decode(errors="replace")
88+
# except BlockingIOError:
89+
# content = ""
90+
# return content.splitlines()
91+
return (self.target.GetProcess().GetSTDOUT(1024*1024*10) + self.target.GetProcess().GetSTDERR(1024*1024*10)).splitlines()
92+
93+
# def pop_output(self):
94+
# """Return and clear debuggee output buffer."""
95+
# output = self.debuggee_output
96+
# self.debuggee_output = []
97+
# return output
98+
99+
def terminate(self):
100+
# if self.process:
101+
# self.process.Kill()
102+
# os.close(self.master_fd)
103+
# os.close(self.slave_fd)
104+
lldb.SBDebugger.Destroy(self.debugger)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from idd.driver import Driver
2+
from idd.debuggers.lldb.lldb_controller import IDDLLDBController
3+
from idd.debuggers.lldb.lldb_extensions import *
4+
from concurrent.futures import ThreadPoolExecutor
5+
6+
7+
class LLDBNewDriver(Driver):
8+
def __init__(
9+
self, base_exe=None, base_pid=None, regressed_exe=None, regressed_pid=None
10+
):
11+
self.base_controller = IDDLLDBController(exe=base_exe)
12+
self.regressed_controller = IDDLLDBController(exe=regressed_exe)
13+
14+
def run_single_command(self, command, target):
15+
if target == "base":
16+
result = self.base_controller.run_lldb_command(command)
17+
return result
18+
elif target == "regressed":
19+
result = self.regressed_controller.run_lldb_command(command)
20+
return result
21+
22+
def run_parallel_command(self, command):
23+
with ThreadPoolExecutor() as executor:
24+
base_future = executor.submit(self.run_single_command, command, "base")
25+
regressed_future = executor.submit(
26+
self.run_single_command, command, "regressed"
27+
)
28+
return {
29+
"base": base_future.result(),
30+
"regressed": regressed_future.result(),
31+
}
32+
33+
def insert_stdin(self, text):
34+
self.base_controller.send_input_to_debuggee(text)
35+
self.regressed_controller.send_input_to_debuggee(text)
36+
37+
def insert_stdin_single(self, text, target):
38+
if target == "base":
39+
self.base_controller.send_input_to_debuggee(text)
40+
elif target == "regressed":
41+
self.regressed_controller.send_input_to_debuggee(text)
42+
43+
def get_state(self, target=None):
44+
if target == "base":
45+
return {
46+
"stack_frames": self.get_current_stack_frames(self.base_controller),
47+
"locals": self.get_current_local_vars(self.base_controller, None),
48+
"args": self.get_current_args(self.base_controller),
49+
"instructions": self.get_current_instructions(self.base_controller),
50+
"registers": self.get_current_registers(self.base_controller),
51+
}
52+
if target == "regressed":
53+
return {
54+
"stack_frames": self.get_current_stack_frames(self.regressed_controller),
55+
"locals": self.get_current_local_vars(self.regressed_controller, None),
56+
"args": self.get_current_args(self.regressed_controller),
57+
"instructions": self.get_current_instructions(self.regressed_controller),
58+
"registers": self.get_current_registers(self.regressed_controller),
59+
}
60+
61+
with ThreadPoolExecutor() as executor:
62+
base_future = executor.submit(self.get_state, "base")
63+
regressed_future = executor.submit(self.get_state, "regressed")
64+
return {
65+
"base": base_future.result(),
66+
"regressed": regressed_future.result(),
67+
}
68+
69+
def get_console_output(self, target=None):
70+
if target == "base":
71+
return self.base_controller.get_debuggee_output()
72+
if target == "regressed":
73+
return self.regressed_controller.get_debuggee_output()
74+
75+
with ThreadPoolExecutor() as executor:
76+
base_future = executor.submit(self.get_console_output, "base")
77+
regressed_future = executor.submit(self.get_console_output, "regressed")
78+
return {
79+
"base": base_future.result(),
80+
"regressed": regressed_future.result(),
81+
}
82+
83+
def get_current_stack_frames(self, controller):
84+
target = controller.debugger.GetTargetAtIndex(0)
85+
return get_current_stack_frame_from_target(target) or []
86+
87+
def get_current_args(self, controller):
88+
target = controller.debugger.GetTargetAtIndex(0)
89+
return get_args_as_list(target) or []
90+
91+
def get_current_local_vars(self, controller, filters):
92+
target = controller.debugger.GetTargetAtIndex(0)
93+
locals = get_local_vars_as_list(target)
94+
if filters == "ignore-order-declaration":
95+
locals.sort()
96+
return locals or []
97+
98+
def get_current_instructions(self, controller):
99+
target = controller.debugger.GetTargetAtIndex(0)
100+
return get_instructions_as_list(target) or []
101+
102+
def get_current_registers(self, controller):
103+
target = controller.debugger.GetTargetAtIndex(0)
104+
return get_registers_as_list(target) or []
105+
106+
def terminate(self):
107+
self.base_controller.terminate()
108+
self.regressed_controller.terminate()

0 commit comments

Comments
 (0)