diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 27e3f23e47c875..e6435835046906 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1253,6 +1253,12 @@ Build changes modules that are missing or packaged separately. (Contributed by Stan Ulbrych and Petr Viktorin in :gh:`139707`.) +* Annotating anonymous mmap usage is now supported if Linux kernel supports + :manpage:`PR_SET_VMA_ANON_NAME ` (Linux 5.17 or newer). + Annotations are visible in ``/proc//maps`` if the kernel supports the feature + and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode `. + (Contributed by Donghee Na in :gh:`141770`) + Porting to Python 3.15 ====================== diff --git a/Include/internal/pycore_mmap.h b/Include/internal/pycore_mmap.h new file mode 100644 index 00000000000000..214fd4362a55fe --- /dev/null +++ b/Include/internal/pycore_mmap.h @@ -0,0 +1,45 @@ +#ifndef Py_INTERNAL_MMAP_H +#define Py_INTERNAL_MMAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#include "pycore_pystate.h" + +#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__) +# include +# include +#endif + +#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__) +static inline void +_PyAnnotateMemoryMap(void *addr, size_t size, const char *name) +{ +#ifndef Py_DEBUG + if (!_Py_GetConfig()->dev_mode) { + return; + } +#endif + assert(strlen(name) < 80); + int old_errno = errno; + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name); + /* Ignore errno from prctl */ + /* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */ + errno = old_errno; +} +#else +static inline void +_PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name)) +{ +} +#endif + +#ifdef __cplusplus +} +#endif +#endif // !Py_INTERNAL_MMAP_H diff --git a/Makefile.pre.in b/Makefile.pre.in index f3086ec1462b6b..2554114fff6d6c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1378,6 +1378,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_long.h \ $(srcdir)/Include/internal/pycore_memoryobject.h \ $(srcdir)/Include/internal/pycore_mimalloc.h \ + $(srcdir)/Include/internal/pycore_mmap.h \ $(srcdir)/Include/internal/pycore_modsupport.h \ $(srcdir)/Include/internal/pycore_moduleobject.h \ $(srcdir)/Include/internal/pycore_namespace.h \ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-18-14-28.gh-issue-141770.JURnvg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-18-14-28.gh-issue-141770.JURnvg.rst new file mode 100644 index 00000000000000..3a5c0fd70edb1e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-29-18-14-28.gh-issue-141770.JURnvg.rst @@ -0,0 +1,2 @@ +Annotate anonymous mmap usage only when supported by the +Linux kernel and if ``-X dev`` is used or Python is built in debug mode. Patch by Donghee Na. diff --git a/Modules/_ctypes/malloc_closure.c b/Modules/_ctypes/malloc_closure.c index db405acf8727b5..62c7aa5d6affbf 100644 --- a/Modules/_ctypes/malloc_closure.c +++ b/Modules/_ctypes/malloc_closure.c @@ -14,6 +14,7 @@ # endif #endif #include "ctypes.h" +#include "pycore_mmap.h" // _PyAnnotateMemoryMap() /* BLOCKSIZE can be adjusted. Larger blocksize will take a larger memory overhead, but allocate less blocks from the system. It may be that some @@ -74,14 +75,16 @@ static void more_core(void) if (item == NULL) return; #else + size_t mem_size = count * sizeof(ITEM); item = (ITEM *)mmap(NULL, - count * sizeof(ITEM), + mem_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (item == (void *)MAP_FAILED) return; + _PyAnnotateMemoryMap(item, mem_size, "cpython:ctypes"); #endif #ifdef MALLOC_CLOSURE_DEBUG diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index ac8521f8aa9b6e..37003020de2688 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -26,6 +26,7 @@ #include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t() #include "pycore_bytesobject.h" // _PyBytes_Find() #include "pycore_fileutils.h" // _Py_stat_struct +#include "pycore_mmap.h" // _PyAnnotateMemoryMap() #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() #include // offsetof() @@ -1951,6 +1952,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) PyErr_SetFromErrno(PyExc_OSError); return NULL; } + _PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap"); m_obj->access = (access_mode)access; return (PyObject *)m_obj; } diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 2b95ebbf8e5ac0..b1f9fa2e692265 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -2,6 +2,7 @@ #include "Python.h" #include "pycore_interp.h" // _PyInterpreterState_HasFeature +#include "pycore_mmap.h" // _PyAnnotateMemoryMap() #include "pycore_object.h" // _PyDebugAllocatorStats() definition #include "pycore_obmalloc.h" #include "pycore_obmalloc_init.h" @@ -467,6 +468,7 @@ _PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size) if (ptr == MAP_FAILED) return NULL; assert(ptr != NULL); + _PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc"); return ptr; #else return malloc(size); diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 85363949c2344f..dcfb75ce162b2f 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -277,6 +277,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 17999690990fb9..247f4b5a784f9c 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -735,6 +735,9 @@ Include\internal + + Include\internal + Include\internal diff --git a/Python/jit.c b/Python/jit.c index 47d3d7a5d27180..7106db8a99a77a 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -16,6 +16,7 @@ #include "pycore_intrinsics.h" #include "pycore_list.h" #include "pycore_long.h" +#include "pycore_mmap.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" #include "pycore_optimizer.h" @@ -75,6 +76,9 @@ jit_alloc(size_t size) int prot = PROT_READ | PROT_WRITE; unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0); int failed = memory == MAP_FAILED; + if (!failed) { + _PyAnnotateMemoryMap(memory, size, "cpython:jit"); + } #endif if (failed) { jit_error("unable to allocate memory"); diff --git a/Python/perf_jit_trampoline.c b/Python/perf_jit_trampoline.c index 8732be973616d4..af7d8f9f1ec0ae 100644 --- a/Python/perf_jit_trampoline.c +++ b/Python/perf_jit_trampoline.c @@ -61,6 +61,7 @@ #include "pycore_ceval.h" // _PyPerf_Callbacks #include "pycore_frame.h" #include "pycore_interp.h" +#include "pycore_mmap.h" // _PyAnnotateMemoryMap() #include "pycore_runtime.h" // _PyRuntime #ifdef PY_HAVE_PERF_TRAMPOLINE @@ -1085,6 +1086,7 @@ static void* perf_map_jit_init(void) { close(fd); return NULL; // Memory mapping failed } + _PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, "cpython:perf_jit_trampoline"); #endif perf_jit_map_state.mapped_size = page_size; diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 987e8d2a11a659..669a47ae17377a 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -132,6 +132,7 @@ any DWARF information available for them). #include "Python.h" #include "pycore_ceval.h" // _PyPerf_Callbacks #include "pycore_interpframe.h" // _PyFrame_GetCode() +#include "pycore_mmap.h" // _PyAnnotateMemoryMap() #include "pycore_runtime.h" // _PyRuntime @@ -290,6 +291,7 @@ new_code_arena(void) perf_status = PERF_STATUS_FAILED; return -1; } + _PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline"); void *start = &_Py_trampoline_func_start; void *end = &_Py_trampoline_func_end; size_t code_size = end - start; diff --git a/configure b/configure index 620878bb181378..066b0b4b30c4ea 100755 --- a/configure +++ b/configure @@ -23950,6 +23950,25 @@ printf "%s\n" "#define HAVE_UT_NAMESIZE 1" >>confdefs.h fi +ac_fn_check_decl "$LINENO" "PR_SET_VMA_ANON_NAME" "ac_cv_have_decl_PR_SET_VMA_ANON_NAME" "#include + #include +" "$ac_c_undeclared_builtin_options" "CFLAGS" +if test "x$ac_cv_have_decl_PR_SET_VMA_ANON_NAME" = xyes +then : + ac_have_decl=1 +else case e in #( + e) ac_have_decl=0 ;; +esac +fi +printf "%s\n" "#define HAVE_DECL_PR_SET_VMA_ANON_NAME $ac_have_decl" >>confdefs.h +if test $ac_have_decl = 1 +then : + +printf "%s\n" "#define HAVE_PR_SET_VMA_ANON_NAME 1" >>confdefs.h + +fi + + # check for openpty, login_tty, and forkpty diff --git a/configure.ac b/configure.ac index 8ef479fe32036c..6213cfa06cf1c6 100644 --- a/configure.ac +++ b/configure.ac @@ -5587,6 +5587,13 @@ AC_CHECK_DECLS([UT_NAMESIZE], [], [@%:@include ]) +AC_CHECK_DECLS([PR_SET_VMA_ANON_NAME], + [AC_DEFINE([HAVE_PR_SET_VMA_ANON_NAME], [1], + [Define if you have the 'PR_SET_VMA_ANON_NAME' constant.])], + [], + [@%:@include + @%:@include ]) + # check for openpty, login_tty, and forkpty AC_CHECK_FUNCS([openpty], [], diff --git a/pyconfig.h.in b/pyconfig.h.in index 8a9f5ca8ec826d..aabf9f0be8da55 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -228,6 +228,10 @@ /* Define to 1 if you have the header file. */ #undef HAVE_DB_H +/* Define to 1 if you have the declaration of 'PR_SET_VMA_ANON_NAME', and to 0 + if you don't. */ +#undef HAVE_DECL_PR_SET_VMA_ANON_NAME + /* Define to 1 if you have the declaration of 'RTLD_DEEPBIND', and to 0 if you don't. */ #undef HAVE_DECL_RTLD_DEEPBIND @@ -996,6 +1000,9 @@ /* Define if your compiler supports function prototype */ #undef HAVE_PROTOTYPES +/* Define if you have the 'PR_SET_VMA_ANON_NAME' constant. */ +#undef HAVE_PR_SET_VMA_ANON_NAME + /* Define to 1 if you have the 'pthread_condattr_setclock' function. */ #undef HAVE_PTHREAD_CONDATTR_SETCLOCK