pax_global_header00006660000000000000000000000064141202240010014474gustar00rootroot0000000000000052 comment=a38f4e1a8f3d60052b5ccc9e690bfcd18a6541c0 ProcDump-for-Linux-1.2/000077500000000000000000000000001412022400100147505ustar00rootroot00000000000000ProcDump-for-Linux-1.2/.github/000077500000000000000000000000001412022400100163105ustar00rootroot00000000000000ProcDump-for-Linux-1.2/.github/issue_template.md000066400000000000000000000002271412022400100216560ustar00rootroot00000000000000### Expected behavior ### Actual behavior ### Steps to reproduce the behavior 1. 2. 3. ### System information (e.g., distro, kernel version, etc.) ProcDump-for-Linux-1.2/.gitignore000066400000000000000000000005211412022400100167360ustar00rootroot00000000000000# IDE folders & Files .vs/ .vscode/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo *.o # Project directories bin/ release/ pkgbuild/ ProcDump-for-Linux-1.2/CODE_OF_CONDUCT.md000066400000000000000000000005271412022400100175530ustar00rootroot00000000000000# Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](opencode@microsoft.com) with any additional questions or comments.ProcDump-for-Linux-1.2/CONTRIBUTING.md000066400000000000000000000113731412022400100172060ustar00rootroot00000000000000# Contributing Before we can accept a pull request from you, you'll need to sign a [Contributor License Agreement (CLA)](https://cla.microsoft.com). It is an automated process and you only need to do it once. To enable us to quickly review and accept your pull requests, always create one pull request per issue and link the issue in the pull request. Never merge multiple requests in one unless they have the same root cause. Be sure to follow our Coding Guidelines and keep code changes as small as possible. Avoid pure formatting changes to code that has not been modified otherwise. Pull requests should contain tests whenever possible. # Branching The master branch contains current development. While CI should ensure that master always builds, it is still considered pre-release code. Release checkpoints will be put into stable branches for maintenance. To contribute, fork the repository and create a branch in your fork for your work. Please keep branch names short and descriptive. Please direct PRs into the upstream master branch. ## Build and run from source ### Environment * Linux OS (dev team is using Ubuntu 18.04) * Development can be done on Windows Subsystem for Linux (WSL), but ProcDump cannot be executed in that environment * git * GDB * GCC (or other C compiler) * GNU Make ## Building from source * Clone the repo * Run `make` from the project root * The procdump executable will be placed into the `bin` directory ```sh git clone https://github.com/microsoft/ProcDump-for-Linux cd ProcDump-for-Linux make ``` ## Testing * There are a multitude of tests included in the `tests` directory of the repository. * Add new tests corresponding to your change, if applicable. Include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. * Make sure that the tests are all passing, including your new tests. ## Creating integration tests The integration tests run using the local procdump built from source. Individual test cases are written as bash scripts and need to be inside `/tests/integration/scenarios/` in order to be called automatically by `run.sh`. Test scripts will return `0` when they succeed and `1` when they fail. Most of the tests are written using [stress-ng](https://wiki.ubuntu.com/Kernel/References/stress-ng "stress-ng manual"), but you can write your own code to simulate the scenario you require. After writing a new test, run the `run.sh` script and verify that no tests fail. ## Pull Requests * Always tag a work item or issue with a pull request. * Limit pull requests to as few issues as possible, preferably 1 per PR ## Coding Guidelines ## Indentation We welcome all whitespace characters, but prefer space expanded tabs. ## Names * Do not use `typedef`, we like to know what is a struct and what is a type * Use PascalCase for: * `struct` names * `enum` names * `function` names * Use camelCase for: * `local variable` names * `enum` names should start with a captol `E`, e.g., `enum ECoreDumpType` * Global variables should be prefixed with `g_`, e.g., `struct ProcDumpConfiguration g_Config;` * `struct Handle`s that contain a `struct Event` should have variable names prefixed by `evt`, e.g., `struct Handle evtIsQuit;` * `struct Handle`s that contain a `sem_t` should have variable names prefixed by `sem`, e.g., `struct Handle semDumpSlot;` * Please use whole words when possible ## Style * Curly brackets, `{ }`, should go on the next line after whatever necessitates them * For structs, put on same line * Put a space before the open paren, `(`, with `for`, `while`, `if`, and `switch` statements * No space after function names and before parameter lists * The `*` for a pointer goes next to the variable name, e.g., `char *variable` * Declare 1 variable at a time * Declare all local variables at the start of a function * Either initialize upon declaration, or initialize before logic * The exception is `for` loop iterators * Wrap header (`.h`) files with: ```c #ifndef HEADER_FILE_NAME_H #define HEADER_FILE_NAME_H //... #endif // HEADER_FILE_NAME_H ``` ## Trace and Error Handling For system calls and other "failable" function calls, please make use of the `Trace` macro and the logging methods, like so: ```c int rc = 0; if ((rc = FailableFunction(...)) != 0) { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed pthread_setcanceltype."); exit(-1); } ``` ## Example of style ```c struct Baz { int Foobar; } int main(int argc, char *argv[]) { int foo = 0; int bar = 1; char[64] str = "This is a string"; struct Baz baz = { 10 }; while (foo < 10) { foo++; } for (int i = 0; i < foo; i++) { printf(str); baz.Foobar--; } printf("baz.Foobar is %d", baz.Foobar); return bar - 1; } ``` ProcDump-for-Linux-1.2/INSTALL.md000066400000000000000000000077301412022400100164070ustar00rootroot00000000000000# Install ProcDump ## Ubuntu 16.04, 18.04 & 20.04 #### 1. Register Microsoft key and feed ```sh wget -q https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb ``` #### 2. Install Procdump ```sh sudo apt-get update sudo apt-get install procdump ``` ## Debian 9 #### 1. Register Microsoft key and feed ```sh wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ wget -q https://packages.microsoft.com/config/debian/9/prod.list sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list ``` #### 2. Install Procdump ```sh sudo apt-get update sudo apt-get install apt-transport-https sudo apt-get update sudo apt-get install procdump ``` ## Debian 10 #### 1. Register Microsoft key and feed ```sh wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ wget -q https://packages.microsoft.com/config/debian/10/prod.list sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list ``` #### 2. Install Procdump ```sh sudo apt-get update sudo apt-get install apt-transport-https sudo apt-get update sudo apt-get install procdump ``` ## Fedora 31 #### 1. Register Microsoft key and feed ```sh sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc sudo wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/31/prod.repo ``` #### 2. Install Procdump ```sh sudo dnf install procdump ``` ## Fedora 32 #### 1. Register Microsoft key and feed ```sh sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc sudo wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/32/prod.repo ``` #### 2. Install Procdump ```sh sudo dnf install procdump ``` ## RHEL 7 #### 1. Register Microsoft key and feed ```sh sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc sudo wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/rhel/7/prod.repo ``` #### 2. Install Procdump ```sh sudo yum install procdump ``` ## RHEL 8 #### 1. Register Microsoft key and feed ```sh sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc sudo wget -q -O /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/rhel/8/prod.repo ``` #### 2. Install Procdump ```sh sudo yum install procdump ``` ## CentOS 7 #### 1. Register Microsoft key and feed ```sh sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm ``` #### 2. Install Procdump ```sh sudo yum install procdump ``` ## CentOS 8 #### 1. Register Microsoft key and feed ```sh sudo rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm ``` #### 2. Install Procdump ```sh sudo yum install procdump ``` ## openSUSE 15 #### 1. Register Microsoft key and feed ```sh sudo zypper install libicu sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc wget -q https://packages.microsoft.com/config/opensuse/15/prod.repo sudo mv prod.repo /etc/zypp/repos.d/microsoft-prod.repo sudo chown root:root /etc/zypp/repos.d/microsoft-prod.repo ``` #### 2. Install Procdump ```sh sudo zypper install procdump ``` ## SLES 12 #### 1. Register Microsoft key and feed ```sh sudo rpm -Uvh https://packages.microsoft.com/config/sles/12/packages-microsoft-prod.rpm ``` #### 2. Install Procdump ```sh sudo zypper install procdump ``` ## SLES 15 #### 1. Register Microsoft key and feed ```sh sudo rpm -Uvh https://packages.microsoft.com/config/sles/15/packages-microsoft-prod.rpm ``` #### 2. Install Procdump ```sh sudo zypper install procdump ``` ProcDump-for-Linux-1.2/LICENSE000066400000000000000000000022121412022400100157520ustar00rootroot00000000000000 MIT License Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ProcDump-for-Linux-1.2/Makefile000066400000000000000000000042731412022400100164160ustar00rootroot00000000000000ROOT=. CC=gcc CFLAGS ?= -Wall CCFLAGS=$(CFLAGS) -I ./include -pthread -std=gnu99 LIBDIR=lib OBJDIR=obj SRCDIR=src INCDIR=include BINDIR=bin TESTDIR=tests/integration DEPS=$(wildcard $(INCDIR)/*.h) SRC=$(wildcard $(SRCDIR)/*.c) TESTSRC=$(wildcard $(TESTDIR)/*.c) OBJS=$(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SRC)) TESTOBJS=$(patsubst $(TESTDIR)/%.c, $(OBJDIR)/%.o, $(TESTSRC)) OUT=$(BINDIR)/procdump TESTOUT=$(BINDIR)/ProcDumpTestApplication # Revision value from build pipeline REVISION:=$(if $(REVISION),$(REVISION),'99999') # installation directory DESTDIR ?= / INSTALLDIR=/usr/bin MANDIR=/usr/share/man/man1 # package creation directories BUILDDIR := $(CURDIR)/pkgbuild # Flags to pass to debbuild/rpmbuild PKGBUILDFLAGS := --define "_topdir $(BUILDDIR)" -bb # Command to create the build directory structure PKGBUILDROOT_CREATE_CMD = mkdir -p $(BUILDDIR)/DEBS $(BUILDDIR)/SDEBS $(BUILDDIR)/RPMS $(BUILDDIR)/SRPMS \ $(BUILDDIR)/SOURCES $(BUILDDIR)/SPECS $(BUILDDIR)/BUILD $(BUILDDIR)/BUILDROOT # package details PKG_VERSION=1.2 all: clean build build: $(OBJDIR) $(BINDIR) $(OUT) $(TESTOUT) install: mkdir -p $(DESTDIR)$(INSTALLDIR) cp $(BINDIR)/procdump $(DESTDIR)$(INSTALLDIR) mkdir -p $(DESTDIR)$(MANDIR) cp procdump.1 $(DESTDIR)$(MANDIR) $(OBJDIR)/%.o: $(SRCDIR)/%.c $(CC) -c -g -o $@ $< $(CCFLAGS) $(OBJDIR)/%.o: $(TESTDIR)/%.c $(CC) -c -g -o $@ $< $(CCFLAGS) $(OUT): $(OBJS) $(CC) -o $@ $^ $(CCFLAGS) $(TESTOUT): $(TESTOBJS) $(CC) -o $@ $^ $(CCFLAGS) $(OBJDIR): -@mkdir -p $(OBJDIR) $(BINDIR): -@mkdir -p $(BINDIR) .PHONY: clean clean: -rm -rf $(OBJDIR) -rm -rf $(BINDIR) -rm -rf $(BUILDDIR) test: build ./tests/integration/run.sh release: clean tarball .PHONY: tarball tarball: $(PKGBUILDROOT_CREATE_CMD) tar --exclude=./pkgbuild --exclude=./.git --transform 's,^\.,procdump-$(PKG_VERSION),' -czf $(BUILDDIR)/SOURCES/procdump-$(PKG_VERSION).tar.gz . sed -e "s/@PKG_VERSION@/$(PKG_VERSION)/g" dist/procdump.spec.in > $(BUILDDIR)/SPECS/procdump.spec .PHONY: deb deb: tarball debbuild --define='_Revision ${REVISION}' $(PKGBUILDFLAGS) $(BUILDDIR)/SPECS/procdump.spec .PHONY: rpm rpm: tarball rpmbuild --define='_Revision ${REVISION}' $(PKGBUILDFLAGS) $(BUILDDIR)/SPECS/procdump.spec ProcDump-for-Linux-1.2/README.md000066400000000000000000000122251412022400100162310ustar00rootroot00000000000000# ProcDump [![Build Status](https://oss-sysinternals.visualstudio.com/Procdump%20for%20Linux/_apis/build/status/Sysinternals.ProcDump-for-Linux?branchName=master)](https://oss-sysinternals.visualstudio.com/Procdump%20for%20Linux/_build/latest?definitionId=21&branchName=master) ProcDump is a Linux reimagining of the classic ProcDump tool from the Sysinternals suite of tools for Windows. ProcDump provides a convenient way for Linux developers to create core dumps of their application based on performance triggers. ![ProcDump in use](procdump.gif "Procdump in use") # Installation & Usage ## Requirements * Minimum OS: * Red Hat Enterprise Linux / CentOS 7 * Fedora 29 * Ubuntu 16.04 LTS * `gdb` >= 7.6.1 * `zlib` (build-time only) ## Install ProcDump Checkout our [install instructions](INSTALL.md) for distribution specific steps to install Procdump. ## Build ProcDump from Scratch To build from scratch you'll need to have a C compiler (supporting C11), `zlib`, and a `make` utility installed. Then simply run: ``` make make install ``` ### Building Procdump Packages The distribution packages for Procdump for Linux are constructed utilizing `debbuild` for Debian targets and `rpmbuild` for Fedora targets. To build a `deb` package of Procdump on Ubuntu simply run: ```sh make && make deb ``` To build a `rpm` package of Procdump on Fedora simply run: ```sh make && make rpm ``` ## Usage ``` Usage: procdump [OPTIONS...] TARGET OPTIONS -h Prints this help screen -C Trigger core dump generation when CPU exceeds or equals specified value (0 to 100 * nCPU) -c Trigger core dump generation when CPU is less than specified value (0 to 100 * nCPU) -M Trigger core dump generation when memory commit exceeds or equals specified value (MB) -m Trigger core dump generation when when memory commit is less than specified value (MB) -T Trigger when thread count exceeds or equals specified value. -F Trigger when file descriptor count exceeds or equals specified value. -G Trigger when signal with the specified value (numeric) is sent (uses PTRACE and will affect performance of target process). -I Polling frequency in milliseconds (default is 1000) -n Number of core dumps to write before exiting (default is 1) -s Consecutive seconds before dump is written (default is 10) -o Path and/or filename prefix where the core dump is written to -d Writes diagnostic logs to syslog TARGET must be exactly one of these: -p pid of the process -w Name of the process executable ``` ### Examples > The following examples all target a process with pid == 1234 The following will create a core dump immediately. ``` sudo procdump -p 1234 ``` The following will create 3 core dumps 10 seconds apart. ``` sudo procdump -n 3 -p 1234 ``` The following will create 3 core dumps 5 seconds apart. ``` sudo procdump -n 3 -s 5 -p 1234 ``` The following will create a core dump each time the process has CPU usage >= 65%, up to 3 times, with at least 10 seconds between each dump. ``` sudo procdump -C 65 -n 3 -p 1234 ``` The following will create a core dump each time the process has CPU usage >= 65%, up to 3 times, with at least 5 seconds between each dump. ``` sudo procdump -C 65 -n 3 -s 5 -p 1234 ``` The following will create a core dump when CPU usage is outside the range [10,65]. ``` sudo procdump -c 10 -C 65 -p 1234 ``` The following will create a core dump when CPU usage is >= 65% or memory usage is >= 100 MB. ``` sudo procdump -C 65 -M 100 -p 1234 ``` The following will create a core dump in the `/tmp` directory immediately. ``` sudo procdump -o /tmp -p 1234 ``` The following will create a core dump in the current directory with the name dump_0.1234. If -n is used, the files will be named dump_0.1234, dump_1.1234 and so on. ``` sudo procdump -o dump -p 1234 ``` The following will create a core dump when a SIGSEGV occurs. ``` sudo procdump -G 11 -p 1234 ``` > All options can also be used with -w instead of -p. -w will wait for a process with the given name. The following waits for a process named `my_application` and creates a core dump immediately when it is found. ``` sudo procdump -w my_application ``` ## Current Limitations * Currently will only run on Linux Kernels version 3.5+ * Does not have full feature parity with Windows version of ProcDump, specifically, stay alive functionality, and custom performance counters # Feedback * Ask a question on StackOverflow (tag with ProcDumpForLinux) * Request a new feature on GitHub * Vote for popular feature requests * File a bug in GitHub Issues # Contributing If you are interested in fixing issues and contributing directly to the code base, please see the [document How to Contribute](CONTRIBUTING.md), which covers the following: * How to build and run from source * The development workflow, including debugging and running tests * Coding Guidelines * Submitting pull requests Please see also our [Code of Conduct](CODE_OF_CONDUCT.md). # License Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. ProcDump-for-Linux-1.2/azure-pipelines.yml000066400000000000000000000041251412022400100206110ustar00rootroot00000000000000# Azure build pipelines for Procdump-for-Linux trigger: branches: include: - release/* exclude: - dev/* - test/* pr: - master jobs: - job: "Run_Unit_Tests" pool: vmImage: 'ubuntu-latest' steps: - script: | sudo apt update sudo apt install -y gdb stress-ng zlib1g-dev displayName: 'Setup build environment' - script: | make displayName: 'Build test binary' - script: | sudo ./tests/integration/run.sh displayName: 'Run unit tests' - job: "DEB_Package_Build" pool: "Ubuntu-Docker-Pool" condition: not(eq(variables['Build.Reason'], 'PullRequest')) dependsOn: - Run_Unit_Tests steps: - script: | make displayName: 'Build procdump Ubuntu' - script: | export REVISION=$(Build.BuildId) make release make deb displayName: 'Building debian package & artifacts' - task: CopyFiles@2 inputs: SourceFolder: '$(Build.SourcesDirectory)/pkgbuild' TargetFolder: '$(Build.ArtifactStagingDirectory)' displayName: 'Copy build artifacts to staging' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container' - job: 'RPM_Package_Build' pool: 'Centos-Docker-Pool' dependsOn: - DEB_Package_Build steps: - script: | make displayName: "Build Procdump Centos" - script: | export REVISION=$(Build.BuildId) make release make rpm displayName: 'Building centos package & artifacts' - task: CopyFiles@2 inputs: SourceFolder: '$(Build.SourcesDirectory)/pkgbuild' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)' displayName: 'Copy build RPM artifacts to staging' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container'ProcDump-for-Linux-1.2/dist/000077500000000000000000000000001412022400100157135ustar00rootroot00000000000000ProcDump-for-Linux-1.2/dist/procdump.spec.in000066400000000000000000000044261412022400100210330ustar00rootroot00000000000000Name: procdump Version: @PKG_VERSION@ Release: %_Revision Summary: Sysinternals process dump utility %if %{_vendor} == "debbuild" Group: devel %else Group: Development/Tools%{?suse_version:/Other} %endif Packager: OSS Tooling Dev Team License: MIT URL: https://github.com/Microsoft/ProcDump-for-Linux Source0: %{url}/releases/download/%{version}/%{name}-%{version}.tar.gz BuildRequires: gcc, make %if %{_vendor} == "debbuild" BuildRequires: zlib1g-dev %else BuildRequires: zlib-devel %endif Requires: gdb >= 7.6.1 %description ProcDump is a command-line utility whose primary purpose is monitoring an application for various resources and generating crash dumps during a spike that an administrator or developer can use to determine the cause of the issue. ProcDump also serves as a general process dump utility that you can embed in other scripts. %prep %autosetup %build # The makefile doesn't like %%make_build (parallel make) make CFLAGS="%{optflags}" %install %make_install %files %license LICENSE %doc README.md procdump.gif %{_bindir}/procdump %{_mandir}/man1/procdump.1* %changelog * Tue Sep 14 2021 Javid Habibi - 1.2 - added signal trigger - added custom filepath for memory dump generation - various small bug fixes * Fri Apr 3 2020 Javid Habibi - 1.1.1 - implimented thread and file descriptor count trigger - added polling interval switch * Mon Dec 9 2019 Javid Habibi - 1.1 - Added support for .Net Core 3.x+ core dump generation that results in more manageable core dump sizes * Fri Nov 8 2019 Javid Habibi - 1.0.2 - implimented -w target flag - fixed pthread cancellation bug - added additional error checking for null process names - implimented a minimal kernel check validation - various bug fixes * Wed Jan 10 2018 Javid Habibi - 1.0.1 - fixed potential deadlock upon malloc failing - fixed improper process name parsing and sanitizing process name for dump file generation - fixed various typos - fixed post-build check failures on openSUSE * Tue Dec 05 2017 Javid Habibi - 1.0 - Initial release ProcDump-for-Linux-1.2/docs/000077500000000000000000000000001412022400100157005ustar00rootroot00000000000000ProcDump-for-Linux-1.2/docs/coreclrintegration.md000066400000000000000000000106321412022400100221210ustar00rootroot00000000000000# Procdump and .NET Core 3 Integration Procdump is a powerful production diagnostics tool that allows you to monitor processes for specific thresholds and generate core dumps based on a specified critera. For example, imagine that you were encountering sporadic CPU spikes in your web app and you would like to generate a core dump for offline analysis. With Procdump you can use the -C switch to specify the CPU threshold of interest (say, 90%) and Procdump will monitor the process and generate a core dump when the CPU goes above 90%. In order to understand how a core dump can help resolve production issues, we first have to understand what a core dump is. Essentially, a core dump is nothing more than a static snapshot of the contents of an applications memory. This content is written to a file that can later be loaded into a debugger and other tools to analyze the contents of memory and see if root cause can be determined. What makes core dumps great production debugging tools is that we don't have to worry about the application stopping while debugging is taking place (as is the case with live debugging where a debugger is attached to the application). How big are these dump files? Well, that depends on how much memory your application is consuming (remember, it roughly writes the contents of the application memory usage to the file). As you can imagine, if you are running a massive database application you could result in a core dump file that is many GB in size. Furthermore, on Linux, core dumps tend to be larger than on Windows. This presents a new challenge of how to effectively manage large core dump files. If you intend to copy the core dump file between production and development machines, it can be pretty time consuming. With the release of Procdump 1.1, we now recognize if the target application is a .NET Core application and use a special core dumping mechanism built into the runtime itself. This renders core dump files that are much smaller than normal while still maintaining all the neccessary information to troubleshoot the .NET Core application. You don't have to specify any new switches to use this new capability, Procdump automatically figures out if the capability is available and uses it. To get a feel for the size difference, let's take a look at a simple example. We created and ran a new .NET Core app using the webapp template: ```console dotnet new webapp -o TestWebApp dotnet run ``` This webapp does very little and as a matter of fact we won't even send any requests to it. Now, using Procdump (**1.0**), if we generate a core dump of the web app we end up with a file that is roughly 18GB in size. Pretty hefty for a simple web app that essentially does nothing. ```console -rw-r--r-- 1 root root 18127292104 Dec 4 11:37 core.3066 ``` Using the same web app, let's use Procdump **1.1** to generate the core dump. This time the file size is much more reasonable: ```console -rw-rw-r-- 1 marioh marioh 273387520 Dec 4 11:44 TestWebApp_time_2019-12-04_11:44:03.3066 ``` _Please note that by default the core dump will be placed into the same directory that the target application is running in._ This time the core dump file size is only about 273MB. Much better and much more managable. To convince ourselves that the core dump still contains all the neccessary data to debug .NET Core 3.0 applications, we can try it out with dotnet-dump analyze (which is a REPL for SOS debugging): ```console dotnet-dump analyze TestWebApp_time_2019-12-04_11:44:03.3066 > dumpheap -stat Statistics: MT Count TotalSize Class Name 00007f7b2f5e4288 1 24 System.Threading.TimerQueueTimer+<>c 00007f7b2f5e2738 1 24 System.Net.Sockets.SocketAsyncEngine+<>c ... ... ... 00007f7b2bcd4a18 397 109816 System.Char[] 00007f7b2b1514c0 628 110601 System.Byte[] 00007f7b2b145510 509 166528 System.Object[] 00007f7b2b150f90 4436 342956 System.String Total 32581 objects ``` How does Procdump achieve this magic? Turns out that .NET Core 3.0 introduced the notion of a diagnostics server which at a high level enables external (out of process) tools to send diagnostics commands to the target process. In our case, we used the dump commands that are available but you can also use the diagnostics server to issue trace commands. For more information, please see the following documentation: [.NET Core Diagnostics](https://github.com/dotnet/diagnostics) ProcDump-for-Linux-1.2/include/000077500000000000000000000000001412022400100163735ustar00rootroot00000000000000ProcDump-for-Linux-1.2/include/CoreDumpWriter.h000066400000000000000000000037371412022400100214710ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //-------------------------------------------------------------------- // // Core dump orchestrator // //-------------------------------------------------------------------- #ifndef CORE_DUMP_WRITER_H #define CORE_DUMP_WRITER_H #include #include #include #include #include #include #include #include #include #include "Handle.h" #include "ProcDumpConfiguration.h" #define DATE_LENGTH 26 #define MAX_LINES 15 #define BUFFER_LENGTH 1024 #define CORECLR_DUMPTYPE_FULL 4 #define CORECLR_DUMPLOGGING_OFF 0 #define CORECLR_DIAG_IPCHEADER_SIZE 24 // Magic version for the IpcHeader struct struct MagicVersion { uint8_t Magic[14]; }; // The header to be associated with every command and response // to/from the diagnostics server struct IpcHeader { union { struct MagicVersion _magic; uint8_t Magic[14]; // Magic Version number }; uint16_t Size; // The size of the incoming packet, size = header + payload size uint8_t CommandSet; // The scope of the Command. uint8_t CommandId; // The command being sent uint16_t Reserved; // reserved for future use }; enum ECoreDumpType { COMMIT, // trigger on memory threshold CPU, // trigger on CPU threshold THREAD, // trigger on thread count FILEDESC, // trigger on file descriptor count SIGNAL, // trigger on signal TIME, // trigger on time interval MANUAL // manual trigger }; struct CoreDumpWriter { struct ProcDumpConfiguration *Config; enum ECoreDumpType Type; }; struct CoreDumpWriter *NewCoreDumpWriter(enum ECoreDumpType type, struct ProcDumpConfiguration *config); int WriteCoreDump(struct CoreDumpWriter *self); #endif // CORE_DUMP_WRITER_HProcDump-for-Linux-1.2/include/Events.h000066400000000000000000000025251412022400100200140ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // Quick events implementation // //-------------------------------------------------------------------- #ifndef EVENTS_H #define EVENTS_H #include #include #include #include #include #include #include #define MAX_EVENT_NAME 64 #define MANUAL_RESET_EVENT_INITIALIZER(NAME) \ {\ .mutex = PTHREAD_MUTEX_INITIALIZER,\ .cond = PTHREAD_COND_INITIALIZER,\ .bManualReset = true,\ .bTriggered = false,\ .nWaiters = 0,\ .Name = NAME\ } struct Event { pthread_mutex_t mutex; pthread_cond_t cond; bool bTriggered; bool bManualReset; char Name[MAX_EVENT_NAME]; int nWaiters; }; struct Event *CreateEvent(bool IsManualReset, bool InitialState); struct Event *CreateNamedEvent(bool IsManualReset, bool InitialState, char *Name); void InitEvent(struct Event *Event, bool IsManualReset, bool InitialState); void InitNamedEvent(struct Event *Event, bool IsManualReset, bool InitialState, char *Name); void DestroyEvent(struct Event *Event); bool SetEvent(struct Event *Event); bool ResetEvent(struct Event *Event); #endif // EVENTS_HProcDump-for-Linux-1.2/include/Handle.h000066400000000000000000000024611412022400100177420ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // Generalization of Events and Semaphores (Critical Sections) // //-------------------------------------------------------------------- #ifndef HANDLE_H #define HANDLE_H #include #include #include #include #include #include "Events.h" #include "Logging.h" #define INFINITE_WAIT -1 #define WAIT_OBJECT_0 0 #define WAIT_TIMEOUT ETIMEDOUT #define WAIT_ABANDONED 0x80 #define HANDLE_MANUAL_RESET_EVENT_INITIALIZER(NAME) \ {\ {\ .event = {\ .mutex = PTHREAD_MUTEX_INITIALIZER,\ .cond = PTHREAD_COND_INITIALIZER,\ .bManualReset = true,\ .bTriggered = false,\ .nWaiters = 0,\ .Name = NAME\ }\ },\ .type = EVENT\ } enum EHandleType { EVENT, SEMAPHORE }; struct Handle { union { struct Event event; sem_t semaphore; }; enum EHandleType type; }; int WaitForSingleObject(struct Handle *Handle, int Milliseconds); int WaitForMultipleObjects(int Count, struct Handle **Handles, bool WaitAll, int Milliseconds); #endif // HANDLE_HProcDump-for-Linux-1.2/include/Logging.h000066400000000000000000000027351412022400100201410ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // A simple logging library for log generation and debugging // //-------------------------------------------------------------------- #ifndef LOGGING_H #define LOGGING_H #include #include #include #include #include #include #include #include #define INTERNAL_ERROR "Internal Error has occurred. If problem continues to occur run procdump with -d flag to trace issue" // double-macro-stringify to expand __FILE__ and __LINE__ properly when they are injected in files #define S1(x) #x #define S2(x) S1(x) #define LOCATION "in "__FILE__ ", at line " S2(__LINE__) enum LogLevel{ debug, info, // standard output warn, crit, error }; void Log(enum LogLevel logLevel, const char *message, ...); void DiagTrace(const char* message, ...); /* * Summary: Used similarly to printf, but requires a format string for all input. * This macro appends line number and file information at the end of the format string and va_args. * Params: * - format: printf style format string literal * - var_args: variable number of format args * Example: Trace("%s", strerror(errno)) // %s format specifier required. */ #define Trace(format, ...) \ DiagTrace(format " %s", ##__VA_ARGS__, LOCATION); #endif // LOGGING_HProcDump-for-Linux-1.2/include/ProcDumpConfiguration.h000066400000000000000000000104571412022400100230340ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // The global configuration structure and utilities header // //-------------------------------------------------------------------- #ifndef PROCDUMPCONFIGURATION_H #define PROCDUMPCONFIGURATION_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Handle.h" #include "TriggerThreadProcs.h" #include "Process.h" #include "Logging.h" #define MAX_TRIGGERS 3 #define NO_PID INT_MAX #define MAX_CMDLINE_LEN 4096+1 #define EMPTY_PROC_NAME "null" #define MIN_KERNEL_VERSION 3 #define MIN_KERNEL_PATCH 5 #define MIN_POLLING_INTERVAL 1000 // default trigger polling interval (ms) // ------------------- // Structs // ------------------- struct ProcDumpConfiguration { // Process and System info pid_t ProcessId; char *ProcessName; struct sysinfo SystemInfo; // Runtime Values int NumberOfDumpsCollecting; // Number of dumps we're collecting int NumberOfDumpsCollected; // Number of dumps we have collected bool bTerminated; // Do we know whether the process has terminated and subsequently whether we are terminating? // Quit int nQuit; // if not 0, then quit struct Handle evtQuit; // for signalling threads we are quitting // Trigger behavior bool bTriggerThenSnoozeCPU; // Detect+Trigger=>Wait N second=>[repeat] bool bTriggerThenSnoozeMemory; // Detect+Trigger=>Wait N second=>[repeat] bool bTriggerThenSnoozeTimer; // Detect+Trigger=>Wait N second=>[repeat] // Options int CpuThreshold; // -C bool bCpuTriggerBelowValue; // -c int MemoryThreshold; // -M bool bMemoryTriggerBelowValue; // -m int ThresholdSeconds; // -s bool bTimerThreshold; // -s int NumberOfDumpsToCollect; // -n bool WaitingForProcessName; // -w bool DiagnosticsLoggingEnabled; // -d int ThreadThreshold; // -T int FileDescriptorThreshold; // -F int SignalNumber; // -G int PollingInterval; // -I char *CoreDumpPath; // -o char *CoreDumpName; // -o // multithreading // set max number of concurrent dumps on init (default to 1) int nThreads; pthread_t Threads[MAX_TRIGGERS]; struct Handle semAvailableDumpSlots; // Events // use these to mimic WaitForSingleObject/MultibleObjects from WinApi struct Handle evtCtrlHandlerCleanupComplete; struct Handle evtBannerPrinted; struct Handle evtConfigurationPrinted; struct Handle evtDebugThreadInitialized; struct Handle evtStartMonitoring; // External pid_t gcorePid; }; int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]); char * GetProcessName(pid_t pid); bool LookupProcessByPid(struct ProcDumpConfiguration *self); bool WaitForProcessName(struct ProcDumpConfiguration *self); int CreateProcessViaDebugThreadAndWaitUntilLaunched(struct ProcDumpConfiguration *self); int CreateTriggerThreads(struct ProcDumpConfiguration *self); int WaitForQuit(struct ProcDumpConfiguration *self, int milliseconds); int WaitForQuitOrEvent(struct ProcDumpConfiguration *self, struct Handle *handle, int milliseconds); int WaitForAllThreadsToTerminate(struct ProcDumpConfiguration *self); bool IsQuit(struct ProcDumpConfiguration *self); int SetQuit(struct ProcDumpConfiguration *self, int quit); bool PrintConfiguration(struct ProcDumpConfiguration *self); bool ContinueMonitoring(struct ProcDumpConfiguration *self); bool BeginMonitoring(struct ProcDumpConfiguration *self); void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self); void InitProcDumpConfiguration(struct ProcDumpConfiguration *self); void InitProcDump(); void ExitProcDump(); void PrintBanner(); int PrintUsage(struct ProcDumpConfiguration *self); bool IsValidNumberArg(const char *arg); bool CheckKernelVersion(); #endif // PROCDUMPCONFIGURATION_H ProcDump-for-Linux-1.2/include/Procdump.h000066400000000000000000000016571412022400100203460ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // This program monitors a process and generates core dumps in // in response to various triggers // //-------------------------------------------------------------------- #ifndef UBUNTU_SYSINTERNALS_PROCDUMP_H #define UBUNTU_SYSINTERNALS_PROCDUMP_H #include #include #include #include #include #include #include #include "ProcDumpConfiguration.h" #include "Logging.h" #define MIN_CPU 0 // minimum CPU value #define DEFAULT_NUMBER_OF_DUMPS 1 // default number of core dumps taken #define DEFAULT_DELTA_TIME 10 // default delta time in seconds between core dumps void termination_handler(int sig_num); #endif //UBUNTU_SYSINTERNALS_PROCDUMP_H ProcDump-for-Linux-1.2/include/Process.h000066400000000000000000000264521412022400100201730ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // This library reads from the /procfs pseudo filesystem // //-------------------------------------------------------------------- #ifndef PROCFSLIB_PROCESS_H #define PROCFSLIB_PROCESS_H #include #include #include #include #include #include #include #include "Logging.h" // ----------------------------------------------------------- // a series of structs for containing infromation from /procfs // ----------------------------------------------------------- // // /proc/[pid]/stat // struct ProcessStat { pid_t pid; // the process ID : %d char *comm; // The filename of the executable : %s char state; // Process State, one of RSDZTtWXxKP : %c pid_t ppid; // the parent process ID : %d gid_t pgrp; // the process group ID of the process : %d int session; // The session ID of the process : %d // The controlling terminal of the process. // (the minor device number is contained in the combination // of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8) : %d int tty_nr; gid_t tpgid; // The ID of the foreground process group of the controlling terminal process : %d unsigned int flags; // Kernel flags word of the process. See PF_* definitions in include/linux/sched.h : %u // Number of minor faults the process has made which have not required loading a memory page from disk unsigned long minflt; // Number of minor faults that the process's waited-for children have made unsigned long cminflt; // Number of major faults the process has made which have required loading a memory page from disk unsigned long majflt; // Number of major faults that the process's waited-for children have made unsigned long cmajflt; // Amount of time that this process has been scheduled in user mode, measured in clock ticks unsigned long utime; // Amount of time that this process has been scheduled in kernel mode, measured in clock ticks unsigned long stime; // Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock ticks unsigned long cutime; // Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock ticks unsigned long cstime; // For processes running under a non-real-time scheduling policy, this is the raw // nice value (setpriority(2)) as represented in the kernel. For processes running in a // real-time scheduling policy, this is the negated scheduling priority minus one. long priority; // The nice value, a value in the range 19 (low priority) to -20 (high priority) long nice; // Number of threads in this process long num_threads; // The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. long itrealvalue; // The time the process started after system boot unsigned long long starttime; // Virtual memory size in bytes unsigned long vsize; // Resident Set Size: # of pages the process has in _real memory_. // This is just the pages which count toward text, data, or stack space. // This does not include pages which have not been demand-loaded in, // or which are swapped out long rss; // Current soft limit in bytes on the rss of the process unsigned long rsslim; //The address above which program text can run unsigned long startcode; //The address below which program text can run unsigned long endcode; // The address of the start (i.e., bottom) of the stack unsigned long startstack; // The current value of the ESP (stack pointer), as found in the kernel stack page for the process // NOTE: due to race conditions, this is not reliable, try ptrace unsigned long kstkesp; // The current EIP (Instruction pointer) // NOTE: due to race conditions, this is not reliable, try ptrace unsigned long kstkeip; // The bitmap of pending signals, displayed as a decimal number. // Obsolete, because it does not provide information on real-time signals; // for that use /proc/[pid]/status instead. unsigned long signal; // The bitmap of blocked signals, displayed as a decimal number; // Obsolete, because it does not provide information on real-time signals; // for that use /proc/[pid]/status instead. unsigned long blocked; // The bitmap of ignored signals, displayed as a decimal number. // Obsolete, because it does not provide information on real-time signals; // for that use /proc/[pid]/status instead. unsigned long sigignore; // The bitmap of caught signals, displayed as a decimal number. // Obsolete, because it does not provide information on real-time signals; // for that use /proc/[pid]/status instead. unsigned long sigcatch; // This is the "channel" in which the process is waiting. It is the // address of a location in the kernel where the process is sleeping. // The corresponding symbolic name can be found in /proc/[pid]/wchan. unsigned long wchan; // Number of pages swapped (not maintained). unsigned long nswap; // Cumulative nswap for child processes (not maintained). unsigned long cnswap; // signal to be sent to parent when we die. (since Linux 2.1.22) int exit_signal; // CPU number last executed on (since Linux 2.2.8) int processor; // Real-time scheduling priority, a number in the range 1 to 99 for // processes scheduled under a real-time policy, or 0, for non-real- // time processes unsigned int rt_priority; // Scheduling policy. Decode using the SCHED_* constants in // linux/sched.h unsigned int policy; // Aggregated block I/O delays, measured in clock ticks (centiseconds) unsigned long long delayacct_blkio_ticks; // Guest time of the process (time spent running a virtual CPU for a // guest operating system), measured in clock ticks (divide by // sysconf(_SC_CLK_TCK)). unsigned long guest_time; // Guest time of the the process's children, measure in clock ticks // (divide by sysconf(_SC_CLK_TCK)). long cguest_time; // Address above which program initialized and uninitialized (BSS) // data are placed. unsigned long start_data; // Address below which program initialized and uninitialized (BSS) // data are placed. unsigned long end_data; // Address above which program heap can be expanded with brk. unsigned long start_brk; // Address above which program command-line arguments (argv) are placed. unsigned long arg_start; // Address below which program command-lin arguments (argv) are placed. unsigned long arg_end; // Address above which program environment is placed. unsigned long env_start; // Address below which program environment is placed. unsigned long env_end; // The thread'd exit status in the form reported by waitpid. int exit_code; // NOTE: This does not come from /proc/[pid]/stat rather is populated by enumerating the /proc/>/fdinfo int num_filedescriptors; }; // // Struct for /proc/[pid]/status // struct ProcessStatus { char *Name; // Command run by this process char State; // Current state of the process. One of RSDTZX gid_t Tgid; // Thread Group ID (i.e., Process ID). pid_t Pid; // Thread ID pid_t PPid; // PID of the parent process pid_t TracerPid; // PID of the processtracing this process (0 if not being traced). uid_t Uid[4]; // Real [0], effective [1], saved set [2], and filesystem [3] UIDs gid_t Gid[4]; // Real [0], effective [1], saved set [2], and filesystem [3] GIDs int FDSize; // Number of file descriptor slots currently allocated. gid_t *Groups; // Supplementary group list (array). int GroupsLen; unsigned long VmPeak; // Peak virtual memory size. unsigned long VmSize; // Virtual memory size. unsigned long VmLck; // Locked virtual memory. unsigned long VmPin; // Pinned memory size (since Linux 3.2). These are pages that can't be moved because something needs to directly access physical memory. unsigned long VmHwM; // Peak resident size ("High Water Mark"). unsigned long VmRSS; // Resident Set Size. unsigned long VmData; // Size of data segment. unsigned long VmStk; // Size of stack segment. unsigned long VmExe; // Size of text segment. unsigned long VmLib; // Shared library code size. unsigned long VmPTE; // Page table entries size (since Linux 2.6.10). unsigned long VmPMD; // Size of second-level page tables (since Linux 4.0). unsigned long VmSwap; // Swapped-out virtual memory size by anonymous private pages; shmem swap usage is not included. int Threads; // Number of threads in process containing this thread. // This field contains two slash-separated numbers that relate to // queued signals for the real user ID of this process. The first [0] of these // is the number of currently queued signals for this real user ID, and the // second [1] is the resource limit on the number of queued signals for this process int SigQ[2]; unsigned long SigPnd; // Number of signals pending for thread. unsigned long ShdPnd; // Number of signals pending for process as a whole. unsigned long SigBlk; // Mask indicating signals being blocked. unsigned long SigIgn; // Mask indicating signals being ignored. unsigned long SigCgt; // Mask indicating signals being caught. unsigned long CapInh; // Mask of capabilities enabled in inheritable sets. unsigned long CapPrm; // Mask of capabilities enabled in permitted sets. unsigned long CapEff; // Mask of capabilities enabled in effective sets. unsigned long CapBnd; // Capability Bounding set. unsigned long CapAmb; // Ambient capability set (since linux 4.3). // Seccomp mode of the process (since Linux 3.8). // 0 means SECCOMP_MODE_DISABLED; // 1 means SECCOMP_MODE_STRICT; // 2 means SECCOMP_MODE_FILTER. // This field is provided only if the kernel was built // with the CONFIG_SECCOMP kernel configuration option enabled. int Seccomp; // These fields are subject to the formats laid out in cpuset(7) // They will be represented as char* here char *Cpus_allowed; // Mask of CPUs on which this process may run. char *Cpus_allowed_list; // Same as previous, but in "list format". char *Mems_allowed; // Mask of memory nodes allowed to this process. char *Mems_allowed_list; // Same as previous, but in "list format". int voluntary_ctxt_switches; // Number of voluntary context switches. int nonvoluntary_ctxt_switches; //Number of involuntary context switches. }; // ----------------------------------------------------------- // a series of functions for collecting infromation from /procfs // ----------------------------------------------------------- bool GetProcessStat(pid_t pid, struct ProcessStat *proc); #endif // PROCFSLIB_PROCESS_HProcDump-for-Linux-1.2/include/TriggerThreadProcs.h000066400000000000000000000024151412022400100223100ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // header - thread processes // //-------------------------------------------------------------------- #ifndef TRIGGER_THREAD_PROCS_H #define TRIGGER_THREAD_PROCS_H #include #include #include #include #include #include #include #include #include #include #include #include "CoreDumpWriter.h" #include "Events.h" #include "ProcDumpConfiguration.h" #include "Process.h" #include "Logging.h" // worker thread process for monitoring memory commit void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *ThreadCountMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */); #endif // TRIGGER_THREAD_PROCS_HProcDump-for-Linux-1.2/procdump.1000066400000000000000000000030021412022400100166560ustar00rootroot00000000000000.\" Manpage for procdump. .TH man 8 "04/03/2020" "1.2" "procdump manpage" .SH NAME procdump \- generate coredumps based off performance triggers. .SH SYNOPSIS procdump [OPTIONS...] TARGET -h Prints this help screen -C Trigger core dump generation when CPU exceeds or equals specified value (0 to 100 * nCPU) -c Trigger core dump generation when CPU is less than specified value (0 to 100 * nCPU) -M Trigger core dump generation when memory commit exceeds or equals specified value (MB) -m Trigger core dump generation when when memory commit is less than specified value (MB) -T Trigger when thread count exceeds or equals specified value. -F Trigger when filedescriptor count exceeds or equals specified value. -I Polling frequency in milliseconds (default is 1000) -n Number of core dumps to write before exiting (default is 1) -s Consecutive seconds before dump is written (default is 10) -o Path and/or filename prefix where the core dump is written to -d Writes diagnostic logs to syslog TARGET must be exactly one of these: -p pid of the process -w Name of the process executable .SH DESCRIPTION procdump is a Linux reimagining of the class ProcDump tool from the Sysinternals suite of tools for Windows. Procdump provides a convenient way for Linux developers to create core dumps of their application based on performance triggers. ProcDump-for-Linux-1.2/procdump.gif000066400000000000000000001231731412022400100172770ustar00rootroot00000000000000GIF89ap5f!! NETSCAPE2.0,p5-@srz`:{xS|iH|uhZ0yzA{|z}@zy~*yl!xvY}L uxI}+~0wu wnw |w+lj#|l+T~lL+LML+LWx6w^&{1|YKLll}8yLl| t{LLll+lL'wlL+++L++l+L+++l+G*\ȰÇA3jȱǏ CZ(Xŋ"S\ɲ˗0UV% +ɳϟ@ %684HrӧPJJ58Yׯ`ÊyU؋%I]˶۷p$q˷ع5$IaïÈ+&3KLeuEԨ8ϠCm((7D!ͺqAeͻNq|+_μΣKN:سO^ӫ_O=t/Ͽ?(H&x 6VX f|nu$V'a(*"n,(㌤H8Nfc<׎>)Z@iSLdPFғRVi%GT^ e`wbdf9)՚pri4my|Cgg9hyhhrhV9i2g2ziiixjjzjV8k:xkk7jk7lxlІ8fԎl^wm:ml&mvn6nڷn6oVvo*onk;mKo p{pK5quqq wp$q(4p,Kvr0cr434s8ts<9r@;sDK{@-o>ï}>ۏp 8:3 ?#p| .A & ‚lA O(6^ ^ìɐ5 sXKѲh$)Ka撘%2heL$Y@$ph+ >55Mmn[4 @Mq, $9NuY|g)ϊѳk'>Itt?{uρ", AqUЅ uh*Q~!4eE3Zr (<*R %FSL_$}ib*vѴ-E)Nuӝ>I1F:*RԥFNmT쀨TTJrX^**V V9  5tkԚW:~-]X0]%Z2%c갖5b3[rvgiѶMjS[ղ6T}m<+[ [ݒp[4 p )"4Z.tϵHUF%q񒗺yId\D}o+_A5/~ w/ u50`IVT#,aRXMkW_-,0d.q8b'.cLYƆqq,/XR,7>N3fcZn3v8DlrӬg%@ͣ?E>tlgE+ю#,A7I4eMoojQҦFSNՓ~5|]-:Եͪs[ZV({Ma*ظ6h|c# ͮ+dS{B/< ȷ qo܋17u#&д-,o7};O>?x}"=~89/w3.4s8=.r099ʿw.N@'4yyf^sC>W{t  t/:ԵYV7TB?:أ7f_N@voaj;%g\v~;3~ԃoK!m2(RCΓ59=G=cϚԃ0==۫T|&>[gS?1п>e} ݇~|>?fG ߗ/^n8B|헀 H$ XUqxs]sPP&( A+ 24)XH7*ȃ@B8"Fx5>"L(E+т?(@VKN\(5ND8U1eh(Rb(5h=r8OHuxxȄz.񇀘6a8dQ(q؈{g؇IH^ Qh!8xAqȊ(y%@Xȃx犽Xh7H6#h(z8 q(h #/(8&XYG (!x X y1a#yX*)g0)9 3 ْȒ7ٓ@IB9C?iGJ)~5 XMi~L9P)iﰕ\yUb&XY~hi6nI/Vt)y0z 2 !",p5+juirqpz+kaqYpxxHniuAma9ks@{psY8{QpMnI~"|pRpx1k1yQnjr~y*|S|}QpnxI1} xSkR~!xxH~Z vv8~(x|1oYxat}{ImLpur|xtuv7y|/nAo u H*\Ȱv#JHŋ3j&hǏ CIIbO\ɲ˗0cK)͛8sA@@ J(HF*]ʴӇJJUjʵWHKYuaϪ]˶biʝKҬv7fܾ /È+vgxǐ#K+˘c̹s]͞C4ӨS'5~žMKֶsޭ7-#_μ9;ΣK>'سʿ/;UOϾ=w~mnH~hVL%_VHfIm+a$Ft%D' 4#c88ύ:9 Dv#bH&J6$NFI$RV#Vf9#ZvId^bI#f"j ni rviz~J&z'6FzVfvjjzZZ:Y?찰.b>+-Nka^mWnm^~+\kZ垫.YٮPȾ+/kTޫoSQ+Il0QF.0L ?,JOlK^1nqI,H!lG2C%D-,3l3B)߬3>9<1,45m4==49I/7M?-6D=hx ӈMΘDj-7qvvv[~uDԊ/t8nw+MyF9l`5\_+|>Ր~窓Bc={\λ_D+|O?Ot X4kot{/t 3_~7s/,D3] 3@%+ G R|31< -&JL4aS‰- c8a4` :Jz(ăٰ#/%210|nD)ˉV*r][(sa:#ץ5dtTG9jvT 7ֱ# A|4Hc B$$#INd%3GrY6'eFV)%U,cTRXer^Y**[VxRbT-NOc|;8cO>N113%g:v2v,eG?2C-K^v̜19f>qf4ka39vލ|7y7xsm-Я94[Eю> #=IS:4tg2pӗaAOZ2>5dRjLjժy5Q#Yֶ s]35f@a&Ʈ d32~-Pڮ6mG>Ljj~̸Ӎp/~`\`ny& ͗}[/]6me6^\Pp˅ #S.8[ qdgAޖ|.?X򇏼fI9̻"6s~,>Xj.c :һoNѣΕS]+PTs?^ .)f?;SҮvFy;܉" v 󾓽='~M/(3>Y闤I}s_$#`k_#?F/Hʟп7Haǀ H  Q"$ x !*؂0X281&h:X =#D(CxJF؄P58PV8X \H Ё_^8]felnrhtXxhp~0 xڐ ؈}8 xѐ ؉8X 8`hX؊π > xĐ؋8 8P >( & H` ڸxޘ <x < !",! ,!, l++l+L+LLlLLl++L-H2kld_[}(S+/! , L++LL+LllL+l+llL++ll+Lll+lLlLLB pp@S%v* 钔CAࢍKb!$9Xz8eY!,# Ll+L+ll++L0AS:Fh IUf^;07 n8 D#YT"!,+ o܁K& !,3 Ll+Lll+L+l+l++lLLL< hZF2*LLU+( PA`J (Z]@Z)ގ!!,;LL+lL++llll++LLllH 1h LL1`8Kt.A"2UtL%&*A!ꤨ]R f @.4lVewN! ,C +LLlLl++LLL+lL++lL+lLLlL? 0pTb Ta)"Т @p,x4j)LI`Vn sas ! ,K l+lll++++ll+++l+l5 *)j}ۦ$ 0]]䊉C` /@,}HјL.M3! ,S Ll+Lll+L+l+l++lLLL> hZF &Dt*JBiGq: dTZEb*^Ŏ@!,[ Ll+LLL+L+++llLl+l;qC"iږgky}HxD Z6{n]W!,c +l+lLllL,1'͇Y'nCcyɊۮ Ac!$,k8LLl+L+l+l++lL+Ll+llllLLLlLllLL+llL+lLl@pH,ql:BJR,`%p(YQIh0IMKc B q |  qZQ Id SUg_jRQP!b VοO|X ݾV h qsom ucK 88A  thbB!,!, L+l++LL+LlL+ll*0 0V`|'0T{Jq|ϟ>0 y!,  ߂Rk)XO~y(!, Ll+Lll+L+l+l++lLLL< hZF2*LLU+( PA`J (Z]@Z)ގ!!,LL+lL++llll++LLllH 1h LL1`8Kt.A"2UtL%&*A!ꤨ]R f @.4lVewN!, +LLlLl++LLL+lL++lL+lLLlL? 0pTb Ta)"Т @p,x4j)LI`Vn sas ! , l+lll++++ll+++l+l5 *)j}ۦ$ 0]]䊉C` /@,}HјL.M3!*, Ll+Lll+L+l+l++lLLL> hZF &Dt*JBiGq: dTZEb*^Ŏ@!, LLl+Ll+LlLl+LL;@<@C,#t;K .%".8(dO 8+uf\/B!, Ll+LLL+L+++llLl+l;qC"iږgky}HxD Z6{n]W! ,L+lLL+LLL0Й9R[aᘕƥ,6ǒ n8 D#YT"!<,L+lLL+LLL9Й98kR灤%艒v.L_ͱܖ+  FRYD˦3e~!, l++lL+llL+L+l+lL+LL+L+LLLlLT@!$¤` P&%:r|(P6R;rQs ! A!.,++LlLl++l+llLl+L+Ll+lLLL+lL+L+LlLLl++lL+LLl+l++LLl+LlLll+lLL++lll@pH,Ȥr$JtJZϫvzxL.znpl|N~hzpRJCE-E J/E jDܬ O &%'D dݾCGhh0`ky aC1y7 pĸfaC!>!A ##sn) "JFaD/A!\9Py2%xGXTE e՞'t\hsd1S =bCbîKx!Bgn`dAz8l4`$tqMӨS^ͺ]S$C,w:4X1#K( ms7NV:ܟזq8+ojIW x!,/!,( +LlL++LLlLll+LlL+LLl++++lLLLl+llLl= p @åGG+mtzBR\1aE '$A#z7,!,( Ll+LLL+L+++llLl+l;qC"iږgky}HxD Z6{n]W!,#%LL+lL++llll++LLllH 1h LL1`8Kt.A"2UtL%&*A!ꤨ]R f @.4lVewN! ,+( +llL+l++L+LlLLLl+? ,/ o܁K& !,/!,& +l++LLl++lLll+lLLl+lLlLl+L+F P4ð\@!k@*Ы/Ua͆C{(i:Jd2^9!,& l++l+l+lllL+LL+LA 1hz$ʙ#;"A 1h00 TۨL7@!axj Hb,/ o܁K& !,& +l++Ll+L+lLll+lLLL++ll+LllLJ `8\@K1E+/ea͆C{,i:J`^g!!,& l++ll+LL+lL++L+ll+L+lJ \ bւT)JsVGEL.sH$ z7,! ,, ퟂRk)XO~y(!,,+lL@3V*I"}6z%w ! ,#( +LlL+L+LlllL+LlL+LLl++++lLLLl+llLlI@"1D RytOaXcٌ۷R [7D N@nMcA'Eh%QCtZe>35Cq 1;B" e |0fzM :y!®=zF'#wo_ʻ{ar "%VqǐHh;khyg'[jׁnrZfvBXUeXU ,(}À;&4) VJsgE emvY/@I)DE 8$4$jebw9$s)٢\hX$n \9^j`6^JA![N;Voڛm1Z &h$ u)r,@ |*SH*$vIpJIj%g@b&ʖ.Bꤞ%D,$ hL~ʬg%3(B[.Зҩ-"bjviJ*B*J[_,9p]$B #A<, >V|q{+lX)2Kh쩿r$(sVY.)P{t,>wtьuCZ//=nc_ y=Fc#҃H!o2#^!˭n >^4wޫR6 !4sj *ލp:D}guF=7U|KkY:쑖NaӴ")zsJʏ{UcpMMaVp''\7QM;: ѓ!(°`$?2.׀dL X8'HKqk0pjD4"$A]!6\@U Iނ!fD9ͦ{h@1tA&vpT" @G$3ePITq$6rƦ%|Lahq ge,i$@0]l""26vg;|% 1J$=xd$?do}f'[ !1Ky )g뉃@f0)f:WkTS|6Ikz#I8I r:vR)|!xRJ"5QEӮYRsc6ɼM_7r:OLDTTL!WAUo*,8%%$5f?ZU/td)O19e$)P[>㵁U2Oi 5*eS`)WY |#遱z%AGGD{s8^PTZ$qvla,, RۣQZtXkDd6RDc=%69Xw״.nc] bIiJDgek[Ri]) ?}:M93 2m{OA'ng):2ūsۭZԴCU"%u[Ezs[!rH. gi} Geo|ljͣn|CyT/y.sҫAoWG ~-lxDc(o].HmثZEnhO{'<]O '_GDA0b8D*C{ڜÁ~Pϵj;LnCm:g=\Y1ϲWIz9oG7,/3Vrj<_Gl#K! 'Q6|Tc*NjGB4~7o86т>4zE7(3vwx1 sJwRgW v5}/mb_]&ngqtM5>}M7A*s>&4?w8qtp-?wv 6 U=#Y%r(#Z*S>rV"XHrkɣ|ԆDh9_b7hG{V˃̣qe`XfJYoQfkA~PTmv$N\G tOe0cw4&j4wCfwE H:W8Bf>XB'WԈV4+!qH(ETDy*CԂ45C;B"q~XB"kts&hF8uA{Mh/G򎠈&W#&S&X҈})Q^%^8!PKtZ`\K&ARɁ^gR)GAzU_Kcֈ.K;yZ֥~$m4#A(I7Oaj)BIdXIɎ2VI!HITco$FaF$fU!ÓpJrU–@84UIt(RVbd9T% JiaqA9 ĔY31O Xt8d 9?u~Qәny`gqaPG*@ yٛ |Q-7Q)PU%PLʹYtU!|!0ֹ""6US99 h  !y|TW{\1%. x Di yՠQL /~^qПJzׄ L?Q\Ǖ\M^` ͨVw+$b Fd5xB(=d!d6Ǒ^bCʒiOIbKŖO!_FC[EWXQ`y R5xVbQej45c1CtlcVfJ%,$7I"jAvR$TiMXm8jmk6fv=|VARlE**i !0tlyL)"#c!j ٪AYt4d %f3Rsu&rsE8sG1{874S3ӒBsV my5 +REȒhJs?׉a30*2*XraȪ%9ԕm&kԍʍ38"HxU}mYIY;w'";|} k.Sln/Fzۼ+*m*jTאjՍ x=m~mj@G?qoz)Hua]F8w ژG(==8r:.*z*w4;ڗ<ԛ5ûsKWr'Z3's=QGu ;217Բ7tZOgܫKri7 ݪ0;Hg,T~{ތv;[:v贗2<\:-K[>{xGBwv3mbe;ww*m@ߏSqz=| MM>\4;s07 2l{9 [meΎP5 ,yD^M\w;∋B<|6òせwaå]\c] 熹s-p2]q›cZ0 J-V+`\h,܈kE؎`h38?!ti'E($CEէ{{x%舎 .#}k셇YϬr6ҝ7=HNYRa!4ȅ$a+֕C]bn9nq /$HNV4sTH@HcϤM3BcKts\ cf\ga(lt_vxz`= ΁.ES|T/}0 Sܣ']&#a 6[OVg5mN?Ohfk.ֶԱ6-Yo]MdG+ୠȿz0DoG.H<g< jP,Sq/b#(\Ipk4Iiϐͪ&&KC<$Q(*xfB%#Њ^ v&hJXL#ԞʶL jᰚ9IOc@E@l3Nk܎wʹв†4rmKެ%5"*r,d@qqbv:D'JъZͨF7kNދDH9 ]vyg1Qxkh3B2R`/DNSlԞ2ufK0۩R0J0 vHkpӶ3T%)QMGt2X .mՔ G) 1&_ҡ" RP:J(vh + F0JZi4@1J5 "DؕAEIђT(M95m;QKպx0bҲ RKuҟ5U%\]\=r! Fp-|K.r3$Sqz)od*v5Ks)_f7q&o;Z6׷d.ߺGظ]7vY<  .QSo$IKn~+rޓ]X{_^.91}wڨ xxYF 6)a:%IW:)H͸.oc<GRf|nJuQjО~j:!Jv",XLLWUV*j)Ɍ o5GOқOWֻ{T^gѷc%)Hi*?@Z} ]BfPjJVU3=\6VL ??e%,-G]5<۶4v(Xvr'XXw$oB!}sAs}$'WtXɷDt,Rg^='GJ&Yo8aq$}Db vamTes74+8I!M1h7>dǂ{cG rIhtIN9yB'<` faĔhI2q8uo!pi ۙoqGe 9Iǜ/9`$مȵ0hubJ&ؠZ5CEq+qG:]s *ٟ|Ɠ?Dr}_8FeM.YtKڟ$`}HVj֜<?sU܄^I@:iÒ9AfeD.&&ιԞ[I8SW6-_׭c)P]+d\cl6`O +0"&ᤄVwѕ ?b-4CUDQrWć@] .@)S 2"]\XXpx6$WP`8$_pDC"9/EX_sY,.RN(mԘ̕Ǟ B.f9%<ߙ]hB^8&㣐FzEVj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+kȎ.쳚6,Z*-f jaA!.,5P+L+l+lLlL+ll+L+lLL+L+LlL++lLlLlL++LlllLLLlL+L+LlLl+LlLlLlLllLllLllLlll++l@,¤rl:ШtJ*%JGxL.zn1$yO$P)LtCY}Qz|~vY jXYB [I ]w+qK`HqeTLIBPBwQBB҅MQԪJC!)I`L2h!Bх-ǯ%<(J~€bnpqPO$/2咇˷$K{DzS$x-%Ӆ,UeDHtaO?S45j:~9(OmHʄ@,b Bp`yoNR d3anJي֍!4Ube)? f35ϴl L!`"KG%mhWVlwV5=ʡ7A_BH .;V|{&'%~^tK$}9adՅS엗|eXS @is'$@IE@"#0 %7UI8SR- Y =RHchXD |0t^&g|}褋1:9ndS>dN IRjhi&!Es8$!@[Qb 1G:#zX`W*0h0& *7V;kƶOtjK@i`~J*~dd45d1]}"KkZY÷fq^f #Wckq#¹LYLƯ 0̢Q"*8;}me3%գ%4Zn!+.UqvNC'X X+qH6TFdMm7,_T0s߄ 2L.*T'Q:' ꜜ$+T FȽ|թlq<p#M7-t1Tz29D-kU>D%%a ;Jկ`. %T 1ػbQ-BB4?+L0!O QK.M%u:- 0XL?)'8HJQ& [Fܯ?do\O2m)飶*7$YWEGE|)oT$2\{`3h`k͔OGQgބ@]v_J7HUwI`x%ę]+BfVJcK73 vĄ蘌O3wӖFJlc.h1̋D,V=( ?5V{Y4H.a(ӛIAKI6Wyz{baht&H8S~Ke_"*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vk dmvz+j;-n!4,UP++Lll+lLL+ll+L+lLL+L+LlL++lL+LLllllLL+LlLl+lLlL+lLlL+L++llLLlL+LllLllLlLllll@`p(¤rl:ШtJ*%JGxL.zn14qO$`1LtCY}Qz|~Bw(jXYB [I ]qK`HpvY!iTLIPIwx{QӅMIOKBCB#-I`@a *OA~,"MX†Q<%-~{e%2y@?#+g%wx_I0UHP R?!FXpi…OIԄT4k8sWLAO#[ * G8yЁ`"$Jpt!@Z&Þ @NVvQA-{2Bi4QMUζܠ0؜ȀYF( жK:4ޤ:ױ a⟂eQMϋ,XnxK_ӗ]W_&Xb'Q^&!ՠpXĠBA f|^_P2U=XЏ%'Tш),F ؘ61Hc BFXf 4%*:Q800]~fXs֩i]XEIhCWJbSSRiVx dtx釕8#IlQ|I9' jO $2:`fb\vQ _@<׮rh+_DkN+ޞj*ݨ:h>5Y|J@mǿ;abu%;{eYn]P"%h7FӪQK׎vmK' _^g=u[5J$-vLzwQs+ 4hS%ES3[MBu#?D7 d^UNh'If iNNqt׸PQ49";uFɛc/딽A&KF57{/ vkQ~B RK*n]h7u w + >f$J7Q`^Y&î$ ڗW r[*_U^SW 7|N4|(_x x0aA^Xz \SSXYxlp" E+"u3AL'xӧ"?0 #I@[ cdC%LQՍh9rSbA]HI:N`# J< &'? l[0 kTҕR})ai!-`K>O$8#rPbg<yGEchuد=~K d4: AAM49S`B'H6#G?\P%eC9Pk`FHK 0ls2^` PtQ(R-"!9hJoS^&;tnjJ3CM{}IRT-TU5&S'dUfQ5Pg/tpUsd BO", 9ɫ^IU[JX74Mbpud71@`df)zvgGKZVV xFi5xbFm-3.F_MU"e%qdaZ\xwGW\~eyIQHh8z6R! Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k.쳝6,f*-f[jmA!4,uP+L+l+lLlL+ll+L+lLL+L+LlL+L++lLlLl+LlllL+llL+LLLLlLl+LlLlLlLlllLlLl++l++++LlLlllllLLl  $  ҂ ЛƉ ܝ,7͔"( 2nA`G9y8HE \TQPJc `80AF&T! Z(@c(;*t6%*&K? 7 &p*+?fo>KnaݭhIq.[*ݾ0'`B$ &6Q6UT(/њJ0w#.mN̈c7%&uU@q-Nhrp+^R Be5A#Nڻ# 5wbJ]KjJypKAer*d2@ZAy桧;XaA6!'{Dv\wbvZqqMVmt#GpW #wXGSa0\q!#c]c !2^#mDj)Xap]Rs)$6"a@(!mY~%e7BT5e-reeٞ8 J H'%$DP"蠖# =%hV^ʤW{4*rJ*dX-b鞬btFlO6wXs]z1igr~5y\U2B-] x;6kαӖ۶ց%KW9ʚɕB53K3F 57[&u>Mulnn'je3v/rsKS2*,ܛDӒ@M(FnL^ j%x+smX/8%&R촾9ms\{$Zӷe?r;7j/t#T7%l_|y/C6BsgW/fGKB0"]Zsω쌗+K^%ЧPVv!!vSըq0 gy@kfRT29 1 M8(_&d́pxFGd:)kM;"q VoR35Lj6C=(+\fFul 8 卾A&;q2]Bi#pij%l9FT7و8)ܳitz۩T3A3V Z *F_n j- qr#BY@2U.RJ"zD z. &=f.9,X}dMۼ NH\sB1Lr:B8V'QW]-IQ] e* ̢64b"l.yIPQ6Z8j<1dDQ;mLDV`aB0^#0Qΰ߫Zx G[I$NWD.-gLcg w7V챐L͡&;Tq*[Oβ[CW沘LVx`r6"Xv\@! ,!,Ll+LL+LLL,)Q[aᘕƥ,2Z RK(?F_!, +LlL+L+LlllL+LlL+LLl++++lLLLl+llLlI@"1D RytOaf.P3H$qJ.$Lt*,hh˦/M9Vtc3L CYc}B6 Q0onmgӮaFuz+AHbjX0! "rɤfw43kdwźv2oҟr93cǐNLqz9|-u'ZdUL 7#B0=ÀO0xt@LwυR^IP,0Ҧ_]#; X_U*x#\p9b4u+XU8E/VDD Nla1&x!CB`;Gygٸ$r:1T9* *>%7iWfI]JHy~nh2`'yfi]sֺ uz>6:iTj,& @|=-լP(׫+tH#ۼ:X 6ozi@p*Z' 8/6.m,›.JK֞pK"ׅ+c:qΗnt\'{$b '105-rN G,pac̦q,֜\L׆Ͱt0+ˋ|galŬ?iU?63Pˈ0zG.| Bؠva,w=op"X9-]Ϡ[qyaps6ɁgmT\ڞsoNp>N֋[.9TDnyGA\:f7tfo㉞N9q)<~v ^g<2s-x# *uw׏ Bo}E ">aτ@aψ) n,t!F+ !ZkFׁ "uבQrB#aQ0k-P$:4D L ,@$VЋ'ݜ8EqE()H )А8D#z` ,_42)T2Uu ZEyRR+iW!ela˥,F^ ·lNq^EI33@Z46IvM$'/Ծ<`s;3&J$]B)QT\+3Q>vTI<,i ,O/ Ս`E3FtW6RUt(}(PcX4J E:jԱBՊ> `.LQ3nl3ŃX0V^1 9eΠ; (ENzpF5<+=6V@"Sc}4b@|=E# vZqDmP;Ҩk1v H*׸esy6-kg4&ඹM,oۢ>3 *i( [v;?nf7Zq*Lܓ)M JBJ:ls)^ya w5Y bxĺ=ȣȈSHxY8pZ`b0Th2Y{d#;2&q2%.+b \QqVl#aEA>d, ( %y@\ϘykZ{~NXҍay47MMs\4G=vj57͢`ŠrIz55rzȭq_}D1lb;ЎMj[ζ*jmoNcܧC :X~vPQNhx; N[Av{ . F0*(a89ewu:s;Pw[Z!",!",! ,!,p5-@sry`:{S|uH|iZhz0y|yA{@z}*yxY}y~v!x u~I}x0ww tt |xnjT~#|MWYw6w1|K&{|{8yu~^'w @,Ȥrl:ШtJZجvzxL.z=|N~B˟؜~'*,EpÇ#Jpŋ3jVqǏ CTqɓ(S,Tr˗0c"l)͛8ɳvJQ*]ʴi(NJz*իX´ׯK,ŤfӪ]{P,۷pfs+ݻ˷,~  0È?2ƌK72˘Z̹͞C zCK^ͺְc|-hoލ6N8Ɠ+י{mCN=س畮wq׿+<rOϾ0˗ߏ_? H)9IHᅿYᆶiᇭy∦H≝⊔⋐ Hڈ I!%*): -JI1beh.n%Q]~)fOaiMXyl) "Iى 3I衩< 8 HJXh) ꨸jK*4j2*+1jk/ު+._*;k,*(.'i>+m= ;DTx hKsYѸ 4K/ޫ0//;m\ ' 7J끺W< gw< \ "Jrɫʌˇp ̎K͈،΃'_K yN~z>~~n ~*³{/nΓ|NO=_mO }~܏;~<k( 4@Yz  I\3x rTPK!3E/юs#ML4MӞI .n82UЮVɩcKӚ'KrkYѰ5ka a3{׿~62-P'6Gmd޶Hq[#>7FFny7#"y?H}!A.p98#nS<2qwp8!򑣣&'R-E `Px9>ЃF?z5z0x:ߑCcVֳus]Rz:.s菏+_>0/dоƈ>uA?.ү\pugoqsryQo 7 h   l瀊 6 iGXvX8 $ &x* )؂Ё0(v/8 8x :p>( @ЃD FxJ L؄P R8PVX C\X ,bh dX0hj;ProcDump-for-Linux-1.2/src/000077500000000000000000000000001412022400100155375ustar00rootroot00000000000000ProcDump-for-Linux-1.2/src/CoreDumpWriter.c000066400000000000000000000561711412022400100206300ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // Core dump orchestrator // //-------------------------------------------------------------------- #include "CoreDumpWriter.h" char *sanitize(char *processName); static const char *CoreDumpTypeStrings[] = { "commit", "cpu", "thread", "filedesc", "signal", "time", "manual" }; bool GenerateCoreClrDump(char* socketName, char* dumpFileName); bool IsCoreClrProcess(struct CoreDumpWriter *self, char** socketName); char* GetPath(char* lineBuf); uint16_t* GetUint16(char* buffer); int WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName); FILE *popen2(const char *command, const char *type, pid_t *pid); extern const struct IpcHeader GenericSuccessHeader; //-------------------------------------------------------------------- // // NewCoreDumpWriter - Helper function for newing a struct CoreDumpWriter // // Returns: struct CoreDumpWriter * // //-------------------------------------------------------------------- struct CoreDumpWriter *NewCoreDumpWriter(enum ECoreDumpType type, struct ProcDumpConfiguration *config) { struct CoreDumpWriter *writer = (struct CoreDumpWriter *)malloc(sizeof(struct CoreDumpWriter)); if (writer == NULL) { Log(error, INTERNAL_ERROR); Trace("NewCoreDumpWriter: failed to allocate memory."); exit(-1); } writer->Config = config; writer->Type = type; return writer; } //-------------------------------------------------------------------- // // GetPath - Parses out the path from a full line read from // /proc/net/unix. Example line: // // 0000000000000000: 00000003 00000000 00000000 0001 03 20287 @/tmp/.X11-unix/X0 // // Returns: path - point to path if it exists, NULL otherwise. // //-------------------------------------------------------------------- char* GetPath(char* lineBuf) { char delim[] = " "; // example of /proc/net/unix line: // 0000000000000000: 00000003 00000000 00000000 0001 03 20287 @/tmp/.X11-unix/X0 char *ptr = strtok(lineBuf, delim); // Move to last column which contains the name of the file (/socket) for(int i=0; i<7; i++) { ptr = strtok(NULL, delim); } if(ptr!=NULL) { ptr[strlen(ptr)-1]='\0'; } return ptr; } //-------------------------------------------------------------------- // // IsCoreClrProcess - Checks to see whether the process is a .NET Core // process by checking the availability of a diagnostics server exposed // as a Unix domain socket. If the pipe is available, we assume its a // .NET Core process // // Returns: true - if the process is a .NET Core process,[out] socketName // will contain the full socket name. Caller owns the // memory allocated for the socketName // false - if the process is NOT a .NET Core process,[out] socketName // will be NULL. // //-------------------------------------------------------------------- bool IsCoreClrProcess(struct CoreDumpWriter *self, char** socketName) { bool bRet = false; *socketName = NULL; FILE *procFile = NULL; char lineBuf[4096]; char tmpFolder[4096]; char* prefixTmpFolder = NULL; // If $TMPDIR is set, use it as the path, otherwise we use /tmp // per https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md prefixTmpFolder = getenv("TMPDIR"); if(prefixTmpFolder==NULL) { snprintf(tmpFolder, 4096, "/tmp/dotnet-diagnostic-%d", self->Config->ProcessId); } else { strncpy(tmpFolder, prefixTmpFolder, 4096); } // Enumerate all open domain sockets exposed from the process. If one // exists by the following prefix, we assume its a .NET Core process: // dotnet-diagnostic-{%d:PID} // The sockets are found in /proc/net/unix procFile = fopen("/proc/net/unix", "r"); if(procFile != NULL) { fgets(lineBuf, sizeof(lineBuf), procFile); // Skip first line with column headers. while(fgets(lineBuf, 4096, procFile) != NULL) { char* ptr = GetPath(lineBuf); if(ptr!=NULL) { if(strncmp(ptr, tmpFolder, strlen(tmpFolder)) == 0) { // Found the correct socket...copy the name to the out param *socketName = malloc(sizeof(char)*strlen(ptr)+1); if(*socketName!=NULL) { memset(*socketName, 0, sizeof(char)*strlen(ptr)+1); if(strncpy(*socketName, ptr, sizeof(char)*strlen(ptr)+1)!=NULL) { Trace("CoreCLR diagnostics socket: %s", socketName); bRet = true; } break; } } } } } else { Trace("Failed to open /proc/net/unix [%d].", errno); } if(procFile!=NULL) { fclose(procFile); procFile = NULL; } if(*socketName!=NULL && bRet==false) { free(*socketName); *socketName = NULL; } return bRet; } //-------------------------------------------------------------------- // // WaitForQuit - Wait for Quit Event or just timeout // // Timed wait with awareness of quit event // // Returns: 0 - Success // -1 - Failure // 1 - Quit/Limit reached // //-------------------------------------------------------------------- int WriteCoreDump(struct CoreDumpWriter *self) { int rc = 0; // Enter critical section (block till we decrement semaphore) rc = WaitForQuitOrEvent(self->Config, &self->Config->semAvailableDumpSlots, INFINITE_WAIT); if(rc == 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed WaitForQuitOrEvent."); exit(-1); } if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed pthread_setcanceltype."); exit(-1); } 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, &socketName); if ((rc = WriteCoreDumpInternal(self, socketName)) == 0) { // We're done here, unlock (increment) the sem if(sem_post(&self->Config->semAvailableDumpSlots.semaphore) == -1){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed sem_post."); if(socketName) free(socketName); exit(-1); } } if(socketName) free(socketName); } break; case WAIT_ABANDONED: // We've hit the dump limit, clean up break; default: Trace("WriteCoreDump: Error in default case"); break; } if(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed pthread_setcanceltype."); exit(-1); } return rc; } //-------------------------------------------------------------------- // // GetUint16 - Quick and dirty conversion from char to uint16_t // // Returns: uint16_t* - if successfully converted, NULL otherwise. // Caller must free upon success // //-------------------------------------------------------------------- uint16_t* GetUint16(char* buffer) { uint16_t* dumpFileNameW = NULL; if(buffer!=NULL) { dumpFileNameW = malloc((strlen(buffer)+1)*sizeof(uint16_t)); for(int i=0; i<(strlen(buffer)+1); i++) { dumpFileNameW[i] = (uint16_t) buffer[i]; } } return dumpFileNameW; } //-------------------------------------------------------------------- // // GenerateCoreClrDump - Generates the .NET core dump using the // diagnostics server. // // Returns: true - if core dump was generated // false - otherwise // //-------------------------------------------------------------------- bool GenerateCoreClrDump(char* socketName, char* dumpFileName) { bool bRet = false; struct sockaddr_un addr = {0}; int fd = 0; uint16_t* dumpFileNameW = NULL; void* temp_buffer = NULL; if( (dumpFileNameW = GetUint16(dumpFileName))!=NULL) { if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { Trace("Failed to create socket for .NET Core dump generation."); } else { // Create socket to diagnostics server memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socketName, sizeof(addr.sun_path)-1); if (connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) == -1) { Trace("Failed to connect to socket for .NET Core dump generation."); } else { unsigned int dumpFileNameLen = ((strlen(dumpFileName)+1)); int payloadSize = sizeof(dumpFileNameLen); payloadSize += dumpFileNameLen*sizeof(wchar_t); unsigned int dumpType = CORECLR_DUMPTYPE_FULL; payloadSize += sizeof(dumpType); unsigned int diagnostics = CORECLR_DUMPLOGGING_OFF; payloadSize += sizeof(diagnostics); uint16_t totalPacketSize = sizeof(struct IpcHeader)+payloadSize; // First initialize header temp_buffer = malloc(totalPacketSize); if(temp_buffer!=NULL) { memset(temp_buffer, 0, totalPacketSize); struct IpcHeader dumpHeader = { { {"DOTNET_IPC_V1"} }, (uint16_t)totalPacketSize, (uint8_t)0x01, (uint8_t)0x01, (uint16_t)0x0000 }; void* temp_buffer_cur = temp_buffer; memcpy(temp_buffer_cur, &dumpHeader, sizeof(struct IpcHeader)); temp_buffer_cur += sizeof(struct IpcHeader); // Now we add the payload memcpy(temp_buffer_cur, &dumpFileNameLen, sizeof(dumpFileNameLen)); temp_buffer_cur += sizeof(dumpFileNameLen); memcpy(temp_buffer_cur, dumpFileNameW, dumpFileNameLen*sizeof(uint16_t)); temp_buffer_cur += dumpFileNameLen*sizeof(uint16_t); // next, the dumpType memcpy(temp_buffer_cur, &dumpType, sizeof(unsigned int)); temp_buffer_cur += sizeof(unsigned int); // next, the diagnostics flag memcpy(temp_buffer_cur, &diagnostics, sizeof(unsigned int)); if(send(fd, temp_buffer, totalPacketSize, 0)==-1) { Trace("Failed sending packet to diagnostics server [%d]", errno); } else { // Lets get the header first struct IpcHeader retHeader; if(recv(fd, &retHeader, sizeof(struct IpcHeader), 0)==-1) { Trace("Failed receiving response header from diagnostics server [%d]", errno); } else { // Check the header to make sure its the right size if(retHeader.Size != CORECLR_DIAG_IPCHEADER_SIZE) { Trace("Failed validating header size in response header from diagnostics server [%d != 24]", retHeader.Size); } else { // Next, get the payload which contains a single uint32 (hresult) int32_t res = -1; if(recv(fd, &res, sizeof(int32_t), 0)==-1) { Trace("Failed receiving result code from response payload from diagnostics server [%d]", errno); } else { if(res==0) { bRet = true; } } } } } } } } } if(temp_buffer!=NULL) { free(temp_buffer); temp_buffer = NULL; } if(fd!=0) { close(fd); fd = 0; } if(dumpFileNameW!=NULL) { free(dumpFileNameW); dumpFileNameW = NULL; } return bRet; } /// CRITICAL SECTION /// Should only ever have running concurrently /// The default value of which is 1 (hard coded) and is set in /// ProcDumpConfiguration.semAvailableDumpSlots /// Returns 1 if we trigger quit in the crit section, 0 otherwise int WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) { char date[DATE_LENGTH]; char command[BUFFER_LENGTH]; char ** outputBuffer; char lineBuffer[BUFFER_LENGTH]; char gcorePrefixName[BUFFER_LENGTH]; char coreDumpFileName[BUFFER_LENGTH]; int lineLength; int i; int rc = 0; time_t rawTime; pid_t gcorePid; struct tm* timerInfo = NULL; FILE *commandPipe = NULL; const char *desc = CoreDumpTypeStrings[self->Type]; char *name = sanitize(self->Config->ProcessName); pid_t pid = self->Config->ProcessId; // allocate output buffer outputBuffer = (char**)malloc(sizeof(char*) * MAX_LINES); if(outputBuffer == NULL){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed gcore output buffer allocation"); exit(-1); } // get time for current dump generated rawTime = time(NULL); if((timerInfo = localtime(&rawTime)) == NULL){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed localtime"); exit(-1); } strftime(date, 26, "%Y-%m-%d_%H:%M:%S", timerInfo); // assemble the full file name (including path) for core dumps if(self->Config->CoreDumpName != NULL) { if(snprintf(gcorePrefixName, BUFFER_LENGTH, "%s/%s_%d", self->Config->CoreDumpPath, self->Config->CoreDumpName, self->Config->NumberOfDumpsCollected) < 0) { Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed sprintf custom output file name"); exit(-1); } } else { if(snprintf(gcorePrefixName, BUFFER_LENGTH, "%s/%s_%s_%s", self->Config->CoreDumpPath, name, desc, date) < 0) { Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed sprintf default output file name"); exit(-1); } } // assemble the command if(snprintf(command, BUFFER_LENGTH, "gcore -o %s %d 2>&1", gcorePrefixName, pid) < 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed sprintf gcore command"); exit(-1); } // assemble filename if(snprintf(coreDumpFileName, BUFFER_LENGTH, "%s.%d", gcorePrefixName, pid) < 0){ Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed sprintf core file name"); exit(-1); } // check if we're allowed to write into the target directory if(access(self->Config->CoreDumpPath, W_OK) < 0) { Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: no write permission to core dump target file %s", coreDumpFileName); exit(-1); } if(socketName!=NULL) { // If we have a socket name, we're dumping a .NET Core 3+ process.... if(GenerateCoreClrDump(socketName, coreDumpFileName)==false) { Log(error, "An error occurred while generating the core dump for .NET 3.x+ process"); } else { // log out sucessful core dump generated Log(info, "Core dump %d generated: %s", self->Config->NumberOfDumpsCollected, coreDumpFileName); self->Config->NumberOfDumpsCollected++; // safe to increment in crit section if (self->Config->NumberOfDumpsCollected >= self->Config->NumberOfDumpsToCollect) { SetEvent(&self->Config->evtQuit.event); // shut it down, we're done here rc = 1; } } free(outputBuffer); } else { // Oterwise, we use gcore dump generation TODO: We might consider adding a forcegcore flag in cases where // someone wants to use gcore even for .NET Core 3.x+ processes. commandPipe = popen2(command, "r", &gcorePid); self->Config->gcorePid = gcorePid; if(commandPipe == NULL){ Log(error, "An error occurred while generating the core dump"); Trace("WriteCoreDumpInternal: Failed to open pipe to gcore"); exit(1); } // read all output from gcore command for(i = 0; i < MAX_LINES && fgets(lineBuffer, sizeof(lineBuffer), commandPipe) != NULL; i++) { lineLength = strlen(lineBuffer); // get # of characters read outputBuffer[i] = (char*)malloc(sizeof(char) * lineLength); if(outputBuffer[i] != NULL) { strncpy(outputBuffer[i], lineBuffer, lineLength - 1); // trim newline off outputBuffer[i][lineLength-1] = '\0'; // append null character } else { Log(error, INTERNAL_ERROR); Trace("WriteCoreDumpInternal: failed to allocate gcore error message buffer"); exit(-1); } } // close pipe reading from gcore self->Config->gcorePid = NO_PID; // reset gcore pid so that signal handler knows we aren't dumping pclose(commandPipe); // check if gcore was able to generate the dump if(strstr(outputBuffer[i-1], "gcore: failed") != NULL){ Log(error, "An error occurred while generating the core dump"); // log gcore message for(int j = 0; j < i; j++){ if(outputBuffer[j] != NULL){ Log(error, "GCORE - %s", outputBuffer[j]); } } exit(1); } for(int j = 0; j < i; j++) { free(outputBuffer[j]); } free(outputBuffer); // On WSL2 there is a delay between the core dump being written to disk and able to succesfully access it in the below check sleep(1); // validate that core dump file was generated if(access(coreDumpFileName, F_OK) != -1) { if(self->Config->nQuit){ // if we are in a quit state from interrupt delete partially generated core dump file int ret = unlink(coreDumpFileName); if (ret < 0 && errno != ENOENT) { Trace("WriteCoreDumpInternal: Failed to remove partial core dump"); exit(-1); } } else{ // log out sucessful core dump generated Log(info, "Core dump %d generated: %s", self->Config->NumberOfDumpsCollected, coreDumpFileName); self->Config->NumberOfDumpsCollected++; // safe to increment in crit section if (self->Config->NumberOfDumpsCollected >= self->Config->NumberOfDumpsToCollect) { SetEvent(&self->Config->evtQuit.event); // shut it down, we're done here rc = 1; } } } } free(name); return rc; } //-------------------------------------------------------------------- // // popen2 - alternate popen that surfaces the pid of the spawned process // // Parameters: command (const char *) - the string containing the command to execute in the child thread // type (const char *) - either "r" for read or "w" for write // pid (pidt_t *) - out variable containing the pid of the spawned process // // Returns: FILE* pointing to the r or w of the pip between this thread and the spawned // //-------------------------------------------------------------------- FILE *popen2(const char *command, const char *type, pid_t *pid) { // per man page: "...opens a process by creating a pipe, forking, and invoking the shell..." int pipefd[2]; // 0 -> read, 1 -> write pid_t childPid; if ((pipe(pipefd)) == -1) { Log(error, INTERNAL_ERROR); Trace("popen2: unable to open pipe"); exit(-1); } // fork and then ensure we have the correct end of the pipe open if ((childPid = fork()) == -1) { Log(error, INTERNAL_ERROR); Trace("popen2: unable to fork process"); exit(-1); } if (childPid == 0) { // Child setpgid(0,0); // give the child and descendants their own pgid so we can terminate gcore separately if (type[0] == 'r') { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to write end of pipe } else { close(pipefd[1]); dup2(pipefd[0], STDIN_FILENO); // redirect pipe read to stdin } execl("/bin/bash", "bash", "-c", command, (char *)NULL); // won't return return NULL; // will never be hit; just for static analyzers } else { // parent setpgid(childPid, childPid); // give the child and descendants their own pgid so we can terminate gcore separately *pid = childPid; if (type[0] == 'r') { close(pipefd[1]); return fdopen(pipefd[0], "r"); } else { close(pipefd[0]); return fdopen(pipefd[1], "w"); } } } //-------------------------------------------------------------------- // // sanitize - Helper function for removing all non-alphanumeric characters from process name // // Returns: char * // //-------------------------------------------------------------------- // remove all non alphanumeric characters from process name and replace with '_' char *sanitize(char * processName) { if(processName == NULL){ Log(error, "NULL process name.\n"); exit(-1); } char *sanitizedProcessName = strdup(processName); for (int i = 0; i < strlen(sanitizedProcessName); i++) { if (!isalnum(sanitizedProcessName[i])) { sanitizedProcessName[i] = '_'; } } return sanitizedProcessName; } ProcDump-for-Linux-1.2/src/Events.c000066400000000000000000000120701412022400100171470ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // Quick events implementation // //-------------------------------------------------------------------- #include "Events.h" #include "Logging.h" //-------------------------------------------------------------------- // // CreateEvent - Create an Event and return a pointer to it // //-------------------------------------------------------------------- struct Event *CreateEvent(bool IsManualReset, bool InitialState) { struct Event *event = (struct Event *)malloc(sizeof(struct Event)); if(event == NULL){ Log(error, "INTERNAL_ERROR"); Trace("CreateEvent: failed memory allocation."); exit(-1); } InitEvent(event, IsManualReset, InitialState); return event; } //-------------------------------------------------------------------- // // CreateNamedEvent - Create a Named Event and return a pointer to it // //-------------------------------------------------------------------- struct Event *CreateNamedEvent(bool IsManualReset, bool InitialState, char *Name) { struct Event *event = (struct Event *)malloc(sizeof(struct Event)); if(event == NULL){ Log(error, INTERNAL_ERROR); Trace("CreateNamedEvent: failed memory allocation."); exit(-1); } InitNamedEvent(event, IsManualReset, InitialState, Name); return event; } //-------------------------------------------------------------------- // // InitEvent - initialize an Event // //-------------------------------------------------------------------- void InitEvent(struct Event *Event, bool IsManualReset, bool InitialState) { InitNamedEvent(Event, IsManualReset, InitialState, NULL); } //-------------------------------------------------------------------- // // InitNamedEvent - Initialize a Named Event // //-------------------------------------------------------------------- void InitNamedEvent(struct Event *Event, bool IsManualReset, bool InitialState, char *Name) { static int unamedEventId = 0; // ID for logging purposes pthread_mutex_init(&(Event->mutex), NULL); if(pthread_cond_init(&(Event->cond), NULL) != 0){ Log(error, INTERNAL_ERROR); Trace("InitNamedEvent: failed pthread_cond_init."); exit(-1); } Event->bManualReset = IsManualReset; Event->bTriggered = InitialState; Event->nWaiters = 0; if (Name == NULL) { sprintf(Event->Name, "Unnamed Event %d", ++unamedEventId); } else if (strlen(Name) >= MAX_EVENT_NAME) { strncpy(Event->Name, Name, MAX_EVENT_NAME); Event->Name[MAX_EVENT_NAME - 1] = '\0'; // null terminate } else { strcpy(Event->Name, Name); } } //-------------------------------------------------------------------- // // DestroyEvent - Clean up an event // //-------------------------------------------------------------------- void DestroyEvent(struct Event *Event) { // destroy mutex and cond if(pthread_cond_destroy(&(Event->cond)) != 0){ Log(error, INTERNAL_ERROR); Trace("DestroyEvent: failed pthread_cond_destroy."); exit(-1); } if(pthread_mutex_destroy(&(Event->mutex)) != 0){ Log(error, INTERNAL_ERROR); Trace("DestroyEvent: failed pthread_mutex_destroy."); exit(-1); } } //-------------------------------------------------------------------- // // SetEvent - Attempts to trigger the event and set Event.bTriggered to true // // Return - A boolean indicating success of firing the event // //-------------------------------------------------------------------- bool SetEvent(struct Event *Event) { int success = 0; if ((success = pthread_mutex_lock(&(Event->mutex))) == 0) { Event->bTriggered = true; Event->bManualReset ? // Are we a manual-reset? pthread_cond_broadcast(&(Event->cond)) : // signal everyone! pthread_cond_signal(&(Event->cond)); // Signal first in line! pthread_mutex_unlock(&(Event->mutex)); } else { Log(error, INTERNAL_ERROR); Trace("SetEvent: failed pthread_mutex_lock."); exit(-1); } return (success == 0); } //-------------------------------------------------------------------- // // ResetEvent - For Events with bManualReset == true // // Return - A boolean indicating success of reseting the event (i.e., Event.bTriggered == false) // //-------------------------------------------------------------------- bool ResetEvent(struct Event *Event) { int success = 0; if ((success = pthread_mutex_lock(&(Event->mutex))) == 0) { Event->bTriggered = false; if(pthread_mutex_unlock(&(Event->mutex)) != 0){ Log(error, INTERNAL_ERROR); Trace("ResetEvent: failed pthread_mutex_unlock."); exit(-1); } } else{ Log(error, INTERNAL_ERROR); Trace("ResetEvent: failed pthread_mutex_lock."); exit(-1); } return (success == 0); } ProcDump-for-Linux-1.2/src/Handle.c000066400000000000000000000241021412022400100170750ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // Generalization of Events and Semaphores (Critical Sections) // //-------------------------------------------------------------------- #include "Handle.h" //-------------------------------------------------------------------- // // WaitForSingleObject - Blocks the current thread until // either the event triggers, semaphore > 0, // or the wait time has passed // // Parameters: // -Handle -> the event/semaphore to wait for // -Milliseconds -> the time to wait (in milliseconds). // '-1' will mean infinite, and 0 will be instant check // // Return - An integer indicating state of wait // 0 -> successful wait, and trigger fired // ETIMEDOUT -> the wait timed out (based on sepcified milliseconds) // other non-zero -> check errno.h // //-------------------------------------------------------------------- int WaitForSingleObject(struct Handle *Handle, int Milliseconds) { struct timespec ts; int rc = 0; // Get current time and add wait time if (Milliseconds != INFINITE_WAIT) { // We aren't waiting infinitely clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += Milliseconds / 1000; // ms->sec ts.tv_nsec += (Milliseconds % 1000) * 1000000; // remaining ms->ns } switch (Handle->type) { case EVENT: if ((rc = pthread_mutex_lock(&(Handle->event.mutex))) == 0) { Handle->event.nWaiters++; while (!Handle->event.bTriggered && rc == 0) { rc = (Milliseconds == INFINITE_WAIT) ? // either wait pthread_cond_wait(&(Handle->event.cond), &(Handle->event.mutex)) : // infinitely pthread_cond_timedwait(&(Handle->event.cond), &(Handle->event.mutex), &ts); // or till specified time passes } Handle->event.nWaiters--; // Check if we should reset if (Handle->event.nWaiters == 0 && !Handle->event.bManualReset) { Handle->event.bTriggered = false; } pthread_mutex_unlock(&(Handle->event.mutex)); } break; case SEMAPHORE: rc = (Milliseconds == INFINITE_WAIT) ? sem_wait(&(Handle->semaphore)) : sem_timedwait(&(Handle->semaphore), &ts); break; default: rc = -1; break; } return rc; } // Helper functions/infrastructure for WaitForMultipleObjects struct thread_result { int retVal; int threadIndex; }; struct coordinator { pthread_cond_t condEventTriggered; pthread_mutex_t mutexEventTriggered; struct thread_result *results; int numberTriggered; // behind mutex int nWaiters; // when 0, delete the struct int stopIssued; // when != 0, proceed to cleanup struct Handle evtCanCleanUp; // trigger when we leave main wait thread struct Handle evtStartWaiting; }; struct thread_args { struct Handle *handle; struct coordinator *coordinator; // for cleanup int milliseconds; int retVal; int threadIndex; }; void *WaiterThread(void *thread_args) { int rc; struct thread_args *input = (struct thread_args *)thread_args; // Wait for go signal if ((rc = WaitForSingleObject(&(input->coordinator->evtStartWaiting), 2000)) != WAIT_OBJECT_0) { // we messed up and the thread can't start... } // wait on the event, and then let parent know if we signal if (input->milliseconds == INFINITE_WAIT) { do { rc = WaitForSingleObject(input->handle, 5000); // loop every 5 seconds to make sure we can get out and cleanup if we don't need to wait anymore } while (!input->coordinator->stopIssued && rc == ETIMEDOUT); } else { rc = WaitForSingleObject(input->handle, input->milliseconds); // blocks till timeout, error, or success } pthread_mutex_lock(&input->coordinator->mutexEventTriggered); struct thread_result result = { .retVal = rc, .threadIndex = input->threadIndex }; input->coordinator->results[input->coordinator->numberTriggered++] = result; pthread_mutex_unlock(&input->coordinator->mutexEventTriggered); pthread_cond_signal(&input->coordinator->condEventTriggered); // Wait for the cleanup signal! WaitForSingleObject(&input->coordinator->evtCanCleanUp, INFINITE_WAIT); // Lock mutex to make sure each thread gets a chance to check status pthread_mutex_lock(&input->coordinator->mutexEventTriggered); input->coordinator->nWaiters--; if (input->coordinator->nWaiters == 0) { // if we're the last one, turn the lights out pthread_mutex_unlock(&input->coordinator->mutexEventTriggered); pthread_mutex_destroy(&input->coordinator->mutexEventTriggered); pthread_cond_destroy(&input->coordinator->condEventTriggered); free(input->coordinator->results); free(input->coordinator); free(input); } else { // otherwise only clean up your own memory pthread_mutex_unlock(&input->coordinator->mutexEventTriggered); free(input); } pthread_exit(NULL); } //-------------------------------------------------------------------- // // WaitForMultipleObjects - Blocks the current thread and waits for multiple Events // // Parameters: // -Count -> The number of Events // -Events -> An array of pointers to Events // -WaitAll -> Should we wait for all the events or whatever comes back first // -Milliseconds -> the number of milliseconds to wait, -1 is infinite // // Return - An integer indicating state of wait: // WAIT_OBJECT_0 to (WAIT_OBJECT_0 + Count-1) -> successful wait, and trigger fired // if WaitAll is TRUE: indicates all objects signaled // if WaitAll is FALSE: returns the index of the event that satisfied the wait *first* // ETIMEDOUT -> the wait timed out (based on sepcified milliseconds) // other non-zero -> check errno.h // //-------------------------------------------------------------------- int WaitForMultipleObjects(int Count, struct Handle **Handles, bool WaitAll, int Milliseconds) { struct coordinator *coordinator; struct thread_result *results; pthread_t *threads; struct thread_args **thread_args; struct timespec ts; int t; int rc; int retVal; threads = (pthread_t *)malloc(sizeof(pthread_t) * Count); thread_args = (struct thread_args **)malloc(sizeof(struct thread_args *) * Count); coordinator = (struct coordinator *)malloc(sizeof(struct coordinator)); if (coordinator == NULL) { printf("ERROR: Failed to malloc in %s\n",__FILE__); exit(-1); } coordinator->numberTriggered = 0; coordinator->stopIssued = 0; coordinator->evtCanCleanUp.type = EVENT; coordinator->evtStartWaiting.type = EVENT; InitNamedEvent(&(coordinator->evtCanCleanUp.event), true, false, "CanCleanUp"); InitNamedEvent(&(coordinator->evtStartWaiting.event), true, false, "StartWaiting"); pthread_cond_init(&coordinator->condEventTriggered, NULL); pthread_mutex_init(&coordinator->mutexEventTriggered, NULL); results = coordinator->results = (struct thread_result *)malloc(sizeof(struct thread_result) * Count); // Get current time and add wait time if (Milliseconds != -1) { // We aren't waiting infinitely clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += Milliseconds / 1000; // ms->sec ts.tv_nsec += (Milliseconds % 1000) * 1000000; // remaining ms->ns } // Create our threads pthread_mutex_lock(&coordinator->mutexEventTriggered); for (t = 0; t < Count; t++) { thread_args[t] = (struct thread_args *)malloc(sizeof(struct thread_args)); if (thread_args[t] == NULL) { printf("ERROR: Failed to alloc in %s\n",__FILE__); exit(-1); } thread_args[t]->handle = Handles[t]; thread_args[t]->milliseconds = (Milliseconds == INFINITE_WAIT) ? Milliseconds : Milliseconds + 100; // prevent race condition of everyone timing out at the same time as the main waiter thread thread_args[t]->threadIndex = t; thread_args[t]->coordinator = coordinator; rc = pthread_create(&threads[t], NULL, WaiterThread, (void *)thread_args[t]); if (rc) { // uh oh :( printf("ERROR: pthread_create failed in %s with error %d\n",__FILE__,rc); exit(-1); } } coordinator->nWaiters = Count; SetEvent(&(coordinator->evtStartWaiting.event)); // listen to our threads in no particular order while (((WaitAll && coordinator->numberTriggered < Count) || (!WaitAll && coordinator->numberTriggered == 0)) && rc == 0) { if (Milliseconds == INFINITE_WAIT) { if ((rc = pthread_cond_wait(&coordinator->condEventTriggered, &coordinator->mutexEventTriggered)) != 0) { break; // we either errored or timed out, go cleanup } } else { if ((rc = pthread_cond_timedwait(&coordinator->condEventTriggered, &coordinator->mutexEventTriggered, &ts)) != 0) { break; // we either errored or timed out, go cleanup } } // A handle fired. Check if we need to kep listening or head to return } coordinator->stopIssued = 1; pthread_mutex_unlock(&coordinator->mutexEventTriggered); // cleanup threads for (t = 0; t < Count; t++) { pthread_detach(threads[t]); } // free everything! SetEvent(&(coordinator->evtCanCleanUp.event)); free(threads); // we don't need handles on those threads anymore free(thread_args); // each thread has already got their copy and is in charge of freeing it // rc will be non-zero if we timed/errored out // retVal will be + threadIndex that fired first (e.g., WAIT_OBJECT_0 + 1, WAIT_ABANDONED + 2) if (rc) { retVal = rc; } else { retVal = (WaitAll) ? rc : results[0].retVal + results[0].threadIndex; } return retVal; } ProcDump-for-Linux-1.2/src/Logging.c000066400000000000000000000037321412022400100172760ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // A simple logging library for log generation and debugging // //-------------------------------------------------------------------- #include "Logging.h" #include "ProcDumpConfiguration.h" static const char *LogLevelStrings[] = { "DEBUG", "INFO", "WARN", "CRITICAL", "ERROR" }; extern struct ProcDumpConfiguration g_config; pthread_mutex_t LoggerLock; void LogFormatter(enum LogLevel logLevel, const char *message, va_list args) { char timeBuff[64]; time_t rawTime; struct tm *timeInfo=NULL; char* trace=NULL; va_list copy; va_copy(copy, args); pthread_mutex_lock(&LoggerLock); rawTime = time(NULL); timeInfo = localtime(&rawTime); strftime(timeBuff, 64, "%T", timeInfo); int traceLen = snprintf(NULL, 0, "[%s - %s]: ", timeBuff, LogLevelStrings[logLevel]); int argsLen = vsnprintf(NULL, 0, message, copy); if(!(trace = malloc(traceLen+argsLen+1))) { pthread_mutex_unlock(&LoggerLock); va_end(copy); return; } sprintf(trace, "[%s - %s]: ", timeBuff, LogLevelStrings[logLevel]); vsprintf(trace+traceLen, message, args); // If a log entry is not 'debug' it simply goes to stdout. // If you want an entry to only go to the syslog, use 'debug' if(logLevel != debug) { puts(trace); } // All log entries also go to the syslog syslog(LOG_DEBUG, "%s", trace); va_end(copy); free(trace); pthread_mutex_unlock(&LoggerLock); } void Log(enum LogLevel logLevel, const char *message, ...) { va_list args; va_start(args, message); LogFormatter(logLevel, message, args); va_end(args); } void DiagTrace(const char *message, ...) { va_list args; va_start(args, message); if(g_config.DiagnosticsLoggingEnabled) LogFormatter(debug, message, args); va_end(args); } ProcDump-for-Linux-1.2/src/ProcDumpConfiguration.c000066400000000000000000001065561412022400100222010ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // The global configuration structure and utilities header // //-------------------------------------------------------------------- #include "Procdump.h" #include "ProcDumpConfiguration.h" struct Handle g_evtConfigurationInitialized = HANDLE_MANUAL_RESET_EVENT_INITIALIZER("ConfigurationInitialized"); static sigset_t sig_set; static pthread_t sig_thread_id; static pthread_t sig_monitor_thread_id; extern pthread_mutex_t LoggerLock; long HZ; // clock ticks per second int MAXIMUM_CPU; // maximum cpu usage percentage (# cores * 100) struct ProcDumpConfiguration g_config; // backbone of the program pthread_mutex_t ptrace_mutex; //-------------------------------------------------------------------- // // SignalThread - Thread for handling graceful Async signals (e.g., SIGINT, SIGTERM) // //-------------------------------------------------------------------- void *SignalThread(void *input) { struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)input; int sig_caught, rc; if ((rc = sigwait(&sig_set, &sig_caught)) != 0) { Log(error, "Failed to wait on signal"); exit(-1); } switch (sig_caught) { case SIGINT: SetQuit(config, 1); if(config->gcorePid != NO_PID) { Log(info, "Shutting down gcore"); if((rc = kill(-config->gcorePid, SIGKILL)) != 0) { // pass negative PID to kill entire PGRP with value of gcore PID Log(error, "Failed to shutdown gcore."); } } // Need to make sure we detach from ptrace (if not attached it will silently fail) // To avoid situations where we have intercepted a signal and CTRL-C is hit, we synchronize // 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(config->SignalNumber != -1) { pthread_mutex_lock(&ptrace_mutex); ptrace(PTRACE_DETACH, config->ProcessId, 0, 0); pthread_mutex_unlock(&ptrace_mutex); if ((rc = pthread_cancel(sig_monitor_thread_id)) != 0) { Log(error, "An error occurred while canceling SignalMonitorThread.\n"); exit(-1); } } Log(info, "Quit"); break; default: fprintf (stderr, "\nUnexpected signal %d\n", sig_caught); break; } pthread_exit(NULL); } //-------------------------------------------------------------------- // // InitProcDump - initalize procdump // //-------------------------------------------------------------------- void InitProcDump() { openlog("ProcDump", LOG_PID, LOG_USER); if(CheckKernelVersion() == false) { Log(error, "Kernel version lower than 3.5+."); exit(-1); } InitProcDumpConfiguration(&g_config); pthread_mutex_init(&LoggerLock, NULL); pthread_mutex_init(&ptrace_mutex, NULL); } //-------------------------------------------------------------------- // // ExitProcDump - cleanup during exit. // //-------------------------------------------------------------------- void ExitProcDump() { pthread_mutex_destroy(&LoggerLock); closelog(); FreeProcDumpConfiguration(&g_config); } //-------------------------------------------------------------------- // // InitProcDumpConfiguration - initalize a config // //-------------------------------------------------------------------- void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) { if (WaitForSingleObject(&g_evtConfigurationInitialized, 0) == WAIT_OBJECT_0) { return; // The configuration has already been initialized } MAXIMUM_CPU = 100 * (int)sysconf(_SC_NPROCESSORS_ONLN); HZ = sysconf(_SC_CLK_TCK); sysinfo(&(self->SystemInfo)); InitNamedEvent(&(self->evtCtrlHandlerCleanupComplete.event), true, false, "CtrlHandlerCleanupComplete"); self->evtCtrlHandlerCleanupComplete.type = EVENT; InitNamedEvent(&(self->evtBannerPrinted.event), true, false, "BannerPrinted"); self->evtBannerPrinted.type = EVENT; InitNamedEvent(&(self->evtConfigurationPrinted.event), true, false, "ConfigurationPrinted"); self->evtConfigurationPrinted.type = EVENT; InitNamedEvent(&(self->evtDebugThreadInitialized.event), true, false, "DebugThreadInitialized"); self->evtDebugThreadInitialized.type = EVENT; InitNamedEvent(&(self->evtQuit.event), true, false, "Quit"); self->evtQuit.type = EVENT; InitNamedEvent(&(self->evtStartMonitoring.event), true, false, "StartMonitoring"); self->evtStartMonitoring.type = EVENT; sem_init(&(self->semAvailableDumpSlots.semaphore), 0, 1); self->semAvailableDumpSlots.type = SEMAPHORE; // Additional initialization self->ProcessId = NO_PID; self->NumberOfDumpsCollected = 0; self->NumberOfDumpsToCollect = DEFAULT_NUMBER_OF_DUMPS; self->CpuThreshold = -1; self->MemoryThreshold = -1; self->ThreadThreshold = -1; self->FileDescriptorThreshold = -1; self->SignalNumber = -1; self->ThresholdSeconds = DEFAULT_DELTA_TIME; self->bCpuTriggerBelowValue = false; self->bMemoryTriggerBelowValue = false; self->bTimerThreshold = false; self->WaitingForProcessName = false; self->DiagnosticsLoggingEnabled = false; self->gcorePid = NO_PID; self->PollingInterval = MIN_POLLING_INTERVAL; self->CoreDumpPath = NULL; self->CoreDumpName = NULL; SetEvent(&g_evtConfigurationInitialized.event); // We've initialized and are now re-entrant safe } //-------------------------------------------------------------------- // // FreeProcDumpConfiguration - ensure destruction of config and contents // //-------------------------------------------------------------------- void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self) { DestroyEvent(&(self->evtCtrlHandlerCleanupComplete.event)); DestroyEvent(&(self->evtBannerPrinted.event)); DestroyEvent(&(self->evtConfigurationPrinted.event)); DestroyEvent(&(self->evtDebugThreadInitialized.event)); DestroyEvent(&(self->evtQuit.event)); DestroyEvent(&(self->evtStartMonitoring.event)); sem_destroy(&(self->semAvailableDumpSlots.semaphore)); if(strcmp(self->ProcessName, EMPTY_PROC_NAME) != 0){ // The string constant is not on the heap. free(self->ProcessName); } free(self->CoreDumpPath); free(self->CoreDumpName); } //-------------------------------------------------------------------- // // GetOptions - Unpack command line inputs // //-------------------------------------------------------------------- int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) { // Make sure config has been initialized if (WaitForSingleObject(&g_evtConfigurationInitialized, 0) != WAIT_OBJECT_0) { Trace("GetOptions: Configuration not initialized."); return -1; } if (argc < 2) { Trace("GetOptions: Invalid number of command line arguments."); return PrintUsage(self); } // parse arguments int next_option; int option_index = 0; const char* short_options = "+p:C:c:M:m:n:s:w:T:F:G:I:o:dh"; const struct option long_options[] = { { "pid", required_argument, NULL, 'p' }, { "cpu", required_argument, NULL, 'C' }, { "lower-cpu", required_argument, NULL, 'c' }, { "memory", required_argument, NULL, 'M' }, { "lower-mem", required_argument, NULL, 'm' }, { "number-of-dumps", required_argument, NULL, 'n' }, { "time-between-dumps", required_argument, NULL, 's' }, { "wait", required_argument, NULL, 'w' }, { "threads", required_argument, NULL, 'T' }, { "filedescriptors", required_argument, NULL, 'F' }, { "signal", required_argument, NULL, 'G' }, { "pollinginterval", required_argument, NULL, 'I' }, { "output-path", required_argument, NULL, 'o' }, { "diag", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; char *tempOutputPath = NULL; struct stat statbuf; // start parsing command line arguments while ((next_option = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { switch (next_option) { case 'p': self->ProcessId = (pid_t)atoi(optarg); if (!LookupProcessByPid(self)) { Log(error, "Invalid PID - failed looking up process name by PID."); return PrintUsage(self); } break; case 'C': if (self->CpuThreshold != -1 || !IsValidNumberArg(optarg) || (self->CpuThreshold = atoi(optarg)) < 0 || self->CpuThreshold > MAXIMUM_CPU) { Log(error, "Invalid CPU threshold specified."); return PrintUsage(self); } break; case 'I': if (!IsValidNumberArg(optarg) || (self->PollingInterval = atoi(optarg)) < 0 || self->PollingInterval < MIN_POLLING_INTERVAL) { Log(error, "Invalid polling interval specified (minimum %d).", MIN_POLLING_INTERVAL); return PrintUsage(self); } break; case 'T': if (self->ThreadThreshold != -1 || !IsValidNumberArg(optarg) || (self->ThreadThreshold = atoi(optarg)) < 0 ) { Log(error, "Invalid threads threshold specified."); return PrintUsage(self); } break; case 'F': if (self->FileDescriptorThreshold != -1 || !IsValidNumberArg(optarg) || (self->FileDescriptorThreshold = atoi(optarg)) < 0 ) { Log(error, "Invalid file descriptor threshold specified."); return PrintUsage(self); } break; case 'G': if (self->SignalNumber != -1 || !IsValidNumberArg(optarg) || (self->SignalNumber = atoi(optarg)) < 0 ) { Log(error, "Invalid signal specified."); return PrintUsage(self); } break; case 'c': if (self->CpuThreshold != -1 || !IsValidNumberArg(optarg) || (self->CpuThreshold = atoi(optarg)) < 0 || self->CpuThreshold > MAXIMUM_CPU) { Log(error, "Invalid CPU threshold specified."); return PrintUsage(self); } self->bCpuTriggerBelowValue = true; break; case 'M': if (self->MemoryThreshold != -1 || !IsValidNumberArg(optarg) || (self->MemoryThreshold = atoi(optarg)) < 0) { Log(error, "Invalid memory threshold specified."); return PrintUsage(self); } break; case 'm': if (self->MemoryThreshold != -1 || !IsValidNumberArg(optarg) || (self->MemoryThreshold = atoi(optarg)) < 0) { Log(error, "Invalid memory threshold specified."); return PrintUsage(self); } self->bMemoryTriggerBelowValue = true; break; case 'n': if (!IsValidNumberArg(optarg) || (self->NumberOfDumpsToCollect = atoi(optarg)) < 0) { Log(error, "Invalid dumps threshold specified."); return PrintUsage(self); } break; case 's': if (!IsValidNumberArg(optarg) || (self->ThresholdSeconds = atoi(optarg)) == 0) { Log(error, "Invalid time threshold specified."); return PrintUsage(self); } break; case 'w': self->WaitingForProcessName = true; self->ProcessName = strdup(optarg); break; case 'o': tempOutputPath = strdup(optarg); // Check if the user provided an existing directory or a path // ending in a '/'. In this case, use the default naming // convention but place the files in the given directory. if ((stat(tempOutputPath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) || tempOutputPath[strlen(tempOutputPath)-1] == '/') { self->CoreDumpPath = tempOutputPath; self->CoreDumpName = NULL; } else { self->CoreDumpPath = strdup(dirname(tempOutputPath)); free(tempOutputPath); tempOutputPath = strdup(optarg); self->CoreDumpName = strdup(basename(tempOutputPath)); free(tempOutputPath); } // Check if the path portion of the output format is valid if (stat(self->CoreDumpPath, &statbuf) < 0 || !S_ISDIR(statbuf.st_mode)) { Log(error, "Invalid directory (\"%s\") provided for core dump output.", self->CoreDumpPath); return PrintUsage(self); } break; case 'd': self->DiagnosticsLoggingEnabled = true; break; case 'h': return PrintUsage(self); default: Log(error, "Invalid switch specified"); return PrintUsage(self); } } // If no path was provided, assume the current directory if (self->CoreDumpPath == NULL) { self->CoreDumpPath = strdup("."); } // Check for multi-arg situations // if number of dumps is set, but no thresholds, just go on timer if (self->NumberOfDumpsToCollect != -1 && self->MemoryThreshold == -1 && self->CpuThreshold == -1 && self->ThreadThreshold == -1 && self->FileDescriptorThreshold == -1) { self->bTimerThreshold = true; } // If signal dump is specified, it can be the only trigger that is used. // Otherwise we might run into a situation where the other triggers invoke // gcore while the target is being ptraced due to signal trigger. // Interval has no meaning during signal monitoring. // if(self->SignalNumber != -1) { if(self->CpuThreshold != -1 || self->ThreadThreshold != -1 || self->FileDescriptorThreshold != -1 || self->MemoryThreshold != -1) { Log(error, "Signal trigger must be the only trigger specified."); return PrintUsage(self); } if(self->PollingInterval != MIN_POLLING_INTERVAL) { Log(error, "Polling interval has no meaning during signal monitoring."); return PrintUsage(self); } // Again, we cant have another trigger (in this case timer) kicking off another dump generation since we will already // be attached via ptrace. self->bTimerThreshold = false; } if(self->ProcessId == NO_PID && !self->WaitingForProcessName){ Log(error, "A valid PID or process name must be specified"); return PrintUsage(self); } if(self->ProcessId != NO_PID && self->WaitingForProcessName){ Log(error, "Please only specify one of -p or -w"); return PrintUsage(self); } if(!self->WaitingForProcessName) { self->ProcessName = GetProcessName(self->ProcessId); } Trace("GetOpts and initial Configuration finished"); return 0; } //-------------------------------------------------------------------- // // LookupProcessByPid - Find process using PID provided. // //-------------------------------------------------------------------- bool LookupProcessByPid(struct ProcDumpConfiguration *self) { char statFilePath[32]; // check to see if pid is an actual process running sprintf(statFilePath, "/proc/%d/stat", self->ProcessId); FILE *fd = fopen(statFilePath, "r"); if (fd == NULL) { Log(error, "No process matching the specified PID can be found."); Log(error, "Try elevating the command prompt (i.e., `sudo procdump ...`)"); return false; } // close file pointer this is a valid process fclose(fd); return true; } //-------------------------------------------------------------------- // // FilterForPid - Helper function for scandir to only return PIDs. // //-------------------------------------------------------------------- static int FilterForPid(const struct dirent *entry) { return IsValidNumberArg(entry->d_name); } //-------------------------------------------------------------------- // // WaitForProcessName - Actively wait until a process with the configured name is launched. // //-------------------------------------------------------------------- bool WaitForProcessName(struct ProcDumpConfiguration *self) { Log(info, "Waiting for process '%s' to launch...", self->ProcessName); while (true) { struct dirent ** nameList; bool moreThanOne = false; pid_t matchingPid = NO_PID; int numEntries = scandir("/proc/", &nameList, FilterForPid, alphasort); for (int i = 0; i < numEntries; i++) { pid_t procPid = atoi(nameList[i]->d_name); char *nameForPid = GetProcessName(procPid); if (strcmp(nameForPid, EMPTY_PROC_NAME) == 0) { continue; } if (strcmp(nameForPid, self->ProcessName) == 0) { if (matchingPid == NO_PID) { matchingPid = procPid; } else { Log(error, "More than one matching process found, exiting..."); moreThanOne = true; free(nameForPid); break; } } free(nameForPid); } // Cleanup for (int i = 0; i < numEntries; i++) { free(nameList[i]); } free(nameList); // Check for exactly one match if (moreThanOne) { self->bTerminated = true; return false; } else if (matchingPid != NO_PID) { self->ProcessId = matchingPid; Log(info, "Found process with PID %d", matchingPid); return true; } } } //-------------------------------------------------------------------- // // GetProcessName - Get process name using PID provided. // Returns EMPTY_PROC_NAME for null process name. // //-------------------------------------------------------------------- char * GetProcessName(pid_t pid){ char procFilePath[32]; char fileBuffer[MAX_CMDLINE_LEN]; int charactersRead = 0; int itr = 0; char * stringItr; char * processName; FILE * procFile; if(sprintf(procFilePath, "/proc/%d/cmdline", pid) < 0){ return EMPTY_PROC_NAME; } procFile = fopen(procFilePath, "r"); if(procFile != NULL){ if(fgets(fileBuffer, MAX_CMDLINE_LEN, procFile) == NULL) { fclose(procFile); if(strlen(fileBuffer) == 0){ Log(debug, "Empty cmdline.\n"); }else{ Log(debug, "Failed to read from %s.\n", procFilePath); } return EMPTY_PROC_NAME; } // close file fclose(procFile); } else{ Log(debug, "Failed to open %s.\n", procFilePath); return EMPTY_PROC_NAME; } // Extract process name stringItr = fileBuffer; charactersRead = strlen(fileBuffer); 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? processName = strrchr(stringItr, '/'); // does this process include a filepath? if(processName != NULL){ return strdup(processName + 1); // +1 to not include '/' character } else{ return strdup(stringItr); } } else{ stringItr += (itr+1); // +1 to move past '\0' } } } Log(debug, "Failed to extract process name from /proc/PID/cmdline"); return EMPTY_PROC_NAME; } //-------------------------------------------------------------------- // // CreateTriggerThreads - Create each of the threads that will be running as a trigger // //-------------------------------------------------------------------- int CreateTriggerThreads(struct ProcDumpConfiguration *self) { int rc = 0; self->nThreads = 0; if((rc=sigemptyset (&sig_set)) < 0) { Trace("CreateTriggerThreads: sigemptyset failed."); return rc; } if((rc=sigaddset (&sig_set, SIGINT)) < 0) { Trace("CreateTriggerThreads: sigaddset failed."); return rc; } if((rc=sigaddset (&sig_set, SIGTERM)) < 0) { Trace("CreateTriggerThreads: sigaddset failed."); return rc; } if((rc = pthread_sigmask (SIG_BLOCK, &sig_set, NULL)) != 0) { Trace("CreateTriggerThreads: pthread_sigmask failed."); return rc; } // create threads if (self->CpuThreshold != -1) { if ((rc = pthread_create(&self->Threads[self->nThreads++], NULL, CpuMonitoringThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create CpuThread."); return rc; } } if (self->MemoryThreshold != -1) { if ((rc = pthread_create(&self->Threads[self->nThreads++], NULL, CommitMonitoringThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create CommitThread."); return rc; } } if (self->ThreadThreshold != -1) { if ((rc = pthread_create(&self->Threads[self->nThreads++], NULL, ThreadCountMonitoringThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create ThreadThread."); return rc; } } if (self->FileDescriptorThreshold != -1) { if ((rc = pthread_create(&self->Threads[self->nThreads++], NULL, FileDescriptorCountMonitoringThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create FileDescriptorThread."); return rc; } } if (self->SignalNumber != -1) { if ((rc = pthread_create(&sig_monitor_thread_id, NULL, SignalMonitoringThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create SignalMonitoringThread."); return rc; } } if (self->bTimerThreshold) { if ((rc = pthread_create(&self->Threads[self->nThreads++], NULL, TimerThread, (void *)self)) != 0) { Trace("CreateTriggerThreads: failed to create TimerThread."); return rc; } } if((rc = pthread_create(&sig_thread_id, NULL, SignalThread, (void *)self))!= 0) { Trace("CreateTriggerThreads: failed to create SignalThread."); return rc; } return 0; } //-------------------------------------------------------------------- // // WaitForQuit - Wait for Quit Event or just timeout // // Timed wait with awareness of quit event // // Returns: WAIT_OBJECT_0 - Quit triggered // WAIT_TIMEOUT - Timeout // WAIT_ABANDONED - At dump limit or terminated // //-------------------------------------------------------------------- int WaitForQuit(struct ProcDumpConfiguration *self, int milliseconds) { if (!ContinueMonitoring(self)) { return WAIT_ABANDONED; } int wait = WaitForSingleObject(&self->evtQuit, milliseconds); if ((wait == WAIT_TIMEOUT) && !ContinueMonitoring(self)) { return WAIT_ABANDONED; } return wait; } //-------------------------------------------------------------------- // // WaitForQuitOrEvent - Wait for Quit Event, an Event, or just timeout // // Use to wait for dumps to complete, yet be aware of quit or finished events // // Returns: WAIT_OBJECT_0 - Quit triggered // WAIT_OBJECT_0+1 - Event triggered // WAIT_TIMEOUT - Timeout // WAIT_ABANDONED - (Abandonded) At dump limit or terminated // //-------------------------------------------------------------------- int WaitForQuitOrEvent(struct ProcDumpConfiguration *self, struct Handle *handle, int milliseconds) { struct Handle *waits[2]; waits[0] = &self->evtQuit; waits[1] = handle; if (!ContinueMonitoring(self)) { return WAIT_ABANDONED; } int wait = WaitForMultipleObjects(2, waits, false, milliseconds); if ((wait == WAIT_TIMEOUT) && !ContinueMonitoring(self)) { return WAIT_ABANDONED; } if ((wait == WAIT_OBJECT_0) && !ContinueMonitoring(self)) { return WAIT_ABANDONED; } return wait; } //-------------------------------------------------------------------- // // WaitForAllThreadsToTerminate - Wait for all threads to terminate // //-------------------------------------------------------------------- int WaitForAllThreadsToTerminate(struct ProcDumpConfiguration *self) { int rc = 0; // Wait for the signal monitoring thread if there is one. If there is one, it will be the only // one. if(self->SignalNumber != -1) { if ((rc = pthread_join(sig_monitor_thread_id, NULL)) != 0) { Log(error, "An error occurred while joining SignalMonitorThread.\n"); exit(-1); } } else { // Wait for the other monitoring threads for (int i = 0; i < self->nThreads; i++) { if ((rc = pthread_join(self->Threads[i], NULL)) != 0) { Log(error, "An error occurred while joining threads\n"); exit(-1); } } } // Cancel the signal handling thread. // We dont care about the return since the signal thread might already be gone. pthread_cancel(sig_thread_id); // Wait for signal handling thread to complete if ((rc = pthread_join(sig_thread_id, NULL)) != 0) { Log(error, "An error occurred while joining SignalThread.\n"); exit(-1); } return rc; } //-------------------------------------------------------------------- // // IsQuit - A check on the underlying value of whether we should quit // //-------------------------------------------------------------------- bool IsQuit(struct ProcDumpConfiguration *self) { return (self->nQuit != 0); } //-------------------------------------------------------------------- // // SetQuit - Sets the quit value and signals the event // //-------------------------------------------------------------------- int SetQuit(struct ProcDumpConfiguration *self, int quit) { self->nQuit = quit; SetEvent(&self->evtQuit.event); return self->nQuit; } //-------------------------------------------------------------------- // // PrintConfiguration - Prints the current configuration to the command line // //-------------------------------------------------------------------- bool PrintConfiguration(struct ProcDumpConfiguration *self) { if (WaitForSingleObject(&self->evtConfigurationPrinted,0) == WAIT_TIMEOUT) { if(self->SignalNumber != -1) { printf("** NOTE ** Signal triggers use PTRACE which will impact the performance of the target process\n\n"); } printf("Process:\t\t%s", self->ProcessName); if (!self->WaitingForProcessName) { printf(" (%d)", self->ProcessId); } else { printf(" (pending)"); } printf("\n"); // CPU if (self->CpuThreshold != -1) { if (self->bCpuTriggerBelowValue) { printf("CPU Threshold:\t\t<%d\n", self->CpuThreshold); } else { printf("CPU Threshold:\t\t>=%d\n", self->CpuThreshold); } } else { printf("CPU Threshold:\t\tn/a\n"); } // Memory if (self->MemoryThreshold != -1) { if (self->bMemoryTriggerBelowValue) { printf("Commit Threshold:\t<%d\n", self->MemoryThreshold); } else { printf("Commit Threshold:\t>=%d\n", self->MemoryThreshold); } } else { printf("Commit Threshold:\tn/a\n"); } // Thread if (self->ThreadThreshold != -1) { printf("Thread Threshold:\t>=%d\n", self->ThreadThreshold); } else { printf("Thread Threshold:\t\tn/a\n"); } // File descriptor if (self->FileDescriptorThreshold != -1) { printf("File descriptor Threshold:\t>=%d\n", self->FileDescriptorThreshold); } else { printf("File descriptor Threshold:\t\tn/a\n"); } // Signal if (self->SignalNumber != -1) { printf("Signal number:\t%d\n", self->SignalNumber); } else { printf("Signal:\t\tn/a\n"); } // Polling inverval printf("Polling interval (ms):\t%d\n", self->PollingInterval); // time printf("Threshold (s):\t%d\n", self->ThresholdSeconds); // number of dumps and others printf("Number of Dumps:\t%d\n", self->NumberOfDumpsToCollect); // Output directory and filename printf("Output directory for core dumps:\t%s\n", self->CoreDumpPath); if (self->CoreDumpName != NULL) { printf("Custom name for core dumps:\t%s_.\n", self->CoreDumpName); } SetEvent(&self->evtConfigurationPrinted.event); return true; } return false; } //-------------------------------------------------------------------- // // ContinueMonitoring - Should we keep monitoring or should we clean up our thread // //-------------------------------------------------------------------- bool ContinueMonitoring(struct ProcDumpConfiguration *self) { // Have we reached the dump limit? if (self->NumberOfDumpsCollected >= self->NumberOfDumpsToCollect) { return false; } // Do we already know the process is terminated? if (self->bTerminated) { return false; } // Let's check to make sure the process is still alive then // note: kill([pid], 0) doesn't send a signal but does perform error checking // therefore, if it returns 0, the process is still alive, -1 means it errored out if (kill(self->ProcessId, 0)) { self->bTerminated = true; Log(error, "Target process is no longer alive"); return false; } // Otherwise, keep going! return true; } //-------------------------------------------------------------------- // // BeginMonitoring - Sync up monitoring threads // //-------------------------------------------------------------------- bool BeginMonitoring(struct ProcDumpConfiguration *self) { return SetEvent(&(self->evtStartMonitoring.event)); } //-------------------------------------------------------------------- // // IsValidNumberArg - quick helper function for ensuring arg is a number // //-------------------------------------------------------------------- bool IsValidNumberArg(const char *arg) { int strLen = strlen(arg); for (int i = 0; i < strLen; i++) { if (!isdigit(arg[i]) && !isspace(arg[i])) { return false; } } return true; } //-------------------------------------------------------------------- // // CheckKernelVersion - Check to see if current kernel is 3.5+. // // ProcDump won't proceed if current kernel is less than 3.5. // Returns true if >= 3.5+, returns false otherwise or error. //-------------------------------------------------------------------- bool CheckKernelVersion() { struct utsname kernelInfo; if(uname(&kernelInfo) == 0) { int version, patch = 0; if(sscanf(kernelInfo.release,"%d.%d",&version,&patch) != 2) { Log(error, "Cannot validate kernel version"); Trace("%s",strerror(errno)); return false; } if(version > MIN_KERNEL_VERSION) return true; if(version == MIN_KERNEL_VERSION && patch >= MIN_KERNEL_PATCH) return true; } else { Log(error, strerror(errno)); } return false; } //-------------------------------------------------------------------- // // PrintBanner - Not re-entrant safe banner printer. Function must be called before trigger threads start. // //-------------------------------------------------------------------- void PrintBanner() { printf("\nProcDump v1.2 - Sysinternals process dump utility\n"); printf("Copyright (C) 2020 Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n"); printf("Mark Russinovich, Mario Hewardt, John Salem, Javid Habibi\n"); printf("Monitors a process and writes a dump file when the process meets the\n"); printf("specified criteria.\n\n"); } //-------------------------------------------------------------------- // // PrintUsage - Print usage // //-------------------------------------------------------------------- int PrintUsage(struct ProcDumpConfiguration *self) { printf("\nUsage: procdump [OPTIONS...] TARGET\n"); printf(" OPTIONS\n"); printf(" -h Prints this help screen\n"); printf(" -C Trigger core dump generation when CPU exceeds or equals specified value (0 to 100 * nCPU)\n"); printf(" -c Trigger core dump generation when CPU is less than specified value (0 to 100 * nCPU)\n"); printf(" -M Trigger core dump generation when memory commit exceeds or equals specified value (MB)\n"); printf(" -m Trigger core dump generation when when memory commit is less than specified value (MB)\n"); printf(" -T Trigger when thread count exceeds or equals specified value.\n"); printf(" -F Trigger when file descriptor count exceeds or equals specified value.\n"); printf(" -G Trigger when signal with the specified value (num) is sent (uses PTRACE and will affect performance of target process).\n"); printf(" -I Polling frequency in milliseconds (default is %d)\n", MIN_POLLING_INTERVAL); printf(" -n Number of core dumps to write before exiting (default is %d)\n", DEFAULT_NUMBER_OF_DUMPS); printf(" -s Consecutive seconds before dump is written (default is %d)\n", DEFAULT_DELTA_TIME); printf(" -o Path and/or filename prefix where the core dump is written to\n"); printf(" -d Writes diagnostic logs to syslog\n"); printf(" TARGET must be exactly one of these:\n"); printf(" -p pid of the process\n"); printf(" -w Name of the process executable\n\n"); return -1; } ProcDump-for-Linux-1.2/src/Procdump.c000066400000000000000000000033201412022400100174720ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // This program monitors a process and generates core dumps in // in response to various triggers // //-------------------------------------------------------------------- #include "Procdump.h" extern struct ProcDumpConfiguration g_config; int main(int argc, char *argv[]) { // print banner and begin intialization PrintBanner(); InitProcDump(); if (GetOptions(&g_config, argc, argv) != 0) { Trace("main: failed to parse command line arguments"); exit(-1); } // print config here PrintConfiguration(&g_config); printf("\nPress Ctrl-C to end monitoring without terminating the process.\n\n"); // print privelege warning if(geteuid() != 0){ Log(warn, "Procdump not running with elevated credentials. If your uid does not match the uid of the target process procdump will not be able to capture memory dumps"); } // actively wait for the specified process name to start if (g_config.WaitingForProcessName) { if (WaitForProcessName(&g_config) == false) { ExitProcDump(); } } // start monitoring process if(CreateTriggerThreads(&g_config) != 0) { Log(error, INTERNAL_ERROR); Trace("main: failed to create trigger threads."); ExitProcDump(); } if(BeginMonitoring(&g_config) == false) { Log(error, INTERNAL_ERROR); Trace("main: failed to start monitoring."); ExitProcDump(); } WaitForAllThreadsToTerminate(&g_config); ExitProcDump(); } ProcDump-for-Linux-1.2/src/Process.c000066400000000000000000000354501412022400100173300ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // This library reads from the /procfs pseudo filesystem // //-------------------------------------------------------------------- #include #include #include "Process.h" bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { char procFilePath[32]; char fileBuffer[1024]; char *token; char *savePtr = NULL; FILE *procFile = NULL; DIR* fddir = NULL; struct dirent* entry = NULL; // Get number of file descriptors in /proc/%d/fdinfo. This directory only contains sub directories for each file descriptor. if(sprintf(procFilePath, "/proc/%d/fdinfo", pid) < 0){ return false; } fddir = opendir(procFilePath); if(fddir) { proc->num_filedescriptors = 0; while ((entry = readdir(fddir)) != NULL) { proc->num_filedescriptors++; } closedir(fddir); } else { Log(error, "Failed to open %s. Exiting...\n", procFilePath); return false; } proc->num_filedescriptors-=2; // Account for "." and ".." // Read /proc/[pid]/stat if(sprintf(procFilePath, "/proc/%d/stat", pid) < 0){ return false; } procFile = fopen(procFilePath, "r"); if(procFile != NULL){ if(fgets(fileBuffer, sizeof(fileBuffer), procFile) == NULL) { Log(error, "Failed to read from %s. Exiting...\n", procFilePath); fclose(procFile); return false; } // close file after reading this iteration of stats fclose(procFile); } else{ Log(error, "Failed to open %s.\n", procFilePath); return false; } // (1) process ID proc->pid = (pid_t)atoi(fileBuffer); // (3) process state if((savePtr = strrchr(fileBuffer, ')')) != NULL){ savePtr += 2; // iterate past ')' and ' ' in /proc/[pid]/stat proc->state = strtok_r(savePtr, " ", &savePtr)[0]; } // (4) parent process ID token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Parent PID."); return false; } proc->ppid = (pid_t)strtol(token, NULL, 10); // (5) process group ID token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Process group ID."); return false; } proc->pgrp = (gid_t)strtol(token, NULL, 10); // (6) session ID token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Session ID."); return false; } proc->session = (int)strtol(token, NULL, 10); // (7) controlling terminal token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Controlling terminal."); return false; } proc->tty_nr = (int)strtol(token, NULL, 10); // (8) Foreground group ID of controlling terminal token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Foreground group ID."); return false; } proc->tpgid = (gid_t)strtol(token, NULL, 10); // (9) Kernel flags token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Kernel flags."); return false; } proc->flags = (unsigned int)strtoul(token, NULL, 10); // (10) minflt token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - Minflt."); return false; } proc->minflt = strtoul(token, NULL, 10); // (11) cminflt token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cminflt."); return false; } proc->cminflt = strtoul(token, NULL, 10); // (12) majflt token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - majflt."); return false; } proc->majflt = strtoul(token, NULL, 10); // (13) cmajflt token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cmajflt."); return false; } proc->cmajflt = strtoul(token, NULL, 10); // (14) utime token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - utime."); return false; } proc->utime = strtoul(token, NULL, 10); // (15) stime token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - stime."); return false; } proc->stime = strtoul(token, NULL, 10); // (16) cutime token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cutime."); return false; } proc->cutime = strtoul(token, NULL, 10); // (17) cstime token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cstime."); return false; } proc->cstime = strtoul(token, NULL, 10); // (18) priority token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - priority."); return false; } proc->priority = strtol(token, NULL, 10); // (19) nice token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - nice."); return false; } proc->nice = strtol(token, NULL, 10); // (20) num_threads token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - num_threads."); return false; } proc->num_threads = strtol(token, NULL, 10); // (21) itrealvalue token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - itrealvalue."); return false; } proc->itrealvalue = strtol(token, NULL, 10); // (22) starttime token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - starttime."); return false; } proc->starttime = strtoull(token, NULL, 10); // (23) vsize token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - vsize."); return false; } proc->vsize = strtoul(token, NULL, 10); // (24) rss token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - rss."); return false; } proc->rss = strtol(token, NULL, 10); // (25) rsslim token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - rsslim."); return false; } proc->rsslim = strtoul(token, NULL, 10); // (26) startcode token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - startcode."); return false; } proc->startcode = strtoul(token, NULL, 10); // (27) endcode token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - endcode."); return false; } proc->endcode = strtoul(token, NULL, 10); // (28) startstack token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - startstack."); return false; } proc->startstack = strtoul(token, NULL, 10); // (29) kstkesp token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - kstkesp."); return false; } proc->kstkesp = strtoul(token, NULL, 10); // (30) kstkeip token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - kstkeip."); return false; } proc->kstkeip = strtoul(token, NULL, 10); // (31) signal token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - signal."); return false; } proc->signal = strtoul(token, NULL, 10); // (32) blocked token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - blocked."); return false; } proc->blocked = strtoul(token, NULL, 10); // (33) sigignore token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - sigignore."); return false; } proc->sigignore = strtoul(token, NULL, 10); // (34) sigcatch token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - sigcatch."); return false; } proc->sigcatch = strtoul(token, NULL, 10); // (35) wchan token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - wchan."); return false; } proc->wchan = strtoul(token, NULL, 10); // (36) nswap token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - nswap."); return false; } proc->nswap = strtoul(token, NULL, 10); // (37) cnswap token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cnswap."); return false; } proc->cnswap = strtoul(token, NULL, 10); // (38) exit_signal token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - exit_signal."); return false; } proc->exit_signal = (int)strtol(token, NULL, 10); // (39) processor token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - processor."); return false; } proc->processor = (int)strtol(token, NULL, 10); // (40) rt_priority token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - rt_priority."); return false; } proc->rt_priority = (unsigned int)strtoul(token, NULL, 10); // (41) policy token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - policy."); return false; } proc->policy = (unsigned int)strtoul(token, NULL, 10); // (42) delayacct_blkio_ticks token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - delayacct_blkio_ticks."); return false; } proc->delayacct_blkio_ticks = strtoull(token, NULL, 10); // (43) guest_time token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - guest_time."); return false; } proc->guest_time = strtoul(token, NULL, 10); // (44) cguest_time token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - cguest_time."); return false; } proc->cguest_time = strtol(token, NULL, 10); // (45) start_data token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - start_data."); return false; } proc->start_data = strtoul(token, NULL, 10); // (46) end_data token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - end_data."); return false; } proc->end_data = strtoul(token, NULL, 10); // (47) start_brk token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - start_brk."); return false; } proc->end_data = strtoul(token, NULL, 10); // (48) arg_start token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - arg_start."); return false; } proc->arg_start = strtoul(token, NULL, 10); // (49) arg_end token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - arg_end."); return false; } proc->arg_end = strtoul(token, NULL, 10); // (50) env_start token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - env_start."); return false; } proc->env_start = strtoul(token, NULL, 10); // (52) env_end token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - env_end."); return false; } proc->env_end = strtoul(token, NULL, 10); // (53) exit_code token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ Trace("GetProcessStat: failed to get token from proc/[pid]/stat - exit_code."); return false; } proc->exit_code = (int)strtol(token, NULL, 10); return true; } ProcDump-for-Linux-1.2/src/TriggerThreadProcs.c000066400000000000000000000262651412022400100214600ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License //-------------------------------------------------------------------- // // thread processes // //-------------------------------------------------------------------- #include "TriggerThreadProcs.h" extern long HZ; // clock ticks per second extern pthread_mutex_t ptrace_mutex; void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("CommitMonitoringThread: Starting Trigger Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; long pageSize_kb; unsigned long memUsage = 0; struct ProcessStat proc = {0}; int rc = 0; struct CoreDumpWriter *writer = NewCoreDumpWriter(COMMIT, config); pageSize_kb = sysconf(_SC_PAGESIZE) >> 10; // convert bytes to kilobytes (2^10) if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { while ((rc = WaitForQuit(config, config->PollingInterval)) == WAIT_TIMEOUT) { if (GetProcessStat(config->ProcessId, &proc)) { // Calc Commit memUsage = (proc.rss * pageSize_kb) >> 10; // get Resident Set Size memUsage += (proc.nswap * pageSize_kb) >> 10; // get Swap size // Commit Trigger if ((config->bMemoryTriggerBelowValue && (memUsage < config->MemoryThreshold)) || (!config->bMemoryTriggerBelowValue && (memUsage >= config->MemoryThreshold))) { Log(info, "Commit: %ld MB", memUsage); rc = WriteCoreDump(writer); if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; } } } else { Log(error, "An error occurred while parsing procfs\n"); exit(-1); } } } free(writer); Trace("CommitMonitoringThread: Exiting Trigger Thread"); pthread_exit(NULL); } void* ThreadCountMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("ThreadCountMonitoringThread: Starting Thread Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; struct ProcessStat proc = {0}; int rc = 0; struct CoreDumpWriter *writer = NewCoreDumpWriter(THREAD, config); if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { while ((rc = WaitForQuit(config, config->PollingInterval)) == WAIT_TIMEOUT) { if (GetProcessStat(config->ProcessId, &proc)) { if (proc.num_threads >= config->ThreadThreshold) { Log(info, "Threads: %ld", proc.num_threads); rc = WriteCoreDump(writer); if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; } } } else { Log(error, "An error occurred while parsing procfs\n"); exit(-1); } } } free(writer); Trace("ThreadCountMonitoringThread: Exiting Thread trigger Thread"); pthread_exit(NULL); } void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("FileDescriptorCountMonitoringThread: Starting Filedescriptor Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; struct ProcessStat proc = {0}; int rc = 0; struct CoreDumpWriter *writer = NewCoreDumpWriter(FILEDESC, config); if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { while ((rc = WaitForQuit(config, config->PollingInterval)) == WAIT_TIMEOUT) { if (GetProcessStat(config->ProcessId, &proc)) { if (proc.num_filedescriptors >= config->FileDescriptorThreshold) { Log(info, "File descriptors: %ld", proc.num_filedescriptors); rc = WriteCoreDump(writer); if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; } } } else { Log(error, "An error occurred while parsing procfs\n"); exit(-1); } } } free(writer); Trace("FileDescriptorCountMonitoringThread: Exiting Filedescriptor trigger Thread"); pthread_exit(NULL); } // // This thread monitors for a specific signal to be sent to target process. // It uses ptrace (PTRACE_SEIZE) and once the signal with the corresponding // signal number is intercepted, it detaches from the target process in a stopped state // followed by invoking gcore to generate the dump. Once completed, a SIGCONT followed by the // original signal is sent to the target process. Signals of non-interest are simply forwarded // to the target process. // // Polling interval has no meaning during signal monitoring. // void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("SignalMonitoringThread: Starting SignalMonitoring Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; int wstatus; int signum=-1; int rc = 0; struct CoreDumpWriter *writer = NewCoreDumpWriter(SIGNAL, config); if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { // Attach to the target process. We use SEIZE here to avoid // the SIGSTOP issues of the ATTACH method. if (ptrace(PTRACE_SEIZE, config->ProcessId, NULL, NULL) == -1) { Log(error, "Unable to PTRACE the target process"); } else { while(1) { // Wait for signal to be delivered waitpid(config->ProcessId, &wstatus, 0); if(WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) { ptrace(PTRACE_DETACH, config->ProcessId, 0, 0); break; } pthread_mutex_lock(&ptrace_mutex); // We are now in a signal-stop state signum = WSTOPSIG(wstatus); if(signum == config->SignalNumber) { // We have to detach in a STOP state so we can invoke gcore if(ptrace(PTRACE_DETACH, config->ProcessId, 0, SIGSTOP) == -1) { Log(error, "Unable to PTRACE (DETACH) the target process"); pthread_mutex_unlock(&ptrace_mutex); break; } // Write core dump Log(info, "Signal intercepted: %d", signum); rc = WriteCoreDump(writer); kill(config->ProcessId, SIGCONT); 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(&ptrace_mutex); break; } ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); // Re-attach to the target process if (ptrace(PTRACE_SEIZE, config->ProcessId, NULL, NULL) == -1) { Log(error, "Unable to PTRACE the target process"); pthread_mutex_unlock(&ptrace_mutex); break; } pthread_mutex_unlock(&ptrace_mutex); continue; } // Resume execution of the target process ptrace(PTRACE_CONT, config->ProcessId, NULL, signum); pthread_mutex_unlock(&ptrace_mutex); } } } free(writer); Trace("SignalMonitoringThread: Exiting SignalMonitoring Thread"); pthread_exit(NULL); } void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("CpuMonitoringThread: Starting Trigger Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; unsigned long totalTime = 0; unsigned long elapsedTime = 0; struct sysinfo sysInfo; int cpuUsage; struct CoreDumpWriter *writer = NewCoreDumpWriter(CPU, config); int rc = 0; struct ProcessStat proc = {0}; if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { while ((rc = WaitForQuit(config, config->PollingInterval)) == WAIT_TIMEOUT) { sysinfo(&sysInfo); if (GetProcessStat(config->ProcessId, &proc)) { // Calc CPU totalTime = (unsigned long)((proc.utime + proc.stime) / HZ); elapsedTime = (unsigned long)(sysInfo.uptime - (long)(proc.starttime / HZ)); cpuUsage = (int)(100 * ((double)totalTime / elapsedTime)); // CPU Trigger if ((config->bCpuTriggerBelowValue && (cpuUsage < config->CpuThreshold)) || (!config->bCpuTriggerBelowValue && (cpuUsage >= config->CpuThreshold))) { Log(info, "CPU:\t%d%%", cpuUsage); rc = WriteCoreDump(writer); if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; } } } else { Log(error, "An error occurred while parsing procfs\n"); exit(-1); } } } free(writer); Trace("CpuTCpuMonitoringThread: Exiting Trigger Thread"); pthread_exit(NULL); } void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("TimerThread: Starting Trigger Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; struct CoreDumpWriter *writer = NewCoreDumpWriter(TIME, config); int rc = 0; if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { while ((rc = WaitForQuit(config, 0)) == WAIT_TIMEOUT) { Log(info, "Timed:"); rc = WriteCoreDump(writer); if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; } } } free(writer); Trace("TimerThread: Exiting Trigger Thread"); pthread_exit(NULL); } ProcDump-for-Linux-1.2/tests/000077500000000000000000000000001412022400100161125ustar00rootroot00000000000000ProcDump-for-Linux-1.2/tests/integration/000077500000000000000000000000001412022400100204355ustar00rootroot00000000000000ProcDump-for-Linux-1.2/tests/integration/ProcDumpTestApplication.c000066400000000000000000000003601412022400100253550ustar00rootroot00000000000000#include #include #include int main(int argc, char *argv[]){ if (argc > 1){ if (strcmp("sleep", argv[1]) == 0){ sleep(5); } else if (strcmp("burn", argv[1]) == 0){ alarm(5); while(1); } } } ProcDump-for-Linux-1.2/tests/integration/run.sh000077500000000000000000000014421412022400100216010ustar00rootroot00000000000000#!/bin/bash failed=0 failedTests="\n" rootcheck () { if [ $(id -u) != "0" ] then sudo "$0" "$@" exit $? fi } rootcheck if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 fi if [ ! -e /usr/bin/stress-ng ]; then echo "Please install stress-ng before running this script!" exit 1 fi DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; function runTest { printf "\nStarting $(basename $1)\n" $1 if [ $? -ne 0 ]; then echo "$(basename $1) failed" failedTests="$failedTests$(basename $1)\n" failed=1 else echo "$(basename $1) passed" fi } for file in $DIR/scenarios/*.sh do runTest $file done printf "\nFailed tests: $failedTests" if [ "$failed" -eq "1" ]; then exit 1 else exit 0 fi ProcDump-for-Linux-1.2/tests/integration/runProcDumpAndValidate.sh000077500000000000000000000023511412022400100253500ustar00rootroot00000000000000#!/bin/bash function runProcDumpAndValidate { DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; PROCDUMPPATH=$(readlink -m "$DIR/../../bin/procdump"); dumpDir=$(mktemp -d -t dump_XXXXXX) cd $dumpDir dumpParam="" if [ "$#" -ge "6" -a -n "$6" ]; then dumpParam="-o $dumpDir/$6" fi if [ -z "$TESTPROGNAME" ]; then if [ "$5" == "MEM" ]; then stress-ng --vm 1 --vm-hang 0 --vm-bytes $1 --timeout 20s -q& else stress-ng -c 1 -l $1 --timeout 20s -q& fi pid=$! echo "PID: $pid" sleep 1s childrenpid=$(pidof -o $pid $(which stress-ng)) echo "ChildrenPID: $childrenpid" childpid=$(echo $childrenpid | cut -d " " -f1) echo "ChildPID: $childpid" echo "$PROCDUMPPATH $2 $3 $dumpParam -p $childpid" $PROCDUMPPATH $2 $3 $dumpParam -p $childpid else TESTPROGPATH=$(readlink -m "$DIR/../../bin/$TESTPROGNAME"); (sleep 2; $TESTPROGPATH "$TESTPROGMODE") & pid=$! echo "PID: $pid" echo "$PROCDUMPPATH $2 $3 $dumpParam -w $TESTPROGNAME" $PROCDUMPPATH $2 $3 $dumpParam -w "$TESTPROGNAME" fi if ps -p $pid > /dev/null then kill $pid fi if find "$dumpDir" -mindepth 1 -print -quit | grep -q .; then if $4; then exit 0 else exit 1 fi else if $4; then exit 1 else exit 0 fi fi } ProcDump-for-Linux-1.2/tests/integration/scenarios/000077500000000000000000000000001412022400100224235ustar00rootroot00000000000000ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu.sh000077500000000000000000000005241412022400100245510ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90 procDumpType="-C" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu_by_name.sh000077500000000000000000000006201412022400100262400ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate TESTPROGNAME="ProcDumpTestApplication" TESTPROGMODE="burn" stressPercentage=90 procDumpType="-C" procDumpTrigger=50 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu_custom_core_file_name.sh000077500000000000000000000006161412022400100311540ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90 procDumpType="-C" procDumpTrigger=80 shouldDump=true customDumpFileName="custom_dump_file" runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" $customDumpFileName ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu_nonexisting_output_directory.sh000077500000000000000000000006361412022400100327060ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90 procDumpType="-C" procDumpTrigger=80 shouldDump=false customDumpFileName="missing_subdir/custom_dump_file" runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" $customDumpFileName ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu_notdump.sh000077500000000000000000000005251412022400100263200ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=20 procDumpType="-C" procDumpTrigger=80 shouldDump=false runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_cpu_trigger_cpu_memory.sh000077500000000000000000000005361412022400100305360ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90 procDumpType="-M 1000 -C" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage "$procDumpType" $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_mem.sh000077500000000000000000000005251412022400100245410ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90M procDumpType="-M" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_mem_notdump.sh000077500000000000000000000005261412022400100263100ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=60M procDumpType="-M" procDumpTrigger=80 shouldDump=false runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/high_mem_trigger_cpu_memory.sh000077500000000000000000000005361412022400100305250ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=90M procDumpType="-C 100 -M" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage "$procDumpType" $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_cpu.sh000077500000000000000000000005241412022400100244330ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=10 procDumpType="-c" procDumpTrigger=20 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_cpu_by_name.sh000077500000000000000000000006201412022400100261220ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); TESTPROGNAME="ProcDumpTestApplication" TESTPROGMODE="sleep" source $runProcDumpAndValidate stressPercentage=10 procDumpType="-c" procDumpTrigger=20 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_cpu_notdump.sh000077500000000000000000000005251412022400100262020ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=80 procDumpType="-c" procDumpTrigger=20 shouldDump=false runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_cpu_trigger_cpu_memory.sh000077500000000000000000000005361412022400100304200ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=10 procDumpType="-M 1000 -c" procDumpTrigger=20 shouldDump=true runProcDumpAndValidate $stressPercentage "$procDumpType" $procDumpTrigger $shouldDump "CPU" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_mem.sh000077500000000000000000000005251412022400100244230ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=40M procDumpType="-m" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_mem_notdump.sh000077500000000000000000000005271412022400100261730ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=200M procDumpType="-m" procDumpTrigger=80 shouldDump=false runProcDumpAndValidate $stressPercentage $procDumpType $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/low_mem_trigger_cpu_memory.sh000077500000000000000000000005361412022400100304070ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=40M procDumpType="-C 100 -m" procDumpTrigger=80 shouldDump=true runProcDumpAndValidate $stressPercentage "$procDumpType" $procDumpTrigger $shouldDump "MEM" ProcDump-for-Linux-1.2/tests/integration/scenarios/ondemand.sh000077500000000000000000000005311412022400100245460ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); source $runProcDumpAndValidate stressPercentage=1 procDumpType="" procDumpTrigger="" shouldDump=true runProcDumpAndValidate "$stressPercentage" "$procDumpType" "$procDumpTrigger" "$shouldDump" "CPU" ProcDump-for-Linux-1.2/tests/integration/signal/000077500000000000000000000000001412022400100217125ustar00rootroot00000000000000ProcDump-for-Linux-1.2/tests/integration/signal/makefile000066400000000000000000000000761412022400100234150ustar00rootroot00000000000000signaltest: signaltest.c gcc -o signaltest signaltest.c -I. ProcDump-for-Linux-1.2/tests/integration/signal/signaltest000077500000000000000000000407501412022400100240230ustar00rootroot00000000000000ELF>@(:@8 @@@@ -==px-==888 XXXDDStd888 Ptd   LLQtdRtd-==``/lib64/ld-linux-x86-64.so.2GNUGNUԦc*MZD!D`~GNU  emQ 3m  | $"libc.so.6exitsignalprintfpause__cxa_finalize__libc_start_mainGLIBC_2.2.5_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTableui E==`@@????? ????HH/HtH5/%/hhhh%}/D%-/D%%/D%/D%/D1I^HHPTLH ?H=.H=/H/H9tH.Ht H=.H5.H)HH?HHHtH.HtfD=.u+UH=.Ht H=.d}.]wUHH}}u EH=0UHH }HuEEH5E}~AWL=s+AVIAUIATAUH-d+SL)HHt1LLDAHH9uH[]A\A]A^A_ff.HHCaught signal: %d ;LXhhx`zRx 8/D$4PFJ w?:*3$"\t@8EC o >EC DeFIE E(D0H8G@n8A0A(B BBB`  ==o ?`H oohooNo=0@P`@GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.08X|N h  H  p  h ===?@@  !`7@F=my=!=== ?   } @58A@H\{@ @  e@/@> @ 3"crtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.8060__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entrysignaltest.c__FRAME_END____init_array_end_DYNAMIC__init_array_start__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE___libc_csu_fini_ITM_deregisterTMCloneTablesig_handler_edataprintf@@GLIBC_2.2.5__libc_start_main@@GLIBC_2.2.5__data_startsignal@@GLIBC_2.2.5__gmon_start____dso_handle_IO_stdin_usedpause@@GLIBC_2.2.5__libc_csu_init__bss_startmainexit@@GLIBC_2.2.5__TMC_END___ITM_registerTMCloneTable__cxa_finalize@@GLIBC_2.2.5.symtab.strtab.shstrtab.interp.note.gnu.property.note.gnu.build-id.note.ABI-tag.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.got.plt.sec.text.fini.rodata.eh_frame_hdr.eh_frame.init_array.fini_array.dynamic.data.bss.comment#88 6XX$I|| Wo$a iqoNN~ohh BHH`  Ppp@    Lh h (=-=-=-?/`@0 @000*@0x. 6O9ProcDump-for-Linux-1.2/tests/integration/signal/signaltest.c000066400000000000000000000021151412022400100242320ustar00rootroot00000000000000// // Used to test the signal triggering (and forwarding) of procdump. // // 1. Run this test app (it registers for the first 23 signals). // 2. Run procdump against this pid // 3. use kill to send whichever signal you are interested in triggering procdump (or not trigger) // 4. Make sure in all cases (except for signals that can't be intercepted) that this program outputs "Caught signal X" // where X is the signal you sent. If the output does not show that signal being handled, it means the signal forwarding // in procdump is not working properly and needs to be investigated. // #include #include #include #include #include #include void sig_handler(int signum) { if(signum==SIGINT) { exit(-1); } // We shouldnt be using printf in a signal handler but in this simple test case its fine printf("Caught signal: %d\n", signum); } void main(int argc, char** argv) { for(int i=0; i<24; i++) { signal(i, sig_handler); } while(1) { pause(); } }