gecko-dev/toolkit/crashreporter/client/ping.cpp

356 строки
9.9 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"
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)
{
// The following list should be kept in sync with the one in CrashManager.jsm
const char *entries[] = {
"AsyncShutdownTimeout",
"AvailablePageFile",
"AvailablePhysicalMemory",
"AvailableVirtualMemory",
"BlockedDllList",
"BlocklistInitFailed",
"BuildID",
"ContainsMemoryReport",
"CrashTime",
"EventLoopNestingLevel",
"ipc_channel_error",
"IsGarbageCollecting",
"MozCrashReason",
"OOMAllocationSize",
"ProductID",
"ProductName",
"ReleaseChannel",
"RemoteType",
"SecondsSinceLastCrash",
"ShutdownProgress",
"StartupCrash",
"SystemMemoryUsePercentage",
"TextureUsage",
"TotalPageFile",
"TotalPhysicalMemory",
"TotalVirtualMemory",
"UptimeTS",
"User32BeforeBlocklist",
"Version",
};
Json::Value node;
for (auto entry : entries) {
if ((strings.find(entry) != strings.end()) && !strings[entry].empty()) {
node[entry] = strings[entry];
}
}
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& aChannel,
const string& aBuildId, const string& aArchitecture,
const string& aXpcomAbi)
{
Json::Value application;
application["vendor"] = aVendor;
application["name"] = aName;
application["buildId"] = aBuildId;
application["displayVersion"] = aVersion;
application["platformVersion"] = aVersion;
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;
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();
}
}
root["environment"] = environment;
}
root["payload"] = CreatePayloadNode(strings, aHash, aSessionId);
root["application"] = CreateApplicationNode(strings["Vendor"], aName,
aVersion, 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