Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to filter on exception message #160

Merged
merged 9 commits into from
Feb 24, 2023
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Options:
-fc File descriptor count threshold above which to create a dump of the process.
-sig Signal number to intercept to create a dump of the process.
-e [.NET] Create dump when the process encounters an exception.
-f [.NET] Filter (include) on the (comma seperated) exception name(s).
-f [.NET] Filter (include) on the (comma seperated) exception name(s) and exception message(s).
-pf Polling frequency.
-o Overwrite existing dump file.
-log Writes extended ProcDump tracing to syslog.
Expand Down Expand Up @@ -122,6 +122,14 @@ The following will create a core dump when the target .NET application throws a
```
sudo procdump -e -f System.InvalidOperationException 1234
```
The include filter supports partial and wildcard matching, so the following will create a core dump too for a System.InvalidOperationException
```
sudo procdump -e -f InvalidOperation 1234
```
or
```
sudo procdump -e -f "*Invali*Operation*" 1234
```
> All options can also be used with `-w`, to wait for any process with the given name.

The following waits for a process named `my_application` and creates a core dump immediately when it is found.
Expand Down
2 changes: 1 addition & 1 deletion procdump.1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Options:
-fc File descriptor count threshold above which to create a dump of the process.
-sig Signal number to intercept to create a dump of the process.
-e [.NET] Create dump when the process encounters an exception.
-f [.NET] Filter (include) on the (comma seperated) exception name(s).
-f [.NET] Filter (include) on the (comma seperated) exception name(s) and exception message(s).
-pf Polling frequency.
-o Overwrite existing dump file.
-log Writes extended ProcDump tracing to syslog.
Expand Down
3 changes: 2 additions & 1 deletion profiler/inc/ProcDumpProfiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ class CorProfiler : public ICorProfilerCallback8
pthread_mutex_t endDumpCondition;

String GetExceptionName(ObjectID objectId);
String GetExceptionMessage(ObjectID objectId);
bool ParseClientData(char* fw);
WCHAR* GetUint16(char* buffer);
std::string GetDumpName(uint16_t dumpCount);
std::string GetDumpName(uint16_t dumpCount,std::string exceptionName);
std::string GetProcessName();
bool GenerateCoreClrDump(char* socketName, char* dumpFileName);
bool IsCoreClrProcess(pid_t pid, char** socketName);
Expand Down
8 changes: 8 additions & 0 deletions profiler/inc/profilerstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ inline int wcslwr(char16_t *str, size_t len)

#endif // defined(__WIN32)

template<typename TARGET, typename SOURCE>
TARGET convertString(const SOURCE &s)
{
TARGET result;
result.assign(s.begin(), s.end());
return result;
}

// 16 bit string type that works cross plat and doesn't require changing widths
// on non-windows platforms
class String
Expand Down
200 changes: 193 additions & 7 deletions profiler/src/ProcDumpProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,21 @@ int CorProfiler::SendDumpCompletedStatus(std::string dump, char status)
return -1;
}

// make sure the procdump listening on sockect before try to connect
int retryCount = 0;
while(access(tmpFolder,F_OK)!=0)
{
usleep(10);
retryCount++;
if(retryCount == 5)
{
LOG(TRACE) << "CorProfiler::SendDumpCompletedStatus: Socket path " << tmpFolder << " not found";
delete[] tmpFolder;
close(s);
return -1;
}
}

LOG(TRACE) << "CorProfiler::SendDumpCompletedStatus: Trying to connect...";

remote.sun_family = AF_UNIX;
Expand Down Expand Up @@ -589,7 +604,7 @@ std::string CorProfiler::GetProcessName()
//------------------------------------------------------------------------------------------------------------------------------------------------------
// CorProfiler::GetDumpName
//------------------------------------------------------------------------------------------------------------------------------------------------------
std::string CorProfiler::GetDumpName(u_int16_t dumpCount)
std::string CorProfiler::GetDumpName(u_int16_t dumpCount,std::string exceptionName)
{
LOG(TRACE) << "CorProfiler::GetDumpName: Enter";
std::ostringstream tmp;
Expand All @@ -616,7 +631,7 @@ std::string CorProfiler::GetDumpName(u_int16_t dumpCount)
strftime(date, 26, "%Y-%m-%d_%H:%M:%S", timerInfo);
LOG(TRACE) << "CorProfiler::GetDumpName: Date/time " << date;

tmp << fullDumpPath << processName.c_str() << "_exception_" << date;
tmp << fullDumpPath << processName.c_str() << "_" << dumpCount << "_" << exceptionName << "_" << date;
LOG(TRACE) << "CorProfiler::GetDumpName: Full path name " << tmp.str();
}
else
Expand All @@ -640,17 +655,31 @@ std::string CorProfiler::GetDumpName(u_int16_t dumpCount)
HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId)
{
LOG(TRACE) << "CorProfiler::ExceptionThrown: Enter";


String exceptionNameAndMsg;
String exceptionName = GetExceptionName(thrownObjectId);
if(exceptionName.Length() == 0)
{
LOG(TRACE) << "CorProfiler::ExceptionThrown: Unable to get the name of the exception.";
return E_FAIL;
}

LOG(TRACE) << "CorProfiler::ExceptionThrown: exception name: " << exceptionName.ToCStr();

WCHAR* exceptionWCHARs = getWCHARs(exceptionName);
String exceptionMsg = GetExceptionMessage(thrownObjectId);

if(exceptionMsg.Length() == 0)
{
exceptionNameAndMsg += exceptionName;
LOG(TRACE) << "CorProfiler::ExceptionThrown: exception name: " << exceptionNameAndMsg.ToCStr();
}
else
{
exceptionNameAndMsg += exceptionName;
exceptionNameAndMsg += WCHAR(": ");
exceptionNameAndMsg += exceptionMsg;
LOG(TRACE) << "CorProfiler::ExceptionThrown: exception name: " << exceptionNameAndMsg.ToCStr();
}

WCHAR* exceptionWCHARs = getWCHARs(exceptionNameAndMsg);

// Check to see if we have any matches on exceptions
for (auto & element : exceptionMonitorList)
Expand All @@ -677,7 +706,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId)

LOG(TRACE) << "CorProfiler::ExceptionThrown: Starting dump generation for exception " << exceptionName.ToCStr() << " with dump count set to " << std::to_string(element.dumpsToCollect);

std::string dump = GetDumpName(element.collectedDumps);
std::string dump = GetDumpName(element.collectedDumps,convertString<std::string,std::wstring>(exceptionName.ToWString()));

// Invoke coreclr dump generation
char* socketName = NULL;
Expand Down Expand Up @@ -817,6 +846,163 @@ String CorProfiler::GetExceptionName(ObjectID objectId)
return name;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------
// CorProfiler::GetExceptionMesssage
//------------------------------------------------------------------------------------------------------------------------------------------------------
String CorProfiler::GetExceptionMessage(ObjectID objectId)
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Enter with objectId =" << objectId;

ClassID classId;
ClassID parentClassId = NULL;
ClassID systemExceptionClassId = NULL;
ModuleID moduleId;
mdTypeDef typeDefToken;
IMetaDataImport* metadata = NULL;
ULONG read = 0;
WCHAR exceptionName[256];
ULONG fieldCount;
COR_FIELD_OFFSET* pFieldOffsets = NULL;
WCHAR fieldName[256];
ULONG stringLengthOffset;
ULONG stringBufferOffset;
LONG_PTR msgField;
INT32 stringLength;
WCHAR * stringBuffer = NULL;
String msg;

HRESULT hRes = corProfilerInfo8->GetClassFromObject(objectId, &classId);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetClassFromObject " << hRes;
return WCHAR("");
}

//
// Loop to find the classId for "System.Exception"
//
do
{
hRes = corProfilerInfo8->GetClassIDInfo2(classId, &moduleId, &typeDefToken, &parentClassId, 0, nullptr, nullptr);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetClassIDInfo2 " << hRes;
return WCHAR("");
}

hRes = corProfilerInfo8->GetModuleMetaData(moduleId, ofRead, IID_IMetaDataImport, (IUnknown**) (&metadata));
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetModuleMetaData " << hRes;
return WCHAR("");
}

hRes = metadata->GetTypeDefProps(typeDefToken, exceptionName, 256, &read, NULL, NULL);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetTypeDefProps " << hRes;
metadata->Release();
return WCHAR("");
}

if(wcscmp(u"System.Exception",exceptionName)==0)
{
systemExceptionClassId = classId;
break;
}
else
{
classId = parentClassId;
metadata->Release();
metadata = NULL;
}
}while(parentClassId != NULL);
oldzhu marked this conversation as resolved.
Show resolved Hide resolved

if(systemExceptionClassId != NULL)
{
hRes = corProfilerInfo8->GetClassLayout(systemExceptionClassId, NULL, 0, &fieldCount, NULL);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetClassLayout " << hRes;
if(metadata != NULL) metadata->Release();
return WCHAR("");
}
if(fieldCount == 0)
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: GetClassLayout returned zero fieldCount";
if(metadata != NULL) metadata->Release();
return WCHAR("");
}

pFieldOffsets = new COR_FIELD_OFFSET[fieldCount];
if(pFieldOffsets==NULL)
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed memory allocation for pFieldOffsets";
if(metadata != NULL) metadata->Release();
return WCHAR("");;
}

hRes = corProfilerInfo8->GetClassLayout(systemExceptionClassId, pFieldOffsets, fieldCount, &fieldCount, NULL);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetClassLayout " << hRes;
if(metadata != NULL) metadata->Release();
delete [] pFieldOffsets;
return WCHAR("");
}

//
// Loop to lcoate the "_message" field
// Get stringLength from the "_message" field stringLengthOffset
// Copy the number of (stringLength+1) WCHARs from the "_message" field stringBufferOffset to our stringBuffer
//
for (ULONG fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++)
{
hRes = metadata->GetFieldProps(pFieldOffsets[fieldIndex].ridOfField, NULL, fieldName, 256, NULL, NULL,
NULL, NULL, NULL, NULL, NULL);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetFieldProps " << hRes;
if(metadata != NULL) metadata->Release();
delete [] pFieldOffsets;
return WCHAR("");
}

if(wcscmp(u"_message",fieldName)==0)
{
hRes = corProfilerInfo8->GetStringLayout2(&stringLengthOffset, &stringBufferOffset);
if(FAILED(hRes))
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed in call to GetStringLayout2 " << hRes;
if(metadata != NULL) metadata->Release();
delete [] pFieldOffsets;
return WCHAR("");
}
msgField = *(PLONG_PTR)(((BYTE *)objectId) + pFieldOffsets[fieldIndex].ulOffset);
stringLength = *(PINT32)((BYTE *)msgField + stringLengthOffset);
stringBuffer = new WCHAR[stringLength+1];
if(stringBuffer==NULL)
{
LOG(TRACE) << "CorProfiler::GetExceptionMessage: Failed memory allocation for stringBuffer";
if(metadata != NULL) metadata->Release();
delete [] pFieldOffsets;
return WCHAR("");;
}
memcpy(stringBuffer, (BYTE *)msgField+stringBufferOffset,(stringLength+1)*sizeof(WCHAR));
msg+=stringBuffer;
break;
}
}
}

if(metadata != NULL) metadata->Release();
if(pFieldOffsets != NULL) delete [] pFieldOffsets;
if(stringBuffer != NULL) delete stringBuffer;

LOG(TRACE) << "CorProfiler::GetExceptionMessage: Exit";
return msg;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------
// IsCoreClrProcess
//------------------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/ProcDumpConfiguration.c
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ int PrintUsage()
printf(" -fc File descriptor count threshold above which to create a dump of the process.\n");
printf(" -sig Signal number to intercept to create a dump of the process.\n");
printf(" -e [.NET] Create dump when the process encounters an exception.\n");
printf(" -f [.NET] Filter (include) on the (comma seperated) exception name(s).\n");
printf(" -f [.NET] Filter (include) on the (comma seperated) exception name(s) and exception messages(s).\n");
printf(" -pf Polling frequency.\n");
printf(" -o Overwrite existing dump file.\n");
printf(" -log Writes extended ProcDump tracing to syslog.\n");
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/runProcDumpAndValidate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function runProcDumpAndValidate {
# We launch procdump in background and wait for 10 secs to complete the monitoring
echo "$PROCDUMPPATH $2 $3 $childpid $dumpParam "
echo [`date +"%T.%3N"`] Starting ProcDump
$PROCDUMPPATH $2 $3 $childpid $dumpParam&
$PROCDUMPPATH -log $2 $3 $childpid $dumpParam&
pidPD=$!
echo "ProcDump PID: $pidPD"
sleep 10s
Expand All @@ -58,7 +58,7 @@ function runProcDumpAndValidate {
# We launch procdump in background and wait for 10 secs to complete the monitoring
echo "$PROCDUMPPATH $2 $3 $dumpParam $TESTPROGNAME"
echo [`date +"%T.%3N"`] Starting ProcDump
$PROCDUMPPATH $2 $3 $dumpParam "$TESTPROGNAME"&
$PROCDUMPPATH -log $2 $3 $dumpParam "$TESTPROGNAME"&
pidPD=$!
echo "ProcDump PID: $pidPD"
sleep 10s
Expand Down
Loading