/* * Copyright: Stanford University / SLAC National Laboratory. * * SPDX-License-Identifier: EPICS * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. * * Author: Till Straumann , 2014 */ /* * Check stack trace functionality */ #include "epicsStackTrace.h" #include "errlog.h" #include "epicsUnitTest.h" #include "testMain.h" #include #include #include #define TST_BUFSZ 10000 #define MAXP 10 /* estimated size of (compiled) epicsStackTraceRecurseGbl */ #define WINDOW_SZ 400 typedef struct TestDataRec_ { char buf[TST_BUFSZ]; int pos; } TestDataRec, *TestData; typedef void (*RecFn)(int); /* We want a stack trace and need a few nested routines. * The whole magic here is intended to prevent a compiler * from optimizing the call stack away: * - call via a volatile pointer * - add a call to a no-op function at the end so that * tail-call optimization doesn't eliminate the call * stack. * * We use a local (static) and a global routine to test * if the stacktrace supports either flavor. */ void epicsStackTraceRecurseGbl(int lvl); static void epicsStackTraceRecurseLcl(int lvl); void nopFn(int lvl) { } RecFn volatile lfp = epicsStackTraceRecurseLcl; RecFn volatile gfp = epicsStackTraceRecurseGbl; RecFn volatile nfp = nopFn; static void epicsStackTraceRecurseLcl(int lvl) { if ( lvl ) gfp(lvl-1); else epicsStackTrace(); /* call something so that the call through gfp() doesn't * get optimized into a jump (tail-call optimization) */ nfp(0); } void epicsStackTraceRecurseGbl(int lvl) { if ( lvl ) lfp(lvl-1); else epicsStackTrace(); /* call something so that the call through gfp() doesn't * get optimized into a jump (tail-call optimization) */ nfp(0); } static void logClient(void *ptr, const char *msg) { TestData td = ptr; size_t sz = strlen(msg); size_t mx = sizeof(td->buf) - td->pos - 1; if ( sz > mx ) sz = mx; memcpy( td->buf+td->pos, msg, sz ); td->pos += sz; } static int findStringOcc(const char *buf, const char *what) { int rval = 0; size_t l = strlen(what); int ch; while ( (buf = strstr(buf, what)) ) { /* Is it just a prefix? */ ch = buf[l]; if ( ! isalnum(ch) && '_' != ch && '.' != ch ) { rval++; } buf += l; } testDiag("found %i x %s\n", rval, what); return rval; } static int findNumOcc(const char *buf) { void *ptrs[MAXP]; int n_ptrs = 0; int i,j; int rval = 0; while ( n_ptrs < sizeof(ptrs)/sizeof(ptrs[0]) && (buf=strchr(buf,'[')) ) { if ( 1 == sscanf(buf+1,"%p", &ptrs[n_ptrs]) ) n_ptrs++; buf++; } /* We should find an address close to epicsStackTraceRecurseGbl twice */ for (i=0; i= (char*)epicsStackTraceRecurseGbl && (char*)ptrs[i] < (char*)epicsStackTraceRecurseGbl + WINDOW_SZ ) { rval ++; testDiag("found address %p again\n", ptrs[i]); } } j++; } } return rval; } MAIN(epicsStackTraceTest) { int features, all_features; TestDataRec testData; int gblFound, lclFound, numFound, dynFound; char *nl, *p; testData.pos = 0; testPlan(5); features = epicsStackTraceGetFeatures(); all_features = EPICS_STACKTRACE_LCL_SYMBOLS | EPICS_STACKTRACE_GBL_SYMBOLS | EPICS_STACKTRACE_DYN_SYMBOLS | EPICS_STACKTRACE_ADDRESSES; if ( ! testOk( (features & ~all_features) == 0, "epicsStackTraceGetFeatures() obtains features") ) testAbort("epicsStackTraceGetFeatures() not working as expected"); testData.pos = 0; testDiag("calling a few nested routines and eventually dump a stack trace"); eltc(0); errlogAddListener( logClient, &testData ); gfp(3); errlogRemoveListeners( logClient, &testData ); eltc(1); /* ensure there's a terminating NUL -- we have reserved space for it */ testData.buf[testData.pos] = 0; testDiag("now scan the result for what we expect"); dynFound = findStringOcc( testData.buf, "epicsStackTrace" ); gblFound = findStringOcc( testData.buf, "epicsStackTraceRecurseGbl" ); lclFound = findStringOcc( testData.buf, "epicsStackTraceRecurseLcl" ); numFound = findNumOcc ( testData.buf ); if ( (features & EPICS_STACKTRACE_DYN_SYMBOLS) ) { testOk( dynFound == 1, "dumping symbol from library" ); } else { testSkip(1, "no support for dumping library symbols on this platform"); } if ( (features & EPICS_STACKTRACE_GBL_SYMBOLS) ) { testOk( gblFound == 2, "dumping global symbols" ); } else { testSkip(1, "no support for dumping global symbols on this platform"); } if ( (features & EPICS_STACKTRACE_LCL_SYMBOLS) ) { testOk( lclFound == 2, "dumping local symbols" ); } else { testSkip(1, "no support for dumping local symbols on this platform"); } if ( (features & EPICS_STACKTRACE_ADDRESSES) ) { testOk( numFound > 0, "dumping addresses" ); } else { testSkip(1 , "no support for dumping addresses on this platform"); } p = testData.buf; while ( (nl = strchr(p,'\n')) ) { *nl = 0; testDiag("%s",p); *nl = '\n'; p = nl+1; } testDiag("%s", p); testDone(); return 0; }