/****************************************************************************** * Memory Checker * * Copyright (C) 2002 Hal Duston * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * ******************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_ERRNO_H # include #endif #ifndef HAVE_DECL_ERRNO extern int errno; #endif #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_SIGNAL_H # include #endif #ifdef HAVE_STRING_H # include #else # ifdef HAVE_STRINGS_H # include # endif #endif #ifdef HAVE_SETJMP_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_MALLOC_H # include #endif /* This mess was copied from the autoconf acspecific.m4, */ /* which in turn was copied from the GNU getpagesize.h. */ #ifndef HAVE_GETPAGESIZE # ifdef _SC_PAGESIZE # define getpagesize() sysconf(_SC_PAGESIZE) # else /* no _SC_PAGESIZE */ # ifdef HAVE_SYS_PARAM_H # include # ifdef EXEC_PAGESIZE # define getpagesize() EXEC_PAGESIZE # else /* no EXEC_PAGESIZE */ # ifdef NBPG # define getpagesize() NBPG * CLSIZE # ifndef CLSIZE # define CLSIZE 1 # endif /* no CLSIZE */ # else /* no NBPG */ # ifdef NBPC # define getpagesize() NBPC # else /* no NBPC */ # ifdef PAGESIZE # define getpagesize() PAGESIZE # endif /* no PAGESIZE */ # endif /* no NBPC */ # endif /* no NBPG */ # endif /* no EXEC_PAGESIZE */ # else /* no HAVE_SYS_PARAM_H */ # define getpagesize() 8192 /* punt totally */ # endif /* no HAVE_SYS_PARAM_H */ # endif /* no _SC_PAGESIZE */ #else extern int getpagesize(void); #endif /* no HAVE_GETPAGESIZE */ #include "memcheck.h" #define MEMCHECK_INFO 3 #define MEMCHECK_WARN 2 #define MEMCHECK_ERROR 1 #define MEMCHECK_NONE 0 #define check_result_error(string,check) \ check_result(string,check,1) #define check_result_warn(string,check) \ check_result(string,check,77) #define check_result(string,check,result) \ if(printf("Checking %s\t: ", string) < 0) \ { \ tmemcheck_error("printf"); \ } \ if(check) \ { \ if(printf("passed\n") < 0) \ { \ tmemcheck_error("printf"); \ } \ } \ else \ { \ if(printf("failed\n") < 0) \ { \ tmemcheck_error("printf"); \ } \ if(tmemcheck_result == 0) \ { \ tmemcheck_result = result; \ } \ else \ { \ tmemcheck_result = result < tmemcheck_result \ ? result : tmemcheck_result; \ } \ } #define check_result_error_in_log(string, tlevel, text) \ if(tmemcheck_config.level >= tlevel) \ { \ if(tmemcheck_log < 0) \ { \ tmemcheck_log = open("memcheck.log", O_RDONLY); \ } \ if(tmemcheck_log >= 0) \ { \ read(tmemcheck_log, buffer, sizeof(buffer) - 1); \ lseek(tmemcheck_log, 0, SEEK_END); \ check_result_error(string, \ !memcmp(buffer, text, strlen(text) - 1)); \ } \ } struct tmemcheck_config { int underruns; int level; size_t large; size_t limit; }; static struct tmemcheck_config tmemcheck_config; static int tmemcheck_result; int main(int argc, char *argv[]); static void tmemcheck_exit(void); #ifdef HAVE_SIGINFO_T static RETSIGTYPE tmemcheck_handler(int signum, siginfo_t *info, void *context, void *address); #else static RETSIGTYPE tmemcheck_handler(int signum, int code, void *context, void *address); #endif static void tmemcheck_error(const char *s); static sig_atomic_t tmemcheck_signalled; static sig_atomic_t tmemcheck_setjmp_active; #ifdef HAVE_SIGJMP_BUF static sigjmp_buf tmemcheck_handler_env; #else # ifdef HAVE_JMP_BUF static jmp_buf tmemcheck_handler_env; # endif #endif static int tmemcheck_log = -1; static int tmemcheck_fail = -1; int main(int argc, char *argv[]) { const char *val; const char *env; static char memcheck[] = "memcheck"; static char *s1; static char *s2; unsigned long line[3]; char buffer[1024]; size_t pagesize; unsigned count; unsigned align; unsigned bit; #ifdef HAVE_SIGACTION # ifdef HAVE_SIGACTION_T sigaction_t act; # else # ifdef HAVE_STRUCT_SIGACTION struct sigaction act; # endif # endif #endif (void)argc; (void)argv; unlink("memcheck.log"); if(atexit(tmemcheck_exit)) { tmemcheck_error("atexit"); } if(setvbuf(stdout, NULL, _IONBF, 0)) { tmemcheck_error("setvbuf"); } pagesize = getpagesize(); if((env = getenv("MEMCHECK"))) { if(strstr(env, "under")) { tmemcheck_config.underruns = 1; } if((val = strstr(env, "level="))) { if(val[6] == 'i') { tmemcheck_config.level = MEMCHECK_INFO; } if(val[6] == 'w') { tmemcheck_config.level = MEMCHECK_WARN; } if(val[6] == 'e') { tmemcheck_config.level = MEMCHECK_ERROR; } } if((val = strstr(env, "large="))) { tmemcheck_config.large = atoi(&val[6]); } if((val = strstr(env, "limit="))) { char *modifier; tmemcheck_config.limit = strtol(&val[6], &modifier, 10); if(modifier[0] == 'k' || modifier[0] == 'K') { tmemcheck_config.limit *= 1024; } if(modifier[0] == 'm' || modifier[0] == 'M') { tmemcheck_config.limit *= 1024 * 1024; } if(modifier[0] == 'g' || modifier[0] == 'G') { tmemcheck_config.limit *= 1024 * 1024 * 1024; } tmemcheck_config.limit /= pagesize; } } /* Basic functionality checking, based on validations in "The Standard C Library" by P J Plauger. */ check_result_error("malloc return value",\ (s1 = malloc(sizeof(memcheck))) != NULL); check_result_error("malloc writable",\ strcpy(s1, memcheck) != NULL) check_result_error("malloc readable",\ !strcmp(s1, memcheck)); check_result_error("calloc return value",\ (s2 = calloc(sizeof(memcheck), 1)) != NULL); check_result_error("calloc initialization",\ s2[0] == 0 && !memcmp(&s2[0], &s2[1], sizeof(memcheck) - 1)); check_result_error("calloc writable",\ strcpy(s2, memcheck) != NULL); check_result_error("calloc readable",\ !strcmp(s2, memcheck)); check_result_error("realloc increase",\ (s1 = realloc(s1, 2 * sizeof(memcheck) - 1)) != NULL); check_result_error("realloc inc writable",\ strcat(s1, memcheck) != NULL); check_result_error("realloc inc readable",\ !strcmp(&s1[sizeof(memcheck) - 1], memcheck)); check_result_error("realloc orig readable",\ !memcmp(s1, memcheck, sizeof(memcheck) - 1)); check_result_error("realloc decrease",\ (s1 = realloc(s1, sizeof(memcheck))) != NULL); check_result_error("realloc dec writeable",\ (s1[sizeof(memcheck) - 1] = 0) == 0); check_result_error("realloc dec readable",\ !memcmp(s1, memcheck, sizeof(memcheck) - 1)); free(s2); free(s1); /* * The POSIX Standard (IEEE Std 1003.1-200x) defers to the ISO C standard. * The ISO C Standard (ISO 9899:1990) states: * * "If the space requested is zero, the behaviour is implementation-defined; * the value returned shall be either a null pointer or a unique pointer." * * This implementation always returns a unique pointer. */ check_result_error("malloc size zero", (s1 = malloc(0)) != NULL); check_result_error_in_log("malloc size zero log", MEMCHECK_INFO, "Zero size"); check_result_error("malloc zero unique", (s2 = malloc(0)) != s1); check_result_error_in_log("malloc zero unique log", MEMCHECK_INFO, "Zero size"); free(s2); free(s1); check_result_error("calloc size zero", (s1 = calloc(1, 0)) != NULL); check_result_error_in_log("calloc size zero log", MEMCHECK_INFO, "Zero size"); check_result_error("calloc zero items", (s2 = calloc(0, 1)) != NULL); check_result_error("calloc zero unique", s2 != s1); free(s2); check_result_error("realloc size zero", (s1 = realloc(s1, 0)) != NULL); check_result_error_in_log("realloc size zero log", MEMCHECK_INFO, "Zero size"); check_result_error("realloc NULL pointer", (s2 = realloc(NULL, 0)) != NULL); check_result_error_in_log("realloc NULL ptr log", MEMCHECK_INFO, "NULL pointer"); free(s2); free(NULL); check_result_error_in_log("free NULL ptr log", MEMCHECK_INFO, "NULL pointer"); #ifdef HAVE_SIGACTION # ifdef HAVE_SIGINFO_T act.sa_sigaction = (RETSIGTYPE (*)(int signum, /* Cast away 4th arg */ siginfo_t *info, /* (address) that SunOS */ void *context))tmemcheck_handler; /* _may_ have */ act.sa_flags = SA_SIGINFO; # else act.sa_handler = (RETSIGTYPE (*)(int))tmemcheck_handler; act.sa_flags = 0; # endif if(!sigemptyset(&act.sa_mask)) { if(sigaction(SIGSEGV, &act, NULL)) { tmemcheck_error("sigaction"); } if(sigaction(SIGBUS, &act, NULL)) { tmemcheck_error("sigaction"); } } else { tmemcheck_error("sigemptyset"); } #endif if(printf("Checking %s %d\t: ", "realloc loop of", 1024) < 0) { tmemcheck_error("printf"); } for(count = 0; count < 1024; ++count) { s1 = realloc(s1, count + 1); } if(printf("passed\n") < 0) { tmemcheck_error("printf"); } align = 1; for(bit = 1; align < pagesize; ++bit) { align = 1 << bit; sprintf(buffer, "alignment of %4u", align); free(s1); check_result_error(buffer, (long)(s1 = malloc(align)) % align == 0); } memset(s1, 2, pagesize); check_result_error("malloc pagesize", s1[0] == 2 && s1[pagesize - 1] == 2); free(s1); for(bit = 1; bit <= sizeof(long); ++bit) { s1 = malloc(pagesize - bit); memset(s1, 2, pagesize - bit); sprintf(buffer, "malloc pagesize - %u", bit); check_result_error(buffer, s1[0] == 2 && s1[pagesize - 1 - bit] == 2); free(s1); } for(bit = 1; bit <= sizeof(long); ++bit) { s1 = malloc(pagesize + bit); memset(s1, 2, pagesize + bit); sprintf(buffer, "malloc pagesize + %u", bit); check_result_error(buffer, s1[0] == 2 && s1[pagesize - 1 + bit] == 2); free(s1); } if(tmemcheck_config.large > 0) { s1 = malloc(tmemcheck_config.large); check_result_error_in_log("large alloc ptr log", MEMCHECK_INFO, "Large allocation"); free(s1); } #ifdef HAVE_SIGACTION s1 = malloc(1); if(sigaction(SIGSEGV, NULL, &act)) { tmemcheck_error("sigaction"); } # ifdef HAVE_SIGINFO_T check_result_error("segv signal handler", act.sa_sigaction != (RETSIGTYPE (*)(int signum, /* Cast away 4th arg */ siginfo_t *info, /* (address) that SunOS */ void *context))tmemcheck_handler); /* _may_ have */ # else check_result_error("segv signal handler", act.sa_handler != (RETSIGTYPE (*)(int))tmemcheck_handler); # endif if(sigaction(SIGBUS, NULL, &act)) { tmemcheck_error("sigaction"); } # ifdef HAVE_SIGINFO_T check_result_error("bus signal handler", act.sa_sigaction != (RETSIGTYPE (*)(int signum, /* Cast away 4th arg */ siginfo_t *info, /* (address) that SunOS */ void *context))tmemcheck_handler); /* _may_ have */ # else check_result_error("bus signal handler", act.sa_handler != (RETSIGTYPE (*)(int))tmemcheck_handler); # endif free(s1); #endif s1 = malloc(2); #ifdef HAVE_SIGLONGJMP if(!sigsetjmp(tmemcheck_handler_env, 1)) #else # ifdef HAVE_LONGJMP if(!setjmp(tmemcheck_handler_env)) # endif #endif { tmemcheck_setjmp_active = 1; if(tmemcheck_config.underruns) { s1[0] = s1[-1]; /* This should cause a segment fault. */ } else { s1[0] = s1[2]; /* This should cause a segment fault. */ } tmemcheck_signalled = 0; } tmemcheck_setjmp_active = 0; if(tmemcheck_config.underruns) { check_result_error("read underrun trapping", tmemcheck_signalled != 0); } else { check_result_error("read overrun trapping", tmemcheck_signalled != 0); } if(tmemcheck_config.level >= MEMCHECK_ERROR) { if(tmemcheck_log < 0) { tmemcheck_log = open("memcheck.log", O_RDONLY); } if(tmemcheck_log >= 0) { read(tmemcheck_log, buffer, sizeof(buffer) - 1); lseek(tmemcheck_log, 0, SEEK_END); if(tmemcheck_config.underruns) { check_result_warn("read underrun log", !memcmp(buffer, "Underrun", 8)); check_result_warn("read underrun addr", strtoul(&buffer[12], NULL, 16) == ((unsigned long)s1)-1); } else { check_result_warn("read overrun log", !memcmp(buffer, "Overrun", 7)); check_result_warn("read overrun addr", strtoul(&buffer[11], NULL, 16) == ((unsigned long)s1)+2); } } } #ifdef HAVE_SIGLONGJMP if(!sigsetjmp(tmemcheck_handler_env, 1)) #else # ifdef HAVE_LONGJMP if(!setjmp(tmemcheck_handler_env)) # endif #endif { tmemcheck_setjmp_active = 1; if(tmemcheck_config.underruns) { s1[-1] = s1[0]; /* This should cause a segment fault. */ } else { s1[2] = s1[0]; /* This should cause a segment fault. */ } tmemcheck_signalled = 0; } tmemcheck_setjmp_active = 0; if(tmemcheck_config.underruns) { check_result_error("write underrun trap", tmemcheck_signalled != 0); } else { check_result_error("write overrun trapping", tmemcheck_signalled != 0); } if(tmemcheck_config.level >= MEMCHECK_ERROR) { if(tmemcheck_log < 0) { tmemcheck_log = open("memcheck.log", O_RDONLY); } if(tmemcheck_log >= 0) { read(tmemcheck_log, buffer, sizeof(buffer) - 1); lseek(tmemcheck_log, 0, SEEK_END); if(tmemcheck_config.underruns) { check_result_warn("write underrun log", !memcmp(buffer, "Underrun", 8)); check_result_warn("write underrun addr", strtoul(&buffer[11], NULL, 16) == (unsigned long)&s1[-1]); } else { check_result_warn("write overrun log", !memcmp(buffer, "Overrun", 7)); check_result_warn("write overrun addr", strtoul(&buffer[11], NULL, 16) == (unsigned long)&s1[2]); } } } free(&s1[1]); check_result_error_in_log("free bad pointer log", MEMCHECK_ERROR, "Invalid"); free(s1); check_result_error_in_log("double free log", MEMCHECK_ERROR, "Already freed"); s1 = malloc(1); if(tmemcheck_config.underruns) { s1[1] = 2; } else { s1[-1] = 2; } free(s1); if(tmemcheck_config.underruns) { check_result_error_in_log("write overrun log", MEMCHECK_ERROR, "Detected overrun"); } else { check_result_error_in_log("write underrun log", MEMCHECK_ERROR, "Detected underrun"); } s1 = malloc(1); line[0] = __LINE__; s1 = realloc(s1, pagesize + 1); line[1] = __LINE__; free(s1); line[2] = __LINE__; free(s1); if(tmemcheck_config.level >= MEMCHECK_ERROR) { if(tmemcheck_log < 0) { tmemcheck_log = open("memcheck.log", O_RDONLY); } if(tmemcheck_log >= 0) { read(tmemcheck_log, buffer, sizeof(buffer) - 1); lseek(tmemcheck_log, 0, SEEK_END); check_result_error("reallocation tracking", strtoul(strchr(strstr(buffer, "realloc"), ':') + 1, NULL, 0) == line[1]); } } s1 = malloc(1); tmemcheck_fail = 0; exit(tmemcheck_result); } static void tmemcheck_error(const char *s) { #ifdef HAVE_PERROR perror(s); #else # ifdef HAVE_STRERROR fprintf(stderr, "%s: %s\n", s, strerror(errno)); # else # ifdef HAVE_SYS_ERRLIST fprintf(stderr, "%s: %s\n", s, sys_errlist[errno]); # else # ifdef HAVE__SYS_ERRLIST fprintf(stderr, "%s: %s\n", s, _sys_errlist[errno]); # else fprintf(stderr, "%s: errno=%d\n", s, errno); # endif # endif # endif #endif } static void tmemcheck_exit(void) { char buffer[1024]; if(tmemcheck_config.level >= MEMCHECK_WARN) { if(tmemcheck_log < 0) { tmemcheck_log = open("memcheck.log", O_RDONLY); } if(tmemcheck_log >= 0) { if(tmemcheck_fail == 0) { read(tmemcheck_log, buffer, sizeof(buffer) - 1); lseek(tmemcheck_log, 0, SEEK_END); /* Macro expanded by hand so we can skip the call to exit(). * check_result_error("never freed log", * !memcmp(buffer, "Never freed log", 11)); */ if(printf("Checking %s\t: ", "never freed log") < 0) { tmemcheck_error("printf"); } if(!memcmp(buffer, "Never freed", 11)) { if(printf("passed\n") < 0) { tmemcheck_error("printf"); } } else { if(printf("check manually\n") < 0) { tmemcheck_error("printf"); } /* atexit might call malloc in some implementations. * If so, by the time we get here, we haven't yet called * libmemcheck's exit_handler. Don't fail it, but * rather tell the builder to check the log manually. */ } } close(tmemcheck_log); } } } #ifdef HAVE_SIGINFO_T static RETSIGTYPE tmemcheck_handler(int signum, siginfo_t *info, void *context, void *address) { (void)info; #else static RETSIGTYPE tmemcheck_handler(int signum, int code, void *context, void *address) { (void)code; #endif (void)context; (void)address; if(tmemcheck_setjmp_active != 0) { tmemcheck_signalled = signum; #ifdef HAVE_SIGLONGJMP siglongjmp(tmemcheck_handler_env, 1); #else # ifdef HAVE_LONGJMP longjmp(tmemcheck_handler_env, 1); # endif #endif } else { #ifdef HAVE_PSIGNAL psignal(signum, "Caught signal"); #else # ifdef HAVE_STRSIGNAL fprintf(stderr, "Caught signal %s\n", strsignal(signum)); # else # ifdef HAVE_SYS_SIGLIST fprintf(stderr, "Caught signal %s\n", sys_siglist[signum]); # else # ifdef HAVE__SYS_SIGLIST fprintf(stderr, "Caught signal %s\n", _sys_siglist[signum]); # else fprintf(stderr, "Caught signal signum=%d\n", signum); # endif # endif # endif #endif abort(); } }