Skip to content

Instantly share code, notes, and snippets.

@tqinli
Last active April 3, 2022 01:58
Show Gist options
  • Save tqinli/043914e49f5e0fafc4bb16a60c3d059c to your computer and use it in GitHub Desktop.
Save tqinli/043914e49f5e0fafc4bb16a60c3d059c to your computer and use it in GitHub Desktop.
using System.Diagnostics;
using System.Runtime.InteropServices;
using TTD.Common.Diagnostics;
// Console.WriteLine("Dumping...");
// var parent = Process.GetCurrentProcess();
// SysCallInterop.Kill(parent, SigNum.SIGSEGV);
// Console.WriteLine("Dumped!");
Task.Run(
() => {
Dumper.CreateDiagnosticDump(DumpType.Normal, "test1.mdmp");
Dumper.CreateDiagnosticDump(DumpType.Triage, "test2.mdmp");
Dumper.CreateDiagnosticDump(DumpType.WithHeap, "test3.mdmp");
Dumper.CreateDiagnosticDump(DumpType.Full, "test4.mdmp");
Dumper.CreateDiagnosticDump(DumpType.Triage, "test5.mdmp", crashThreadId: Thread.CurrentThread.ManagedThreadId);
Dumper.CreateDiagnosticDump(DumpType.Triage, "test6.mdmp", crashReport: true);
Dumper.CreateDiagnosticDump(DumpType.Triage, "test7.mdmp", signal: SigNum.SIGHUP);
Dumper.CreateDiagnosticDump(DumpType.Triage, "test7.mdmp", diag: true);
}
).Wait();
Environment.Exit(0);
namespace TTD.Common.Diagnostics
{
public enum DumpType
{
Normal = 1,
WithHeap = 2,
Triage = 3,
Full = 4
}
public static class Dumper
{
public static void CreateDiagnosticDump(DumpType dumpType, string dumpPath, int? crashThreadId = default, bool crashReport = false, SigNum? signal = default, bool diag = false)
{
lock (Dumper._lock)
{
Log("{0}, {1}\n{2}", dumpType, dumpPath, Environment.StackTrace);
if (OSPlatform != PlatformID.Unix)
{
Log(" --> ERROR: unsupported OS {0}, exit without creating dump", OSPlatform);
}
var dumperExe = DetectDumperExecutable();
if (dumperExe == null)
{
Log(" --> ERROR: createdump not found, exit without creating dump");
return;
}
Log("found dumper executable {0}", dumperExe);
List<string> dumpCmd = new List<string>();
dumpCmd.Add(dumperExe);
dumpCmd.Add(GetDumpTypeArgumentString(dumpType));
dumpCmd.Add("--name");
dumpCmd.Add(dumpPath);
if (crashThreadId.HasValue)
{
dumpCmd.Add("--crashthread");
dumpCmd.Add(crashThreadId.Value.ToString());
}
if (crashReport)
{
dumpCmd.Add("--crashreport");
}
if (signal.HasValue)
{
dumpCmd.Add("--signal");
dumpCmd.Add(((int)signal.Value).ToString());
}
if (diag)
{
dumpCmd.Add("--diag");
}
int pid = Process.GetCurrentProcess().Id;
dumpCmd.Add(pid.ToString());
// Logic below is borrowed from CoreCLR's PAL library
// https://github.com/dotnet/runtime/blob/main/src/coreclr/pal/src/thread/process.cpp PROCCreateCrashDump
var child = SysCallInterop.Fork();
if (child == 0)
{
var args = dumpCmd.ToArray();
Log("fork-child execve({0})", string.Join(" ", args));
SysCallInterop.Exec(args);
}
else
{
Log("fork-parent setptracer {0}...", child);
var set = SysCallInterop.SetPTracer(child);
Log("fork-parent setptracer {0} exited {1}", child, set);
Log("fork-parent waitpid {0}...", child);
var waited = SysCallInterop.WaitPid(child, WaitFlags.WUNTRACED);
Log("fork-parent waitpid {0} exited {1}", child, waited);
}
}
}
private static void Log(string format, params object[] objs)
{
var ts = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
Console.Error.WriteLine(ts + " [Dumper.CreateDiagnosticDump] " + format, objs);
}
private static string? DetectDumperExecutable()
{
var cwd = Environment.CurrentDirectory;
var path1 = Path.Join(cwd, ExecutableName);
if (File.Exists(path1))
{
return path1;
}
var moduleDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule!.FileName);
var path2 = Path.Join(moduleDir, ExecutableName);
if (File.Exists(path2))
{
return path2;
}
var runVer = Environment.Version.ToString();
var path3 = Path.Join(RuntimeSharedFolder, runVer, ExecutableName);
if (File.Exists(path3))
{
return path3;
}
return null;
}
private static string GetDumpTypeArgumentString(DumpType dumpType)
{
switch (dumpType)
{
case DumpType.Full:
return "--full";
case DumpType.Triage:
return "--triage";
case DumpType.WithHeap:
return "--withheap";
case DumpType.Normal:
return "--normal";
default:
throw new NotSupportedException(dumpType.ToString());
}
}
public static string ExecutableName = "createdump";
public static string RuntimeSharedFolder = "/usr/share/dotnet/shared/Microsoft.NETCore.App";
private static readonly PlatformID OSPlatform = Environment.OSVersion.Platform;
private static readonly object _lock = new object();
}
public static class SysCallInterop
{
[DllImport ("libc", SetLastError=true, EntryPoint="kill")]
private static extern int sys_kill(int pid, int sig);
[DllImport ("libc", SetLastError=true, EntryPoint="fork")]
private static extern int sys_fork();
[DllImport ("libc", SetLastError=true, EntryPoint="waitpid")]
private static extern int sys_waitpid(int pid, IntPtr pstat, int options);
[DllImport ("libc", SetLastError=true, EntryPoint="execve")]
private static extern int sys_execve(
[MarshalAs(UnmanagedType.LPStr)] string cmd,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] string[] args,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] string[] env);
[DllImport ("libc", SetLastError=true, EntryPoint="prctl")]
private static extern int sys_prctl(int option, uint arg2, uint arg3, uint arg4, uint arg5);
public static void Kill(this Process process, SigNum sig)
{
sys_kill(process.Id, (int)sig);
}
public static bool WaitPid(this Process process, WaitFlags waitFlags)
{
return WaitPid(process.Id, waitFlags);
}
public static bool WaitPid(int pid, WaitFlags waitFlags)
{
var pstat = new IntPtr(0);
int ret = sys_waitpid(pid, pstat, (int)waitFlags);
// Console.WriteLine("sys_waitpid({0}, [Out], {1}) returned {2}", pid, waitFlags, ret);
return ret == pid;
}
public static int Fork()
{
return sys_fork();
}
public static void Exec(params string[] args)
{
Exec(args, EmptyStringArray);
}
public static void Exec(string[] args, string[] env)
{
var argsArg = args.Concat(NullValueStringArray).ToArray();
var envArg = env.Length == 0 ? NullValueStringArray : env.Concat(NullValueStringArray).ToArray();
int ret = sys_execve(args[0], argsArg, envArg);
// Console.WriteLine("sys_execve(...) returned {0}", ret);
}
public static bool SetPTracer(int pid)
{
int ret = sys_prctl((int)PRCtlFlags.PR_SET_PTRACER, (uint)pid, 0u, 0u, 0u);
// Console.WriteLine("sys_prctl(...) returned {0}", ret);
return ret == 0;
}
private static readonly string[] EmptyStringArray = new string[] {};
#pragma warning disable CS8625
private static readonly string[] NullValueStringArray = new string[] { null };
#pragma warning restore CS8625
}
public enum SigNum : int
{
SIGHUP = 1, // Hangup (POSIX).
SIGINT = 2, // Interrupt (ANSI).
SIGQUIT = 3, // Quit (POSIX).
SIGILL = 4, // Illegal instruction (ANSI).
SIGTRAP = 5, // Trace trap (POSIX).
SIGABRT = 6, // Abort (ANSI).
SIGIOT = 6, // IOT trap (4.2 BSD).
SIGBUS = 7, // BUS error (4.2 BSD).
SIGFPE = 8, // Floating-point exception (ANSI).
SIGKILL = 9, // Kill, unblockable (POSIX).
SIGUSR1 = 10, // User-defined signal 1 (POSIX).
SIGSEGV = 11, // Segmentation violation (ANSI).
SIGUSR2 = 12, // User-defined signal 2 (POSIX).
SIGPIPE = 13, // Broken pipe (POSIX).
SIGALRM = 14, // Alarm clock (POSIX).
SIGTERM = 15, // Termination (ANSI).
SIGSTKFLT = 16, // Stack fault.
SIGCLD = SIGCHLD, // Same as SIGCHLD (System V).
SIGCHLD = 17, // Child status has changed (POSIX).
SIGCONT = 18, // Continue (POSIX).
SIGSTOP = 19, // Stop, unblockable (POSIX).
SIGTSTP = 20, // Keyboard stop (POSIX).
SIGTTIN = 21, // Background read from tty (POSIX).
SIGTTOU = 22, // Background write to tty (POSIX).
SIGURG = 23, // Urgent condition on socket (4.2 BSD).
SIGXCPU = 24, // CPU limit exceeded (4.2 BSD).
SIGXFSZ = 25, // File size limit exceeded (4.2 BSD).
SIGVTALRM = 26, // Virtual alarm clock (4.2 BSD).
SIGPROF = 27, // Profiling alarm clock (4.2 BSD).
SIGWINCH = 28, // Window size change (4.3 BSD, Sun).
SIGPOLL = SIGIO, // Pollable event occurred (System V).
SIGIO = 29, // I/O now possible (4.2 BSD).
SIGPWR = 30, // Power failure restart (System V).
SIGSYS = 31, // Bad system call.
SIGUNUSED = 31
}
public enum WaitFlags : int
{
NONE = 1,
WNOHANG = 1,
WUNTRACED = 2,
WEXITED = 4,
WCONTINUED = 8
}
public enum PRCtlFlags : int
{
PR_SET_PTRACER = 0x59616d61
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment