fune/toolkit/crashreporter/client/ping.cpp
Gabriele Svelto 15adf94f4d Bug 1348273 - Convert crash annotations into a machine-readable list of constants; r=ted.mielczarek,njn,dholbert,mak,cpearce,mcmanus,froydnj,Dexter,jrmuizel,jchen,jimm,bz,surkov
This introduces the machinery needed to generate crash annotations from a YAML
file. The relevant C++ functions are updated to take a typed enum. JavaScript
calls are unaffected but they will throw if the string argument does not
correspond to one of the known entries in the C++ enum. The existing whitelists
and blacklists of annotations are also generated from the YAML file and all
duplicate code related to them has been consolidated. Once written out to the
.extra file the annotations are converted in string form and are no different
than the existing ones.

All existing annotations have been included in the list (and some obsolete ones
have been removed) and all call sites have been updated including tests where
appropriate.

--HG--
extra : source : 4f6c43f2830701ec5552e08e3f1b06fe6d045860
2018-07-05 15:42:11 +02:00

339 lines
9.8 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "crashreporter.h"
#include <cstring>
#include <string>
#if defined(XP_LINUX)
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#elif defined(XP_MACOSX)
#include <CoreFoundation/CoreFoundation.h>
#elif defined(XP_WIN)
#include <objbase.h>
#endif
#include "json/json.h"
#include "CrashAnnotations.h"
using std::string;
namespace CrashReporter {
struct UUID {
uint32_t m0;
uint16_t m1;
uint16_t m2;
uint8_t m3[8];
};
// Generates an UUID; the code here is mostly copied from nsUUIDGenerator.cpp
static string
GenerateUUID()
{
UUID id = {};
#if defined(XP_WIN) // Windows
HRESULT hr = CoCreateGuid((GUID*)&id);
if (FAILED(hr)) {
return "";
}
#elif defined(XP_MACOSX) // MacOS X
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
if (!uuid) {
return "";
}
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
memcpy(&id, &bytes, sizeof(UUID));
CFRelease(uuid);
#elif defined(HAVE_ARC4RANDOM_BUF) // Android, BSD, ...
arc4random_buf(id, sizeof(UUID));
#else // Linux
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
return "";
}
if (read(fd, &id, sizeof(UUID)) != sizeof(UUID)) {
close(fd);
return "";
}
close(fd);
#endif
/* Put in the version */
id.m2 &= 0x0fff;
id.m2 |= 0x4000;
/* Put in the variant */
id.m3[0] &= 0x3f;
id.m3[0] |= 0x80;
const char* kUUIDFormatString =
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
const size_t kUUIDFormatStringLength = 36;
char str[kUUIDFormatStringLength + 1] = { '\0' };
int num = snprintf(str, kUUIDFormatStringLength + 1, kUUIDFormatString,
id.m0, id.m1, id.m2, id.m3[0], id.m3[1], id.m3[2],
id.m3[3], id.m3[4], id.m3[5], id.m3[6], id.m3[7]);
if (num != kUUIDFormatStringLength) {
return "";
}
return str;
}
const char kISO8601Date[] = "%F";
const char kISO8601DateHours[] = "%FT%H:00:00.000Z";
// Return the current date as a string in the specified format, the following
// constants are provided:
// - kISO8601Date, the ISO 8601 date format, YYYY-MM-DD
// - kISO8601DateHours, the ISO 8601 full date format, YYYY-MM-DDTHH:00:00.000Z
static string
CurrentDate(string format)
{
time_t now;
time(&now);
char buf[64]; // This should be plenty
strftime(buf, sizeof buf, format.c_str(), gmtime(&now));
return buf;
}
const char kTelemetryClientId[] = "TelemetryClientId";
const char kTelemetryUrl[] = "TelemetryServerURL";
const char kTelemetrySessionId[] = "TelemetrySessionId";
const int kTelemetryVersion = 4;
// Create the payload.metadata node of the crash ping using fields extracted
// from the .extra file
static Json::Value
CreateMetadataNode(StringTable& strings)
{
Json::Value node;
for (auto line : strings) {
Annotation annotation;
if (AnnotationFromString(annotation, line.first.c_str())) {
if (IsAnnotationWhitelistedForPing(annotation)) {
node[line.first] = line.second;
}
}
}
return node;
}
// Create the payload node of the crash ping
static Json::Value
CreatePayloadNode(StringTable& strings, const string& aHash,
const string& aSessionId)
{
Json::Value payload;
payload["sessionId"] = aSessionId;
payload["version"] = 1;
payload["crashDate"] = CurrentDate(kISO8601Date);
payload["crashTime"] = CurrentDate(kISO8601DateHours);
payload["hasCrashEnvironment"] = true;
payload["crashId"] = GetDumpLocalID();
payload["minidumpSha256Hash"] = aHash;
payload["processType"] = "main"; // This is always a main crash
// Parse the stack traces
Json::Value stackTracesValue;
Json::Reader reader;
if (reader.parse(strings["StackTraces"], stackTracesValue,
/* collectComments */ false)) {
payload["stackTraces"] = stackTracesValue;
}
// Assemble the payload metadata
payload["metadata"] = CreateMetadataNode(strings);
return payload;
}
// Create the application node of the crash ping
static Json::Value
CreateApplicationNode(const string& aVendor, const string& aName,
const string& aVersion, const string& aDisplayVersion,
const string& aPlatformVersion, const string& aChannel,
const string& aBuildId, const string& aArchitecture,
const string& aXpcomAbi)
{
Json::Value application;
application["vendor"] = aVendor;
application["name"] = aName;
application["buildId"] = aBuildId;
application["displayVersion"] = aDisplayVersion;
application["platformVersion"] = aPlatformVersion;
application["version"] = aVersion;
application["channel"] = aChannel;
if (!aArchitecture.empty()) {
application["architecture"] = aArchitecture;
}
if (!aXpcomAbi.empty()) {
application["xpcomAbi"] = aXpcomAbi;
}
return application;
}
// Create the root node of the crash ping
static Json::Value
CreateRootNode(StringTable& strings, const string& aUuid, const string& aHash,
const string& aClientId, const string& aSessionId,
const string& aName, const string& aVersion,
const string& aChannel, const string& aBuildId)
{
Json::Value root;
root["type"] = "crash"; // This is a crash ping
root["id"] = aUuid;
root["version"] = kTelemetryVersion;
root["creationDate"] = CurrentDate(kISO8601DateHours);
root["clientId"] = aClientId;
// Parse the telemetry environment
Json::Value environment;
Json::Reader reader;
string architecture;
string xpcomAbi;
string displayVersion;
string platformVersion;
if (reader.parse(strings["TelemetryEnvironment"], environment,
/* collectComments */ false)) {
if (environment.isMember("build") && environment["build"].isObject()) {
Json::Value build = environment["build"];
if (build.isMember("architecture") && build["architecture"].isString()) {
architecture = build["architecture"].asString();
}
if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
xpcomAbi = build["xpcomAbi"].asString();
}
if (build.isMember("displayVersion") && build["displayVersion"].isString()) {
displayVersion = build["displayVersion"].asString();
}
if (build.isMember("platformVersion") && build["platformVersion"].isString()) {
platformVersion = build["platformVersion"].asString();
}
}
root["environment"] = environment;
}
root["payload"] = CreatePayloadNode(strings, aHash, aSessionId);
root["application"] = CreateApplicationNode(strings["Vendor"], aName,
aVersion, displayVersion,
platformVersion,
aChannel, aBuildId,
architecture, xpcomAbi);
return root;
}
// Generates the URL used to submit the crash ping, see TelemetrySend.jsm
string
GenerateSubmissionUrl(const string& aUrl, const string& aId,
const string& aName, const string& aVersion,
const string& aChannel, const string& aBuildId)
{
return aUrl + "/submit/telemetry/" + aId + "/crash/" + aName + "/" +
aVersion + "/" + aChannel + "/" + aBuildId +
"?v=" + std::to_string(kTelemetryVersion);
}
// Write out the ping into the specified file.
//
// Returns true if the ping was written out successfully, false otherwise.
static bool
WritePing(const string& aPath, const string& aPing)
{
ofstream* f = UIOpenWrite(aPath.c_str());
bool success = false;
if (f->is_open()) {
*f << aPing;
f->close();
success = f->good();
}
delete f;
return success;
}
// Assembles the crash ping using the strings extracted from the .extra file
// and sends it using the crash sender. All the telemetry specific data but the
// environment will be stripped from the annotations so that it won't be sent
// together with the crash report.
//
// Note that the crash ping sender is invoked in a fire-and-forget way so this
// won't block waiting for the ping to be delivered.
//
// Returns true if the ping was assembled and handed over to the pingsender
// correctly, false otherwise and populates the aUUID field with the ping UUID.
bool
SendCrashPing(StringTable& strings, const string& aHash, string& pingUuid,
const string& pingDir)
{
string clientId = strings[kTelemetryClientId];
string serverUrl = strings[kTelemetryUrl];
string sessionId = strings[kTelemetrySessionId];
// Remove the telemetry-related data from the crash annotations
strings.erase(kTelemetryClientId);
strings.erase(kTelemetryUrl);
strings.erase(kTelemetrySessionId);
string buildId = strings["BuildID"];
string channel = strings["ReleaseChannel"];
string name = strings["ProductName"];
string version = strings["Version"];
string uuid = GenerateUUID();
string url = GenerateSubmissionUrl(serverUrl, uuid, name, version,
channel, buildId);
if (serverUrl.empty() || uuid.empty()) {
return false;
}
Json::Value root = CreateRootNode(strings, uuid, aHash, clientId, sessionId,
name, version, channel, buildId);
// Write out the result to the pending pings directory
Json::FastWriter writer;
string ping = writer.write(root);
string pingPath = pingDir + UI_DIR_SEPARATOR + uuid + ".json";
if (!WritePing(pingPath, ping)) {
return false;
}
// Hand over the ping to the sender
vector<string> args = { url, pingPath };
if (UIRunProgram(GetProgramPath(UI_PING_SENDER_FILENAME), args)) {
pingUuid = uuid;
return true;
} else {
return false;
}
}
} // namespace crashreporter