зеркало из https://github.com/mozilla/gecko-dev.git
782 строки
23 KiB
C++
782 строки
23 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ErrorHandler.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/CmdLineAndEnvUtils.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/JSONWriter.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/WinTokenUtils.h"
|
|
#include "mozilla/WindowsVersion.h"
|
|
#include "mozilla/XREAppData.h"
|
|
#include "mozilla/glue/WindowsDllServices.h"
|
|
#include "mozilla/mscom/ProcessRuntime.h"
|
|
#include "nsWindowsHelpers.h"
|
|
|
|
#if defined(MOZ_LAUNCHER_PROCESS)
|
|
# include "mozilla/LauncherRegistryInfo.h"
|
|
#endif // defined(MOZ_LAUNCHER_PROCESS)
|
|
|
|
#include <algorithm>
|
|
#include <process.h>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <objbase.h>
|
|
#include <rpc.h>
|
|
#if !defined(__MINGW32__)
|
|
# include <comutil.h>
|
|
# include <iwscapi.h>
|
|
# include <wscapi.h>
|
|
#endif // !defined(__MINGW32__)
|
|
#include <tlhelp32.h>
|
|
|
|
#if !defined(RRF_SUBKEY_WOW6464KEY)
|
|
# define RRF_SUBKEY_WOW6464KEY 0x00010000
|
|
#endif // !defined(RRF_SUBKEY_WOW6464KEY)
|
|
|
|
#define QUOTE_ME2(x) #x
|
|
#define QUOTE_ME(x) QUOTE_ME2(x)
|
|
|
|
#define TELEMETRY_BASE_URL L"https://incoming.telemetry.mozilla.org/submit"
|
|
#define TELEMETRY_NAMESPACE L"/firefox-launcher-process"
|
|
#define TELEMETRY_LAUNCHER_PING_DOCTYPE L"/launcher-process-failure"
|
|
#define TELEMETRY_LAUNCHER_PING_VERSION L"/1"
|
|
|
|
static const wchar_t kUrl[] = TELEMETRY_BASE_URL TELEMETRY_NAMESPACE
|
|
TELEMETRY_LAUNCHER_PING_DOCTYPE TELEMETRY_LAUNCHER_PING_VERSION L"/";
|
|
static const uint32_t kGuidCharLenWithNul = 39;
|
|
static const uint32_t kGuidCharLenNoBracesNoNul = 36;
|
|
static const mozilla::StaticXREAppData* gAppData;
|
|
static bool gForceEventLog = false;
|
|
|
|
namespace {
|
|
|
|
struct EventSourceDeleter {
|
|
using pointer = HANDLE;
|
|
|
|
void operator()(pointer aEvtSrc) { ::DeregisterEventSource(aEvtSrc); }
|
|
};
|
|
|
|
using EventLog = mozilla::UniquePtr<HANDLE, EventSourceDeleter>;
|
|
|
|
struct SerializedEventData {
|
|
HRESULT mHr;
|
|
uint32_t mLine;
|
|
char mFile[1];
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
static void PostErrorToLog(const mozilla::LauncherError& aError) {
|
|
// This is very bare-bones; just enough to spit out an HRESULT to the
|
|
// Application event log.
|
|
EventLog log(::RegisterEventSourceW(nullptr, L"Firefox"));
|
|
if (!log) {
|
|
return;
|
|
}
|
|
|
|
size_t fileLen = strlen(aError.mFile);
|
|
size_t dataLen = sizeof(HRESULT) + sizeof(uint32_t) + fileLen;
|
|
auto evtDataBuf = mozilla::MakeUnique<char[]>(dataLen);
|
|
SerializedEventData& evtData =
|
|
*reinterpret_cast<SerializedEventData*>(evtDataBuf.get());
|
|
evtData.mHr = aError.mError.AsHResult();
|
|
evtData.mLine = aError.mLine;
|
|
// Since this is binary data, we're not concerning ourselves with null
|
|
// terminators.
|
|
memcpy(evtData.mFile, aError.mFile, fileLen);
|
|
|
|
::ReportEventW(log.get(), EVENTLOG_ERROR_TYPE, 0, aError.mError.AsHResult(),
|
|
nullptr, 0, dataLen, nullptr, evtDataBuf.get());
|
|
}
|
|
|
|
#if defined(MOZ_TELEMETRY_REPORTING)
|
|
|
|
namespace {
|
|
|
|
// This JSONWriteFunc writes directly to a temp file. By creating this file
|
|
// with the FILE_ATTRIBUTE_TEMPORARY attribute, we hint to the OS that this
|
|
// file is short-lived. The OS will try to avoid flushing it to disk if at
|
|
// all possible.
|
|
class TempFileWriter final : public mozilla::JSONWriteFunc {
|
|
public:
|
|
TempFileWriter() : mFailed(false), mSuccessfulHandoff(false) {
|
|
wchar_t name[MAX_PATH + 1] = {};
|
|
if (_wtmpnam_s(name)) {
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
|
|
mTempFileName = name;
|
|
|
|
mTempFile.own(::CreateFileW(name, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
|
|
CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr));
|
|
if (mTempFile.get() == INVALID_HANDLE_VALUE) {
|
|
mFailed = true;
|
|
}
|
|
}
|
|
|
|
~TempFileWriter() {
|
|
if (mSuccessfulHandoff) {
|
|
// It is no longer our responsibility to delete the temp file if we have
|
|
// successfully handed it off to pingsender.
|
|
return;
|
|
}
|
|
|
|
mTempFile.reset();
|
|
::DeleteFileW(mTempFileName.c_str());
|
|
}
|
|
|
|
explicit operator bool() const { return !mFailed; }
|
|
|
|
void Write(const mozilla::Span<const char>& aStr) override {
|
|
if (mFailed) {
|
|
return;
|
|
}
|
|
|
|
DWORD bytesWritten = 0;
|
|
if (!::WriteFile(mTempFile, aStr.data(), aStr.size(), &bytesWritten,
|
|
nullptr) ||
|
|
bytesWritten != aStr.size()) {
|
|
mFailed = true;
|
|
}
|
|
}
|
|
|
|
const std::wstring& GetFileName() const { return mTempFileName; }
|
|
|
|
void SetSuccessfulHandoff() { mSuccessfulHandoff = true; }
|
|
|
|
private:
|
|
bool mFailed;
|
|
bool mSuccessfulHandoff;
|
|
std::wstring mTempFileName;
|
|
nsAutoHandle mTempFile;
|
|
};
|
|
|
|
using SigMap = mozilla::Vector<std::wstring, 0, InfallibleAllocPolicy>;
|
|
|
|
} // anonymous namespace
|
|
|
|
// This is the guideline for maximum string length for telemetry intake
|
|
static const size_t kMaxStrLen = 80;
|
|
|
|
static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
|
|
const size_t aStrLenExclNul) {
|
|
// Yes, this might not handle surrogate pairs correctly. Let's just let
|
|
// WideCharToMultiByte fail in that unlikely case.
|
|
size_t cvtLen = std::min(aStrLenExclNul, kMaxStrLen);
|
|
|
|
int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, nullptr, 0,
|
|
nullptr, nullptr);
|
|
if (!numConv) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Include room for the null terminator by adding one
|
|
auto buf = mozilla::MakeUnique<char[]>(numConv + 1);
|
|
|
|
numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, buf.get(), numConv,
|
|
nullptr, nullptr);
|
|
if (!numConv) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Add null termination. numConv does not include the terminator, so we don't
|
|
// subtract 1 when indexing into buf.
|
|
buf[numConv] = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
|
|
return WideToUTF8(aStr, wcslen(aStr));
|
|
}
|
|
|
|
static mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
|
|
return WideToUTF8(aStr.c_str(), aStr.length());
|
|
}
|
|
|
|
// MinGW does not support the Windows Security Center APIs.
|
|
# if !defined(__MINGW32__)
|
|
|
|
static mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
|
|
return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct ProviderKey {
|
|
WSC_SECURITY_PROVIDER mProviderType;
|
|
const char* mKey;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
static bool EnumWSCProductList(RefPtr<IWSCProductList>& aProdList,
|
|
mozilla::JSONWriter& aJson) {
|
|
LONG count;
|
|
HRESULT hr = aProdList->get_Count(&count);
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
// Unlikely, but put a bound on the max length of the output array for the
|
|
// purposes of telemetry intake.
|
|
count = std::min(count, 1000L);
|
|
|
|
// Record the name(s) of each active registered product in this category
|
|
for (LONG index = 0; index < count; ++index) {
|
|
RefPtr<IWscProduct> product;
|
|
hr = aProdList->get_Item(index, getter_AddRefs(product));
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
WSC_SECURITY_PRODUCT_STATE state;
|
|
hr = product->get_ProductState(&state);
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
// We only care about products that are active
|
|
if (state == WSC_SECURITY_PRODUCT_STATE_OFF ||
|
|
state == WSC_SECURITY_PRODUCT_STATE_SNOOZED ||
|
|
state == WSC_SECURITY_PRODUCT_STATE_EXPIRED) {
|
|
continue;
|
|
}
|
|
|
|
_bstr_t bName;
|
|
hr = product->get_ProductName(bName.GetAddress());
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
auto buf = WideToUTF8(bName);
|
|
if (!buf) {
|
|
return false;
|
|
}
|
|
|
|
aJson.StringElement(mozilla::MakeStringSpan(buf.get()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static const ProviderKey gProvKeys[] = {
|
|
{WSC_SECURITY_PROVIDER_ANTIVIRUS, "av"},
|
|
{WSC_SECURITY_PROVIDER_ANTISPYWARE, "antispyware"},
|
|
{WSC_SECURITY_PROVIDER_FIREWALL, "firewall"}};
|
|
|
|
static bool AddWscInfo(mozilla::JSONWriter& aJson) {
|
|
if (!mozilla::IsWin8OrLater()) {
|
|
// We haven't written anything yet, so we can return true here and continue
|
|
// capturing data.
|
|
return true;
|
|
}
|
|
|
|
// We need COM for this. Using ProcessRuntime so that process-global COM
|
|
// configuration is done correctly
|
|
mozilla::mscom::ProcessRuntime mscom(
|
|
mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
|
|
if (!mscom) {
|
|
// We haven't written anything yet, so we can return true here and continue
|
|
// capturing data.
|
|
return true;
|
|
}
|
|
|
|
aJson.StartObjectProperty("security");
|
|
|
|
const CLSID clsid = __uuidof(WSCProductList);
|
|
const IID iid = __uuidof(IWSCProductList);
|
|
|
|
for (uint32_t index = 0; index < mozilla::ArrayLength(gProvKeys); ++index) {
|
|
// NB: A separate instance of IWSCProductList is needed for each distinct
|
|
// security provider type; MSDN says that we cannot reuse the same object
|
|
// and call Initialize() to pave over the previous data.
|
|
RefPtr<IWSCProductList> prodList;
|
|
HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid,
|
|
getter_AddRefs(prodList));
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
hr = prodList->Initialize(gProvKeys[index].mProviderType);
|
|
if (FAILED(hr)) {
|
|
return false;
|
|
}
|
|
|
|
aJson.StartArrayProperty(mozilla::MakeStringSpan(gProvKeys[index].mKey));
|
|
|
|
if (!EnumWSCProductList(prodList, aJson)) {
|
|
return false;
|
|
}
|
|
|
|
aJson.EndArray();
|
|
}
|
|
|
|
aJson.EndObject();
|
|
|
|
return true;
|
|
}
|
|
# endif // !defined(__MINGW32__)
|
|
|
|
// Max array length for telemetry intake.
|
|
static const size_t kMaxArrayLen = 1000;
|
|
|
|
static bool AddModuleInfo(const nsAutoHandle& aSnapshot,
|
|
mozilla::JSONWriter& aJson) {
|
|
if (aSnapshot.get() == INVALID_HANDLE_VALUE) {
|
|
// We haven't written anything yet, so we can return true here and continue
|
|
// capturing data.
|
|
return true;
|
|
}
|
|
|
|
SigMap signatures;
|
|
size_t moduleCount = 0;
|
|
|
|
MODULEENTRY32W module = {sizeof(module)};
|
|
if (!::Module32FirstW(aSnapshot, &module)) {
|
|
// We haven't written anything yet, so we can return true here and continue
|
|
// capturing data.
|
|
return true;
|
|
}
|
|
|
|
mozilla::glue::BasicDllServices dllServices;
|
|
|
|
aJson.StartObjectProperty("modules");
|
|
|
|
// For each module, add its version number (or empty string if not present),
|
|
// followed by an optional index into the signatures array
|
|
do {
|
|
++moduleCount;
|
|
|
|
wchar_t leaf[_MAX_FNAME] = {};
|
|
if (::_wsplitpath_s(module.szExePath, nullptr, 0, nullptr, 0, leaf,
|
|
mozilla::ArrayLength(leaf), nullptr, 0)) {
|
|
return false;
|
|
}
|
|
|
|
if (_wcslwr_s(leaf, mozilla::ArrayLength(leaf))) {
|
|
return false;
|
|
}
|
|
|
|
auto leafUtf8 = WideToUTF8(leaf);
|
|
if (!leafUtf8) {
|
|
return false;
|
|
}
|
|
|
|
aJson.StartArrayProperty(mozilla::MakeStringSpan(leafUtf8.get()));
|
|
|
|
std::string version;
|
|
DWORD verInfoSize = ::GetFileVersionInfoSizeW(module.szExePath, nullptr);
|
|
if (verInfoSize) {
|
|
auto verInfoBuf = mozilla::MakeUnique<BYTE[]>(verInfoSize);
|
|
|
|
if (::GetFileVersionInfoW(module.szExePath, 0, verInfoSize,
|
|
verInfoBuf.get())) {
|
|
VS_FIXEDFILEINFO* fixedInfo = nullptr;
|
|
UINT fixedInfoLen = 0;
|
|
|
|
if (::VerQueryValueW(verInfoBuf.get(), L"\\",
|
|
reinterpret_cast<LPVOID*>(&fixedInfo),
|
|
&fixedInfoLen)) {
|
|
std::ostringstream oss;
|
|
oss << HIWORD(fixedInfo->dwFileVersionMS) << '.'
|
|
<< LOWORD(fixedInfo->dwFileVersionMS) << '.'
|
|
<< HIWORD(fixedInfo->dwFileVersionLS) << '.'
|
|
<< LOWORD(fixedInfo->dwFileVersionLS);
|
|
version = oss.str();
|
|
}
|
|
}
|
|
}
|
|
|
|
aJson.StringElement(version);
|
|
|
|
mozilla::Maybe<ptrdiff_t> sigIndex;
|
|
auto signedBy = dllServices.GetBinaryOrgName(module.szExePath);
|
|
if (signedBy) {
|
|
std::wstring strSignedBy(signedBy.get());
|
|
auto entry = std::find(signatures.begin(), signatures.end(), strSignedBy);
|
|
if (entry == signatures.end()) {
|
|
mozilla::Unused << signatures.append(std::move(strSignedBy));
|
|
entry = &signatures.back();
|
|
}
|
|
|
|
sigIndex = mozilla::Some(entry - signatures.begin());
|
|
}
|
|
|
|
if (sigIndex) {
|
|
aJson.IntElement(sigIndex.value());
|
|
}
|
|
|
|
aJson.EndArray();
|
|
} while (moduleCount < kMaxArrayLen && ::Module32NextW(aSnapshot, &module));
|
|
|
|
aJson.EndObject();
|
|
|
|
aJson.StartArrayProperty("signatures");
|
|
|
|
// Serialize each entry in the signatures array
|
|
for (auto&& itr : signatures) {
|
|
auto sigUtf8 = WideToUTF8(itr);
|
|
if (!sigUtf8) {
|
|
continue;
|
|
}
|
|
|
|
aJson.StringElement(mozilla::MakeStringSpan(sigUtf8.get()));
|
|
}
|
|
|
|
aJson.EndArray();
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct PingThreadContext {
|
|
explicit PingThreadContext(const mozilla::LauncherError& aError,
|
|
const char* aProcessType)
|
|
: mLauncherError(aError),
|
|
mModulesSnapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)),
|
|
mProcessType(aProcessType ? aProcessType : "") {}
|
|
mozilla::LauncherError mLauncherError;
|
|
nsAutoHandle mModulesSnapshot;
|
|
std::string mProcessType;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
static bool PrepPing(const PingThreadContext& aContext, const std::wstring& aId,
|
|
mozilla::JSONWriter& aJson) {
|
|
# if defined(DEBUG)
|
|
const mozilla::JSONWriter::CollectionStyle style =
|
|
mozilla::JSONWriter::MultiLineStyle;
|
|
# else
|
|
const mozilla::JSONWriter::CollectionStyle style =
|
|
mozilla::JSONWriter::SingleLineStyle;
|
|
# endif // defined(DEBUG)
|
|
|
|
aJson.Start(style);
|
|
|
|
aJson.StringProperty("type", "launcher-process-failure");
|
|
aJson.IntProperty("version", 1);
|
|
|
|
auto idUtf8 = WideToUTF8(aId);
|
|
if (idUtf8) {
|
|
aJson.StringProperty("id", mozilla::MakeStringSpan(idUtf8.get()));
|
|
}
|
|
|
|
time_t now;
|
|
time(&now);
|
|
tm gmTm;
|
|
if (!gmtime_s(&gmTm, &now)) {
|
|
char isoTimeBuf[32] = {};
|
|
if (strftime(isoTimeBuf, mozilla::ArrayLength(isoTimeBuf), "%FT%T.000Z",
|
|
&gmTm)) {
|
|
aJson.StringProperty("creationDate", isoTimeBuf);
|
|
}
|
|
}
|
|
|
|
aJson.StringProperty("update_channel", QUOTE_ME(MOZ_UPDATE_CHANNEL));
|
|
|
|
if (gAppData) {
|
|
aJson.StringProperty("build_id",
|
|
mozilla::MakeStringSpan(gAppData->buildID));
|
|
aJson.StringProperty("build_version",
|
|
mozilla::MakeStringSpan(gAppData->version));
|
|
}
|
|
|
|
OSVERSIONINFOEXW osv = {sizeof(osv)};
|
|
if (::GetVersionExW(reinterpret_cast<OSVERSIONINFOW*>(&osv))) {
|
|
std::ostringstream oss;
|
|
oss << osv.dwMajorVersion << "." << osv.dwMinorVersion << "."
|
|
<< osv.dwBuildNumber;
|
|
|
|
if (osv.dwMajorVersion == 10 && osv.dwMinorVersion == 0) {
|
|
// Get the "Update Build Revision" (UBR) value
|
|
DWORD ubrValue;
|
|
DWORD ubrValueLen = sizeof(ubrValue);
|
|
LSTATUS ubrOk =
|
|
::RegGetValueW(HKEY_LOCAL_MACHINE,
|
|
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
|
L"UBR", RRF_RT_DWORD | RRF_SUBKEY_WOW6464KEY, nullptr,
|
|
&ubrValue, &ubrValueLen);
|
|
if (ubrOk == ERROR_SUCCESS) {
|
|
oss << "." << ubrValue;
|
|
}
|
|
}
|
|
|
|
if (oss) {
|
|
aJson.StringProperty("os_version", oss.str());
|
|
}
|
|
|
|
bool isServer = osv.wProductType == VER_NT_DOMAIN_CONTROLLER ||
|
|
osv.wProductType == VER_NT_SERVER;
|
|
aJson.BoolProperty("server_os", isServer);
|
|
}
|
|
|
|
WCHAR localeName[LOCALE_NAME_MAX_LENGTH] = {};
|
|
int localeNameLen =
|
|
::GetUserDefaultLocaleName(localeName, mozilla::ArrayLength(localeName));
|
|
if (localeNameLen) {
|
|
auto localeNameUtf8 = WideToUTF8(localeName, localeNameLen - 1);
|
|
if (localeNameUtf8) {
|
|
aJson.StringProperty("os_locale",
|
|
mozilla::MakeStringSpan(localeNameUtf8.get()));
|
|
}
|
|
}
|
|
|
|
SYSTEM_INFO sysInfo;
|
|
::GetNativeSystemInfo(&sysInfo);
|
|
aJson.IntProperty("cpu_arch", sysInfo.wProcessorArchitecture);
|
|
aJson.IntProperty("num_logical_cpus", sysInfo.dwNumberOfProcessors);
|
|
|
|
mozilla::LauncherResult<bool> isAdminWithoutUac =
|
|
mozilla::IsAdminWithoutUac();
|
|
if (isAdminWithoutUac.isOk()) {
|
|
aJson.BoolProperty("is_admin_without_uac", isAdminWithoutUac.unwrap());
|
|
}
|
|
|
|
if (!aContext.mProcessType.empty()) {
|
|
aJson.StringProperty("process_type", aContext.mProcessType);
|
|
}
|
|
|
|
MEMORYSTATUSEX memStatus = {sizeof(memStatus)};
|
|
if (::GlobalMemoryStatusEx(&memStatus)) {
|
|
aJson.StartObjectProperty("memory");
|
|
aJson.IntProperty("total_phys", memStatus.ullTotalPhys);
|
|
aJson.IntProperty("avail_phys", memStatus.ullAvailPhys);
|
|
aJson.IntProperty("avail_page_file", memStatus.ullAvailPageFile);
|
|
aJson.IntProperty("avail_virt", memStatus.ullAvailVirtual);
|
|
aJson.EndObject();
|
|
}
|
|
|
|
aJson.StringProperty("xpcom_abi", TARGET_XPCOM_ABI);
|
|
|
|
aJson.StartObjectProperty("launcher_error", style);
|
|
|
|
std::string srcFileLeaf(aContext.mLauncherError.mFile);
|
|
// Obtain the leaf name of the file for privacy reasons
|
|
// (In case this is somebody's local build)
|
|
auto pos = srcFileLeaf.find_last_of("/\\");
|
|
if (pos != std::string::npos) {
|
|
srcFileLeaf = srcFileLeaf.substr(pos + 1);
|
|
}
|
|
|
|
aJson.StringProperty("source_file", srcFileLeaf);
|
|
|
|
aJson.IntProperty("source_line", aContext.mLauncherError.mLine);
|
|
aJson.IntProperty("hresult", aContext.mLauncherError.mError.AsHResult());
|
|
|
|
# if defined(NIGHTLY_BUILD)
|
|
if (aContext.mLauncherError.mDetourError.isSome()) {
|
|
static const char* kHexMap = "0123456789abcdef";
|
|
char hexStr[sizeof(mozilla::DetourError::mOrigBytes) * 2 + 1];
|
|
int cnt = 0;
|
|
for (uint8_t byte : aContext.mLauncherError.mDetourError->mOrigBytes) {
|
|
hexStr[cnt++] = kHexMap[(byte >> 4) & 0x0f];
|
|
hexStr[cnt++] = kHexMap[byte & 0x0f];
|
|
}
|
|
hexStr[cnt] = 0;
|
|
aJson.StringProperty("detour_orig_bytes", hexStr);
|
|
}
|
|
# endif // defined(NIGHTLY_BUILD)
|
|
|
|
aJson.EndObject();
|
|
|
|
# if !defined(__MINGW32__)
|
|
if (!AddWscInfo(aJson)) {
|
|
return false;
|
|
}
|
|
# endif // !defined(__MINGW32__)
|
|
|
|
if (!AddModuleInfo(aContext.mModulesSnapshot, aJson)) {
|
|
return false;
|
|
}
|
|
|
|
aJson.End();
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool DoSendPing(const PingThreadContext& aContext) {
|
|
auto writeFunc = mozilla::MakeUnique<TempFileWriter>();
|
|
if (!(*writeFunc)) {
|
|
return false;
|
|
}
|
|
|
|
mozilla::JSONWriter json(std::move(writeFunc));
|
|
|
|
UUID uuid;
|
|
if (::UuidCreate(&uuid) != RPC_S_OK) {
|
|
return false;
|
|
}
|
|
|
|
wchar_t guidBuf[kGuidCharLenWithNul] = {};
|
|
if (::StringFromGUID2(uuid, guidBuf, kGuidCharLenWithNul) !=
|
|
kGuidCharLenWithNul) {
|
|
return false;
|
|
}
|
|
|
|
// Strip the curly braces off of the guid
|
|
std::wstring guidNoBraces(guidBuf + 1, kGuidCharLenNoBracesNoNul);
|
|
|
|
// Populate json with the ping information
|
|
if (!PrepPing(aContext, guidNoBraces, json)) {
|
|
return false;
|
|
}
|
|
|
|
// Obtain the name of the temp file that we have written
|
|
TempFileWriter& tempFile = *static_cast<TempFileWriter*>(json.WriteFunc());
|
|
if (!tempFile) {
|
|
return false;
|
|
}
|
|
|
|
const std::wstring& fileName = tempFile.GetFileName();
|
|
|
|
// Using the path to our executable binary, construct the path to
|
|
// pingsender.exe
|
|
mozilla::UniquePtr<wchar_t[]> exePath(mozilla::GetFullBinaryPath());
|
|
|
|
wchar_t drive[_MAX_DRIVE] = {};
|
|
wchar_t dir[_MAX_DIR] = {};
|
|
if (_wsplitpath_s(exePath.get(), drive, mozilla::ArrayLength(drive), dir,
|
|
mozilla::ArrayLength(dir), nullptr, 0, nullptr, 0)) {
|
|
return false;
|
|
}
|
|
|
|
wchar_t pingSenderPath[MAX_PATH + 1] = {};
|
|
if (_wmakepath_s(pingSenderPath, mozilla::ArrayLength(pingSenderPath), drive,
|
|
dir, L"pingsender", L"exe")) {
|
|
return false;
|
|
}
|
|
|
|
// Construct the telemetry URL
|
|
wchar_t urlBuf[mozilla::ArrayLength(kUrl) + kGuidCharLenNoBracesNoNul] = {};
|
|
if (wcscpy_s(urlBuf, kUrl)) {
|
|
return false;
|
|
}
|
|
|
|
if (wcscat_s(urlBuf, guidNoBraces.c_str())) {
|
|
return false;
|
|
}
|
|
|
|
// Now build the command line arguments to pingsender
|
|
wchar_t* pingSenderArgv[] = {pingSenderPath, urlBuf,
|
|
const_cast<wchar_t*>(fileName.c_str())};
|
|
|
|
mozilla::UniquePtr<wchar_t[]> pingSenderCmdLine(mozilla::MakeCommandLine(
|
|
mozilla::ArrayLength(pingSenderArgv), pingSenderArgv));
|
|
|
|
// Now start pingsender to handle the rest
|
|
PROCESS_INFORMATION pi;
|
|
|
|
STARTUPINFOW si = {sizeof(si)};
|
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
|
si.wShowWindow = SW_HIDE;
|
|
|
|
if (!::CreateProcessW(pingSenderPath, pingSenderCmdLine.get(), nullptr,
|
|
nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
|
|
return false;
|
|
}
|
|
|
|
tempFile.SetSuccessfulHandoff();
|
|
|
|
nsAutoHandle proc(pi.hProcess);
|
|
nsAutoHandle thread(pi.hThread);
|
|
|
|
return true;
|
|
}
|
|
|
|
static unsigned __stdcall SendPingThread(void* aContext) {
|
|
mozilla::UniquePtr<PingThreadContext> context(
|
|
reinterpret_cast<PingThreadContext*>(aContext));
|
|
|
|
if (!DoSendPing(*context) || gForceEventLog) {
|
|
PostErrorToLog(context->mLauncherError);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif // defined(MOZ_TELEMETRY_REPORTING)
|
|
|
|
static bool SendPing(const mozilla::LauncherError& aError,
|
|
const char* aProcessType) {
|
|
#if defined(MOZ_TELEMETRY_REPORTING)
|
|
# if defined(MOZ_LAUNCHER_PROCESS)
|
|
mozilla::LauncherRegistryInfo regInfo;
|
|
mozilla::LauncherResult<bool> telemetryEnabled = regInfo.IsTelemetryEnabled();
|
|
if (telemetryEnabled.isErr() || !telemetryEnabled.unwrap()) {
|
|
// Do not send anything if telemetry has been opted out
|
|
return false;
|
|
}
|
|
# endif // defined(MOZ_LAUNCHER_PROCESS)
|
|
|
|
// We send this ping when the launcher process fails. After we start the
|
|
// SendPingThread, this thread falls back from running as the launcher process
|
|
// to running as the browser main thread. Once this happens, it will be unsafe
|
|
// to set up PoisonIOInterposer (since we have already spun up a background
|
|
// thread).
|
|
mozilla::SaveToEnv("MOZ_DISABLE_POISON_IO_INTERPOSER=1");
|
|
|
|
// Capture aError and our module list into context for processing on another
|
|
// thread.
|
|
auto thdParam = mozilla::MakeUnique<PingThreadContext>(aError, aProcessType);
|
|
|
|
// The ping does a lot of file I/O. Since we want this thread to continue
|
|
// executing browser startup, we should gather that information on a
|
|
// background thread.
|
|
uintptr_t thdHandle =
|
|
_beginthreadex(nullptr, 0, &SendPingThread, thdParam.get(),
|
|
STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
|
|
if (!thdHandle) {
|
|
return false;
|
|
}
|
|
|
|
// We have handed off thdParam to the background thread
|
|
mozilla::Unused << thdParam.release();
|
|
|
|
::CloseHandle(reinterpret_cast<HANDLE>(thdHandle));
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
void HandleLauncherError(const LauncherError& aError,
|
|
const char* aProcessType) {
|
|
#if defined(MOZ_LAUNCHER_PROCESS)
|
|
LauncherRegistryInfo regInfo;
|
|
Unused << regInfo.DisableDueToFailure();
|
|
#endif // defined(MOZ_LAUNCHER_PROCESS)
|
|
|
|
if (SendPing(aError, aProcessType) && !gForceEventLog) {
|
|
return;
|
|
}
|
|
|
|
PostErrorToLog(aError);
|
|
}
|
|
|
|
void SetLauncherErrorAppData(const StaticXREAppData& aAppData) {
|
|
gAppData = &aAppData;
|
|
}
|
|
|
|
void SetLauncherErrorForceEventLog() { gForceEventLog = true; }
|
|
|
|
} // namespace mozilla
|