.. versionadded:: 3.13
+ .. method:: set_name(name, /)
+
+ Annotate the memory mapping with the given *name* for easier identification
+ in ``/proc/<pid>/maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed
+ to Python or if Python is built in :ref:`debug mode <debug-build>`.
+ The length of *name* must not exceed 67 bytes including the ``'\0'`` terminator.
+
+ .. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option)
+
+ .. versionadded:: next
+
.. method:: size()
Return the length of the file, which can be larger than the size of the
not be duplicated.
(Contributed by Serhiy Storchaka in :gh:`78502`.)
+* Added the :meth:`mmap.mmap.set_name` method
+ to annotate an anonymous memory mapping
+ if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
+ (Contributed by Donghee Na in :gh:`142419`.)
+
os
--
#endif
#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
-static inline void
+static inline int
_PyAnnotateMemoryMap(void *addr, size_t size, const char *name)
{
#ifndef Py_DEBUG
if (!_Py_GetConfig()->dev_mode) {
- return;
+ return 0;
}
#endif
+ // The name length cannot exceed 80 (including the '\0').
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;
+ int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
}
#else
-static inline void
+static inline int
_PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name))
{
+ return 0;
}
#endif
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok
+import errno
import unittest
import os
import re
m.flush(PAGESIZE)
m.flush(PAGESIZE, PAGESIZE)
+ @unittest.skipUnless(sys.platform == 'linux', 'Linux only')
+ @support.requires_linux_version(5, 17, 0)
+ def test_set_name(self):
+ # Test setting name on anonymous mmap
+ m = mmap.mmap(-1, PAGESIZE)
+ self.addCleanup(m.close)
+ try:
+ result = m.set_name('test_mapping')
+ except OSError as exc:
+ if exc.errno == errno.EINVAL:
+ # gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
+ # EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
+ # disabled.
+ # See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
+ self.skipTest("prctl() failed with EINVAL")
+ else:
+ raise
+ self.assertIsNone(result)
+
+ # Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
+ # Prefix is 13 chars, so max name is 66 chars
+ long_name = 'x' * 66
+ result = m.set_name(long_name)
+ self.assertIsNone(result)
+
+ # Test name too long
+ too_long_name = 'x' * 67
+ with self.assertRaises(ValueError):
+ m.set_name(too_long_name)
+
+ # Test that file-backed mmap raises error
+ with open(TESTFN, 'wb+') as f:
+ f.write(b'x' * PAGESIZE)
+ f.flush()
+ m2 = mmap.mmap(f.fileno(), PAGESIZE)
+ self.addCleanup(m2.close)
+
+ with self.assertRaises(ValueError):
+ m2.set_name('should_fail')
+
class LargeMmapTests(unittest.TestCase):
--- /dev/null
+:meth:`mmap.mmap.set_name` method added to annotate an anonymous memory map
+if Linux kernel supports ``PR_SET_VMA_ANON_NAME`` (Linux 5.17 or newer).
+Patch by Donghee Na.
return return_value;
}
+PyDoc_STRVAR(mmap_mmap_set_name__doc__,
+"set_name($self, name, /)\n"
+"--\n"
+"\n");
+
+#define MMAP_MMAP_SET_NAME_METHODDEF \
+ {"set_name", (PyCFunction)mmap_mmap_set_name, METH_O, mmap_mmap_set_name__doc__},
+
+static PyObject *
+mmap_mmap_set_name_impl(mmap_object *self, const char *name);
+
+static PyObject *
+mmap_mmap_set_name(PyObject *self, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ const char *name;
+
+ if (!PyUnicode_Check(arg)) {
+ _PyArg_BadArgument("set_name", "argument", "str", arg);
+ goto exit;
+ }
+ Py_ssize_t name_length;
+ name = PyUnicode_AsUTF8AndSize(arg, &name_length);
+ if (name == NULL) {
+ goto exit;
+ }
+ if (strlen(name) != (size_t)name_length) {
+ PyErr_SetString(PyExc_ValueError, "embedded null character");
+ goto exit;
+ }
+ return_value = mmap_mmap_set_name_impl((mmap_object *)self, name);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(mmap_mmap_seekable__doc__,
"seekable($self, /)\n"
"--\n"
#ifndef MMAP_MMAP_MADVISE_METHODDEF
#define MMAP_MMAP_MADVISE_METHODDEF
#endif /* !defined(MMAP_MMAP_MADVISE_METHODDEF) */
-/*[clinic end generated code: output=381f6cf4986ac867 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=fd9ca0ef425af934 input=a9049054013a1b77]*/
return NULL;
}
+/*[clinic input]
+mmap.mmap.set_name
+
+ name: str
+ /
+
+[clinic start generated code]*/
+
+static PyObject *
+mmap_mmap_set_name_impl(mmap_object *self, const char *name)
+/*[clinic end generated code: output=1edaf4fd51277760 input=6c7dd91cad205f07]*/
+{
+#if defined(MAP_ANONYMOUS) && defined(__linux__)
+ const char *prefix = "cpython:mmap:";
+ if (strlen(name) + strlen(prefix) > 79) {
+ PyErr_SetString(PyExc_ValueError, "name is too long");
+ return NULL;
+ }
+ if (self->flags & MAP_ANONYMOUS) {
+ char buf[80];
+ sprintf(buf, "%s%s", prefix, name);
+ if (_PyAnnotateMemoryMap(self->data, self->size, buf) < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+ }
+ else {
+ /* cannot name non-anonymous mappings */
+ PyErr_SetString(PyExc_ValueError,
+ "Cannot set annotation on non-anonymous mappings");
+ return NULL;
+ }
+#else
+ /* naming not supported on this platform */
+ PyErr_SetString(PyExc_NotImplementedError,
+ "Annotation of mmap is not supported on this platform");
+ return NULL;
+#endif
+}
+
/*[clinic input]
mmap.mmap.seekable
MMAP_MMAP_RESIZE_METHODDEF
MMAP_MMAP_SEEK_METHODDEF
MMAP_MMAP_SEEKABLE_METHODDEF
+ MMAP_MMAP_SET_NAME_METHODDEF
MMAP_MMAP_SIZE_METHODDEF
MMAP_MMAP_TELL_METHODDEF
MMAP_MMAP_WRITE_METHODDEF
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
- _PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
+#ifdef MAP_ANONYMOUS
+ if (m_obj->flags & MAP_ANONYMOUS) {
+ (void)_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
+ }
+#endif
m_obj->access = (access_mode)access;
return (PyObject *)m_obj;
}
if (ptr == MAP_FAILED)
return NULL;
assert(ptr != NULL);
- _PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
+ (void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
return ptr;
#else
return malloc(size);
unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0);
int failed = memory == MAP_FAILED;
if (!failed) {
- _PyAnnotateMemoryMap(memory, size, "cpython:jit");
+ (void)_PyAnnotateMemoryMap(memory, size, "cpython:jit");
}
#endif
if (failed) {
close(fd);
return NULL; // Memory mapping failed
}
- _PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, "cpython:perf_jit_trampoline");
+ (void)_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size,
+ "cpython:perf_jit_trampoline");
#endif
perf_jit_map_state.mapped_size = page_size;
perf_status = PERF_STATUS_FAILED;
return -1;
}
- _PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline");
+ (void)_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;