Last active
April 3, 2022 01:58
-
-
Save tqinli/043914e49f5e0fafc4bb16a60c3d059c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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