Skip to content

Commit 8d74c57

Browse files
[WIP] [gdb] Using pty for stdout (#50)
* Fix exception occuring in case of illigal stack_frame string. * Refactor to use pty for capturing output. * [CD] Upload package to PyPI when creating a release (#48) * enable running the script without installing as a package While developing, we can test out changes without pip install * [CD] Upload package to PyPI when creating a release * fix according to code review * fix permissions * Fix buffer. * Include stdout and stdin as subwindows. * Add debugee stdout and stdin windows. * Intercept gdb output even if it appears with delays. * Remove " as they may break the json format. * Fix parsing of the speacial commands to follow the new approach. * Disable not implemented function call. * Reduce timeout so that commands are completed quickley. * Add default values. * Use full path of the file. * Use parallel and non blocking calls to speed command execution. * Fix issuing single commands * Fix receiving of debuggee output. * Remove " from the stack, args and locals output. Prevents from violating the json generated. * Disable debuginfod. * Ensure debuggee output is flused before a ywrite. * Disable stdout flush as it discards debuggee stdout. * remove additional panels used while developing (#58) --------- Co-authored-by: Vipul Cariappa <[email protected]>
1 parent f30aadb commit 8d74c57

File tree

4 files changed

+445
-169
lines changed

4 files changed

+445
-169
lines changed

src/idd/cli.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import argparse
44
import sys
5+
import os
6+
from asyncio import sleep
57

68
from textual import on
79
from textual import events
@@ -18,7 +20,7 @@
1820

1921

2022
class DiffDebug(App):
21-
CSS_PATH = "layout.tcss"
23+
CSS_PATH = os.path.join(os.path.dirname(__file__), "layout.tcss")
2224

2325
current_index: Reactive[int] = Reactive(-1)
2426
tab_index = ["parallel_command_bar", "base_command_bar", "regressed_command_bar"]
@@ -41,6 +43,9 @@ class DiffDebug(App):
4143
diff_asm2 = TextScrollView(title="Regression Asm", component_id = "diff-asm2")
4244
diff_reg1 = TextScrollView(title="Base Registers", component_id = "diff-reg1")
4345
diff_reg2 = TextScrollView(title="Regression Registers", component_id = "diff-reg2")
46+
base_input_bar = Input(placeholder="Input for base debuggee...", id="base-input-bar")
47+
regressed_input_bar = Input(placeholder="Input for regression debuggee...", id="regressed-input-bar")
48+
4449
#executable_path1 = DiffArea(title="base executable and arguments", value="")
4550
#executable_path2 = DiffArea(title="regression executable and arguments", value="")
4651

@@ -60,6 +65,13 @@ def __init__(self, disable_asm=False, disable_registers=False, only_base=False):
6065
self.base_history_index = 0
6166
self.regressed_history = [""]
6267
self.regressed_history_index = 0
68+
self.base_awaiting_shown = False
69+
self.regressed_awaiting_shown = False
70+
71+
async def on_mount(self) -> None:
72+
#self.set_interval(0.1, self.refresh_debuggee_status)
73+
self.set_interval(0.25, self.watch_debuggee_output)
74+
6375

6476
async def set_command_result(self, version) -> None:
6577
state = Debugger.get_state(version)
@@ -215,6 +227,29 @@ async def set_pregisters_command_result(self, state) -> None:
215227
diff2 = self.diff_driver.get_diff(regressed_file_contents, base_file_contents, "regressed")
216228
self.diff_reg2.text(diff2)
217229

230+
async def watch_debuggee_output(self) -> None:
231+
if hasattr(Debugger, "base_gdb_instance"):
232+
base_output = Debugger.base_gdb_instance.pop_debuggee_output()
233+
if base_output:
234+
self.diff_area1.append(base_output)
235+
if Debugger.base_gdb_instance.is_waiting_for_input():
236+
if not self.base_awaiting_shown:
237+
self.diff_area1.append("[awaiting input]")
238+
self.base_awaiting_shown = True
239+
else:
240+
self.base_awaiting_shown = False
241+
242+
if hasattr(Debugger, "regressed_gdb_instance"):
243+
regressed_output = Debugger.regressed_gdb_instance.pop_debuggee_output()
244+
if regressed_output:
245+
self.diff_area2.append(regressed_output)
246+
if Debugger.regressed_gdb_instance.is_waiting_for_input():
247+
if not self.regressed_awaiting_shown:
248+
self.diff_area2.append("[awaiting input]")
249+
self.regressed_awaiting_shown = True
250+
else:
251+
self.regressed_awaiting_shown = False
252+
218253
def compose(self) -> ComposeResult:
219254
"""Compose the layout of the application."""
220255
if self.only_base:
@@ -313,11 +348,14 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
313348
self.parallel_command_bar.value == "exit":
314349
Debugger.terminate()
315350
exit(0)
316-
351+
317352
if self.parallel_command_bar.value.startswith("stdin "):
318-
Debugger.insert_stdin(self.parallel_command_bar.value[6:] + "\n")
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])
319357
result = {}
320-
358+
321359
elif self.parallel_command_bar.value != "":
322360
result = Debugger.run_parallel_command(self.parallel_command_bar.value)
323361

@@ -347,6 +385,7 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
347385

348386
if self.base_command_bar.value.startswith("stdin "):
349387
Debugger.insert_stdin_single(self.base_command_bar.value[6:] + "\n", "base")
388+
self.diff_area1.append([self.base_command_bar.value[6:]])
350389

351390
elif self.base_command_bar.value != "":
352391
result = Debugger.run_single_command(self.base_command_bar.value, "base")
@@ -371,6 +410,7 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
371410
elif event.control.id == 'regressed-command-bar':
372411
if self.regressed_command_bar.value.startswith("stdin "):
373412
Debugger.insert_stdin_single(self.regressed_command_bar.value[6:] + "\n", "regressed")
413+
self.diff_area1.append([self.base_command_bar.value[6:]])
374414

375415
elif self.regressed_command_bar.value != "":
376416
result = Debugger.run_single_command(self.regressed_command_bar.value, "regressed")
@@ -391,6 +431,12 @@ async def execute_debugger_command(self, event: Input.Changed) -> None:
391431

392432
self.regressed_command_bar.value = ""
393433

434+
elif event.input.id == "base-input-bar":
435+
Debugger.base_gdb_instance.send_input_to_debuggee(event.value)
436+
437+
elif event.input.id == "regressed-input-bar":
438+
Debugger.regressed_gdb_instance.send_input_to_debuggee(event.value)
439+
394440
async def on_key(self, event: events.Key) -> None:
395441
if self.focused is None:
396442
return
@@ -484,6 +530,5 @@ def main() -> None:
484530
dd = DiffDebug(disable_assembly, disable_registers, base_only)
485531
dd.run()
486532

487-
488533
if __name__ == "__main__":
489534
main()

src/idd/debuggers/gdb/gdb_commands.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ class PrintState (gdb.Command):
2121
def __init__ (self):
2222
super (PrintState, self).__init__ ("pstate", gdb.COMMAND_USER)
2323

24+
def trim_middle_quotes(input_string):
25+
# Ensure the string starts and ends with quotes
26+
if input_string.startswith('"') and input_string.endswith('"'):
27+
# Remove the first and last quotes temporarily
28+
trimmed = input_string[1:-1]
29+
# Remove all remaining quotes
30+
trimmed = trimmed.replace('"', '')
31+
# Add back the starting and ending quotes
32+
# print(trimmed)
33+
return f'"{trimmed}"'
34+
return input_string # Return as-is if it doesn't start and end with quotes
35+
36+
def trim_quotes(self, input_string):
37+
trimmed = input_string.replace('"','')
38+
return trimmed
39+
2440
def invoke (self, arg, from_tty):
2541
result = {}
2642

@@ -33,8 +49,12 @@ def invoke (self, arg, from_tty):
3349

3450
# get stack frame
3551
command_result = gdb.execute("bt", to_string=True)
36-
stack_frames = command_result.split('\n')
37-
result['stack_frames'] = stack_frames
52+
53+
# leave only the starting and ending quotes
54+
# ensures correct parsing of the stack frames as
55+
stack_frames = [line for line in (command_result or "").split('\n') if line.strip()]
56+
trimmed_stack_frames = [self.trim_quotes(frame) for frame in stack_frames]
57+
result['stack_frames'] = trimmed_stack_frames or []
3858

3959
frame = gdb.selected_frame()
4060
block = frame.block()
@@ -45,13 +65,13 @@ def invoke (self, arg, from_tty):
4565
if (symbol.is_variable):
4666
name = symbol.name
4767
if not name in names:
48-
locals.append('{} = {}'.format(name, symbol.value(frame)))
68+
locals.append('{} = {}'.format(name, symbol.value(frame)).replace('"',''))
4969

5070
# get args
5171
if (symbol.is_argument):
5272
name = symbol.name
5373
if not name in names:
54-
args.append('{} = {}\n'.format(name, symbol.value(frame)))
74+
args.append('{} = {}\n'.format(name, symbol.value(frame)).replace('"',''))
5575
block = block.superblock
5676

5777
# get instructions
@@ -86,22 +106,23 @@ def invoke (self, arg, from_tty):
86106
except Exception as e:
87107
registers.append(str(e))
88108

89-
result['locals'] = locals
90-
result['args'] = args
91-
result['instructions'] = instructions
92-
result['registers'] = registers
109+
result['locals'] = locals or []
110+
result['args'] = args or []
111+
result['instructions'] = instructions or []
112+
result['registers'] = registers or []
93113

94114
json_result = json.dumps(result)
95115

96-
print(json_result)
116+
gdb.write(f'{json_result}\n', gdb.STDOUT)
117+
97118

98119
class PrintFrame (gdb.Command):
99120
def __init__ (self):
100121
super (PrintFrame, self).__init__ ("pframe", gdb.COMMAND_USER)
101122

102123
def invoke (self, arg, from_tty):
103124
result = gdb.execute("bt", to_string=True)
104-
print(result)
125+
gdb.write(result, gdb.STDOUT)
105126

106127
class PrintLocals (gdb.Command):
107128
def __init__ (self):

0 commit comments

Comments
 (0)