[Python-checkins] r75104 - in python/trunk: Include/objimpl.h Include/pymem.h Objects/obmalloc.c

kristjan.jonsson python-checkins at python.org
Mon Sep 28 15:12:38 CEST 2009


Author: kristjan.jonsson
Date: Mon Sep 28 15:12:38 2009
New Revision: 75104

Log:
http://bugs.python.org/issue6836
The debug memory api now keeps track of which external API (PyMem_* or PyObject_*) was used to allocate each block and treats any API violation as an error.  Added separate _PyMem_DebugMalloc functions for the Py_Mem API instead of having it use the _PyObject_DebugMalloc functions.

Modified:
   python/trunk/Include/objimpl.h
   python/trunk/Include/pymem.h
   python/trunk/Objects/obmalloc.c

Modified: python/trunk/Include/objimpl.h
==============================================================================
--- python/trunk/Include/objimpl.h	(original)
+++ python/trunk/Include/objimpl.h	Mon Sep 28 15:12:38 2009
@@ -108,6 +108,13 @@
 PyAPI_FUNC(void) _PyObject_DebugDumpAddress(const void *p);
 PyAPI_FUNC(void) _PyObject_DebugCheckAddress(const void *p);
 PyAPI_FUNC(void) _PyObject_DebugMallocStats(void);
+PyAPI_FUNC(void *) _PyObject_DebugMallocApi(char api, size_t nbytes);
+PyAPI_FUNC(void *) _PyObject_DebugReallocApi(char api, void *p, size_t nbytes);
+PyAPI_FUNC(void) _PyObject_DebugFreeApi(char api, void *p);
+PyAPI_FUNC(void) _PyObject_DebugCheckAddressApi(char api, const void *p);
+PyAPI_FUNC(void *) _PyMem_DebugMalloc(size_t nbytes);
+PyAPI_FUNC(void *) _PyMem_DebugRealloc(void *p, size_t nbytes);
+PyAPI_FUNC(void) _PyMem_DebugFree(void *p);
 #define PyObject_MALLOC		_PyObject_DebugMalloc
 #define PyObject_Malloc		_PyObject_DebugMalloc
 #define PyObject_REALLOC	_PyObject_DebugRealloc

Modified: python/trunk/Include/pymem.h
==============================================================================
--- python/trunk/Include/pymem.h	(original)
+++ python/trunk/Include/pymem.h	Mon Sep 28 15:12:38 2009
@@ -59,9 +59,9 @@
 /* Macros. */
 #ifdef PYMALLOC_DEBUG
 /* Redirect all memory operations to Python's debugging allocator. */
-#define PyMem_MALLOC		PyObject_MALLOC
-#define PyMem_REALLOC		PyObject_REALLOC
-#define PyMem_FREE		PyObject_FREE
+#define PyMem_MALLOC		_PyMem_DebugMalloc
+#define PyMem_REALLOC		_PyMem_DebugRealloc
+#define PyMem_FREE		_PyMem_DebugFree
 
 #else	/* ! PYMALLOC_DEBUG */
 

Modified: python/trunk/Objects/obmalloc.c
==============================================================================
--- python/trunk/Objects/obmalloc.c	(original)
+++ python/trunk/Objects/obmalloc.c	Mon Sep 28 15:12:38 2009
@@ -1241,6 +1241,10 @@
 #define DEADBYTE       0xDB    /* dead (newly freed) memory */
 #define FORBIDDENBYTE  0xFB    /* untouchable bytes at each end of a block */
 
+/* We tag each block with an API ID in order to tag API violations */
+#define _PYMALLOC_MEM_ID 'm'   /* the PyMem_Malloc() API */
+#define _PYMALLOC_OBJ_ID 'o'   /* The PyObject_Malloc() API */
+
 static size_t serialno = 0;	/* incremented on each debug {m,re}alloc */
 
 /* serialno is always incremented via calling this routine.  The point is
@@ -1331,9 +1335,50 @@
     instant at which this block was passed out.
 */
 
+/* debug replacements for the PyMem_* memory API */
+void *
+_PyMem_DebugMalloc(size_t nbytes)
+{
+	return _PyObject_DebugMallocApi(_PYMALLOC_MEM_ID, nbytes);
+}
+void *
+_PyMem_DebugRealloc(void *p, size_t nbytes)
+{
+	return _PyObject_DebugReallocApi(_PYMALLOC_MEM_ID, p, nbytes);
+}
+void
+_PyMem_DebugFree(void *p)
+{
+	_PyObject_DebugFreeApi(_PYMALLOC_MEM_ID, p);
+}
+
+/* debug replacements for the PyObject_* memory API */
 void *
 _PyObject_DebugMalloc(size_t nbytes)
 {
+	return _PyObject_DebugMallocApi(_PYMALLOC_OBJ_ID, nbytes);
+}
+void *
+_PyObject_DebugRealloc(void *p, size_t nbytes)
+{
+	return _PyObject_DebugReallocApi(_PYMALLOC_OBJ_ID, p, nbytes);
+}
+void
+_PyObject_DebugFree(void *p)
+{
+	_PyObject_DebugFreeApi(_PYMALLOC_OBJ_ID, p);
+}
+void
+_PyObject_DebugCheckAddress(void *p)
+{
+	_PyObject_DebugCheckAddressApi(_PYMALLOC_OBJ_ID, p);
+}
+
+
+/* generic debug memory api, with an "id" to identify the API in use */
+void *
+_PyObject_DebugMallocApi(char id, size_t nbytes)
+{
 	uchar *p;	/* base address of malloc'ed block */
 	uchar *tail;	/* p + 2*SST + nbytes == pointer to tail pad bytes */
 	size_t total;	/* nbytes + 4*SST */
@@ -1348,12 +1393,15 @@
 	if (p == NULL)
 		return NULL;
 
+	/* at p, write size (SST bytes), id (1 byte), pad (SST-1 bytes) */
 	write_size_t(p, nbytes);
-	memset(p + SST, FORBIDDENBYTE, SST);
+	p[SST] = (uchar)id;
+	memset(p + SST + 1 , FORBIDDENBYTE, SST-1);
 
 	if (nbytes > 0)
 		memset(p + 2*SST, CLEANBYTE, nbytes);
 
+	/* at tail, write pad (SST bytes) and serialno (SST bytes) */
 	tail = p + 2*SST + nbytes;
 	memset(tail, FORBIDDENBYTE, SST);
 	write_size_t(tail + SST, serialno);
@@ -1362,27 +1410,28 @@
 }
 
 /* The debug free first checks the 2*SST bytes on each end for sanity (in
-   particular, that the FORBIDDENBYTEs are still intact).
+   particular, that the FORBIDDENBYTEs with the api ID are still intact).
    Then fills the original bytes with DEADBYTE.
    Then calls the underlying free.
 */
 void
-_PyObject_DebugFree(void *p)
+_PyObject_DebugFreeApi(char api, void *p)
 {
 	uchar *q = (uchar *)p - 2*SST;  /* address returned from malloc */
 	size_t nbytes;
 
 	if (p == NULL)
 		return;
-	_PyObject_DebugCheckAddress(p);
+	_PyObject_DebugCheckAddressApi(api, p);
 	nbytes = read_size_t(q);
+	nbytes += 4*SST;
 	if (nbytes > 0)
 		memset(q, DEADBYTE, nbytes);
 	PyObject_Free(q);
 }
 
 void *
-_PyObject_DebugRealloc(void *p, size_t nbytes)
+_PyObject_DebugReallocApi(char api, void *p, size_t nbytes)
 {
 	uchar *q = (uchar *)p;
 	uchar *tail;
@@ -1391,9 +1440,9 @@
 	int i;
 
 	if (p == NULL)
-		return _PyObject_DebugMalloc(nbytes);
+		return _PyObject_DebugMallocApi(api, nbytes);
 
-	_PyObject_DebugCheckAddress(p);
+	_PyObject_DebugCheckAddressApi(api, p);
 	bumpserialno();
 	original_nbytes = read_size_t(q - 2*SST);
 	total = nbytes + 4*SST;
@@ -1403,16 +1452,20 @@
 
 	if (nbytes < original_nbytes) {
 		/* shrinking:  mark old extra memory dead */
-		memset(q + nbytes, DEADBYTE, original_nbytes - nbytes);
+		memset(q + nbytes, DEADBYTE, original_nbytes - nbytes + 2*SST);
 	}
 
-	/* Resize and add decorations. */
+	/* Resize and add decorations. We may get a new pointer here, in which
+	 * case we didn't get the chance to mark the old memory with DEADBYTE,
+	 * but we live with that.
+	 */
 	q = (uchar *)PyObject_Realloc(q - 2*SST, total);
 	if (q == NULL)
 		return NULL;
 
 	write_size_t(q, nbytes);
-	for (i = 0; i < SST; ++i)
+	assert(q[SST] == (uchar)api);
+	for (i = 1; i < SST; ++i)
 		assert(q[SST + i] == FORBIDDENBYTE);
 	q += 2*SST;
 	tail = q + nbytes;
@@ -1431,26 +1484,38 @@
 /* Check the forbidden bytes on both ends of the memory allocated for p.
  * If anything is wrong, print info to stderr via _PyObject_DebugDumpAddress,
  * and call Py_FatalError to kill the program.
+ * The API id, is also checked.
  */
  void
-_PyObject_DebugCheckAddress(const void *p)
+_PyObject_DebugCheckAddressApi(char api, const void *p)
 {
 	const uchar *q = (const uchar *)p;
+	char msgbuf[64];
 	char *msg;
 	size_t nbytes;
 	const uchar *tail;
 	int i;
+	char id;
 
 	if (p == NULL) {
 		msg = "didn't expect a NULL pointer";
 		goto error;
 	}
 
+	/* Check the API id */
+	id = (char)q[-SST];
+	if (id != api) {
+		msg = msgbuf;
+		snprintf(msg, sizeof(msgbuf), "bad ID: Allocated using API '%c', verified using API '%c'", id, api);
+		msgbuf[sizeof(msgbuf)-1] = 0;
+		goto error;
+	}
+
 	/* Check the stuff at the start of p first:  if there's underwrite
 	 * corruption, the number-of-bytes field may be nuts, and checking
 	 * the tail could lead to a segfault then.
 	 */
-	for (i = SST; i >= 1; --i) {
+	for (i = SST-1; i >= 1; --i) {
 		if (*(q-i) != FORBIDDENBYTE) {
 			msg = "bad leading pad byte";
 			goto error;
@@ -1482,19 +1547,24 @@
 	size_t nbytes, serial;
 	int i;
 	int ok;
+	char id;
 
-	fprintf(stderr, "Debug memory block at address p=%p:\n", p);
-	if (p == NULL)
+	fprintf(stderr, "Debug memory block at address p=%p:", p);
+	if (p == NULL) {
+		fprintf(stderr, "\n");
 		return;
+	}
+	id = (char)q[-SST];
+	fprintf(stderr, " API '%c'\n", id);
 
 	nbytes = read_size_t(q - 2*SST);
 	fprintf(stderr, "    %" PY_FORMAT_SIZE_T "u bytes originally "
 	                "requested\n", nbytes);
 
 	/* In case this is nuts, check the leading pad bytes first. */
-	fprintf(stderr, "    The %d pad bytes at p-%d are ", SST, SST);
+	fprintf(stderr, "    The %d pad bytes at p-%d are ", SST-1, SST-1);
 	ok = 1;
-	for (i = 1; i <= SST; ++i) {
+	for (i = 1; i <= SST-1; ++i) {
 		if (*(q-i) != FORBIDDENBYTE) {
 			ok = 0;
 			break;
@@ -1505,7 +1575,7 @@
 	else {
 		fprintf(stderr, "not all FORBIDDENBYTE (0x%02x):\n",
 			FORBIDDENBYTE);
-		for (i = SST; i >= 1; --i) {
+		for (i = SST-1; i >= 1; --i) {
 			const uchar byte = *(q-i);
 			fprintf(stderr, "        at p-%d: 0x%02x", i, byte);
 			if (byte != FORBIDDENBYTE)


More information about the Python-checkins mailing list