Skip to content

Commit e8f9acf

Browse files
authored
bpo-36611: Disable serialno field of debug memory allocators (#12796)
Omit serialno field from debug hooks on Python memory allocators to reduce the memory footprint by 5%. Enable tracemalloc to get the traceback where a memory block has been allocated when a fatal memory error is logged to decide where to put a breakpoint. Compile Python with PYMEM_DEBUG_SERIALNO defined to get back the field.
1 parent 0fc91ee commit e8f9acf

File tree

3 files changed

+61
-21
lines changed

3 files changed

+61
-21
lines changed

Lib/test/test_capi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ def test_buffer_overflow(self):
483483
r" at tail\+1: 0xfd\n"
484484
r" at tail\+2: 0xfd\n"
485485
r" .*\n"
486-
r" The block was made by call #[0-9]+ to debug malloc/realloc.\n"
486+
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
487487
r" Data at p: cd cd cd .*\n"
488488
r"\n"
489489
r"Enable tracemalloc to get the memory block allocation traceback\n"
@@ -499,7 +499,7 @@ def test_api_misuse(self):
499499
r" 16 bytes originally requested\n"
500500
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
501501
r" The [0-9] pad bytes at tail={ptr} are FORBIDDENBYTE, as expected.\n"
502-
r" The block was made by call #[0-9]+ to debug malloc/realloc.\n"
502+
r"( The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
503503
r" Data at p: cd cd cd .*\n"
504504
r"\n"
505505
r"Enable tracemalloc to get the memory block allocation traceback\n"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Debug memory allocators: disable serialno field by default from debug hooks on
2+
Python memory allocators to reduce the memory footprint by 5%. Enable
3+
:mod:`tracemalloc` to get the traceback where a memory block has been allocated
4+
when a fatal memory error is logged to decide where to put a breakpoint.
5+
Compile Python with ``PYMEM_DEBUG_SERIALNO`` defined to get back the field.

Objects/obmalloc.c

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,10 @@ _Py_GetAllocatedBlocks(void)
19261926
#define DEADBYTE 0xDD /* dead (newly freed) memory */
19271927
#define FORBIDDENBYTE 0xFD /* untouchable bytes at each end of a block */
19281928

1929+
/* Uncomment this define to add the "serialno" field */
1930+
/* #define PYMEM_DEBUG_SERIALNO */
1931+
1932+
#ifdef PYMEM_DEBUG_SERIALNO
19291933
static size_t serialno = 0; /* incremented on each debug {m,re}alloc */
19301934

19311935
/* serialno is always incremented via calling this routine. The point is
@@ -1936,9 +1940,16 @@ bumpserialno(void)
19361940
{
19371941
++serialno;
19381942
}
1943+
#endif
19391944

19401945
#define SST SIZEOF_SIZE_T
19411946

1947+
#ifdef PYMEM_DEBUG_SERIALNO
1948+
# define PYMEM_DEBUG_EXTRA_BYTES 4 * SST
1949+
#else
1950+
# define PYMEM_DEBUG_EXTRA_BYTES 3 * SST
1951+
#endif
1952+
19421953
/* Read sizeof(size_t) bytes at p as a big-endian size_t. */
19431954
static size_t
19441955
read_size_t(const void *p)
@@ -1967,7 +1978,7 @@ write_size_t(void *p, size_t n)
19671978
}
19681979
}
19691980

1970-
/* Let S = sizeof(size_t). The debug malloc asks for 4*S extra bytes and
1981+
/* Let S = sizeof(size_t). The debug malloc asks for 4 * S extra bytes and
19711982
fills them with useful stuff, here calling the underlying malloc's result p:
19721983
19731984
p[0: S]
@@ -1991,6 +2002,9 @@ p[2*S+n+S: 2*S+n+2*S]
19912002
If "bad memory" is detected later, the serial number gives an
19922003
excellent way to set a breakpoint on the next run, to capture the
19932004
instant at which this block was passed out.
2005+
2006+
If PYMEM_DEBUG_SERIALNO is not defined (default), the debug malloc only asks
2007+
for 3 * S extra bytes, and omits the last serialno field.
19942008
*/
19952009

19962010
static void *
@@ -2000,21 +2014,24 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes)
20002014
uint8_t *p; /* base address of malloc'ed pad block */
20012015
uint8_t *data; /* p + 2*SST == pointer to data bytes */
20022016
uint8_t *tail; /* data + nbytes == pointer to tail pad bytes */
2003-
size_t total; /* 2 * SST + nbytes + 2 * SST */
2017+
size_t total; /* nbytes + PYMEM_DEBUG_EXTRA_BYTES */
20042018

2005-
if (nbytes > (size_t)PY_SSIZE_T_MAX - 4 * SST) {
2019+
if (nbytes > (size_t)PY_SSIZE_T_MAX - PYMEM_DEBUG_EXTRA_BYTES) {
20062020
/* integer overflow: can't represent total as a Py_ssize_t */
20072021
return NULL;
20082022
}
2009-
total = nbytes + 4 * SST;
2023+
total = nbytes + PYMEM_DEBUG_EXTRA_BYTES;
20102024

20112025
/* Layout: [SSSS IFFF CCCC...CCCC FFFF NNNN]
2012-
* ^--- p ^--- data ^--- tail
2026+
^--- p ^--- data ^--- tail
20132027
S: nbytes stored as size_t
20142028
I: API identifier (1 byte)
20152029
F: Forbidden bytes (size_t - 1 bytes before, size_t bytes after)
20162030
C: Clean bytes used later to store actual data
2017-
N: Serial number stored as size_t */
2031+
N: Serial number stored as size_t
2032+
2033+
If PYMEM_DEBUG_SERIALNO is not defined (default), the last NNNN field
2034+
is omitted. */
20182035

20192036
if (use_calloc) {
20202037
p = (uint8_t *)api->alloc.calloc(api->alloc.ctx, 1, total);
@@ -2027,7 +2044,9 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes)
20272044
}
20282045
data = p + 2*SST;
20292046

2047+
#ifdef PYMEM_DEBUG_SERIALNO
20302048
bumpserialno();
2049+
#endif
20312050

20322051
/* at p, write size (SST bytes), id (1 byte), pad (SST-1 bytes) */
20332052
write_size_t(p, nbytes);
@@ -2041,7 +2060,9 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes)
20412060
/* at tail, write pad (SST bytes) and serialno (SST bytes) */
20422061
tail = data + nbytes;
20432062
memset(tail, FORBIDDENBYTE, SST);
2063+
#ifdef PYMEM_DEBUG_SERIALNO
20442064
write_size_t(tail + SST, serialno);
2065+
#endif
20452066

20462067
return data;
20472068
}
@@ -2081,7 +2102,7 @@ _PyMem_DebugRawFree(void *ctx, void *p)
20812102

20822103
_PyMem_DebugCheckAddress(api->api_id, p);
20832104
nbytes = read_size_t(q);
2084-
nbytes += 4 * SST;
2105+
nbytes += PYMEM_DEBUG_EXTRA_BYTES;
20852106
memset(q, DEADBYTE, nbytes);
20862107
api->alloc.free(api->alloc.ctx, q);
20872108
}
@@ -2101,7 +2122,6 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
21012122
uint8_t *tail; /* data + nbytes == pointer to tail pad bytes */
21022123
size_t total; /* 2 * SST + nbytes + 2 * SST */
21032124
size_t original_nbytes;
2104-
size_t block_serialno;
21052125
#define ERASED_SIZE 64
21062126
uint8_t save[2*ERASED_SIZE]; /* A copy of erased bytes. */
21072127

@@ -2110,47 +2130,57 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
21102130
data = (uint8_t *)p;
21112131
head = data - 2*SST;
21122132
original_nbytes = read_size_t(head);
2113-
if (nbytes > (size_t)PY_SSIZE_T_MAX - 4*SST) {
2133+
if (nbytes > (size_t)PY_SSIZE_T_MAX - PYMEM_DEBUG_EXTRA_BYTES) {
21142134
/* integer overflow: can't represent total as a Py_ssize_t */
21152135
return NULL;
21162136
}
2117-
total = nbytes + 4*SST;
2137+
total = nbytes + PYMEM_DEBUG_EXTRA_BYTES;
21182138

21192139
tail = data + original_nbytes;
2120-
block_serialno = read_size_t(tail + SST);
2140+
#ifdef PYMEM_DEBUG_SERIALNO
2141+
size_t block_serialno = read_size_t(tail + SST);
2142+
#endif
21212143
/* Mark the header, the trailer, ERASED_SIZE bytes at the begin and
21222144
ERASED_SIZE bytes at the end as dead and save the copy of erased bytes.
21232145
*/
21242146
if (original_nbytes <= sizeof(save)) {
21252147
memcpy(save, data, original_nbytes);
2126-
memset(data - 2*SST, DEADBYTE, original_nbytes + 4*SST);
2148+
memset(data - 2 * SST, DEADBYTE,
2149+
original_nbytes + PYMEM_DEBUG_EXTRA_BYTES);
21272150
}
21282151
else {
21292152
memcpy(save, data, ERASED_SIZE);
2130-
memset(head, DEADBYTE, ERASED_SIZE + 2*SST);
2153+
memset(head, DEADBYTE, ERASED_SIZE + 2 * SST);
21312154
memcpy(&save[ERASED_SIZE], tail - ERASED_SIZE, ERASED_SIZE);
2132-
memset(tail - ERASED_SIZE, DEADBYTE, ERASED_SIZE + 2*SST);
2155+
memset(tail - ERASED_SIZE, DEADBYTE,
2156+
ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 * SST);
21332157
}
21342158

21352159
/* Resize and add decorations. */
21362160
r = (uint8_t *)api->alloc.realloc(api->alloc.ctx, head, total);
21372161
if (r == NULL) {
2162+
/* if realloc() failed: rewrite header and footer which have
2163+
just been erased */
21382164
nbytes = original_nbytes;
21392165
}
21402166
else {
21412167
head = r;
2168+
#ifdef PYMEM_DEBUG_SERIALNO
21422169
bumpserialno();
21432170
block_serialno = serialno;
2171+
#endif
21442172
}
2173+
data = head + 2*SST;
21452174

21462175
write_size_t(head, nbytes);
21472176
head[SST] = (uint8_t)api->api_id;
21482177
memset(head + SST + 1, FORBIDDENBYTE, SST-1);
2149-
data = head + 2*SST;
21502178

21512179
tail = data + nbytes;
21522180
memset(tail, FORBIDDENBYTE, SST);
2181+
#ifdef PYMEM_DEBUG_SERIALNO
21532182
write_size_t(tail + SST, block_serialno);
2183+
#endif
21542184

21552185
/* Restore saved bytes. */
21562186
if (original_nbytes <= sizeof(save)) {
@@ -2170,7 +2200,7 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
21702200
}
21712201

21722202
if (nbytes > original_nbytes) {
2173-
/* growing: mark new extra memory clean */
2203+
/* growing: mark new extra memory clean */
21742204
memset(data + original_nbytes, CLEANBYTE, nbytes - original_nbytes);
21752205
}
21762206

@@ -2278,7 +2308,7 @@ _PyObject_DebugDumpAddress(const void *p)
22782308
{
22792309
const uint8_t *q = (const uint8_t *)p;
22802310
const uint8_t *tail;
2281-
size_t nbytes, serial;
2311+
size_t nbytes;
22822312
int i;
22832313
int ok;
22842314
char id;
@@ -2347,9 +2377,11 @@ _PyObject_DebugDumpAddress(const void *p)
23472377
}
23482378
}
23492379

2350-
serial = read_size_t(tail + SST);
2380+
#ifdef PYMEM_DEBUG_SERIALNO
2381+
size_t serial = read_size_t(tail + SST);
23512382
fprintf(stderr, " The block was made by call #%" PY_FORMAT_SIZE_T
23522383
"u to debug malloc/realloc.\n", serial);
2384+
#endif
23532385

23542386
if (nbytes > 0) {
23552387
i = 0;
@@ -2575,8 +2607,11 @@ _PyObject_DebugMallocStats(FILE *out)
25752607
quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size);
25762608
}
25772609
fputc('\n', out);
2578-
if (_PyMem_DebugEnabled())
2610+
#ifdef PYMEM_DEBUG_SERIALNO
2611+
if (_PyMem_DebugEnabled()) {
25792612
(void)printone(out, "# times object malloc called", serialno);
2613+
}
2614+
#endif
25802615
(void)printone(out, "# arenas allocated total", ntimes_arena_allocated);
25812616
(void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas);
25822617
(void)printone(out, "# arenas highwater mark", narenas_highwater);

0 commit comments

Comments
 (0)