Skip to content

Commit 14f0b51

Browse files
authored
gh-142419: Add mmap.set_name method for user custom annotation (gh-142480)
1 parent d2abd57 commit 14f0b51

File tree

11 files changed

+159
-14
lines changed

11 files changed

+159
-14
lines changed

Doc/library/mmap.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,17 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
328328

329329
.. versionadded:: 3.13
330330

331+
.. method:: set_name(name, /)
332+
333+
Annotate the memory mapping with the given *name* for easier identification
334+
in ``/proc/<pid>/maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed
335+
to Python or if Python is built in :ref:`debug mode <debug-build>`.
336+
The length of *name* must not exceed 67 bytes including the ``'\0'`` terminator.
337+
338+
.. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option)
339+
340+
.. versionadded:: next
341+
331342
.. method:: size()
332343

333344
Return the length of the file, which can be larger than the size of the

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,11 @@ mmap
592592
not be duplicated.
593593
(Contributed by Serhiy Storchaka in :gh:`78502`.)
594594

595+
* Added the :meth:`mmap.mmap.set_name` method
596+
to annotate an anonymous memory mapping
597+
if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
598+
(Contributed by Donghee Na in :gh:`142419`.)
599+
595600

596601
os
597602
--

Include/internal/pycore_mmap.h

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,27 @@ extern "C" {
1717
#endif
1818

1919
#if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
20-
static inline void
20+
static inline int
2121
_PyAnnotateMemoryMap(void *addr, size_t size, const char *name)
2222
{
2323
#ifndef Py_DEBUG
2424
if (!_Py_GetConfig()->dev_mode) {
25-
return;
25+
return 0;
2626
}
2727
#endif
28+
// The name length cannot exceed 80 (including the '\0').
2829
assert(strlen(name) < 80);
29-
int old_errno = errno;
30-
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
31-
/* Ignore errno from prctl */
32-
/* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */
33-
errno = old_errno;
30+
int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
31+
if (res < 0) {
32+
return -1;
33+
}
34+
return 0;
3435
}
3536
#else
36-
static inline void
37+
static inline int
3738
_PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name))
3839
{
40+
return 0;
3941
}
4042
#endif
4143

Lib/test/test_mmap.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from test.support.import_helper import import_module
77
from test.support.os_helper import TESTFN, unlink
88
from test.support.script_helper import assert_python_ok
9+
import errno
910
import unittest
1011
import os
1112
import re
@@ -1165,6 +1166,46 @@ def test_flush_parameters(self):
11651166
m.flush(PAGESIZE)
11661167
m.flush(PAGESIZE, PAGESIZE)
11671168

1169+
@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
1170+
@support.requires_linux_version(5, 17, 0)
1171+
def test_set_name(self):
1172+
# Test setting name on anonymous mmap
1173+
m = mmap.mmap(-1, PAGESIZE)
1174+
self.addCleanup(m.close)
1175+
try:
1176+
result = m.set_name('test_mapping')
1177+
except OSError as exc:
1178+
if exc.errno == errno.EINVAL:
1179+
# gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
1180+
# EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
1181+
# disabled.
1182+
# See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
1183+
self.skipTest("prctl() failed with EINVAL")
1184+
else:
1185+
raise
1186+
self.assertIsNone(result)
1187+
1188+
# Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
1189+
# Prefix is 13 chars, so max name is 66 chars
1190+
long_name = 'x' * 66
1191+
result = m.set_name(long_name)
1192+
self.assertIsNone(result)
1193+
1194+
# Test name too long
1195+
too_long_name = 'x' * 67
1196+
with self.assertRaises(ValueError):
1197+
m.set_name(too_long_name)
1198+
1199+
# Test that file-backed mmap raises error
1200+
with open(TESTFN, 'wb+') as f:
1201+
f.write(b'x' * PAGESIZE)
1202+
f.flush()
1203+
m2 = mmap.mmap(f.fileno(), PAGESIZE)
1204+
self.addCleanup(m2.close)
1205+
1206+
with self.assertRaises(ValueError):
1207+
m2.set_name('should_fail')
1208+
11681209

11691210
class LargeMmapTests(unittest.TestCase):
11701211

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`mmap.mmap.set_name` method added to annotate an anonymous memory map
2+
if Linux kernel supports ``PR_SET_VMA_ANON_NAME`` (Linux 5.17 or newer).
3+
Patch by Donghee Na.

Modules/clinic/mmapmodule.c.h

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/mmapmodule.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,47 @@ mmap_mmap_seek_impl(mmap_object *self, Py_ssize_t dist, int how)
11171117
return NULL;
11181118
}
11191119

1120+
/*[clinic input]
1121+
mmap.mmap.set_name
1122+
1123+
name: str
1124+
/
1125+
1126+
[clinic start generated code]*/
1127+
1128+
static PyObject *
1129+
mmap_mmap_set_name_impl(mmap_object *self, const char *name)
1130+
/*[clinic end generated code: output=1edaf4fd51277760 input=6c7dd91cad205f07]*/
1131+
{
1132+
#if defined(MAP_ANONYMOUS) && defined(__linux__)
1133+
const char *prefix = "cpython:mmap:";
1134+
if (strlen(name) + strlen(prefix) > 79) {
1135+
PyErr_SetString(PyExc_ValueError, "name is too long");
1136+
return NULL;
1137+
}
1138+
if (self->flags & MAP_ANONYMOUS) {
1139+
char buf[80];
1140+
sprintf(buf, "%s%s", prefix, name);
1141+
if (_PyAnnotateMemoryMap(self->data, self->size, buf) < 0) {
1142+
PyErr_SetFromErrno(PyExc_OSError);
1143+
return NULL;
1144+
}
1145+
Py_RETURN_NONE;
1146+
}
1147+
else {
1148+
/* cannot name non-anonymous mappings */
1149+
PyErr_SetString(PyExc_ValueError,
1150+
"Cannot set annotation on non-anonymous mappings");
1151+
return NULL;
1152+
}
1153+
#else
1154+
/* naming not supported on this platform */
1155+
PyErr_SetString(PyExc_NotImplementedError,
1156+
"Annotation of mmap is not supported on this platform");
1157+
return NULL;
1158+
#endif
1159+
}
1160+
11201161
/*[clinic input]
11211162
mmap.mmap.seekable
11221163
@@ -1397,6 +1438,7 @@ static struct PyMethodDef mmap_object_methods[] = {
13971438
MMAP_MMAP_RESIZE_METHODDEF
13981439
MMAP_MMAP_SEEK_METHODDEF
13991440
MMAP_MMAP_SEEKABLE_METHODDEF
1441+
MMAP_MMAP_SET_NAME_METHODDEF
14001442
MMAP_MMAP_SIZE_METHODDEF
14011443
MMAP_MMAP_TELL_METHODDEF
14021444
MMAP_MMAP_WRITE_METHODDEF
@@ -1952,7 +1994,11 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
19521994
PyErr_SetFromErrno(PyExc_OSError);
19531995
return NULL;
19541996
}
1955-
_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
1997+
#ifdef MAP_ANONYMOUS
1998+
if (m_obj->flags & MAP_ANONYMOUS) {
1999+
(void)_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
2000+
}
2001+
#endif
19562002
m_obj->access = (access_mode)access;
19572003
return (PyObject *)m_obj;
19582004
}

Objects/obmalloc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ _PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size)
468468
if (ptr == MAP_FAILED)
469469
return NULL;
470470
assert(ptr != NULL);
471-
_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
471+
(void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
472472
return ptr;
473473
#else
474474
return malloc(size);

Python/jit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jit_alloc(size_t size)
7777
unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0);
7878
int failed = memory == MAP_FAILED;
7979
if (!failed) {
80-
_PyAnnotateMemoryMap(memory, size, "cpython:jit");
80+
(void)_PyAnnotateMemoryMap(memory, size, "cpython:jit");
8181
}
8282
#endif
8383
if (failed) {

Python/perf_jit_trampoline.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,8 @@ static void* perf_map_jit_init(void) {
10861086
close(fd);
10871087
return NULL; // Memory mapping failed
10881088
}
1089-
_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, "cpython:perf_jit_trampoline");
1089+
(void)_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size,
1090+
"cpython:perf_jit_trampoline");
10901091
#endif
10911092

10921093
perf_jit_map_state.mapped_size = page_size;

0 commit comments

Comments
 (0)