diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fdb921..1496e49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,14 @@ add_executable(procdump ${PROJECT_BINARY_DIR}/ProcDumpProfiler.o ) -target_compile_options(procdump PRIVATE -g -pthread -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Werror -D_GNU_SOURCE -std=c++11 -O2) +target_compile_options(procdump PRIVATE -g -pthread -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Werror -D_GNU_SOURCE -std=c++11) +if (NOT DEFINED ENV{DEBUG}) + message(STATUS "procdump DEBUG disabled. Enabling optimizations.") + add_compile_options(procdump -O2) +else() + message(STATUS "procdump DEBUG enabled. Disabling optimizations.") +endif() + target_include_directories(procdump PUBLIC ${procdump_INC} @@ -210,7 +217,14 @@ add_executable(ProcDumpTestApplication ${procdump_Test}/ProcDumpTestApplication.c ) -target_compile_options(ProcDumpTestApplication PRIVATE -g -pthread -std=gnu99 -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -Werror -O2) +target_compile_options(ProcDumpTestApplication PRIVATE -g -pthread -std=gnu99 -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -Werror) +if (NOT DEFINED ENV{DEBUG}) + message(STATUS "ProcDumpTestApplication DEBUG disabled. Enabling optimizations.") + add_compile_options(ProcDumpTestApplication -O2) +else() + message(STATUS "ProcDumpTestApplication DEBUG enabled. Disabling optimizations.") +endif() + target_include_directories(ProcDumpTestApplication PUBLIC /usr/include diff --git a/include/GenHelpers.h b/include/GenHelpers.h index 41827a5..d9c6229 100644 --- a/include/GenHelpers.h +++ b/include/GenHelpers.h @@ -108,6 +108,7 @@ static inline void cancel_pthread(unsigned long* val) int* GetSeparatedValues(char* src, char* separator, int* numValues); bool ConvertToInt(const char* src, int* conv); +bool ConvertToIntHex(const char* src, int* conv); bool IsValidNumberArg(const char *arg); bool CheckKernelVersion(int major, int minor); uint16_t* GetUint16(char* buffer); @@ -121,6 +122,8 @@ char* GetSocketPath(char* prefix, pid_t pid, pid_t targetPid); int send_all(int socket, void *buffer, size_t length); int recv_all(int socket, void* buffer, size_t length); pid_t gettid() noexcept; +unsigned long GetCoreDumpFilter(int pid); +bool SetCoreDumpFilter(int pid, unsigned long filter); #endif // GENHELPERS_H diff --git a/include/ProcDumpConfiguration.h b/include/ProcDumpConfiguration.h index 32e2352..65156ac 100644 --- a/include/ProcDumpConfiguration.h +++ b/include/ProcDumpConfiguration.h @@ -104,7 +104,8 @@ struct ProcDumpConfiguration bool DiagnosticsLoggingEnabled; // -log int ThreadThreshold; // -tc int FileDescriptorThreshold; // -fc - int SignalNumber; // -sig + int* SignalNumber; // -sig + int SignalCount; int PollingInterval; // -pf char *CoreDumpPath; // char *CoreDumpName; // @@ -115,6 +116,7 @@ struct ProcDumpConfiguration bool bRestrackEnabled; // -restrack bool bLeakReportInProgress; int SampleRate; // Record every X resource allocation in restrack + int CoreDumpMask; // -mc (core dump mask) // // Keeps track of the memory allocations when -restrack is specified. diff --git a/include/Process.h b/include/Process.h index 5efe91a..654962a 100644 --- a/include/Process.h +++ b/include/Process.h @@ -276,7 +276,8 @@ struct ProcessStatus { // ----------------------------------------------------------- bool GetProcessStat(pid_t pid, struct ProcessStat *proc); -char * GetProcessName(pid_t pid); +char* GetProcessName(pid_t pid); +char* GetProcessNameFromCmdLine(char* cmdLine); pid_t GetProcessPgid(pid_t pid); bool LookupProcessByPid(pid_t pid); bool LookupProcessByPgid(pid_t pid); diff --git a/src/CoreDumpWriter.cpp b/src/CoreDumpWriter.cpp index c0d6617..129f785 100644 --- a/src/CoreDumpWriter.cpp +++ b/src/CoreDumpWriter.cpp @@ -106,32 +106,54 @@ char* WriteCoreDump(struct CoreDumpWriter *self) // Enter critical section (block till we decrement semaphore) rc = WaitForQuitOrEvent(self->Config, &self->Config->semAvailableDumpSlots, INFINITE_WAIT); - if(rc == 0){ + if(rc == 0) + { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed WaitForQuitOrEvent."); exit(-1); } - if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0){ + if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0) + { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed pthread_setcanceltype."); exit(-1); } - switch (rc) { + switch (rc) + { case WAIT_OBJECT_0: // QUIT! Time for cleanup, no dump break; case WAIT_OBJECT_0+1: // We got a dump slot! { char* socketName = NULL; IsCoreClrProcess(self->Config->ProcessId, &socketName); - if ((dumpFileName = WriteCoreDumpInternal(self, socketName)) != NULL) { + unsigned int currentCoreDumpFilter = -1; + if(self->Config->CoreDumpMask != -1) + { + currentCoreDumpFilter = GetCoreDumpFilter(self->Config->ProcessId); + SetCoreDumpFilter(self->Config->ProcessId, self->Config->CoreDumpMask); + } + if ((dumpFileName = WriteCoreDumpInternal(self, socketName)) != NULL) + { // We're done here, unlock (increment) the sem - if(sem_post(&self->Config->semAvailableDumpSlots.semaphore) == -1){ + if(sem_post(&self->Config->semAvailableDumpSlots.semaphore) == -1) + { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed sem_post."); if(socketName) free(socketName); + if(self->Config->CoreDumpMask != -1 && currentCoreDumpFilter != -1) + { + SetCoreDumpFilter(self->Config->ProcessId, currentCoreDumpFilter); + } + exit(-1); } } + + if(self->Config->CoreDumpMask != -1 && currentCoreDumpFilter != -1) + { + SetCoreDumpFilter(self->Config->ProcessId, currentCoreDumpFilter); + } + if(socketName) free(socketName); } break; @@ -141,7 +163,9 @@ char* WriteCoreDump(struct CoreDumpWriter *self) Trace("WriteCoreDump: Error in default case"); break; } - if(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0){ + + if(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0) + { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed pthread_setcanceltype."); exit(-1); diff --git a/src/GenHelpers.cpp b/src/GenHelpers.cpp index f0cad62..7624836 100644 --- a/src/GenHelpers.cpp +++ b/src/GenHelpers.cpp @@ -94,6 +94,45 @@ bool ConvertToInt(const char* src, int* conv) return true; } +//-------------------------------------------------------------------- +// +// ConvertToIntHex - Helper to convert from a char* (hex) to int +// +//-------------------------------------------------------------------- +bool ConvertToIntHex(const char* src, int* conv) +{ + int temp = 0; + + for (size_t i=0; src[i] != '\0'; i++) + { + if ((src[i] >= '0') && (src[i] <= '9')) + { + // Shift left by 0x10 (16) and add the digit using an ASCII delta + temp *= 0x10; + temp += src[i] - '0'; + } + else if ((src[i] >= 'A') && (src[i] <= 'F')) + { + // Shift left by 0x10 (16) and add the digit using an ASCII delta + temp *= 0x10; + temp += 10 + (src[i] - 'A'); + } + else if ((src[i] >= 'a') && (src[i] <= 'f')) + { + // Shift left by 0x10 (16) and add the digit using an ASCII delta + temp *= 0x10; + temp += 10 + (src[i] - 'a'); + } + else + { + return false; + } + } + + *conv = temp; + return true; +} + //-------------------------------------------------------------------- // // CheckKernelVersion - Check to see if current kernel is greater than @@ -571,3 +610,55 @@ pid_t gettid() noexcept return 0; } + +//-------------------------------------------------------------------- +// +// GetCoreDumpFilter +// +// Returns the core dump filter for the specified process id. +//-------------------------------------------------------------------- +unsigned long GetCoreDumpFilter(int pid) +{ + unsigned long filter = -1; + + char filepath[PATH_MAX]; + snprintf(filepath, sizeof(filepath), "/proc/%d/coredump_filter", pid); + + FILE* file = fopen(filepath, "r"); + if (file != NULL) + { + int itemsRead = fscanf(file, "%lx", &filter); + if (itemsRead != 1) + { + filter = -1; + } + } + + fclose(file); + return filter; +} + +//-------------------------------------------------------------------- +// +// SetCoreDumpFilter +// +// Sets the core dump filter for the specified process id. +//-------------------------------------------------------------------- +bool SetCoreDumpFilter(int pid, unsigned long filter) +{ + bool ret = false; + char filepath[PATH_MAX]; + snprintf(filepath, sizeof(filepath), "/proc/%d/coredump_filter", pid); + + FILE *file = fopen(filepath, "w"); + if (file != NULL) + { + if(fprintf(file, "%ld", filter) > 0) + { + ret = true; + } + } + + fclose(file); + return ret; +} diff --git a/src/Monitor.cpp b/src/Monitor.cpp index e6711ae..1af8e5f 100644 --- a/src/Monitor.cpp +++ b/src/Monitor.cpp @@ -95,7 +95,7 @@ void* SignalThread(void *input) // access to the signal path (in SignalMonitoringThread). Note, there is still a race but // acceptable since it is very unlikely to occur. We also cancel the SignalMonitorThread to // break it out of waitpid call. - if(it->second->SignalNumber != -1) + if(it->second->SignalCount > 0) { for(int i=0; isecond->nThreads; i++) { @@ -537,7 +537,7 @@ int CreateMonitorThreads(struct ProcDumpConfiguration *self) } } - if (self->SignalNumber != -1 && !tooManyTriggers) + if (self->SignalCount > 0 && !tooManyTriggers) { if ((rc = CreateMonitorThread(self, Signal, SignalMonitoringThread, (void *)self)) != 0 ) { @@ -1152,7 +1152,17 @@ void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* // We are now in a signal-stop state signum = WSTOPSIG(wstatus); - if(signum == config->SignalNumber) + bool found = false; + for(int i = 0; i < config->SignalCount; i++) + { + if(signum == config->SignalNumber[i]) + { + found = true; + break; + } + } + + if(found == true) { // We have to detach in a STOP state so we can invoke gcore if(ptrace(PTRACE_DETACH, config->ProcessId, 0, SIGSTOP) == -1) @@ -1167,7 +1177,9 @@ void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* dumpFileName = WriteCoreDump(writer); if(dumpFileName == NULL) { - SetQuit(config, 1); + ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); + ptrace(PTRACE_DETACH, config->ProcessId, 0, 0); + break; } // @@ -1176,28 +1188,22 @@ void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, (std::string(dumpFileName) + ".restrack").c_str()); - if (id == 0) - { - SetQuit(config, 1); - } - else + if (id != 0) { leakReportThreads.push_back(id); } } - kill(config->ProcessId, SIGCONT); + ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); if(config->NumberOfDumpsCollected >= config->NumberOfDumpsToCollect) { // If we are over the max number of dumps to collect, send the original signal we intercepted. - kill(config->ProcessId, signum); pthread_mutex_unlock(&config->ptrace_mutex); + ptrace(PTRACE_DETACH, config->ProcessId, 0, 0); break; } - ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); - // Re-attach to the target process if (ptrace(PTRACE_SEIZE, config->ProcessId, NULL, NULL) == -1) { @@ -1213,11 +1219,6 @@ void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* // Resume execution of the target process ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); pthread_mutex_unlock(&config->ptrace_mutex); - - if(dumpFileName == NULL) - { - break; - } } } } diff --git a/src/ProcDumpConfiguration.cpp b/src/ProcDumpConfiguration.cpp index 1a08d20..f0c08f7 100644 --- a/src/ProcDumpConfiguration.cpp +++ b/src/ProcDumpConfiguration.cpp @@ -162,7 +162,8 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) self->DumpGCGeneration = -1; self->ThreadThreshold = -1; self->FileDescriptorThreshold = -1; - self->SignalNumber = -1; + self->SignalNumber = NULL; + self->SignalCount = 0; self->ThresholdSeconds = -1; self->bMemoryTriggerBelowValue = false; self->bTimerThreshold = false; @@ -180,6 +181,7 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) self->bRestrackEnabled = false; self->bLeakReportInProgress = false; self->SampleRate = 0; + self->CoreDumpMask = -1; self->socketPath = NULL; self->statusSocket = -1; @@ -266,6 +268,12 @@ void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self) self->MemoryThreshold = NULL; } + if(self->SignalNumber) + { + free(self->SignalNumber); + self->SignalNumber = NULL; + } + for (const auto& pair : self->memAllocMap) { if(pair.second) @@ -336,6 +344,7 @@ struct ProcDumpConfiguration * CopyProcDumpConfiguration(struct ProcDumpConfigur copy->bRestrackEnabled = self->bRestrackEnabled; copy->bLeakReportInProgress = self->bLeakReportInProgress; copy->SampleRate = self->SampleRate; + copy->CoreDumpMask = self->CoreDumpMask; copy->bMemoryTriggerBelowValue = self->bMemoryTriggerBelowValue; copy->MemoryThresholdCount = self->MemoryThresholdCount; copy->bMonitoringGCMemory = self->bMonitoringGCMemory; @@ -347,7 +356,30 @@ struct ProcDumpConfiguration * CopyProcDumpConfiguration(struct ProcDumpConfigur copy->DiagnosticsLoggingEnabled = self->DiagnosticsLoggingEnabled; copy->ThreadThreshold = self->ThreadThreshold; copy->FileDescriptorThreshold = self->FileDescriptorThreshold; - copy->SignalNumber = self->SignalNumber; + + if(self->SignalNumber != NULL) + { + copy->SignalCount = self->SignalCount; + copy->SignalNumber = (int*) malloc(self->SignalCount*sizeof(int)); + if(copy->SignalNumber == NULL) + { + Trace("Failed to alloc memory for SignalNumber"); + if(copy->ProcessName) + { + free(copy->ProcessName); + } + + if(copy->MemoryThreshold) + { + free(copy->MemoryThreshold); + } + + return NULL; + } + + memcpy(copy->SignalNumber, self->SignalNumber, self->SignalCount*sizeof(int)); + } + copy->PollingInterval = self->PollingInterval; copy->CoreDumpPath = self->CoreDumpPath == NULL ? NULL : strdup(self->CoreDumpPath); copy->CoreDumpName = self->CoreDumpName == NULL ? NULL : strdup(self->CoreDumpName); @@ -584,11 +616,32 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) else if( 0 == strcasecmp( argv[i], "/sig" ) || 0 == strcasecmp( argv[i], "-sig" )) { - if( i+1 >= argc || self->SignalNumber != -1 ) return PrintUsage(); - if(!ConvertToInt(argv[i+1], &self->SignalNumber)) return PrintUsage(); - if(self->SignalNumber < 0) + if( i+1 >= argc || self->SignalCount != 0 ) return PrintUsage(); + self->SignalNumber = GetSeparatedValues(argv[i+1], const_cast(","), &self->SignalCount); + + if(self->SignalNumber == NULL || self->SignalCount == 0) return PrintUsage(); + + for(int i = 0; i < self->SignalCount; i++) + { + if(self->SignalNumber[i] < 0) + { + Log(error, "Invalid signal specified."); + free(self->SignalNumber); + return PrintUsage(); + } + } + + i++; + } + else if( 0 == strcasecmp( argv[i], "/mc" ) || + 0 == strcasecmp( argv[i], "-mc" )) + { + if( i+1 >= argc || self->CoreDumpMask != -1 ) return PrintUsage(); + + if(ConvertToIntHex(argv[i+1], &self->CoreDumpMask) == false) return PrintUsage(); + if(self->CoreDumpMask < 0) { - Log(error, "Invalid signal specified."); + Log(error, "Invalid core dump mask specified."); return PrintUsage(); } @@ -896,13 +949,14 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) (self->MemoryThreshold == NULL) && (self->ThreadThreshold == -1) && (self->FileDescriptorThreshold == -1) && - (self->DumpGCGeneration == -1)) + (self->DumpGCGeneration == -1) && + (self->SignalCount == 0)) { self->bTimerThreshold = true; } // Signal trigger can only be specified alone - if(self->SignalNumber != -1 || self->bDumpOnException) + if(self->SignalCount > 0 || self->bDumpOnException) { if(self->CpuThreshold != -1 || self->ThreadThreshold != -1 || self->FileDescriptorThreshold != -1 || self->MemoryThreshold != NULL) { @@ -931,7 +985,6 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) ApplyDefaults(self); Trace("GetOpts and initial Configuration finished"); - return 0; } @@ -944,7 +997,7 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) { if (WaitForSingleObject(&self->evtConfigurationPrinted,0) == WAIT_TIMEOUT) { - if(self->SignalNumber != -1) + if(self->SignalCount > 0) { printf("** NOTE ** Signal triggers use PTRACE which will impact the performance of the target process\n\n"); } @@ -1047,9 +1100,21 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) } // Signal - if (self->SignalNumber != -1) + if (self->SignalCount > 0) { - printf("%-40s%d\n", "Signal:", self->SignalNumber); + printf("%-40s", "Signal(s):"); + for(int i=0; iSignalCount; i++) + { + printf("%d", self->SignalNumber[i]); + if(i < self->SignalCount -1) + { + printf(","); + } + else + { + printf("\n"); + } + } } else { @@ -1138,10 +1203,11 @@ int PrintUsage() printf(" [-sr Sample_Rate]\n"); printf(" [-tc Thread_Threshold]\n"); printf(" [-fc FileDescriptor_Threshold]\n"); - printf(" [-sig Signal_Number]\n"); + printf(" [-sig Signal_Number1[,Signal_Number2...]]\n"); printf(" [-e]\n"); printf(" [-f Include_Filter,...]\n"); printf(" [-fx Exclude_Filter]\n"); + printf(" [-mc Custom_Dump_Mask]\n"); printf(" [-pf Polling_Frequency]\n"); printf(" [-o]\n"); printf(" [-log]\n"); @@ -1162,10 +1228,11 @@ int PrintUsage() printf(" -sr Sample rate when using -restrack.\n"); printf(" -tc Thread count threshold above which to create a dump of the process.\n"); 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(" -sig Comma separated list of signal number(s) during which either signal results in a dump of the process.\n"); printf(" -e [.NET] Create dump when the process encounters an exception.\n"); printf(" -f Filter (include) on the content of .NET exceptions (comma separated). Wildcards (*) are supported.\n"); printf(" -fx Filter (exclude) on the content of -restrack call stacks. Wildcards (*) are supported.\n"); + printf(" -mc Custom core dump mask (in hex) indicating what memory should be included in the core dump. Please see 'man core' (/proc/[pid]/coredump_filter) for available options.\n"); printf(" -pf Polling frequency.\n"); printf(" -o Overwrite existing dump file.\n"); printf(" -log Writes extended ProcDump tracing to syslog.\n"); diff --git a/src/Process.cpp b/src/Process.cpp index 4c52b4d..f96ea81 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -7,6 +7,7 @@ // //-------------------------------------------------------------------- #include "Includes.h" +#include //-------------------------------------------------------------------- // @@ -517,6 +518,27 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { return true; } +//-------------------------------------------------------------------- +// +// GetProcessName - Extracts the process name from the specified +// commandline. +// +//-------------------------------------------------------------------- +char * GetProcessNameFromCmdLine(char* cmdLine) +{ + char* retString = cmdLine; + + std::string procName = ""; + std::string inputString = cmdLine; + size_t firstSpacePos = inputString.find(' '); + if (firstSpacePos != std::string::npos) + { + procName = inputString.substr(0, firstSpacePos); + retString = const_cast(procName.c_str()); + } + + return strdup(retString); +} //-------------------------------------------------------------------- // @@ -524,7 +546,8 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { // Returns EMPTY_PROC_NAME for null process name. // //-------------------------------------------------------------------- -char * GetProcessName(pid_t pid){ +char * GetProcessName(pid_t pid) +{ char procFilePath[32]; char fileBuffer[MAX_CMDLINE_LEN]; int charactersRead = 0; @@ -533,24 +556,27 @@ char * GetProcessName(pid_t pid){ char * processName; auto_free_file FILE * procFile = NULL; - if(sprintf(procFilePath, "/proc/%d/cmdline", pid) < 0) { + if(sprintf(procFilePath, "/proc/%d/cmdline", pid) < 0) + { return NULL; } procFile = fopen(procFilePath, "r"); - if(procFile != NULL) { - if(fgets(fileBuffer, MAX_CMDLINE_LEN, procFile) == NULL) { - - if(strlen(fileBuffer) == 0) { + if(procFile != NULL) + { + if(fgets(fileBuffer, MAX_CMDLINE_LEN, procFile) == NULL) + { + if(strlen(fileBuffer) == 0) + { Log(debug, "Empty cmdline.\n"); } - else{ - } + return NULL; } } - else { + else + { Log(debug, "Failed to open %s.\n", procFilePath); return NULL; } @@ -559,21 +585,28 @@ char * GetProcessName(pid_t pid){ // Extract process name stringItr = fileBuffer; charactersRead = strlen(fileBuffer); - for(int i = 0; i <= charactersRead; i++){ - if(fileBuffer[i] == '\0'){ + for(int i = 0; i <= charactersRead; i++) + { + if(fileBuffer[i] == '\0') + { itr = i - itr; - if(strcmp(stringItr, "sudo") != 0){ // do we have the process name including filepath? + // do we have the process name including filepath? + if(strcmp(stringItr, "sudo") != 0) + { processName = strrchr(stringItr, '/'); // does this process include a filepath? - if(processName != NULL){ - return strdup(processName + 1); // +1 to not include '/' character + if(processName != NULL) + { + return GetProcessNameFromCmdLine(processName + 1); // +1 to not include '/' character } - else{ - return strdup(stringItr); + else + { + return GetProcessNameFromCmdLine(stringItr); } } - else{ + else + { stringItr += (itr+1); // +1 to move past '\0' } }