/* debug.c * * This file proides debug and run time error analysis support. Some of the * settings are very performance intense and my be turned off during a release * build. * * File begun on 2008-01-22 by RGerhards * * There is some in-depth documentation available in doc/dev_queue.html * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it * if you are getting aquainted to the object. * * Copyright 2008 Rainer Gerhards and Adiscon GmbH. * * This file is part of rsyslog. * * Rsyslog 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 3 of the License, or * (at your option) any later version. * * Rsyslog 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 Rsyslog. If not, see . * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ #include "config.h" /* autotools! */ #include #include #include #include #include #include #include #include #include "rsyslog.h" #include "debug.h" /* static data (some time to be replaced) */ int Debug; /* debug flag - read-only after startup */ int debugging_on = 0; /* read-only, except on sig USR1 */ static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */ static FILE *stddbg; typedef struct dbgMutLog_s { struct dbgMutLog_s *pNext; struct dbgMutLog_s *pPrev; pthread_mutex_t *mut; pthread_t thrd; const char *file; short ln; /* more than 64K source lines are forbidden ;) */ short mutexOp; } dbgMutLog_t; static dbgMutLog_t *dbgMutLogListRoot = NULL; static dbgMutLog_t *dbgMutLogListLast = NULL; static pthread_mutex_t mutMutLog = PTHREAD_MUTEX_INITIALIZER; /* the structure below was originally just the thread's call stack, but it has * a bit evolved over time. So we have now ended up with the fact that it * all debug info we know about the thread. */ typedef struct dbgCallStack_s { pthread_t thrd; const char* callStack[500]; const char* callStackFile[500]; int stackPtr; int stackPtrMax; char *pszThrdName; struct dbgCallStack_s *pNext; struct dbgCallStack_s *pPrev; } dbgThrdInfo_t; static dbgThrdInfo_t *dbgCallStackListRoot = NULL; static dbgThrdInfo_t *dbgCallStackListLast = NULL; static pthread_mutex_t mutCallStack = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutdbgprintf = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t keyCallStack; /* we do not have templates, so we use some macros to create linked list handlers * for the several types * DLL means "doubly linked list" * rgerhards, 2008-01-23 */ #define DLL_Del(type, pThis) \ if(pThis->pPrev != NULL) \ pThis->pPrev->pNext = pThis->pNext; \ if(pThis->pNext != NULL) \ pThis->pNext->pPrev = pThis->pPrev; \ if(pThis == dbg##type##ListRoot) \ dbg##type##ListRoot = pThis->pNext; \ if(pThis == dbg##type##ListLast) \ dbg##type##ListLast = pThis->pPrev; \ free(pThis); #define DLL_Add(type, pThis) \ if(dbg##type##ListRoot == NULL) { \ dbg##type##ListRoot = pThis; \ dbg##type##ListLast = pThis; \ } else { \ pThis->pPrev = dbg##type##ListLast; \ dbg##type##ListLast->pNext = pThis; \ dbg##type##ListLast = pThis; \ } /* we need to do our own mutex cancel cleanup handler as it shall not * be subject to the debugging instrumentation (that would probably run us * into an infinite loop */ static void dbgMutexCancelCleanupHdlr(void *pmut) { pthread_mutex_unlock((pthread_mutex_t*) pmut); } /* ------------------------- mutex tracking code ------------------------- */ /* ########################################################################### * IMPORTANT NOTE * Mutex instrumentation reduces the code's concurrency and thus affects its * order of execution. It is vital to test the code also with mutex * instrumentation turned off! Some bugs may not show up while it on... * ########################################################################### */ /* constructor & add new entry to list */ dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, const char *file, int line) { dbgMutLog_t *pLog; pLog = calloc(1, sizeof(dbgMutLog_t)); assert(pLog != NULL); /* fill data members */ pLog->mut = pmut; pLog->thrd = pthread_self(); pLog->mutexOp = mutexOp; pLog->file = file; pLog->ln = line; DLL_Add(MutLog, pLog); return pLog; } /* destruct log entry */ void dbgMutLogDelEntry(dbgMutLog_t *pLog) { assert(pLog != NULL); DLL_Del(MutLog, pLog); } /* print a single mutex log entry */ static void dbgMutLogPrintOne(dbgMutLog_t *pLog) { char *strmutop; char buf[64]; assert(pLog != NULL); switch(pLog->mutexOp) { case MUTOP_LOCKWAIT: strmutop = "waited on"; break; case MUTOP_LOCK: strmutop = "owned"; break; default: snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp); strmutop = buf; break; } } /* print the complete mutex log */ static void dbgMutLogPrintAll(void) { dbgMutLog_t *pLog; dbgprintf("Mutex log for all known mutex operations:\n"); for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext) dbgMutLogPrintOne(pLog); } /* find the last log entry for that specific mutex object. Is used to delete * a thread's own requests. Searches occur from the back. * file and line are optional. If they are NULL and -1, they are ignored. This is * important for the unlock case. */ dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, const char *file, int line) { dbgMutLog_t *pLog; pthread_t mythrd = pthread_self(); pLog = dbgMutLogListLast; while(pLog != NULL) { if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop && ((file == NULL) || !strcmp(pLog->file, file)) && ((line == -1) || (pLog->ln == line))) break; pLog = pLog->pPrev; } return pLog; } /* find mutex object from the back of the list */ dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast) { dbgMutLog_t *pLog; if(pLast == NULL) pLog = dbgMutLogListLast; else pLog = pLast; while(pLog != NULL) { if(pLog->mut == pmut) break; pLog = pLog->pPrev; } return pLog; } /* find lock aquire for mutex from back of list */ dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut) { dbgMutLog_t *pLog; pLog = dbgMutLogFindFromBack(pmut, NULL); while(pLog != NULL) { if(pLog->mutexOp == MUTOP_LOCK) break; pLog = dbgMutLogFindFromBack(pmut, pLog); } return pLog; } /* report wait on a mutex and add it to the mutex log */ static void dbgMutexPreLockLog(pthread_mutex_t *pmut, const char *file, const char* func, int line) { dbgMutLog_t *pHolder; dbgMutLog_t *pLog; char pszBuf[128]; char *pszHolder; pthread_mutex_lock(&mutMutLog); pHolder = dbgMutLogFindHolder(pmut); pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, file, line); if(pHolder == NULL) pszHolder = "[NONE(available)]"; else { snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d", pLog->file, pLog->ln); pszHolder = pszBuf; } dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", file, line, func, (void*)pmut, pszHolder); pthread_mutex_unlock(&mutMutLog); } /* report aquired mutex */ static void dbgMutexLockLog(pthread_mutex_t *pmut, const char *file, const char* func, int line) { dbgMutLog_t *pLog; pthread_mutex_lock(&mutMutLog); pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, file, line); assert(pLog != NULL); dbgMutLogDelEntry(pLog); pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCK, file, line); pthread_mutex_unlock(&mutMutLog); dbgprintf("%s:%d:%s: mutex %p aquired\n", file, line, func, (void*)pmut); } /* if we unlock, we just remove the lock aquired entry from the log list */ static void dbgMutexUnlockLog(pthread_mutex_t *pmut, const char *file, const char* func, int line) { dbgMutLog_t *pLog; pthread_mutex_lock(&mutMutLog); pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL, -1); assert(pLog != NULL); dbgMutLogDelEntry(pLog); pthread_mutex_unlock(&mutMutLog); dbgprintf("%s:%d:%s: mutex %p UNlocked\n", file, line, func, (void*)pmut); } int dbgMutexLock(pthread_mutex_t *pmut, const char *file, const char* func, int line) { int ret; dbgMutexPreLockLog(pmut, file, func, line); ret = pthread_mutex_lock(pmut); dbgMutexLockLog(pmut, file, func, line); return ret; } int dbgMutexUnlock(pthread_mutex_t *pmut, const char *file, const char* func, int line) { int ret; dbgMutexUnlockLog(pmut, file, func, line); ret = pthread_mutex_unlock(pmut); if(1)dbgprintf("mutex %p UNlock done %s, %s(), line %d\n",(void*)pmut, file, func, line); return ret; } int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const char *file, const char* func, int line) { int ret; dbgMutexUnlockLog(pmut, file, func, line); dbgMutexPreLockLog(pmut, file, func, line); ret = pthread_cond_wait(cond, pmut); dbgMutexLockLog(pmut, file, func, line); return ret; } int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, const char *file, const char* func, int line) { int ret; dbgMutexUnlockLog(pmut, file, func, line); dbgMutexPreLockLog(pmut, file, func, line); ret = pthread_cond_timedwait(cond, pmut, abstime); dbgMutexLockLog(pmut, file, func, line); return ret; } /* ------------------------- end mutex tracking code ------------------------- */ /* ------------------------- thread tracking code ------------------------- */ /* get ptr to call stack - if none exists, create a new stack */ static dbgThrdInfo_t *dbgGetThrdInfo(void) { dbgThrdInfo_t *pThrd; pthread_mutex_lock(&mutCallStack); if((pThrd = pthread_getspecific(keyCallStack)) == NULL) { /* construct object */ pThrd = calloc(1, sizeof(dbgThrdInfo_t)); pThrd->thrd = pthread_self(); (void) pthread_setspecific(keyCallStack, pThrd); fprintf(stdout, "dbgGetThrdInfo Create thrd %lx, pstack %p, thrd %lx\n", (unsigned long) pthread_self(), pThrd, pThrd->thrd); DLL_Add(CallStack, pThrd); } pthread_mutex_unlock(&mutCallStack); return pThrd; } /* find a specific thread ID. It must be present, else something is wrong */ static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd) { dbgThrdInfo_t *pThrd; for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { if(pThrd->thrd == thrd) break; } return pThrd; } /* build a string with the thread name. If none is set, the thread ID is * used instead. Caller must provide buffer space. If bIncludeNumID is set * to 1, the numerical ID is always included. * rgerhards 2008-01-23 */ static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID) { dbgThrdInfo_t *pThrd; assert(pszBuf != NULL); pThrd = dbgFindThrd(thrd); if(pThrd == 0 || pThrd->pszThrdName == NULL) { /* no thread name, use numeric value */ snprintf(pszBuf, lenBuf, "%lx", thrd); } else { if(bIncludeNumID) { snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, thrd); } else { snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName); } } } /* set a name for the current thread. The caller provided string is duplicated. */ void dbgSetThrdName(uchar *pszName) { dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); if(pThrd->pszThrdName != NULL) free(pThrd->pszThrdName); pThrd->pszThrdName = strdup((char*)pszName); } /* destructor for a call stack object */ static void dbgCallStackDestruct(void *arg) { dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg; dbgprintf("destructor for debug call stack %p called\n", pThrd); DLL_Del(CallStack, pThrd); } /* print a thread's call stack */ static void dbgCallStackPrint(dbgThrdInfo_t *pThrd) { int i; char pszThrdName[64]; pthread_mutex_lock(&mutCallStack); dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1); dbgprintf("\n"); dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName); for(i = 0 ; i < pThrd->stackPtr ; i++) { dbgprintf("%d, %s:%s\n", i, pThrd->callStackFile[i], pThrd->callStack[i]); } dbgprintf("NOTE: not all calls may have been recorded, highest number of nested calls %d.\n", pThrd->stackPtrMax); pthread_mutex_unlock(&mutCallStack); } /* print all threads call stacks */ static void dbgCallStackPrintAll(void) { dbgThrdInfo_t *pThrd; /* stack info */ for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { dbgCallStackPrint(pThrd); } } /* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat * more meaningful way. * rgerhards, 2008-01-22 */ void sigsegvHdlr(int signum) { struct sigaction sigAct; char *signame; if(signum == SIGSEGV) { signame = " (SIGSEGV)"; } else { signame = ""; } dbgprintf("Signal %d%s occured, execution must be terminated %d.\n", signum, signame, SIGSEGV); dbgCallStackPrintAll(); fflush(stddbg); /* re-instantiate original handler ... */ memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = SIG_DFL; sigaction(SIGSEGV, &sigAct, NULL); /* and call it */ int ir = raise(signum); printf("raise returns %d, errno %d: %s\n", ir, errno, strerror(errno)); /* we should never arrive here - but we provide some code just in case... */ dbgprintf("sigsegvHdlr: oops, returned from raise(), doing exit(), something really wrong...\n"); exit(1); } /* print some debug output */ void dbgprintf(char *fmt, ...) { static pthread_t ptLastThrdID = 0; static int bWasNL = 0; va_list ap; static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ if(!(Debug && debugging_on)) return; pthread_mutex_lock(&mutdbgprintf); pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprintf); /* The bWasNL handler does not really work. It works if no thread * switching occurs during non-NL messages. Else, things are messed * up. Anyhow, it works well enough to provide useful help during * getting this up and running. It is questionable if the extra effort * is worth fixing it, giving the limited appliability. * rgerhards, 2005-10-25 * I have decided that it is not worth fixing it - especially as it works * pretty well. * rgerhards, 2007-06-15 */ if(ptLastThrdID != pthread_self()) { if(!bWasNL) { fprintf(stddbg, "\n"); bWasNL = 1; } ptLastThrdID = pthread_self(); dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); } if(bWasNL) { fprintf(stddbg, "%s: ", pszThrdName); } bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; va_start(ap, fmt); vfprintf(stddbg, fmt, ap); va_end(ap); fflush(stddbg); pthread_cleanup_pop(1); } /* handler called when a function is entered */ int dbgEntrFunc(char* file, int line, const char* func) { int iStackPtr; dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); if(bLogFuncFlow) dbgprintf("%s:%d: %s: enter\n", file, line, func); if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(const char*))) { dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n", file, line, func); iStackPtr = pThrd->stackPtr; } else { iStackPtr = pThrd->stackPtr++; if(pThrd->stackPtr > pThrd->stackPtrMax) pThrd->stackPtrMax = pThrd->stackPtr; pThrd->callStackFile[iStackPtr] = file; pThrd->callStack[iStackPtr] = func; } return iStackPtr; } /* handler called when a function is exited */ void dbgExitFunc(int iStackPtrRestore, char* file, int line, const char* func) { dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); assert(iStackPtrRestore >= 0); if(bLogFuncFlow) dbgprintf("%s:%d: %s: exit\n", file, line, func); pThrd->stackPtr = iStackPtrRestore; if(pThrd->stackPtr < 0) { dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", pthread_self()); pThrd->stackPtr = 0; } } /* Handler for SIGUSR2. Dumps all available debug output */ static void sigusr2Hdlr(int __attribute__((unused)) signum) { dbgprintf("SIGUSR2 received, dumping debug information\n"); dbgCallStackPrintAll(); dbgMutLogPrintAll(); } rsRetVal dbgClassInit(void) { struct sigaction sigAct; (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = sigusr2Hdlr; //sigaction(SIGUSR2, &sigAct, NULL); sigaction(SIGUSR1, &sigAct, NULL); stddbg = stdout; dbgSetThrdName((uchar*)"main thread"); return RS_RET_OK; } rsRetVal dbgClassExit(void) { pthread_key_delete(keyCallStack); return RS_RET_OK; } /* * vi:set ai: */