Skip to content

Commit 14b2e27

Browse files
committed
debugpy: Enhance breakpoint handling and path mapping in PdbAdapter
Signed-off-by: Jos Verlinde <[email protected]>
1 parent 756d174 commit 14b2e27

File tree

1 file changed

+71
-35
lines changed

1 file changed

+71
-35
lines changed

python-ecosys/debugpy/debugpy/server/pdb_adapter.py

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class PdbAdapter:
3131
"""Adapter between DAP protocol and MicroPython's sys.settrace functionality."""
3232

3333
def __init__(self):
34-
self.breakpoints = {} # filename -> {line_no: breakpoint_info}
34+
self.breakpoints : dict[str,dict[int,dict]] = {} # filename -> {line_no: breakpoint_info} # todo - simplify - reduce info stored
3535
self.current_frame = None
3636
self.step_mode = None # None, 'over', 'into', 'out'
3737
self.step_frame = None
@@ -40,8 +40,8 @@ def __init__(self):
4040
self.continue_event = False
4141
self.variables_cache = {} # frameId -> variables
4242
self.frame_id_counter = 1
43-
self.path_mappings : list[tuple[str,str]] = [] # runtime_path -> vscode_path mapping
44-
self.file_mappings : dict[str,str] = {} # runtime_path -> vscode_path mapping
43+
self.path_mappings : list[tuple[str,str]] = [] # runtime_path -> vscode_path mapping # todo: move to session level
44+
self.file_mappings : dict[str,str] = {} # runtime_path -> vscode_path mapping # todo : merge with .breakpoints
4545

4646
def _debug_print(self, message):
4747
"""Print debug message only if debug logging is enabled."""
@@ -69,17 +69,61 @@ def set_trace_function(self, trace_func):
6969
else:
7070
raise RuntimeError("sys.settrace not available")
7171

72-
def set_breakpoints(self, filename, breakpoints:list[dict]):
72+
def _filename_as_debugee(self, path:str):
73+
# check if we have a 1:1 file mapping for this path
74+
if self.file_mappings.get(path):
75+
return self.file_mappings[path]
76+
# Check if we have a folder mapping for this path
77+
for runtime_path, vscode_path in self.path_mappings:
78+
if path.startswith(vscode_path):
79+
path = path.replace(vscode_path, runtime_path, 1)
80+
if path.startswith('//'):
81+
path = path[1:]
82+
# If no mapping found, return the original path
83+
return path
84+
85+
def _filename_as_debugger(self, path:str):
86+
"""Convert a file path to the debugger's expected format."""
87+
path = path or ""
88+
if not path:
89+
return path
90+
if path.startswith('<'):
91+
# Special case for <stdin> or similar
92+
return path
93+
# Check if we have a 1:1 file mapping for this path
94+
for runtime_path, vscode_path in self.path_mappings:
95+
if path.startswith(runtime_path):
96+
path = path.replace(runtime_path, vscode_path, 1)
97+
return path
98+
99+
# Check if we have a folder mapping for this path
100+
for runtime_path, vscode_path in self.path_mappings:
101+
if path.startswith(runtime_path):
102+
path = path.replace(runtime_path, vscode_path, 1)
103+
if path.startswith('//'):
104+
path = path[1:]
105+
# If no mapping found, return the original path
106+
return path
107+
108+
def set_breakpoints(self, filename:str, breakpoints:list[dict]):
73109
"""Set breakpoints for a file."""
74110
self.breakpoints[filename] = {}
111+
local_name = self._filename_as_debugee(filename)
112+
self.file_mappings[local_name] = filename
75113
actual_breakpoints = []
76-
77-
# Debug log the breakpoint path
78114
self._debug_print(f"[PDB] Setting breakpoints for file: {filename}")
79115

80116
for bp in breakpoints:
81117
line = bp.get("line")
82118
if line:
119+
if local_name != filename:
120+
self.breakpoints[local_name] = {}
121+
self._debug_print(f"[>>>] Setting breakpoints for local: {local_name}:{line}")
122+
self.breakpoints[local_name][line] = {
123+
"line": line,
124+
"verified": True,
125+
"source": {"path": filename}
126+
}
83127
self.breakpoints[filename][line] = {
84128
"line": line,
85129
"verified": True,
@@ -91,6 +135,8 @@ def set_breakpoints(self, filename, breakpoints:list[dict]):
91135
"source": {"path": filename}
92136
})
93137

138+
self._debug_print(f"[PDB] Breakpoints set : {self.breakpoints}")
139+
94140
return actual_breakpoints
95141

96142
def should_stop(self, frame, event:str, arg):
@@ -106,33 +152,18 @@ def should_stop(self, frame, event:str, arg):
106152
if lineno in self.breakpoints[filename]:
107153
self._debug_print(f"[PDB] HIT BREAKPOINT (exact match) at {filename}:{lineno}")
108154
# Record the path mapping (in this case, they're already the same)
109-
self.file_mappings[filename] = filename
155+
self.file_mappings[filename] = self._filename_as_debugger(filename)
110156
self.hit_breakpoint = True
111157
return True
112-
113-
file_basename = basename(filename)
114-
self._debug_print(f"[PDB] Fallback basename match: '{file_basename}' vs available files")
115-
for bp_file in self.breakpoints:
116-
bp_basename = basename(bp_file)
117-
self._debug_print(f"[PDB] Comparing '{file_basename}' == '{bp_basename}' ?")
118-
if bp_basename == file_basename:
119-
self._debug_print(f"[PDB] Basename match found! Checking line {lineno} in {list(self.breakpoints[bp_file].keys())}")
120-
if lineno in self.breakpoints[bp_file]:
121-
self._debug_print(f"[PDB] HIT BREAKPOINT (fallback basename match) at {filename}:{lineno} -> {bp_file}")
122-
# Record the path mapping so we can report the correct path in stack traces
123-
self.file_mappings[filename] = bp_file
124-
self.hit_breakpoint = True
125-
return True
126-
127-
# Also check if the runtime path might be relative and the breakpoint path absolute
128-
if ends_with_path(bp_file, filename):
129-
self._debug_print(f"[PDB] Relative path match: {bp_file} ends with {filename}")
130-
if lineno in self.breakpoints[bp_file]:
131-
self._debug_print(f"[PDB] HIT BREAKPOINT (relative path match) at {filename}:{lineno} -> {bp_file}")
132-
# Record the path mapping so we can report the correct path in stack traces
133-
self.file_mappings[filename] = bp_file
134-
self.hit_breakpoint = True
135-
return True
158+
# path/file.py matched - but not the line number - keep running
159+
else:
160+
# file not (yet) matched - this is slow so we do not want to do this often.
161+
# TODO: use builins - sys.path method to find the file
162+
# if we have a path match , but no breakpoints - add it to the file_mappings dict avoid this check
163+
self.breakpoints[filename] = {} # Ensure the filename is in the breakpoints dict
164+
if not filename in self.file_mappings:
165+
self.file_mappings[filename] = self._filename_as_debugger(filename)
166+
self._debug_print(f"[PDB] add mapping for :'{filename}' -> '{self.file_mappings[filename]}'")
136167

137168
# Check stepping
138169
if self.step_mode == 'into':
@@ -216,15 +247,20 @@ def get_stack_trace(self):
216247
else :
217248
hint = 'normal'
218249

250+
# self._debug_print("=" * 40 )
251+
# self._debug_print(f"[PDB] file mappings: {repr(self.file_mappings)} " )
252+
# self._debug_print(f"[PDB] path mappings: {repr(self.path_mappings)}" )
253+
# self._debug_print("=" * 40 )
254+
219255
# Use the VS Code path if we have a mapping, otherwise use the original path
220-
display_path = self.file_mappings.get(filename, filename)
221-
if filename != display_path:
222-
self._debug_print(f"[PDB] Stack trace path mapping: {filename} -> {display_path}")
256+
debugger_path = self._filename_as_debugger(filename)
257+
if filename != debugger_path:
258+
self._debug_print(f"[PDB] Stack trace path mapping: {filename} -> {debugger_path}")
223259
# Create StackFrame info
224260
frames.append({
225261
"id": frame_id,
226262
"name": name,
227-
"source": {"path": display_path},
263+
"source": {"path": debugger_path},
228264
"line": line,
229265
"column": 1,
230266
"endLine": line,

0 commit comments

Comments
 (0)