From 4d30e94aa6338a3b11b69cc147118afbd32a0c93 Mon Sep 17 00:00:00 2001 From: oldzhu Date: Fri, 16 Dec 2022 01:44:50 -0800 Subject: [PATCH 1/3] Add wildcard filtering support when dump on exceptions --- profiler/inc/profilerstring.h | 36 ++++++++ profiler/src/ProcDumpProfiler.cpp | 145 +++++++++++++++++++++++++++++- src/ProcDumpConfiguration.c | 2 +- src/ProfilerHelpers.c | 25 +++++- 4 files changed, 200 insertions(+), 8 deletions(-) diff --git a/profiler/inc/profilerstring.h b/profiler/inc/profilerstring.h index 16ac3ec..139ee9b 100644 --- a/profiler/inc/profilerstring.h +++ b/profiler/inc/profilerstring.h @@ -59,6 +59,30 @@ inline int wcscmp(const char16_t *lhs, const char16_t *rhs) return lhs[i] - rhs[i]; } +inline int wcscpy(char16_t *dst, size_t dmax,const char16_t *src) +{ + while (dmax > 0) { + *dst = *src; + if (*dst == u'\0') { + return 0; + } + + dmax--; + dst++; + src++; + } + return 0; +} +inline int wcslwr(char16_t *str, size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + if ((str[i] >= u'A') && (str[i] <= u'Z')) + str[i] = str[i] + (u'a' - u'A'); + } + return 0; +} + #endif // defined(__WIN32) // 16 bit string type that works cross plat and doesn't require changing widths @@ -333,3 +357,15 @@ inline bool EndsWith(const String &lhs, const String &rhs) return true; } + +inline WCHAR *getWCHARs(const String &src) +{ + size_t len = src.Length(); + WCHAR* tempbuff = new WCHAR[len+1]; + for (size_t i = 0; i < len; ++i) + { + tempbuff[i] = src[i]; + } + tempbuff[len] = u'\0'; + return tempbuff; +} \ No newline at end of file diff --git a/profiler/src/ProcDumpProfiler.cpp b/profiler/src/ProcDumpProfiler.cpp index a8fb114..c901c7c 100644 --- a/profiler/src/ProcDumpProfiler.cpp +++ b/profiler/src/ProcDumpProfiler.cpp @@ -11,6 +11,143 @@ INITIALIZE_EASYLOGGINGPP +//-------------------------------------------------------------------- +// +// WildcardSearch - Search string supports '*' anywhere and any number of times +// +//-------------------------------------------------------------------- +bool WildcardSearch(WCHAR* szClassName, WCHAR* szSearch) +{ + if ((szClassName == NULL) || (szSearch == NULL)) + return false; + + WCHAR* szClassLowerMalloc = (WCHAR*)malloc(sizeof(WCHAR)*(wcslen(szClassName)+1)); + if (szClassLowerMalloc == NULL) + return false; + + WCHAR* szSearchLowerMalloc = (WCHAR*)malloc(sizeof(WCHAR)*(wcslen(szSearch)+1)); + if (szSearchLowerMalloc == NULL) + { + free(szClassLowerMalloc); + return false; + } + + WCHAR* szClassLower = szClassLowerMalloc; + wcscpy(szClassLower, (wcslen(szClassName)+1), szClassName); + wcslwr(szClassLower, (wcslen(szClassName)+1)); + + WCHAR* szSearchLower = szSearchLowerMalloc; + wcscpy(szSearchLower, (wcslen(szSearch)+1), szSearch); + wcslwr(szSearchLower, (wcslen(szSearch)+1)); + + while ((*szSearchLower != u'\0') && (*szClassLower != u'\0')) + { + if (*szSearchLower != u'*') + { + // Straight (case insensitive) compare + if (*szSearchLower != *szClassLower) + { + free(szClassLowerMalloc); + szClassLowerMalloc = NULL; + + free(szSearchLowerMalloc); + szSearchLowerMalloc = NULL; + + return false; + } + + szSearchLower++; + szClassLower++; + continue; + } + + // + // Wildcard processing + // + +ContinueWildcard: + szSearchLower++; + + // The wildcard is on the end; e.g. '*' 'blah*' or '*blah*' + // Must be a match + if (*szSearchLower == u'\0') + { + free(szClassLowerMalloc); + szClassLowerMalloc = NULL; + + free(szSearchLowerMalloc); + szSearchLowerMalloc = NULL; + return true; + } + + // Double Wildcard; e.g. '**' 'blah**' or '*blah**' + if (*szSearchLower == u'*') + goto ContinueWildcard; + + // Find the length of the sub-string to search for + int endpos = 0; + while ((szSearchLower[endpos] != u'\0') && (szSearchLower[endpos] != u'*')) + endpos++; + + // Find a match of the sub-search string anywhere within the class string + int cc = 0; // Offset in to the Class + int ss = 0; // Offset in to the Sub-Search + while (ss < endpos) + { + if (szClassLower[ss+cc] == u'\0') + { + free(szClassLowerMalloc); + szClassLowerMalloc = NULL; + + free(szSearchLowerMalloc); + szSearchLowerMalloc = NULL; + + return false; + } + + if (szSearchLower[ss] != szClassLower[ss+cc]) + { + cc++; + ss = 0; + continue; + } + ss++; + } + + // If we get here, we found a match; move each string forward + szSearchLower += ss; + szClassLower += (ss + cc); + } + + // Do we have a trailing wildcard? + // This happens when Class = ABC.XYZ and Search = *XYZ* + // Needed as the trailing wildcard code (above) doesn't run after the ss/cc search as Class is null + while (*szSearchLower == u'*') + { + szSearchLower++; + } + + // If Class and Search have no residual, this is a match. + if ((*szSearchLower == u'\0') && (*szClassLower == u'\0')) + { + free(szClassLowerMalloc); + szClassLowerMalloc = NULL; + + free(szSearchLowerMalloc); + szSearchLowerMalloc = NULL; + + return true; + } + + free(szClassLowerMalloc); + szClassLowerMalloc = NULL; + + free(szSearchLowerMalloc); + szSearchLowerMalloc = NULL; + + return false; +} + //------------------------------------------------------------------------------------------------------------------------------------------------------ // HealthThread // @@ -512,6 +649,8 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) } LOG(TRACE) << "CorProfiler::ExceptionThrown: exception name: " << exceptionName.ToCStr(); + + WCHAR* exceptionWCHARs = getWCHARs(exceptionName); // Check to see if we have any matches on exceptions for (auto & element : exceptionMonitorList) @@ -524,9 +663,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) return E_FAIL; } - String exc(exception); - free(exception); - if((exceptionName==exc || element.exception.compare("") == 0) && element.exceptionID != thrownObjectId) + if(WildcardSearch(exceptionWCHARs,exception) && element.exceptionID != thrownObjectId) { // // We have to serialize calls to the diag pipe to avoid concurrency issues @@ -597,7 +734,9 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) UnloadProfiler(); } } + free(exception); } + free(exceptionWCHARs); LOG(TRACE) << "CorProfiler::ExceptionThrown: Exit"; return S_OK; diff --git a/src/ProcDumpConfiguration.c b/src/ProcDumpConfiguration.c index 470ec5b..ef5128d 100644 --- a/src/ProcDumpConfiguration.c +++ b/src/ProcDumpConfiguration.c @@ -439,8 +439,8 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) 0 == strcasecmp( argv[i], "-f" )) { if( i+1 >= argc ) return PrintUsage(); - self->ExceptionFilter = strdup(argv[i+1]); + if( tolower( self->ExceptionFilter[0] ) > 'z' || ( self->ExceptionFilter[0] != '*' && tolower( self->ExceptionFilter[0] ) < 'a' ) ) return PrintUsage(); i++; } diff --git a/src/ProfilerHelpers.c b/src/ProfilerHelpers.c index 79852ec..fcba7aa 100644 --- a/src/ProfilerHelpers.c +++ b/src/ProfilerHelpers.c @@ -297,9 +297,10 @@ char* GetEncodedExceptionFilter(char* exceptionFilterCmdLine, unsigned int numDu char* exceptionFilter = NULL; char* exceptionFilterCur = NULL; char tmp[10]; + size_t len; // If no exceptions were specified using -f we should dump on any exception (hence we add ) - char* cpy = exceptionFilterCmdLine ? strdup(exceptionFilterCmdLine) : strdup(""); + char* cpy = exceptionFilterCmdLine ? strdup(exceptionFilterCmdLine) : strdup("*"); numberOfDumpsLen = sprintf(tmp, "%d", numDumps); @@ -313,11 +314,11 @@ char* GetEncodedExceptionFilter(char* exceptionFilterCmdLine, unsigned int numDu free(cpy); - cpy = exceptionFilterCmdLine ? strdup(exceptionFilterCmdLine) : strdup(""); + cpy = exceptionFilterCmdLine ? strdup(exceptionFilterCmdLine) : strdup("*"); totalExceptionNameLen++; // NULL terminator - exceptionFilter = malloc(totalExceptionNameLen+numExceptions*(numberOfDumpsLen+2)); // +1 for : seperator +1 for ; seperator + exceptionFilter = malloc(totalExceptionNameLen+numExceptions*(numberOfDumpsLen+2+2)); // +1 for : seperator +1 for ; seperator +2 for 2 '*' wildcard if(exceptionFilter==NULL) { return NULL; @@ -328,7 +329,23 @@ char* GetEncodedExceptionFilter(char* exceptionFilterCmdLine, unsigned int numDu token = strtok(cpy, ","); while(token!=NULL) { - exceptionFilterCur += sprintf(exceptionFilterCur, "%s:%d;", token, numDumps); + len = strlen(token); + if(token[0] != '*' && token[len-1] != '*') + { + exceptionFilterCur += sprintf(exceptionFilterCur, "*%s*:%d;", token, numDumps); + } + else if(token[0] != '*') + { + exceptionFilterCur += sprintf(exceptionFilterCur, "*%s:%d;", token, numDumps); + } + else if(token[len-1] != '*') + { + exceptionFilterCur += sprintf(exceptionFilterCur, "%s*:%d;", token, numDumps); + } + else + { + exceptionFilterCur += sprintf(exceptionFilterCur, "%s:%d;", token, numDumps); + } token = strtok(NULL, ","); } From fe9199c86cd5c775fcfe6bb28ef35cce70b433df Mon Sep 17 00:00:00 2001 From: oldzhu Date: Sat, 17 Dec 2022 18:07:04 -0800 Subject: [PATCH 2/3] Make WildcardSearch be part of the profiler class --- profiler/inc/ProcDumpProfiler.h | 1 + profiler/src/ProcDumpProfiler.cpp | 86 +++++++++++++++---------------- src/ProfilerHelpers.c | 2 +- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/profiler/inc/ProcDumpProfiler.h b/profiler/inc/ProcDumpProfiler.h index bba1305..769e934 100644 --- a/profiler/inc/ProcDumpProfiler.h +++ b/profiler/inc/ProcDumpProfiler.h @@ -120,6 +120,7 @@ class CorProfiler : public ICorProfilerCallback8 void SendCatastrophicFailureStatus(); int send_all(int socket, void* buffer, size_t length); int recv_all(int socket, void* buffer, size_t length); + bool WildcardSearch(WCHAR*, WCHAR*); public: CorProfiler(); diff --git a/profiler/src/ProcDumpProfiler.cpp b/profiler/src/ProcDumpProfiler.cpp index c901c7c..3121416 100644 --- a/profiler/src/ProcDumpProfiler.cpp +++ b/profiler/src/ProcDumpProfiler.cpp @@ -10,13 +10,54 @@ INITIALIZE_EASYLOGGINGPP +//------------------------------------------------------------------------------------------------------------------------------------------------------ +// HealthThread +// +// Periodically (default 5s) calls the procdump status pipe for a health check. If we're unable to communicate with procdump it means it has been +// terminated and we can unload. +//------------------------------------------------------------------------------------------------------------------------------------------------------ +void* HealthThread(void* args) +{ + LOG(TRACE) << "HealthThread: Enter"; + + CorProfiler* profiler = static_cast (args); + char* sockPath = NULL; + + while(true) + { + if(profiler->SendDumpCompletedStatus("", PROFILER_STATUS_HEALTH)==-1) + { + LOG(TRACE) << "HealthThread: Procdump not reachable..unloading ourselves"; + sockPath = profiler->GetSocketPath("procdump/procdump-status-", profiler->procDumpPid, getpid()); + if(sockPath) + { + LOG(TRACE) << "HealthThread: Unlinking the socket path " << sockPath; + // if procdump exited abnormally, the socket file will still exist so we clean it up. + unlink(sockPath); + + free(sockPath); + } + + profiler->UnloadProfiler(); + break; + } + + LOG(TRACE) << "HealthThread: Procdump running..."; + + sleep(HEALTH_POLL_FREQ); // sleep is a thread cancellation point + } + + LOG(TRACE) << "HealthThread: Exit"; + return NULL; +} //-------------------------------------------------------------------- // -// WildcardSearch - Search string supports '*' anywhere and any number of times +// CorProfiler::WildcardSearch +// Search string supports '*' anywhere and any number of times // //-------------------------------------------------------------------- -bool WildcardSearch(WCHAR* szClassName, WCHAR* szSearch) +bool CorProfiler::WildcardSearch(WCHAR* szClassName, WCHAR* szSearch) { if ((szClassName == NULL) || (szSearch == NULL)) return false; @@ -148,47 +189,6 @@ bool WildcardSearch(WCHAR* szClassName, WCHAR* szSearch) return false; } -//------------------------------------------------------------------------------------------------------------------------------------------------------ -// HealthThread -// -// Periodically (default 5s) calls the procdump status pipe for a health check. If we're unable to communicate with procdump it means it has been -// terminated and we can unload. -//------------------------------------------------------------------------------------------------------------------------------------------------------ -void* HealthThread(void* args) -{ - LOG(TRACE) << "HealthThread: Enter"; - - CorProfiler* profiler = static_cast (args); - char* sockPath = NULL; - - while(true) - { - if(profiler->SendDumpCompletedStatus("", PROFILER_STATUS_HEALTH)==-1) - { - LOG(TRACE) << "HealthThread: Procdump not reachable..unloading ourselves"; - sockPath = profiler->GetSocketPath("procdump/procdump-status-", profiler->procDumpPid, getpid()); - if(sockPath) - { - LOG(TRACE) << "HealthThread: Unlinking the socket path " << sockPath; - // if procdump exited abnormally, the socket file will still exist so we clean it up. - unlink(sockPath); - - free(sockPath); - } - - profiler->UnloadProfiler(); - break; - } - - LOG(TRACE) << "HealthThread: Procdump running..."; - - sleep(HEALTH_POLL_FREQ); // sleep is a thread cancellation point - } - - LOG(TRACE) << "HealthThread: Exit"; - return NULL; -} - //------------------------------------------------------------------------------------------------------------------------------------------------------ // CorProfiler::CorProfiler //------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/ProfilerHelpers.c b/src/ProfilerHelpers.c index fcba7aa..1e62d6e 100644 --- a/src/ProfilerHelpers.c +++ b/src/ProfilerHelpers.c @@ -297,7 +297,7 @@ char* GetEncodedExceptionFilter(char* exceptionFilterCmdLine, unsigned int numDu char* exceptionFilter = NULL; char* exceptionFilterCur = NULL; char tmp[10]; - size_t len; + size_t len = 0; // If no exceptions were specified using -f we should dump on any exception (hence we add ) char* cpy = exceptionFilterCmdLine ? strdup(exceptionFilterCmdLine) : strdup("*"); From a0c587020a623a943e6d1199e5474424623140d6 Mon Sep 17 00:00:00 2001 From: oldzhu Date: Mon, 19 Dec 2022 23:48:47 -0800 Subject: [PATCH 3/3] add testing cases for wildcard exception filltering --- ...tnet_0WildcardExceptionDump1Thrown_dump.sh | 31 +++++++++++++++++ ...tnet_1WildcardExceptionDump1Thrown_dump.sh | 31 +++++++++++++++++ ...tnet_2WildcardExceptionDump2Thrown_dump.sh | 33 +++++++++++++++++++ .../dotnet_wildcardexception_notdump.sh | 32 ++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100755 tests/integration/scenarios/dotnet_0WildcardExceptionDump1Thrown_dump.sh create mode 100755 tests/integration/scenarios/dotnet_1WildcardExceptionDump1Thrown_dump.sh create mode 100755 tests/integration/scenarios/dotnet_2WildcardExceptionDump2Thrown_dump.sh create mode 100755 tests/integration/scenarios/dotnet_wildcardexception_notdump.sh diff --git a/tests/integration/scenarios/dotnet_0WildcardExceptionDump1Thrown_dump.sh b/tests/integration/scenarios/dotnet_0WildcardExceptionDump1Thrown_dump.sh new file mode 100755 index 0000000..989e054 --- /dev/null +++ b/tests/integration/scenarios/dotnet_0WildcardExceptionDump1Thrown_dump.sh @@ -0,0 +1,31 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); + +pushd . +cd $TESTWEBAPIPATH +dotnet run --urls=http://localhost:5032& +TESTPID=$! +sleep 10s +sudo $PROCDUMPPATH -e -f invalid TestWebApi testdump& +sleep 5s +wget http://localhost:5032/throwinvalidoperation +sleep 5s +if [ -f "testdump_0" ]; then + rm -rf testdump_0 + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi diff --git a/tests/integration/scenarios/dotnet_1WildcardExceptionDump1Thrown_dump.sh b/tests/integration/scenarios/dotnet_1WildcardExceptionDump1Thrown_dump.sh new file mode 100755 index 0000000..6247579 --- /dev/null +++ b/tests/integration/scenarios/dotnet_1WildcardExceptionDump1Thrown_dump.sh @@ -0,0 +1,31 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); + +pushd . +cd $TESTWEBAPIPATH +dotnet run --urls=http://localhost:5032& +TESTPID=$! +sleep 10s +sudo $PROCDUMPPATH -e -f Invali*xception TestWebApi testdump& +sleep 5s +wget http://localhost:5032/throwinvalidoperation +sleep 5s +if [ -f "testdump_0" ]; then + rm -rf testdump_0 + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi diff --git a/tests/integration/scenarios/dotnet_2WildcardExceptionDump2Thrown_dump.sh b/tests/integration/scenarios/dotnet_2WildcardExceptionDump2Thrown_dump.sh new file mode 100755 index 0000000..f0ccdf7 --- /dev/null +++ b/tests/integration/scenarios/dotnet_2WildcardExceptionDump2Thrown_dump.sh @@ -0,0 +1,33 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); + +pushd . +cd $TESTWEBAPIPATH +dotnet run --urls=http://localhost:5032& +TESTPID=$! +sleep 10s +sudo $PROCDUMPPATH -n 2 -e -f In*rat*ption TestWebApi testdump& +sleep 5s +wget http://localhost:5032/throwinvalidoperation +wget http://localhost:5032/throwinvalidoperation +sleep 5s +if [[ -f "testdump_0" && -f "testdump_1" ]]; then + rm -rf testdump_0 + rm -rf testdump_1 + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi diff --git a/tests/integration/scenarios/dotnet_wildcardexception_notdump.sh b/tests/integration/scenarios/dotnet_wildcardexception_notdump.sh new file mode 100755 index 0000000..9a9e6e9 --- /dev/null +++ b/tests/integration/scenarios/dotnet_wildcardexception_notdump.sh @@ -0,0 +1,32 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); + +pushd . +cd $TESTWEBAPIPATH +dotnet run --urls=http://localhost:5032& +TESTPID=$! +sleep 10s +sudo $PROCDUMPPATH -e -f on*Existing TestWebApi testdump& +sleep 5s +wget http://localhost:5032/throwinvalidoperation +sleep 5s +sudo pkill -9 procdump +if [ -f "testdump_0" ]; then + rm -rf testdump_0 + sudo pkill -9 TestWebApi + popd + exit 1 +else + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +fi