Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,41 @@ def _extract_coroutine_stacks_lineno_only(self, stack_trace):
# ============================================================================


class TestSelfStackTrace(RemoteInspectionTestBase):
@skip_if_not_supported
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Test only runs on Linux with process_vm_readv support",
)
def test_self_trace_with_large_linetable(self):
script = textwrap.dedent("""\
import os
import _remote_debugging

assignments = "\\n".join(
f"value_{i} = {i}" for i in range(1000)
)
source = (
f"{assignments}\\n"
"_remote_debugging.RemoteUnwinder(os.getpid()).get_stack_trace()\\n"
)
code = compile(source, "large_linetable.py", "exec")
assert len(code.co_linetable) > 4096, len(code.co_linetable)
exec(code)
""")

result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}\nstderr: {result.stderr}"
)


@requires_remote_subprocess_debugging()
class TestGetStackTrace(RemoteInspectionTestBase):
@skip_if_not_supported
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``_remote_debugging`` stack traces for code objects with large line
tables.
7 changes: 5 additions & 2 deletions Modules/_remote_debugging/code_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

#include "_remote_debugging.h"

#define MAX_LINETABLE_SIZE (1024 * 1024)
#define MAX_LINETABLE_ENTRIES 65536

/* ============================================================================
* TLBC CACHING FUNCTIONS (Py_GIL_DISABLED only)
* ============================================================================ */
Expand Down Expand Up @@ -186,7 +189,6 @@ parse_linetable(const uintptr_t addrq, const char* linetable, Py_ssize_t linetab
int computed_line = firstlineno; // Running accumulator, separate from output
int val; // Temporary for varint results
uint8_t byte; // Temporary for byte reads
const size_t MAX_LINETABLE_ENTRIES = 65536;
size_t entry_count = 0;

while (ptr < end && *ptr != '\0' && entry_count < MAX_LINETABLE_ENTRIES) {
Expand Down Expand Up @@ -387,7 +389,8 @@ parse_code_object(RemoteUnwinderObject *unwinder,
}

linetable = read_py_bytes(unwinder,
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.linetable), 4096);
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.linetable),
MAX_LINETABLE_SIZE);
if (!linetable) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read linetable from code object");
goto error;
Expand Down
Loading