diff options
author | Robin Watts <robin.watts@artifex.com> | 2011-08-09 15:55:30 +0700 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2011-09-14 17:41:30 +0100 |
commit | cefb81f1886685580a40b17b5e495a8a8a1ebeaf (patch) | |
tree | 737c5f342474cc20464e9ae72a15af69e6bd6f4e | |
parent | 32ee8f594db8c6221db2c42949e0ee58cc47c176 (diff) | |
download | mupdf-cefb81f1886685580a40b17b5e495a8a8a1ebeaf.tar.xz |
Add Memento build option.
Import memento files. Update Makefile to support: make build=memento
(like debug, but with memento too).
Tiny tweak to cmapdump to avoid memento building that utility.
-rw-r--r-- | Makerules | 2 | ||||
-rw-r--r-- | fitz/fitz.h | 2 | ||||
-rw-r--r-- | fitz/memento.c | 1040 | ||||
-rw-r--r-- | fitz/memento.h | 213 | ||||
-rw-r--r-- | scripts/cmapdump.c | 3 |
5 files changed, 1260 insertions, 0 deletions
@@ -14,6 +14,8 @@ else ifeq "$(build)" "release" CFLAGS += -pipe -O2 -DNDEBUG -fomit-frame-pointer else ifeq "$(build)" "native" CFLAGS += -pipe -O2 -DNDEBUG -fomit-frame-pointer -march=native -mfpmath=sse +else ifeq "$(build)" "memento" +CFLAGS += -pipe -g -DMEMENTO else $(error unknown build setting: '$(build)') endif diff --git a/fitz/fitz.h b/fitz/fitz.h index dff6b8d4..30b037e8 100644 --- a/fitz/fitz.h +++ b/fitz/fitz.h @@ -18,6 +18,8 @@ #include <float.h> /* FLT_EPSILON */ #include <fcntl.h> /* O_RDONLY & co */ +#include "memento.h" + #define nelem(x) (sizeof(x)/sizeof((x)[0])) #define ABS(x) ( (x) < 0 ? -(x) : (x) ) diff --git a/fitz/memento.c b/fitz/memento.c new file mode 100644 index 00000000..638bb2b2 --- /dev/null +++ b/fitz/memento.c @@ -0,0 +1,1040 @@ +/* Copyright (C) 2011 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, modified + or distributed except as expressly authorized under the terms of that + license. Refer to licensing information at http://www.artifex.com/ + or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134, + San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information. +*/ + +/* Inspired by Fortify by Simon P Bullen. */ + +/* Set the following if you're only looking for leaks, not memory overwrites + * to speed the operation */ +#undef MEMENTO_LEAKONLY + +#ifndef MEMENTO_STACKTRACE_METHOD +#ifdef __GNUC__ +#define MEMENTO_STACKTRACE_METHOD 1 +#endif +#endif + +/* Don't keep blocks around if they'd mean losing more than a quarter of + * the freelist. */ +#define MEMENTO_FREELIST_MAX_SINGLE_BLOCK (MEMENTO_FREELIST_MAX/4) + +#define COMPILING_MEMENTO_C +#include "memento.h" +#include <stdio.h> + +#if defined(__linux__) +#define MEMENTO_HAS_FORK +#elif defined(__APPLE__) && defined(__MACH__) +#define MEMENTO_HAS_FORK +#endif + +/* Define the underlying allocators, just in case */ +void *MEMENTO_UNDERLYING_MALLOC(size_t); +void MEMENTO_UNDERLYING_FREE(void *); +void *MEMENTO_UNDERLYING_REALLOC(void *,size_t); +void *MEMENTO_UNDERLYING_CALLOC(size_t,size_t); + +/* And some other standard functions we use. We don't include the header + * files, just in case they pull in unexpected others. */ +int atoi(const char *); +char *getenv(const char *); +//void *memset(void *,int,size_t); +//int atexit(void (*)(void)); + +#ifdef MEMENTO + +#include "valgrind/memcheck.h" + +enum { + Memento_PreSize = 16, + Memento_PostSize = 16 +}; + +typedef struct Memento_BlkHeader Memento_BlkHeader; + +struct Memento_BlkHeader +{ + size_t rawsize; + int sequence; + int lastCheckedOK; + Memento_BlkHeader *next; + char preblk[Memento_PreSize]; +}; + +/* In future this could (should) be a smarter data structure, like, say, + * splay trees. For now, we use a list. + */ +typedef struct Memento_Blocks +{ + Memento_BlkHeader *head; + Memento_BlkHeader **tail; +} Memento_Blocks; + +/* And our global structure */ +static struct { + int inited; + Memento_Blocks used; + Memento_Blocks free; + size_t freeListSize; + int sequence; + int paranoia; + int paranoidAt; + int countdown; + int lastChecked; + int breakAt; + int failAt; + int failing; + int squeezeAt; + int squeezing; + int squeezed; + size_t alloc; + size_t peakAlloc; + size_t totalAlloc; + size_t numMallocs; + size_t numFrees; + size_t numReallocs; +} globals; + + +#define MEMENTO_EXTRASIZE (sizeof(Memento_BlkHeader) + Memento_PostSize) + +/* Round up size S to the next multiple of N (where N is a power of 2) */ +#define MEMENTO_ROUNDUP(S,N) ((S + N-1)&~(N-1)) + +#define MEMBLK_SIZE(s) MEMENTO_ROUNDUP(s + MEMENTO_EXTRASIZE, MEMENTO_MAXALIGN) + +#define MEMBLK_FROMBLK(B) (&((Memento_BlkHeader*)(void *)(B))[-1]) +#define MEMBLK_TOBLK(B) ((void*)(&((Memento_BlkHeader*)(void*)(B))[1])) +#define MEMBLK_POSTPTR(B) \ + (&((char *)(void *)(B))[(B)->rawsize + sizeof(Memento_BlkHeader)]) + +void Memento_breakpoint(void) +{ + /* A handy externally visible function for breakpointing */ +#if 0 /* Enable this to force automatic breakpointing */ +#ifdef DEBUG +#ifdef _MSC_VER + __asm int 3; +#endif +#endif +#endif +} + +static void Memento_addBlockHead(Memento_Blocks *blks, + Memento_BlkHeader *b, + int type) +{ + if (blks->tail == &blks->head) { + /* Adding into an empty list, means the tail changes too */ + blks->tail = &b->next; + } + b->next = blks->head; + blks->head = b; +#ifndef MEMENTO_LEAKONLY + memset(b->preblk, MEMENTO_PREFILL, Memento_PreSize); + memset(MEMBLK_POSTPTR(b), MEMENTO_POSTFILL, Memento_PostSize); +#endif + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(b), Memento_PostSize); + if (type == 0) { /* malloc */ + VALGRIND_MAKE_MEM_UNDEFINED(MEMBLK_TOBLK(b), b->rawsize); + } else if (type == 1) { /* free */ + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_TOBLK(b), b->rawsize); + } + VALGRIND_MAKE_MEM_NOACCESS(b, sizeof(Memento_BlkHeader)); +} + +static void Memento_addBlockTail(Memento_Blocks *blks, + Memento_BlkHeader *b, + int type) +{ + VALGRIND_MAKE_MEM_DEFINED(blks->tail, sizeof(Memento_BlkHeader *)); + *blks->tail = b; + blks->tail = &b->next; + b->next = NULL; + VALGRIND_MAKE_MEM_NOACCESS(blks->tail, sizeof(Memento_BlkHeader *)); +#ifndef MEMENTO_LEAKONLY + memset(b->preblk, MEMENTO_PREFILL, Memento_PreSize); + memset(MEMBLK_POSTPTR(b), MEMENTO_POSTFILL, Memento_PostSize); +#endif + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(b), Memento_PostSize); + if (type == 0) { /* malloc */ + VALGRIND_MAKE_MEM_UNDEFINED(MEMBLK_TOBLK(b), b->rawsize); + } else if (type == 1) { /* free */ + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_TOBLK(b), b->rawsize); + } + VALGRIND_MAKE_MEM_NOACCESS(b, sizeof(Memento_BlkHeader)); +} + +typedef struct BlkCheckData { + int found; + int preCorrupt; + int postCorrupt; + int freeCorrupt; + int index; +} BlkCheckData; + +static int Memento_Internal_checkAllocedBlock(Memento_BlkHeader *b, void *arg) +{ +#ifndef MEMENTO_LEAKONLY + int i; + char *p; + int corrupt = 0; + BlkCheckData *data = (BlkCheckData *)arg; + + p = b->preblk; + i = Memento_PreSize; + do { + corrupt |= (*p++ ^ (char)MEMENTO_PREFILL); + } while (--i); + if (corrupt) { + data->preCorrupt = 1; + } + p = MEMBLK_POSTPTR(b); + i = Memento_PreSize; + do { + corrupt |= (*p++ ^ (char)MEMENTO_POSTFILL); + } while (--i); + if (corrupt) { + data->postCorrupt = 1; + } + if ((data->freeCorrupt | data->preCorrupt | data->postCorrupt) == 0) { + b->lastCheckedOK = globals.sequence; + } + data->found |= 1; +#endif + return 0; +} + +static int Memento_Internal_checkFreedBlock(Memento_BlkHeader *b, void *arg) +{ +#ifndef MEMENTO_LEAKONLY + int i; + char *p; + BlkCheckData *data = (BlkCheckData *)arg; + + p = MEMBLK_TOBLK(b); + i = b->rawsize; + /* Attempt to speed this up by checking an (aligned) int at a time */ + do { + if (((size_t)p) & 1) { + if (*p++ != (char)MEMENTO_FREEFILL) + break; + i--; + if (i == 0) + break; + } + if ((i >= 2) && (((size_t)p) & 2)) { + if (*(short *)p != (short)(MEMENTO_FREEFILL | (MEMENTO_FREEFILL<<8))) + goto mismatch; + p += 2; + i -= 2; + if (i == 0) + break; + } + i -= 4; + while (i >= 0) { + if (*(int *)p != (MEMENTO_FREEFILL | + (MEMENTO_FREEFILL<<8) | + (MEMENTO_FREEFILL<<16) | + (MEMENTO_FREEFILL<<24))) + goto mismatch; + p += 4; + i -= 4; + } + i += 4; + if ((i >= 2) && (((size_t)p) & 2)) { + if (*(short *)p != (short)(MEMENTO_FREEFILL | (MEMENTO_FREEFILL<<8))) + goto mismatch; + p += 2; + i -= 2; + } +mismatch: + while (i) { + if (*p++ != (char)MEMENTO_FREEFILL) + break; + i--; + } + } while (0); + if (i) { + data->freeCorrupt = 1; + data->index = b->rawsize-i; + } + return Memento_Internal_checkAllocedBlock(b, arg); +#else + return 0; +#endif +} + +static void Memento_removeBlock(Memento_Blocks *blks, + Memento_BlkHeader *b) +{ + Memento_BlkHeader *head = blks->head; + Memento_BlkHeader *prev = NULL; + while ((head) && (head != b)) { + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head)); + prev = head; + head = head->next; + VALGRIND_MAKE_MEM_NOACCESS(prev, sizeof(*prev)); + } + if (head == NULL) { + /* FAIL! Will have been reported to user earlier, so just exit. */ + return; + } + if (*blks->tail == head) { + /* Removing the tail of the list */ + if (prev == NULL) { + /* Which is also the head */ + blks->tail = &blks->head; + } else { + /* Which isn't the head */ + blks->tail = &prev->next; + } + } + if (prev == NULL) { + /* Removing from the head of the list */ + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head)); + blks->head = head->next; + VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(*head)); + } else { + /* Removing from not-the-head */ + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head)); + VALGRIND_MAKE_MEM_DEFINED(prev, sizeof(*prev)); + prev->next = head->next; + VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(*head)); + VALGRIND_MAKE_MEM_NOACCESS(prev, sizeof(*prev)); + } +} + +static int Memento_Internal_makeSpace(size_t space) +{ + /* If too big, it can never go on the freelist */ + if (space > MEMENTO_FREELIST_MAX_SINGLE_BLOCK) + return 0; + /* Pretend we added it on. */ + globals.freeListSize += space; + /* Ditch blocks until it fits within our limit */ + while (globals.freeListSize > MEMENTO_FREELIST_MAX) { + Memento_BlkHeader *head = globals.free.head; + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head)); + globals.free.head = head->next; + globals.freeListSize -= MEMBLK_SIZE(head->rawsize); + MEMENTO_UNDERLYING_FREE(head); + } + /* Make sure we haven't just completely emptied the free list */ + /* (This should never happen, but belt and braces... */ + if (globals.free.head == NULL) + globals.free.tail = &globals.free.head; + return 1; +} + +static int Memento_appBlocks(Memento_Blocks *blks, + int (*app)(Memento_BlkHeader *, + void *), + void *arg) +{ + Memento_BlkHeader *head = blks->head; + Memento_BlkHeader *next; + int result; + while (head) { + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader)); + VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(head), + head->rawsize + Memento_PostSize); + result = app(head, arg); + next = head->next; + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize); + VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(Memento_BlkHeader)); + if (result) + return result; + head = next; + } + return 0; +} + +static int Memento_appBlock(Memento_Blocks *blks, + int (*app)(Memento_BlkHeader *, + void *), + void *arg, + Memento_BlkHeader *b) +{ + Memento_BlkHeader *head = blks->head; + Memento_BlkHeader *next; + int result; + while (head && head != b) { + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader)); + next = head->next; + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize); + head = next; + } + if (head == b) { + VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader)); + VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(head), + head->rawsize + Memento_PostSize); + result = app(head, arg); + VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize); + VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(Memento_BlkHeader)); + return result; + } + return 0; +} + +static int Memento_listBlock(Memento_BlkHeader *b, + void *arg) +{ + int *counts = (int *)arg; + fprintf(stderr, " 0x%p:(size=%d,num=%d)\n", + MEMBLK_TOBLK(b), (int)b->rawsize, b->sequence); + counts[0]++; + counts[1]+= b->rawsize; + return 0; +} + +static void Memento_listBlocks(void) { + int counts[2]; + counts[0] = 0; + counts[1] = 0; + fprintf(stderr, "Allocated blocks:\n"); + Memento_appBlocks(&globals.used, Memento_listBlock, &counts[0]); + fprintf(stderr, " Total number of blocks = %d\n", counts[0]); + fprintf(stderr, " Total size of blocks = %d\n", counts[1]); +} + +static void Memento_fin(void) +{ + Memento_checkAllMemory(); + fprintf(stderr, "Total memory malloced = %d bytes\n", globals.totalAlloc); + fprintf(stderr, "Peak memory malloced = %d bytes\n", globals.peakAlloc); + fprintf(stderr, "%d mallocs, %d frees, %d reallocs\n", globals.numMallocs, + globals.numFrees, globals.numReallocs); + fprintf(stderr, "Average allocation size %d bytes\n", + globals.totalAlloc/globals.numMallocs); + if (globals.used.head != NULL) { + Memento_listBlocks(); + Memento_breakpoint(); + } + if (globals.squeezed) { + fprintf(stderr, "Memory squeezing @ %d complete\n", globals.squeezed); + } +} + +static void Memento_inited(void) +{ + /* A good place for a breakpoint */ +} + +static void Memento_init(void) +{ + char *env; + memset(&globals, 0, sizeof(globals)); + globals.inited = 1; + globals.used.head = NULL; + globals.used.tail = &globals.used.head; + globals.free.head = NULL; + globals.free.tail = &globals.free.head; + globals.sequence = 0; + globals.countdown = 1024; + + env = getenv("MEMENTO_FAILAT"); + globals.failAt = (env ? atoi(env) : 0); + + env = getenv("MEMENTO_PARANOIA"); + globals.paranoia = (env ? atoi(env) : 0); + if (globals.paranoia == 0) + globals.paranoia = 1024; + + env = getenv("MEMENTO_PARANOIDAT"); + globals.paranoidAt = (env ? atoi(env) : 0); + + env = getenv("MEMENTO_SQUEEZEAT"); + globals.squeezeAt = (env ? atoi(env) : 0); + + atexit(Memento_fin); + + Memento_inited(); +} + +static void Memento_event(void) +{ + globals.sequence++; + if ((globals.sequence >= globals.paranoidAt) && (globals.paranoidAt != 0)) { + globals.paranoia = 1; + globals.countdown = 1; + } + if ((globals.sequence >= globals.failAt) && (globals.failAt != 0)) { + globals.failing = 1; + } + if ((globals.sequence >= globals.squeezeAt) && (globals.squeezeAt != 0)) { + globals.squeezing = 1; + } + + if (--globals.countdown == 0) { + Memento_checkAllMemory(); + globals.countdown = globals.paranoia; + } + + if (globals.sequence == globals.breakAt) + Memento_breakpoint(); +} + +int Memento_breakAt(int event) +{ + globals.breakAt = event; + return event; +} + +#ifdef MEMENTO_HAS_FORK +#include <unistd.h> +#include <sys/wait.h> +#include <signal.h> +#ifdef MEMENTO_STACKTRACE_METHOD +#if MEMENTO_STACKTRACE_METHOD == 1 +#include <signal.h> +#endif +#endif + +/* FIXME: Find some portable way of getting this */ +/* MacOSX has 10240, Ubuntu seems to have 256 */ +#define OPEN_MAX 10240 + +/* stashed_map[j] = i means that filedescriptor i-1 was duplicated to j */ +int stashed_map[OPEN_MAX]; + +static void Memento_signal(void) +{ + fprintf(stderr, "SEGV after Memory squeezing @ %d\n", globals.squeezed); + +#ifdef MEMENTO_STACKTRACE_METHOD +#if MEMENTO_STACKTRACE_METHOD == 1 + { + void *array[100]; + size_t size; + + size = backtrace(array, 100); + fprintf(stderr, "------------------------------------------------------------------------\n"); + fprintf(stderr, "Backtrace:\n"); + backtrace_symbols_fd(array, size, 2); + fprintf(stderr, "------------------------------------------------------------------------\n"); + } +#endif +#endif + + exit(1); +} + +static void squeeze(void) +{ + pid_t pid; + int i, status; + + fprintf(stderr, "Memory squeezing @ %d\n", globals.sequence); + + /* When we fork below, the child is going to snaffle all our file pointers + * and potentially corrupt them. Let's make copies of all of them before + * we fork, so we can restore them when we restart. */ + for (i = 0; i < OPEN_MAX; i++) { + if (stashed_map[i] == 0) { + int j = dup(i); + stashed_map[j] = i+1; + } + } + + pid = fork(); + if (pid == 0) { + /* Child */ + signal(SIGSEGV, Memento_signal); + /* We must fail all new allocations from here */ + globals.failing = 1; + globals.squeezed = globals.sequence; + return; + } + + /* Wait for pid to finish */ + waitpid(pid, &status, 0); + + /* Put the files back */ + for (i = 0; i < OPEN_MAX; i++) { + if (stashed_map[i] != 0) { + dup2(i, stashed_map[i]-1); + close(i); + stashed_map[i] = 0; + } + } +} +#else +void squeeze(void) +{ + fprintf(stderr, "Memento memory squeezing disabled as no fork!\n"); +} +#endif + +int Memento_failThisEvent(void) +{ + if (!globals.inited) + Memento_init(); + + Memento_event(); + + if ((globals.squeezing) && (!globals.squeezed)) + squeeze(); + + return globals.failing; +} + +void *Memento_malloc(size_t s) +{ + Memento_BlkHeader *memblk; + size_t smem = MEMBLK_SIZE(s); + + if (!globals.inited) + Memento_init(); + + Memento_event(); + + if ((globals.squeezing) && (!globals.squeezed)) + squeeze(); + + if (globals.failing) + return NULL; + + if (s == 0) + return NULL; + + globals.numMallocs++; + + memblk = MEMENTO_UNDERLYING_MALLOC(smem); + if (memblk == NULL) + return NULL; + + globals.alloc += s; + globals.totalAlloc += s; + if (globals.peakAlloc < globals.alloc) + globals.peakAlloc = globals.alloc; +#ifndef MEMENTO_LEAKONLY + memset(MEMBLK_TOBLK(memblk), MEMENTO_ALLOCFILL, s); +#endif + memblk->rawsize = s; + memblk->sequence = globals.sequence; + memblk->lastCheckedOK = memblk->sequence; + Memento_addBlockHead(&globals.used, memblk, 0); + return MEMBLK_TOBLK(memblk); +} + +void *Memento_calloc(size_t n, size_t s) +{ + void *block = Memento_malloc(n*s); + + if (block) + memset(block, 0, n*s); + return block; +} + +static int checkBlock(Memento_BlkHeader *memblk, const char *action) +{ +#ifndef MEMENTO_LEAKONLY + BlkCheckData data; + + memset(&data, 0, sizeof(data)); + Memento_appBlock(&globals.used, Memento_Internal_checkAllocedBlock, + &data, memblk); + if (!data.found) { + /* Failure! */ + fprintf(stderr, "Attempt to %s block 0x%p(size=%d,num=%d) not on allocated list!\n", + action, memblk, memblk->rawsize, memblk->sequence); + Memento_breakpoint(); + return 1; + } else if (data.preCorrupt || data.postCorrupt) { + fprintf(stderr, "Block 0x%p(size=%d,num=%d) found to be corrupted on %s!\n", + action, memblk->rawsize, memblk->sequence, action); + if (data.preCorrupt) { + fprintf(stderr, "Preguard corrupted\n"); + } + if (data.postCorrupt) { + fprintf(stderr, "Postguard corrupted\n"); + } + fprintf(stderr, "Block last checked OK at allocation %d. Now %d.\n", + memblk->lastCheckedOK, globals.sequence); + Memento_breakpoint(); + return 1; + } +#endif + return 0; +} + +void Memento_free(void *blk) +{ + Memento_BlkHeader *memblk; + + if (!globals.inited) + Memento_init(); + + Memento_event(); + + if (blk == NULL) + return; + + memblk = MEMBLK_FROMBLK(blk); + VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk)); + if (checkBlock(memblk, "free")) + return; + + VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk)); + globals.alloc -= memblk->rawsize; + globals.numFrees++; + + Memento_removeBlock(&globals.used, memblk); + + VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk)); + if (Memento_Internal_makeSpace(MEMBLK_SIZE(memblk->rawsize))) { + VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk)); + VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(memblk), + memblk->rawsize + Memento_PostSize); +#ifndef MEMENTO_LEAKONLY + memset(MEMBLK_TOBLK(memblk), MEMENTO_FREEFILL, memblk->rawsize); +#endif + Memento_addBlockTail(&globals.free, memblk, 1); + } else { + MEMENTO_UNDERLYING_FREE(memblk); + } +} + +void *Memento_realloc(void *blk, size_t newsize) +{ + Memento_BlkHeader *memblk, *newmemblk; + size_t newsizemem; + + if (!globals.inited) + Memento_init(); + + if (blk == NULL) + return Memento_malloc(newsize); + if (newsize == 0) { + Memento_free(blk); + return NULL; + } + if ((globals.squeezing) && (!globals.squeezed)) + squeeze(); + if (globals.failing) + return NULL; + + memblk = MEMBLK_FROMBLK(blk); + + Memento_event(); + + if (checkBlock(memblk, "realloc")) + return NULL; + + newsizemem = MEMBLK_SIZE(newsize); + Memento_removeBlock(&globals.used, memblk); + newmemblk = MEMENTO_UNDERLYING_REALLOC(memblk, newsizemem); + if (newmemblk == NULL) + { + Memento_addBlockHead(&globals.used, memblk, 2); + return NULL; + } + globals.numReallocs++; + globals.totalAlloc += newsize; + globals.alloc -= newmemblk->rawsize; + globals.alloc += newsize; + if (globals.peakAlloc < globals.alloc) + globals.peakAlloc = globals.alloc; + if (newmemblk->rawsize < newsize) { + char *newbytes = ((char *)MEMBLK_TOBLK(newmemblk))+newmemblk->rawsize; +#ifndef MEMENTO_LEAKONLY + memset(newbytes, MEMENTO_ALLOCFILL, newsize - newmemblk->rawsize); +#endif + VALGRIND_MAKE_MEM_UNDEFINED(newbytes, newsize - newmemblk->rawsize); + } + newmemblk->rawsize = newsize; +#ifndef MEMENTO_LEAKONLY + memset(newmemblk->preblk, MEMENTO_PREFILL, Memento_PreSize); + memset(MEMBLK_POSTPTR(newmemblk), MEMENTO_POSTFILL, Memento_PostSize); +#endif + Memento_addBlockHead(&globals.used, newmemblk, 2); + return MEMBLK_TOBLK(newmemblk); +} + +int Memento_checkBlock(void *blk) +{ + Memento_BlkHeader *memblk; + + if (blk == NULL) + return 0; + memblk = MEMBLK_FROMBLK(blk); + return checkBlock(memblk, "check"); +} + +static int Memento_Internal_checkAllAlloced(Memento_BlkHeader *memblk, void *arg) +{ + BlkCheckData *data = (BlkCheckData *)arg; + + Memento_Internal_checkAllocedBlock(memblk, data); + if (data->preCorrupt || data->postCorrupt) { + if ((data->found & 2) == 0) { + fprintf(stderr, "Allocated blocks:\n"); + data->found |= 2; + } + fprintf(stderr, " Block 0x%p(size=%d,num=%d)", + memblk, memblk->rawsize, memblk->sequence); + if (data->preCorrupt) { + fprintf(stderr, " Preguard "); + } + if (data->postCorrupt) { + fprintf(stderr, "%s Postguard ", + (data->preCorrupt ? "&" : "")); + } + fprintf(stderr, "corrupted.\n " + "Block last checked OK at allocation %d. Now %d.\n", + memblk->lastCheckedOK, globals.sequence); + data->preCorrupt = 0; + data->postCorrupt = 0; + data->freeCorrupt = 0; + } + return 0; +} + +static int Memento_Internal_checkAllFreed(Memento_BlkHeader *memblk, void *arg) +{ + BlkCheckData *data = (BlkCheckData *)arg; + + Memento_Internal_checkFreedBlock(memblk, data); + if (data->preCorrupt || data->postCorrupt || data->freeCorrupt) { + if ((data->found & 4) == 0) { + fprintf(stderr, "Freed blocks:\n"); + data->found |= 4; + } + fprintf(stderr, " 0x%p(size=%d,num=%d) ", + MEMBLK_TOBLK(memblk), memblk->rawsize, memblk->sequence); + if (data->freeCorrupt) { + fprintf(stderr, "index %d (address 0x%p) onwards ", data->index, + &((char *)MEMBLK_TOBLK(memblk))[data->index]); + if (data->preCorrupt) { + fprintf(stderr, "+ preguard "); + } + if (data->postCorrupt) { + fprintf(stderr, "+ postguard "); + } + } else { + if (data->preCorrupt) { + fprintf(stderr, " preguard "); + } + if (data->postCorrupt) { + fprintf(stderr, "%s Postguard ", + (data->preCorrupt ? "+" : "")); + } + } + fprintf(stderr, "corrupted.\n " + "Block last checked OK at allocation %d. Now %d.\n", + memblk->lastCheckedOK, globals.sequence); + data->preCorrupt = 0; + data->postCorrupt = 0; + data->freeCorrupt = 0; + } + return 0; +} + +int Memento_checkAllMemory(void) +{ +#ifndef MEMENTO_LEAKONLY + BlkCheckData data; + + memset(&data, 0, sizeof(data)); + Memento_appBlocks(&globals.used, Memento_Internal_checkAllAlloced, &data); + Memento_appBlocks(&globals.free, Memento_Internal_checkAllFreed, &data); + if (data.found & 6) { + Memento_breakpoint(); + return 1; + } +#endif + return 0; +} + +int Memento_setParanoia(int i) +{ + globals.paranoia = i; + globals.countdown = globals.paranoia; + return i; +} + +int Memento_paranoidAt(int i) +{ + globals.paranoidAt = i; + return i; +} + +int Memento_getBlockNum(void *b) +{ + Memento_BlkHeader *memblk; + if (b == NULL) + return 0; + memblk = MEMBLK_FROMBLK(b); + return (memblk->sequence); +} + +int Memento_check(void) +{ + int result; + + fprintf(stderr, "Checking memory\n"); + result = Memento_checkAllMemory(); + fprintf(stderr, "Memory checked!\n"); + return result; +} + +typedef struct findBlkData { + void *addr; + Memento_BlkHeader *blk; + int flags; +} findBlkData; + +static int Memento_containsAddr(Memento_BlkHeader *b, + void *arg) +{ + findBlkData *data = (findBlkData *)arg; + char *blkend = &((char *)MEMBLK_TOBLK(b))[b->rawsize]; + if ((MEMBLK_TOBLK(b) <= data->addr) && + ((void *)blkend > data->addr)) { + data->blk = b; + data->flags = 1; + return 1; + } + if (((void *)b <= data->addr) && + (MEMBLK_TOBLK(b) > data->addr)) { + data->blk = b; + data->flags = 2; + return 1; + } + if (((void *)blkend <= data->addr) && + ((void *)(blkend + Memento_PostSize) > data->addr)) { + data->blk = b; + data->flags = 3; + return 1; + } + return 0; +} + +int Memento_find(void *a) +{ + findBlkData data; + + data.addr = a; + data.blk = NULL; + data.flags = 0; + Memento_appBlocks(&globals.used, Memento_containsAddr, &data); + if (data.blk != NULL) { + fprintf(stderr, "Address 0x%p is in %sallocated block 0x%p(size=%d,num=%d)\n", + data.addr, + (data.flags == 1 ? "" : (data.flags == 2 ? + "preguard of " : "postguard of ")), + MEMBLK_TOBLK(data.blk), data.blk->rawsize, data.blk->sequence); + return data.blk->sequence; + } + data.blk = NULL; + data.flags = 0; + Memento_appBlocks(&globals.free, Memento_containsAddr, &data); + if (data.blk != NULL) { + fprintf(stderr, "Address 0x%p is in %sfreed block 0x%p(size=%d,num=%d)\n", + data.addr, + (data.flags == 1 ? "" : (data.flags == 2 ? + "preguard of " : "postguard of ")), + MEMBLK_TOBLK(data.blk), data.blk->rawsize, data.blk->sequence); + return data.blk->sequence; + } + return 0; +} + +int Memento_failAt(int i) +{ + globals.failAt = i; + if ((globals.sequence > globals.failAt) && + (globals.failing != 0)) + globals.failing = 1; + return i; +} + +#else + +/* Just in case anyone has left some debugging code in... */ +void (Memento_breakpoint)(void) +{ +} + +int (Memento_checkBlock)(void *b) +{ + return 0; +} + +int (Memento_checkAllMemory)(void) +{ + return 0; +} + +int (Memento_check)(void) +{ + return 0; +} + +int (Memento_setParanoia)(int i) +{ + return 0; +} + +int (Memento_paranoidAt)(int i) +{ + return 0; +} + +int (Memento_breakAt)(int i) +{ + return 0; +} + +int (Memento_getBlockNum)(void *i) +{ + return 0; +} + +int (Memento_find)(void *a) +{ + return 0; +} + +int (Memento_failAt)(int i) +{ + return 0; +} + +#undef Memento_malloc +#undef Memento_free +#undef Memento_realloc +#undef Memento_calloc + +void *Memento_malloc(size_t size) +{ + return MEMENTO_UNDERLYING_MALLOC(size); +} + +void Memento_free(void *b) +{ + MEMENTO_UNDERLYING_FREE(b); +} + +void *Memento_realloc(void *b, size_t s) +{ + return MEMENTO_UNDERLYING_REALLOC(b, s); +} + +void *Memento_calloc(size_t n, size_t s) +{ + return MEMENTO_UNDERLYING_CALLOC(n, s); +} + + +#endif diff --git a/fitz/memento.h b/fitz/memento.h new file mode 100644 index 00000000..a2b4b724 --- /dev/null +++ b/fitz/memento.h @@ -0,0 +1,213 @@ +/* Copyright (C) 2011 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, modified + or distributed except as expressly authorized under the terms of that + license. Refer to licensing information at http://www.artifex.com/ + or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134, + San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information. +*/ + +/* Memento: A library to aid debugging of memory leaks/heap corruption. + * + * Usage: + * First, build your project with MEMENTO defined, and include this + * header file whereever you use malloc, realloc or free. + * This header file will use macros to point malloc, realloc and free to + * point to Memento_malloc, Memento_realloc, Memento_free. + * + * Run your program, and all mallocs/frees/reallocs should be redirected + * through here. When the program exits, you will get a list of all the + * leaked blocks, together with some helpful statistics. You can get the + * same list of allocated blocks at any point during program execution by + * calling Memento_listBlocks(); + * + * Every call to malloc/free/realloc counts as an 'allocation event'. + * On each event Memento increments a counter. Every block is tagged with + * the current counter on allocation. Every so often during program + * execution, the heap is checked for consistency. By default this happens + * every 1024 events. This can be changed at runtime by using + * Memento_setParanoia(int level). 0 turns off such checking, 1 sets + * checking to happen on every event, any other number n sets checking to + * happen once every n events. + * + * Memento keeps blocks around for a while after they have been freed, and + * checks them as part of these heap checks to see if they have been + * written to (or are freed twice etc). + * + * A given heap block can be checked for consistency (it's 'pre' and + * 'post' guard blocks are checked to see if they have been written to) + * by calling Memento_checkBlock(void *blockAddress); + * + * A check of all the memory can be triggered by calling Memento_check(); + * (or Memento_checkAllMemory(); if you'd like it to be quieter). + * + * A good place to breakpoint is Memento_breakpoint, as this will then + * trigger your debugger if an error is detected. This is done + * automatically for debug windows builds. + * + * If a block is found to be corrupt, information will be printed to the + * console, including the address of the block, the size of the block, + * the type of corruption, the number of the block and the event on which + * it last passed a check for correctness. + * + * If you rerun, and call Memento_paranoidAt(int event); with this number + * the the code will wait until it reaches that event and then start + * checking the heap after every allocation event. Assuming it is a + * deterministic failure, you should then find out where in your program + * the error is occuring (between event x-1 and event x). + * + * Then you can rerun the program again, and call + * Memento_breakAt(int event); and the program will call + * Memento_Breakpoint() when event x is reached, enabling you to step + * through. + * + * Memento_find(address) will tell you what block (if any) the given + * address is in. + * + * An example: + * Suppose we have a gs invocation that crashes with memory corruption. + * * Build with -DMEMENTO. + * * In your debugger put breakpoints on Memento_inited and + * Memento_Breakpoint. + * * Run the program. It will stop in Memento_inited. + * * Execute Memento_setParanoia(1); (In VS use Ctrl-Alt-Q). (Note #1) + * * Continue execution. + * * It will detect the memory corruption on the next allocation event + * after it happens, and stop in Memento_breakpoint. The console should + * show something like: + * + * Freed blocks: + * 0x172e610(size=288,num=1415) index 256 (0x172e710) onwards corrupted + * Block last checked OK at allocation 1457. Now 1458. + * + * * This means that the block became corrupted between allocation 1457 + * and 1458 - so if we rerun and stop the program at 1457, we can then + * step through, possibly with a data breakpoint at 0x172e710 and see + * when it occurs. + * * So restart the program from the beginning. When we hit Memento_inited + * execute Memento_breakAt(1457); (and maybe Memento_setParanoia(1), or + * Memento_setParanoidAt(1457)) + * * Continue execution until we hit Memento_breakpoint. + * * Now you can step through and watch the memory corruption happen. + * + * Note #1: Using Memento_setParanoia(1) can cause your program to run + * very slowly. You may instead choose to use Memento_setParanoia(100) + * (or some other figure). This will only exhaustively check memory on + * every 100th allocation event. This trades speed for the size of the + * average allocation event range in which detection of memory corruption + * occurs. You may (for example) choose to run once checking every 100 + * allocations and discover that the corruption happens between events + * X and X+100. You can then rerun using Memento_paranoidAt(X), and + * it'll only start exhaustively checking when it reaches X. + * + * More than one memory allocator? + * + * If you have more than one memory allocator in the system (like for + * instance the ghostscript chunk allocator, that builds on top of the + * standard malloc and returns chunks itself), then there are some things + * to note: + * + * * If the secondary allocator gets its underlying blocks from calling + * malloc, then those will be checked by Memento, but 'subblocks' that + * are returned to the secondary allocator will not. There is currently + * no way to fix this other than trying to bypass the secondary + * allocator. One way I have found to do this with the chunk allocator + * is to tweak its idea of a 'large block' so that it puts every + * allocation in its own chunk. Clearly this negates the point of having + * a secondary allocator, and is therefore not recommended for general + * use. + * + * * Again, if the secondary allocator gets its underlying blocks from + * calling malloc (and hence Memento) leak detection should still work + * (but whole blocks will be detected rather than subblocks). + * + * * If on every allocation attempt the secondary allocator calls into + * Memento_failThisEvent(), and fails the allocation if it returns true + * then more useful features can be used; firstly memory squeezing will + * work, and secondly, Memento will have a "finer grained" paranoia + * available to it. + */ + +#ifndef MEMENTO_H + +#include <memory.h> + +#define MEMENTO_H + +#ifndef MEMENTO_UNDERLYING_MALLOC +#define MEMENTO_UNDERLYING_MALLOC malloc +#endif +#ifndef MEMENTO_UNDERLYING_FREE +#define MEMENTO_UNDERLYING_FREE free +#endif +#ifndef MEMENTO_UNDERLYING_REALLOC +#define MEMENTO_UNDERLYING_REALLOC realloc +#endif +#ifndef MEMENTO_UNDERLYING_CALLOC +#define MEMENTO_UNDERLYING_CALLOC calloc +#endif + +#ifndef MEMENTO_MAXALIGN +#define MEMENTO_MAXALIGN (sizeof(int)) +#endif + +#define MEMENTO_PREFILL 0xa6 +#define MEMENTO_POSTFILL 0xa7 +#define MEMENTO_ALLOCFILL 0xa8 +#define MEMENTO_FREEFILL 0xa9 + +#define MEMENTO_FREELIST_MAX 0x2000000 + +int Memento_checkBlock(void *); +int Memento_checkAllMemory(void); +int Memento_check(void); + +int Memento_setParanoia(int); +int Memento_paranoidAt(int); +int Memento_breakAt(int); +int Memento_getBlockNum(void *); +int Memento_find(void *a); +void Memento_breakpoint(void); +int Memento_failAt(int); +int Memento_failThisEvent(void); + +void *Memento_malloc(size_t s); +void *Memento_realloc(void *, size_t s); +void Memento_free(void *); +void *Memento_calloc(size_t, size_t); + +#ifdef MEMENTO + +#ifndef COMPILING_MEMENTO_C +#define malloc Memento_malloc +#define free Memento_free +#define realloc Memento_realloc +#define calloc Memento_calloc +#endif + +#else + +#define Memento_malloc MEMENTO_UNDERLYING_MALLOC +#define Memento_free MEMENTO_UNDERLYING_FREE +#define Memento_realloc MEMENTO_UNDERLYING_REALLOC +#define Memento_calloc MEMENTO_UNDERLYING_CALLOC + +#define Memento_checkBlock(A) 0 +#define Memento_checkAllMemory() 0 +#define Memento_check() 0 +#define Memento_setParanoia(A) 0 +#define Memento_paranoidAt(A) 0 +#define Memento_breakAt(A) 0 +#define Memento_getBlockNum(A) 0 +#define Memento_find(A) 0 +#define Memento_breakpoint() do {} while (0) +#define Memento_failAt(A) 0 +#define Memento_failThisEvent() 0 + +#endif /* MEMENTO */ + +#endif /* MEMENTO_H */ diff --git a/scripts/cmapdump.c b/scripts/cmapdump.c index 16ef1b8a..dfb3faa0 100644 --- a/scripts/cmapdump.c +++ b/scripts/cmapdump.c @@ -3,6 +3,9 @@ #include <stdio.h> #include <string.h> +/* We never want to build memento versions of the cmapdump util */ +#undef MEMENTO + #include "fitz.h" #include "mupdf.h" |