| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 3fc48c1 commit e2ab90b
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -150,6 +150,44 @@ def foo(): | |||
| 150 | 150 | else: | |
| 151 | 151 | self.fail("Main thread stack trace not found in result") | |
| 152 | 152 | ||
| 153 | + @skip_if_not_supported | ||
| 154 | + @unittest.skipIf( | ||
| 155 | + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | ||
| 156 | + "Test only runs on Linux with process_vm_readv support", | ||
| 157 | + ) | ||
| 158 | + def test_self_trace_after_ctypes_import(self): | ||
| 159 | + """Test that RemoteUnwinder works on the same process after _ctypes import. | ||
| 160 | + | ||
| 161 | + When _ctypes is imported, it may call dlopen on the libpython shared | ||
| 162 | + library, creating a duplicate mapping in the process address space. | ||
| 163 | + The remote debugging code must skip these uninitialized duplicate | ||
| 164 | + mappings and find the real PyRuntime. See gh-144563. | ||
| 165 | + """ | ||
| 166 | + # Run the test in a subprocess to avoid side effects | ||
| 167 | + script = textwrap.dedent("""\ | ||
| 168 | + import os | ||
| 169 | + import _remote_debugging | ||
| 170 | + | ||
| 171 | + # Should work before _ctypes import | ||
| 172 | + unwinder = _remote_debugging.RemoteUnwinder(os.getpid()) | ||
| 173 | + | ||
| 174 | + import _ctypes | ||
| 175 | + | ||
| 176 | + # Should still work after _ctypes import (gh-144563) | ||
| 177 | + unwinder = _remote_debugging.RemoteUnwinder(os.getpid()) | ||
| 178 | + """) | ||
| 179 | + | ||
| 180 | + result = subprocess.run( | ||
| 181 | + [sys.executable, "-c", script], | ||
| 182 | + capture_output=True, | ||
| 183 | + text=True, | ||
| 184 | + timeout=SHORT_TIMEOUT, | ||
| 185 | + ) | ||
| 186 | + self.assertEqual( | ||
| 187 | + result.returncode, 0, | ||
| 188 | + f"stdout: {result.stdout}\nstderr: {result.stderr}" | ||
| 189 | + ) | ||
| 190 | + | ||
| 153 | 191 | @skip_if_not_supported | |
| 154 | 192 | @unittest.skipIf( | |
| 155 | 193 | sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + Fix interaction of the Tachyon profiler and :mod:`ctypes` and other modules | ||
| 2 | + that load the Python shared library (if present) in an independent map as | ||
| 3 | + this was causing the mechanism that loads the binary information to be | ||
| 4 | + confused. Patch by Pablo Galindo | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -805,7 +805,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) | |||
| 805 | 805 | ||
| 806 | 806 | #ifdef MS_WINDOWS | |
| 807 | 807 | // On Windows, search for asyncio debug in executable or DLL | |
| 808 | - address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio"); | ||
| 808 | + address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio", NULL); | ||
| 809 | 809 | if (address == 0) { | |
| 810 | 810 | // Error out: 'python' substring covers both executable and DLL | |
| 811 | 811 | PyObject *exc = PyErr_GetRaisedException(); | |
@@ -814,7 +814,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) | |||
| 814 | 814 | } | |
| 815 | 815 | #elif defined(__linux__) | |
| 816 | 816 | // On Linux, search for asyncio debug in executable or DLL | |
| 817 | - address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython"); | ||
| 817 | + address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL); | ||
| 818 | 818 | if (address == 0) { | |
| 819 | 819 | // Error out: 'python' substring covers both executable and DLL | |
| 820 | 820 | PyObject *exc = PyErr_GetRaisedException(); | |
@@ -823,10 +823,10 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) | |||
| 823 | 823 | } | |
| 824 | 824 | #elif defined(__APPLE__) && TARGET_OS_OSX | |
| 825 | 825 | // On macOS, try libpython first, then fall back to python | |
| 826 | - address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython"); | ||
| 826 | + address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL); | ||
| 827 | 827 | if (address == 0) { | |
| 828 | 828 | PyErr_Clear(); | |
| 829 | - address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython"); | ||
| 829 | + address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL); | ||
| 830 | 830 | } | |
| 831 | 831 | if (address == 0) { | |
| 832 | 832 | // Error out: 'python' substring covers both executable and DLL | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -133,6 +133,31 @@ typedef struct { | |||
| 133 | 133 | Py_ssize_t page_size; | |
| 134 | 134 | } proc_handle_t; | |
| 135 | 135 | ||
| 136 | + // Forward declaration for use in validation function | ||
| 137 | + static int | ||
| 138 | + _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst); | ||
| 139 | + | ||
| 140 | + // Optional callback to validate a candidate section address found during | ||
| 141 | + // memory map searches. Returns 1 if the address is valid, 0 to skip it. | ||
| 142 | + // This allows callers to filter out duplicate/stale mappings (e.g. from | ||
| 143 | + // ctypes dlopen) whose sections were never initialized. | ||
| 144 | + typedef int (*section_validator_t)(proc_handle_t *handle, uintptr_t address); | ||
| 145 | + | ||
| 146 | + // Validate that a candidate address starts with _Py_Debug_Cookie. | ||
| 147 | + static int | ||
| 148 | + _Py_RemoteDebug_ValidatePyRuntimeCookie(proc_handle_t *handle, uintptr_t address) | ||
| 149 | + { | ||
| 150 | + if (address == 0) { | ||
| 151 | + return 0; | ||
| 152 | + } | ||
| 153 | + char buf[sizeof(_Py_Debug_Cookie) - 1]; | ||
| 154 | + if (_Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(buf), buf) != 0) { | ||
| 155 | + PyErr_Clear(); | ||
| 156 | + return 0; | ||
| 157 | + } | ||
| 158 | + return memcmp(buf, _Py_Debug_Cookie, sizeof(buf)) == 0; | ||
| 159 | + } | ||
| 160 | + | ||
| 136 | 161 | static void | |
| 137 | 162 | _Py_RemoteDebug_FreePageCache(proc_handle_t *handle) | |
| 138 | 163 | { | |
@@ -490,7 +515,8 @@ pid_to_task(pid_t pid) | |||
| 490 | 515 | } | |
| 491 | 516 | ||
| 492 | 517 | static uintptr_t | |
| 493 | - search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr) { | ||
| 518 | + search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr, | ||
| 519 | + section_validator_t validator) { | ||
| 494 | 520 | mach_vm_address_t address = 0; | |
| 495 | 521 | mach_vm_size_t size = 0; | |
| 496 | 522 | mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); | |
@@ -542,7 +568,9 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s | |||
| 542 | 568 | if (strncmp(filename, substr, strlen(substr)) == 0) { | |
| 543 | 569 | uintptr_t result = search_section_in_file( | |
| 544 | 570 | secname, map_filename, address, size, proc_ref); | |
| 545 | - if (result != 0) { | ||
| 571 | + if (result != 0 | ||
| 572 | + && (validator == NULL || validator(handle, result))) | ||
| 573 | + { | ||
| 546 | 574 | return result; | |
| 547 | 575 | } | |
| 548 | 576 | } | |
@@ -659,7 +687,8 @@ search_elf_file_for_section( | |||
| 659 | 687 | } | |
| 660 | 688 | ||
| 661 | 689 | static uintptr_t | |
| 662 | - search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr) | ||
| 690 | + search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr, | ||
| 691 | + section_validator_t validator) | ||
| 663 | 692 | { | |
| 664 | 693 | char maps_file_path[64]; | |
| 665 | 694 | sprintf(maps_file_path, "/proc/%d/maps", handle->pid); | |
@@ -734,9 +763,12 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c | |||
| 734 | 763 | ||
| 735 | 764 | if (strstr(filename, substr)) { | |
| 736 | 765 | retval = search_elf_file_for_section(handle, secname, start, path); | |
| 737 | - if (retval) { | ||
| 766 | + if (retval | ||
| 767 | + && (validator == NULL || validator(handle, retval))) | ||
| 768 | + { | ||
| 738 | 769 | break; | |
| 739 | 770 | } | |
| 771 | + retval = 0; | ||
| 740 | 772 | } | |
| 741 | 773 | } | |
| 742 | 774 | ||
@@ -832,7 +864,8 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* | |||
| 832 | 864 | ||
| 833 | 865 | ||
| 834 | 866 | static uintptr_t | |
| 835 | - search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr) { | ||
| 867 | + search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr, | ||
| 868 | + section_validator_t validator) { | ||
| 836 | 869 | HANDLE hProcSnap; | |
| 837 | 870 | do { | |
| 838 | 871 | hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid); | |
@@ -855,8 +888,11 @@ search_windows_map_for_section(proc_handle_t* handle, const char* secname, const | |||
| 855 | 888 | for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule; hasModule = Module32NextW(hProcSnap, &moduleEntry)) { | |
| 856 | 889 | // Look for either python executable or DLL | |
| 857 | 890 | if (wcsstr(moduleEntry.szModule, substr)) { | |
| 858 | - runtime_addr = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname); | ||
| 859 | - if (runtime_addr != NULL) { | ||
| 891 | + void *candidate = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname); | ||
| 892 | + if (candidate != NULL | ||
| 893 | + && (validator == NULL || validator(handle, (uintptr_t)candidate))) | ||
| 894 | + { | ||
| 895 | + runtime_addr = candidate; | ||
| 860 | 896 | break; | |
| 861 | 897 | } | |
| 862 | 898 | } | |
@@ -877,7 +913,8 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) | |||
| 877 | 913 | ||
| 878 | 914 | #ifdef MS_WINDOWS | |
| 879 | 915 | // On Windows, search for 'python' in executable or DLL | |
| 880 | - address = search_windows_map_for_section(handle, "PyRuntime", L"python"); | ||
| 916 | + address = search_windows_map_for_section(handle, "PyRuntime", L"python", | ||
| 917 | + _Py_RemoteDebug_ValidatePyRuntimeCookie); | ||
| 881 | 918 | if (address == 0) { | |
| 882 | 919 | // Error out: 'python' substring covers both executable and DLL | |
| 883 | 920 | PyObject *exc = PyErr_GetRaisedException(); | |
@@ -888,7 +925,8 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) | |||
| 888 | 925 | } | |
| 889 | 926 | #elif defined(__linux__) | |
| 890 | 927 | // On Linux, search for 'python' in executable or DLL | |
| 891 | - address = search_linux_map_for_section(handle, "PyRuntime", "python"); | ||
| 928 | + address = search_linux_map_for_section(handle, "PyRuntime", "python", | ||
| 929 | + _Py_RemoteDebug_ValidatePyRuntimeCookie); | ||
| 892 | 930 | if (address == 0) { | |
| 893 | 931 | // Error out: 'python' substring covers both executable and DLL | |
| 894 | 932 | PyObject *exc = PyErr_GetRaisedException(); | |
@@ -902,7 +940,8 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) | |||
| 902 | 940 | const char* candidates[] = {"libpython", "python", "Python", NULL}; | |
| 903 | 941 | for (const char** candidate = candidates; *candidate; candidate++) { | |
| 904 | 942 | PyErr_Clear(); | |
| 905 | - address = search_map_for_section(handle, "PyRuntime", *candidate); | ||
| 943 | + address = search_map_for_section(handle, "PyRuntime", *candidate, | ||
| 944 | + _Py_RemoteDebug_ValidatePyRuntimeCookie); | ||
| 906 | 945 | if (address != 0) { | |
| 907 | 946 | break; | |
| 908 | 947 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments