Xamarin Public Jenkins (auto-signing) 64ac736ec5 Imported Upstream version 6.0.0.172
Former-commit-id: f3cc9b82f3e5bd8f0fd3ebc098f789556b44e9cd
2019-04-12 14:10:50 +00:00

637 lines
24 KiB
C#

/**
* \file UploadToSentry.cs
* Support for reading verbose unmanaged crash dumps
*
* Author:
* Alexander Kyte (alkyte@microsoft.com)
*
* (C) 2018 Microsoft, Inc.
*
*/
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Mono.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Net;
[assembly: CLSCompliant (false)]
namespace UploadToSentry
{
// Modeled after https://github.com/getsentry/raven-csharp/blob/develop/src/app/SharpRaven/Dsn.cs
class Dsn {
public readonly string PrivateKey;
public readonly string PublicKey;
public readonly string ProjectID;
private readonly string path;
private readonly int port;
private readonly string sentryUriString;
private readonly Uri uri;
public Dsn (string dsn) {
this.uri = new Uri(dsn);
this.PrivateKey = GetPrivateKey(this.uri);
this.PublicKey = GetPublicKey(this.uri) ?? throw new ArgumentException("A publicKey is required.", nameof(dsn));
this.port = this.uri.Port;
this.ProjectID = GetProjectID(this.uri);
this.path = GetPath(this.uri);
this.sentryUriString = String.Format("{0}://{1}:{2}{3}/api/{4}/store/", this.uri.Scheme, this.uri.DnsSafeHost, this.port, this.path, this.ProjectID);
}
private static string GetPath(Uri uri)
{
int lastSlash = uri.AbsolutePath.LastIndexOf("/", StringComparison.Ordinal);
return uri.AbsolutePath.Substring(0, lastSlash);
}
private static string GetProjectID(Uri uri)
{
int lastSlash = uri.AbsoluteUri.LastIndexOf("/", StringComparison.Ordinal);
return uri.AbsoluteUri.Substring(lastSlash + 1);
}
public Uri SentryUri {
get {
return new Uri (sentryUriString);
}
}
private static string GetPrivateKey(Uri uri)
{
var parts = uri.UserInfo.Split(':');
return parts.Length == 2 ? parts[1] : null;
}
private static string GetPublicKey(Uri uri)
{
var publicKey = uri.UserInfo.Split(':')[0];
return publicKey != string.Empty ? publicKey : null;
}
}
class CodeCollection
{
Dictionary<Tuple<string, uint>, Collection<SequencePoint>> Lookup;
Dictionary<Tuple<string, uint>, Tuple<string, string, string>> Types;
public void Add (string assembly, string klass, string function, string mvid, uint token, Collection<SequencePoint> seqs)
{
var key = new Tuple<string, uint>(mvid, token);
Lookup[key] = seqs;
Types[key] = new Tuple<string, string, string>(assembly, klass, function);
}
public CodeCollection(string [] assemblies)
{
Lookup = new Dictionary<Tuple<string, uint>, Collection<SequencePoint>>();
Types = new Dictionary<Tuple<string, uint>, Tuple<string, string, string>>();
foreach (string assembly in assemblies)
{
if (assembly.EndsWith(".dll") || assembly.EndsWith(".exe"))
{
// Console.WriteLine("Reading {0}", assembly);
var readerParameters = new ReaderParameters { ReadSymbols = true, InMemory = true };
AssemblyDefinition myLibrary = null;
try
{
myLibrary = AssemblyDefinition.ReadAssembly(assembly, readerParameters);
string mvid = myLibrary.MainModule.Mvid.ToString().ToUpper();
Console.WriteLine("\t-- Success Parsing {0}: {1}", assembly, mvid);
foreach (var ty in myLibrary.MainModule.Types)
{
for (int i = 0; i < ty.Methods.Count; i++)
{
string klass = ty.FullName;
string function = ty.Methods[i].FullName;
uint token = Convert.ToUInt32(ty.Methods[i].MetadataToken.ToInt32());
this.Add(assembly, klass, function, mvid, token, ty.Methods[i].DebugInformation.SequencePoints);
}
}
}
catch (SymbolsNotFoundException)
{
// ignore assemblies without debug symbols
continue;
}
catch (Exception e)
{
Console.WriteLine("\t-- Error Parsing {0}: {1}", assembly, e.Message);
}
}
}
}
public JObject Find (string mvid, uint token, uint goal)
{
var method_idx = new Tuple<string, uint>(mvid, token);
if (!Lookup.ContainsKey(method_idx))
return null;
var seqs = Lookup[method_idx];
var accum = new JObject();
foreach (var seq in seqs)
{
if (goal != seq.Offset)
continue;
accum.Add (new JProperty("lineno", seq.StartLine));
accum.Add (new JProperty("filename", seq.Document.Url));
break;
}
var typ = Types[method_idx];
var assembly = typ.Item1;
var klass = typ.Item2;
accum.Add (new JProperty("module", String.Format("{0} {1}", assembly, klass)));
accum.Add (new JProperty("function", typ.Item3));
return accum;
}
}
class Uploader
{
CodeCollection codebase;
public JObject Format_0_0_3 (string fileName, JObject payload, string hash)
{
var event_id = new JProperty("event_id", Guid.NewGuid().ToString("n"));
var timestamp = new JProperty("timestamp", DateTime.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
var exc_objs = new List<JObject> ();
var thread_objs = new List<JObject> ();
var stackTraces = payload["threads"] as JArray;
var path = Path.GetDirectoryName (fileName); // Best differentiator in-tree for files is where they are run from
foreach (var st in stackTraces) {
var thread_id = st["native_thread_id"]?.ToString();
var unmanaged_frames = new List<JObject>();
var managed_frames = new List<JObject>();
var exceptions = new List<JObject>();
var thread_name = st["thread_name"]?.ToString ();
if (thread_name == null || thread_name?.Length == 0)
thread_name = "Unnamed thread";
var payload_unmanaged_frames = st["unmanaged_frames"] as JArray;
for (int fr=0; payload_unmanaged_frames != null && fr < payload_unmanaged_frames.Count; fr++)
{
var frame = payload_unmanaged_frames [fr] as JObject;
var native_address = frame["native_address"];
var unmanaged_name = frame["unmanaged_name"] != null ? frame["unmanaged_name"].ToString() : "";
var fn_filename = new JProperty("filename", "");
var function = new JProperty("function", unmanaged_name);
var module = new JProperty("module", "mono-sgen");
var vars = new JProperty("vars", new JObject(new JProperty ("native_address", native_address)));
var blob = new JObject(fn_filename, function, module, vars);
unmanaged_frames.Add (blob);
}
var payload_managed_frames = st["managed_frames"] as JArray;
for (int fr = 0; payload_managed_frames != null && fr < payload_managed_frames.Count; fr++)
{
var frame = payload_managed_frames [fr] as JObject;
if (frame["is_managed"] != null && frame["is_managed"].ToString ().ToUpper () == "TRUE")
{
var guid_val = frame["guid"].ToString ();
var token_val = Convert.ToUInt32(frame["token"].ToString (), 16);
var offset_val = Convert.ToUInt32(frame["il_offset"].ToString (), 16);
var output_frame = codebase.Find (guid_val, token_val, offset_val);
if (output_frame == null)
continue;
var guid = new JProperty("guid", guid_val);
var token = new JProperty("token", token_val);
var il_offset = new JProperty("il_offset", offset_val);
output_frame.Add (new JProperty("vars", new JObject(guid, token, il_offset)));
managed_frames.Add(output_frame);
} else {
var native_address = frame["native_address"];
var unmanaged_name = frame["unmanaged_name"] != null ? frame["unmanaged_name"].ToString() : "";
var fn_filename = new JProperty("filename", "mono-sgen");
var function = new JProperty("function", unmanaged_name);
var module = new JProperty("module", "");
var vars = new JProperty("vars", frame);
var blob = new JObject(fn_filename, function, module, vars);
managed_frames.Add (blob);
}
}
var payload_exceptions = st["exceptions"] as JArray;
for (int exc=0; payload_exceptions != null && exc < payload_exceptions.Count; exc++)
{
var exception_obj = payload_exceptions [exc] as JObject;
var exc_managed_frames = new List<JObject>();
var payload_exception_frames = exception_obj ["managed_frames"] as JArray;
for (int fr = 0; payload_exception_frames != null && fr < payload_exception_frames.Count; fr++)
{
var frame = payload_exception_frames [fr] as JObject;
if (frame["is_managed"] != null && frame["is_managed"].ToString ().ToUpper () == "TRUE")
{
var guid_val = frame["guid"].ToString ();
var token_val = Convert.ToUInt32(frame["token"].ToString (), 16);
var offset_val = Convert.ToUInt32(frame["il_offset"].ToString (), 16);
var output_frame = codebase.Find (guid_val, token_val, offset_val);
if (output_frame == null)
continue;
var guid = new JProperty("guid", guid_val);
var token = new JProperty("token", token_val);
var il_offset = new JProperty("il_offset", offset_val);
output_frame.Add (new JProperty("vars", new JObject(guid, token, il_offset)));
exc_managed_frames.Add(output_frame);
} else {
var native_address = frame["native_address"];
var unmanaged_name = frame["unmanaged_name"] != null ? frame["unmanaged_name"].ToString() : "";
var fn_filename = new JProperty("filename", "mono-sgen");
var function = new JProperty("function", unmanaged_name);
var module = new JProperty("module", "");
var vars = new JProperty("vars", frame);
var blob = new JObject(fn_filename, function, module, vars);
exc_managed_frames.Add (blob);
}
}
var type = new JProperty ("type", exception_obj ["type"].ToString ());
var managed_st = new JProperty("frames", new JArray(exc_managed_frames.ToArray()));
var exc_blob = new JObject(type, managed_st);
exceptions.Add (exc_blob);
}
if (unmanaged_frames.Count > 0) {
var unmanaged_st = new JObject(new JProperty("frames", new JArray(unmanaged_frames.ToArray ())));
var id = String.Format ("{0}_unmanaged", st ["native_thread_id"]);
var active = new JProperty ("active", "true");
if (st["crashed"]?.ToString ().ToUpper () == "TRUE") {
var unmanaged_thread = new JObject (active, new JProperty ("crashed", "true"), new JProperty ("name", String.Format ("{0} unmanaged", thread_name)), new JProperty ("id", id));
var unmanaged_exc = new JObject(new JProperty("module", String.Format("{0}_managed_frames", thread_id)),
new JProperty("type", path),
new JProperty("value", ""),
new JProperty("stacktrace", unmanaged_st), new JProperty("thread_id", id));
thread_objs.Add(unmanaged_thread);
exc_objs.Add (unmanaged_exc);
} else {
var unmanaged_thread = new JObject (active, new JProperty ("name", String.Format ("{0} Unmanaged", thread_name)),
new JProperty ("id", id), new JProperty ("stacktrace", unmanaged_st));
thread_objs.Add(unmanaged_thread);
}
}
if (managed_frames.Count > 0) {
var managed_st = new JObject(new JProperty("frames", new JArray(managed_frames.ToArray())));
// If we are the crashing thread, set the exception object to the
// managed stacktrace and the thread object to the managed thread
//
// If we aren't, add the thread + st to
var id = String.Format ("{0}_managed", st["native_thread_id"]);
var active = new JProperty ("active", "true");
if (unmanaged_frames.Count == 0 && st["crashed"]?.ToString ().ToUpper () == "TRUE") {
var managed_thread = new JObject (active, new JProperty ("crashed", "true"), new JProperty ("name", String.Format ("{0} managed", thread_name)), new JProperty ("id", id));
var managed_exc = new JObject(new JProperty("module", String.Format("{0}_managed_frames", thread_id)),
new JProperty("type", path),
new JProperty("value", ""),
new JProperty("stacktrace", managed_st), new JProperty("thread_id", id));
thread_objs.Add(managed_thread);
exc_objs.Add (managed_exc);
} else {
var managed_thread = new JObject (active, new JProperty ("name", String.Format ("{0} managed", thread_name)),
new JProperty ("id", id), new JProperty ("stacktrace", managed_st));
thread_objs.Add(managed_thread);
}
}
bool first_exception = true;
foreach (var exc_input in exceptions) {
// The 0.0.3 managed exception only has one thread, so we want it to be active and set crashed to true.
var active = new JProperty ("active", first_exception ? "true" : "false");
first_exception = false;
var name = "Caught Managed Exception";
var exception_thread = new JObject (active, new JProperty ("crashed", "true"), new JProperty ("name", name), new JProperty ("id", name), new JProperty ("exception", exc_input));
var exception_exc = new JObject(new JProperty("module", name),
new JProperty("type", path),
new JProperty("value", ""),
new JProperty("stacktrace", exc_input ["frames"]));
thread_objs.Add (exception_thread);
exc_objs.Add (exception_exc);
}
}
var exception = new JProperty("exception", new JObject (new JProperty ("values", new JArray(exc_objs.ToArray ()))));
var threads = new JProperty("threads", new JObject (new JProperty ("values", new JArray(thread_objs.ToArray ()))));
// Bake in the whole blob
var embedded = new JProperty("extra", payload);
var fingerprint = new JProperty ("fingerprint", new JArray (new JValue (hash)));
var sentry_message = new JObject (timestamp, event_id, exception, embedded, threads, fingerprint);
return sentry_message;
}
public JObject Format_0_0_2 (string fileName, JObject payload, string hash)
{
var event_id = new JProperty("event_id", Guid.NewGuid().ToString("n"));
var timestamp = new JProperty("timestamp", DateTime.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
var exc_objs = new List<JObject> ();
var thread_objs = new List<JObject> ();
var stackTraces = payload["threads"] as JArray;
var path = Path.GetDirectoryName (fileName); // Best differentiator in-tree for files is where they are run from
for (int i=0; i < stackTraces.Count; i++){
var thread_id = stackTraces[i]["native_thread_id"]?.ToString();
var unmanaged_frames = new List<JObject>();
var managed_frames = new List<JObject>();
var thread_name = stackTraces [i]["thread_name"].ToString ();
if (thread_name == null || thread_name.Length == 0)
thread_name = "Unnamed thread";
var payload_unmanaged_frames = stackTraces[i]["unmanaged_frames"] as JArray;
for (int fr=0; payload_unmanaged_frames != null && fr < payload_unmanaged_frames.Count; fr++)
{
var frame = payload_unmanaged_frames [fr] as JObject;
var native_address = frame["native_address"];
var unmanaged_name = frame["unmanaged_name"] != null ? frame["unmanaged_name"].ToString() : "";
var fn_filename = new JProperty("filename", "");
var function = new JProperty("function", unmanaged_name);
var module = new JProperty("module", "mono-sgen");
var vars = new JProperty("vars", new JObject(new JProperty ("native_address", native_address)));
var blob = new JObject(fn_filename, function, module, vars);
unmanaged_frames.Add (blob);
}
var payload_managed_frames = stackTraces[i]["managed_frames"] as JArray;
for (int fr = 0; payload_managed_frames != null && fr < payload_managed_frames.Count; fr++)
{
var frame = payload_managed_frames [fr] as JObject;
if (frame["is_managed"] != null && frame["is_managed"].ToString ().ToUpper () == "TRUE")
{
var guid_val = frame["guid"].ToString ();
var token_val = Convert.ToUInt32(frame["token"].ToString (), 16);
var offset_val = Convert.ToUInt32(frame["il_offset"].ToString (), 16);
var output_frame = codebase.Find (guid_val, token_val, offset_val);
if (output_frame == null)
continue;
var guid = new JProperty("guid", guid_val);
var token = new JProperty("token", token_val);
var il_offset = new JProperty("il_offset", offset_val);
output_frame.Add (new JProperty("vars", new JObject(guid, token, il_offset)));
managed_frames.Add(output_frame);
} else {
var native_address = frame["native_address"];
var unmanaged_name = frame["unmanaged_name"] != null ? frame["unmanaged_name"].ToString() : "";
var fn_filename = new JProperty("filename", "mono-sgen");
var function = new JProperty("function", unmanaged_name);
var module = new JProperty("module", "");
var vars = new JProperty("vars", frame);
var blob = new JObject(fn_filename, function, module, vars);
managed_frames.Add (blob);
}
}
if (unmanaged_frames.Count > 0) {
var unmanaged_st = new JObject(new JProperty("frames", new JArray(unmanaged_frames.ToArray ())));
var id = String.Format ("{0}_unmanaged", stackTraces[i]["native_thread_id"]);
var active = new JProperty ("active", "true");
if (stackTraces[i]["crashed"].ToString ().ToUpper () == "TRUE") {
var unmanaged_thread = new JObject (active, new JProperty ("crashed", "true"), new JProperty ("name", String.Format ("{0} unmanaged", thread_name)), new JProperty ("id", id));
var unmanaged_exc = new JObject(new JProperty("module", String.Format("{0}_managed_frames", thread_id)),
new JProperty("type", path),
new JProperty("value", ""),
new JProperty("stacktrace", unmanaged_st), new JProperty("thread_id", id));
thread_objs.Add(unmanaged_thread);
exc_objs.Add (unmanaged_exc);
} else {
var unmanaged_thread = new JObject (active, new JProperty ("name", String.Format ("{0} Unmanaged", thread_name)),
new JProperty ("id", id), new JProperty ("stacktrace", unmanaged_st));
thread_objs.Add(unmanaged_thread);
}
}
if (managed_frames.Count > 0) {
var managed_st = new JObject(new JProperty("frames", new JArray(managed_frames.ToArray())));
// If we are the crashing thread, set the exception object to the
// managed stacktrace and the thread object to the managed thread
//
// If we aren't, add the thread + st to
var id = String.Format ("{0}_managed", stackTraces[i]["native_thread_id"]);
var active = new JProperty ("active", "true");
if (unmanaged_frames.Count == 0 && stackTraces[i]["crashed"].ToString ().ToUpper () == "TRUE") {
var managed_thread = new JObject (active, new JProperty ("crashed", "true"), new JProperty ("name", String.Format ("{0} managed", thread_name)), new JProperty ("id", id));
var managed_exc = new JObject(new JProperty("module", String.Format("{0}_managed_frames", thread_id)),
new JProperty("type", path),
new JProperty("value", ""),
new JProperty("stacktrace", managed_st), new JProperty("thread_id", id));
thread_objs.Add(managed_thread);
exc_objs.Add (managed_exc);
} else {
var managed_thread = new JObject (active, new JProperty ("name", String.Format ("{0} managed", thread_name)),
new JProperty ("id", id), new JProperty ("stacktrace", managed_st));
thread_objs.Add(managed_thread);
}
}
}
var exception = new JProperty("exception", new JObject (new JProperty ("values", new JArray(exc_objs.ToArray ()))));
var threads = new JProperty("threads", new JObject (new JProperty ("values", new JArray(thread_objs.ToArray ()))));
// Bake in the whole blob
var embedded = new JProperty("extra", payload);
var fingerprint = new JProperty ("fingerprint", new JArray (new JValue (hash)));
var sentry_message = new JObject (timestamp, event_id, exception, embedded, threads, fingerprint);
return sentry_message;
}
public void SendMessage (JObject sentry_message, Dsn url)
{
// Console.WriteLine ("Sending {0}", sentry_message.ToString ());
var request = (HttpWebRequest) WebRequest.Create (url.SentryUri);
request.Method = "POST";
request.ContentType = "application/json";
request.UserAgent = "MonoSentryUploader/1.0.0.0";
var sentryVersion = 7;
var time = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
var key = url.PrivateKey != null ? ", sentry_secret=" + url.PrivateKey : null;
var header = String.Format("Sentry sentry_version={0}" +
", sentry_client={1}, sentry_timestamp={2}, sentry_key={3}{4}",
sentryVersion, request.UserAgent, time, url.PublicKey, key);
request.Headers ["X-Sentry-Auth"] = header;
byte[] byteArray = Encoding.UTF8.GetBytes(sentry_message.ToString ());
request.ContentLength = byteArray.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
try {
WebResponse response = request.GetResponse ();
// Display the status.
// Console.WriteLine(((HttpWebResponse)response).StatusDescription);
StreamReader reader = new StreamReader(response.GetResponseStream());
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
Console.WriteLine("\t-- HTTP POST Success {0}", responseFromServer);
// Clean up the streams.
} catch (WebException ex) {
Console.WriteLine("\t-- HTTP POST Error:", ex.Response.Headers [""]);
for (int i=0; i < ex.Response.Headers.Count; i++)
Console.WriteLine("\t\t {0} : {1}", ex.Response.Headers.Keys [i], ex.Response.Headers [i]);
}
}
public void Upload (string filePath, string os_tag, Dsn url)
{
if (!File.Exists(filePath))
throw new Exception(String.Format("Json file not found {0}", filePath));
var dump = File.ReadAllText(filePath);
//var message = new SentryMessage(dump);
// var blob = new SentryEvent(message);
var payload = JObject.Parse(dump);
// Try to extract a test name
var fileName = Path.GetFileName (filePath);
var extract = Regex.Match(fileName, @"mono_crash\.([A-Za-z0-9]+)\.(\d)\.json");
if (!extract.Success)
throw new Exception ("File name does not match correct format");
var groups = extract.Groups;
var hash = groups[1].Value;
// var increment = groups[2].Value;
var version_string = payload["protocol_version"].ToString();
JObject sentry_message = null;
if (version_string == "0.0.4") {
// Same for now
sentry_message = Format_0_0_3 (filePath, payload, hash);
} else if (version_string == "0.0.3") {
sentry_message = Format_0_0_3 (filePath, payload, hash);
} else if (version_string == "0.0.2") {
sentry_message = Format_0_0_2 (filePath, payload, hash);
} else {
Console.WriteLine ("ERROR: Crash reporting version mismatch");
return;
}
// sent to url via post?
// Console.WriteLine (sentry_message);
SendMessage (sentry_message, url);
}
public Uploader (CodeCollection assemblies)
{
this.codebase = assemblies;
}
static string[] GetAssemblies (string fileRoot) {
var dlls = Directory.GetFiles (fileRoot, "*.dll", SearchOption.AllDirectories);
var exes = Directory.GetFiles (fileRoot, "*.exe", SearchOption.AllDirectories);
return dlls.Concat (exes).ToArray ();
}
static string[] GetFiles (string fileRoot) {
return Directory.GetFiles (fileRoot, "mono_crash.*.json", SearchOption.AllDirectories);
}
public static void Main (string[] args)
{
var url = System.Environment.GetEnvironmentVariable ("MONO_SENTRY_URL");
if (url == null) {
Console.WriteLine ("MONO_SENTRY_URL missing");
return;
}
var fileRoot = System.Environment.GetEnvironmentVariable ("MONO_SENTRY_ROOT");
if (fileRoot == null) {
Console.WriteLine ("MONO_SENTRY_ROOT missing");
return;
}
var os_tag = System.Environment.GetEnvironmentVariable ("MONO_SENTRY_OS");
if (os_tag == null) {
Console.WriteLine ("MONO_SENTRY_OS missing");
return;
}
var dsn = new Dsn(url);
var files = GetFiles (fileRoot);
if (files.Length == 0)
return;
// Find all of the assemblies in tree that could have made the crash dump
var assemblies = GetAssemblies (fileRoot);
var codebase = new CodeCollection (assemblies);
foreach (var file in files) {
Console.WriteLine ($"Processing {file} ...");
var state = new Uploader (codebase);
state.Upload (file, os_tag, dsn);
}
}
}
}