Skip to content

Commit 9096375

Browse files
committed
Handle arbitrarily long paths in /proc/<PID>/maps
1 parent 7c340e1 commit 9096375

File tree

1 file changed

+98
-57
lines changed

1 file changed

+98
-57
lines changed

Python/remote_debugging.c

Lines changed: 98 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -294,55 +294,12 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s
294294

295295
#if defined(__linux__) && HAVE_PROCESS_VM_READV
296296
static uintptr_t
297-
find_map_start_address(proc_handle_t *handle, char* result_filename, const char* map)
297+
search_elf_file_for_section(
298+
proc_handle_t *handle,
299+
const char* secname,
300+
uintptr_t start_address,
301+
const char *elf_file)
298302
{
299-
char maps_file_path[64];
300-
sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
301-
302-
FILE* maps_file = fopen(maps_file_path, "r");
303-
if (maps_file == NULL) {
304-
PyErr_SetFromErrno(PyExc_OSError);
305-
return 0;
306-
}
307-
308-
int match_found = 0;
309-
310-
char line[256];
311-
char map_filename[PATH_MAX];
312-
uintptr_t result_address = 0;
313-
while (fgets(line, sizeof(line), maps_file) != NULL) {
314-
unsigned long start_address = 0;
315-
sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename);
316-
char* filename = strrchr(map_filename, '/');
317-
if (filename != NULL) {
318-
filename++; // Move past the '/'
319-
} else {
320-
filename = map_filename; // No path, use the whole string
321-
}
322-
323-
if (!match_found && strncmp(filename, map, strlen(map)) == 0) {
324-
match_found = 1;
325-
result_address = start_address;
326-
strcpy(result_filename, map_filename);
327-
break;
328-
}
329-
}
330-
331-
fclose(maps_file);
332-
333-
if (!match_found) {
334-
map_filename[0] = '\0';
335-
}
336-
337-
return result_address;
338-
}
339-
340-
static uintptr_t
341-
search_map_for_section(proc_handle_t *handle, const char* secname, const char* map)
342-
{
343-
char elf_file[256];
344-
uintptr_t start_address = find_map_start_address(handle, elf_file, map);
345-
346303
if (start_address == 0) {
347304
return 0;
348305
}
@@ -412,6 +369,83 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* m
412369
return result;
413370
}
414371

372+
static uintptr_t
373+
search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr)
374+
{
375+
char maps_file_path[64];
376+
sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
377+
378+
FILE* maps_file = fopen(maps_file_path, "r");
379+
if (maps_file == NULL) {
380+
PyErr_SetFromErrno(PyExc_OSError);
381+
return 0;
382+
}
383+
384+
size_t linelen = 0;
385+
size_t linesz = PATH_MAX;
386+
char *line = PyMem_Malloc(linesz);
387+
if (!line) {
388+
fclose(maps_file);
389+
PyErr_NoMemory();
390+
return 0;
391+
}
392+
393+
uintptr_t retval = 0;
394+
while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) {
395+
linelen = strlen(line);
396+
if (line[linelen - 1] != '\n') {
397+
// Read a partial line: realloc and keep reading where we left off.
398+
// Note that even the last line will be terminated by a newline.
399+
linesz *= 2;
400+
char *biggerline = PyMem_Realloc(line, linesz);
401+
if (!biggerline) {
402+
PyMem_Free(line);
403+
fclose(maps_file);
404+
PyErr_NoMemory();
405+
return 0;
406+
}
407+
line = biggerline;
408+
continue;
409+
}
410+
411+
// Read a full line: strip the newline
412+
line[linelen - 1] = '\0';
413+
// and prepare to read the next line into the start of the buffer.
414+
linelen = 0;
415+
416+
unsigned long start = 0;
417+
unsigned long path_pos = 0;
418+
sscanf(line, "%lx-%*x %*s %*s %*s %*s %ln", &start, &path_pos);
419+
420+
if (!path_pos) {
421+
// Line didn't match our format string. This shouldn't be
422+
// possible, but let's be defensive and skip the line.
423+
continue;
424+
}
425+
426+
const char *path = line + path_pos;
427+
const char *filename = strrchr(path, '/');
428+
if (filename) {
429+
filename++; // Move past the '/'
430+
} else {
431+
filename = path; // No directories, or an empty string
432+
}
433+
434+
if (strstr(filename, substr)) {
435+
retval = search_elf_file_for_section(handle, secname, start, path);
436+
if (retval) {
437+
break;
438+
}
439+
}
440+
}
441+
442+
PyMem_Free(line);
443+
fclose(maps_file);
444+
445+
return retval;
446+
}
447+
448+
415449
#endif // __linux__
416450

417451
#ifdef MS_WINDOWS
@@ -512,21 +546,28 @@ get_py_runtime(proc_handle_t* handle)
512546
{
513547
uintptr_t address = 0;
514548

515-
#ifndef MS_WINDOWS
516-
// On non-Windows platforms, try libpython first, then fall back to python
517-
address = search_map_for_section(handle, "PyRuntime", "libpython");
518-
if (address == 0) {
519-
// TODO: Differentiate between not found and error
520-
PyErr_Clear();
521-
address = search_map_for_section(handle, "PyRuntime", "python");
522-
}
523-
#else
549+
#ifdef MS_WINDOWS
524550
// On Windows, search for 'python' in executable or DLL
525551
address = search_windows_map_for_section(handle, "PyRuntime", L"python");
526552
if (address == 0) {
527553
// Error out: 'python' substring covers both executable and DLL
528554
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
529555
}
556+
#elif defined(__linux__)
557+
// On Linux, search for 'python' in executable or DLL
558+
address = search_linux_map_for_section(handle, "PyRuntime", "python");
559+
if (address == 0) {
560+
// Error out: 'python' substring covers both executable and DLL
561+
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
562+
}
563+
#else
564+
// On macOS, try libpython first, then fall back to python
565+
address = search_map_for_section(handle, "PyRuntime", "libpython");
566+
if (address == 0) {
567+
// TODO: Differentiate between not found and error
568+
PyErr_Clear();
569+
address = search_map_for_section(handle, "PyRuntime", "python");
570+
}
530571
#endif
531572

532573
return address;

0 commit comments

Comments
 (0)