michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 ci et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/PoisonIOInterposer.h" michael@0: #include "mozilla/ProcessedStack.h" michael@0: #include "mozilla/SHA1.h" michael@0: #include "mozilla/Scoped.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsStackWalk.h" michael@0: #include "plstr.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #define NS_T(str) L ## str michael@0: #define NS_SLASH "\\" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #else michael@0: #define NS_SLASH "/" michael@0: #endif michael@0: michael@0: #include "LateWriteChecks.h" michael@0: michael@0: #if !defined(XP_WIN) || (!defined(MOZ_OPTIMIZE) || defined(MOZ_PROFILING) || defined(DEBUG)) michael@0: #define OBSERVE_LATE_WRITES michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /*************************** Auxiliary Declarations ***************************/ michael@0: michael@0: // This a wrapper over a file descriptor that provides a Printf method and michael@0: // computes the sha1 of the data that passes through it. michael@0: class SHA1Stream michael@0: { michael@0: public: michael@0: explicit SHA1Stream(FILE *stream) michael@0: : mFile(stream) michael@0: { michael@0: MozillaRegisterDebugFILE(mFile); michael@0: } michael@0: michael@0: void Printf(const char *aFormat, ...) michael@0: { michael@0: MOZ_ASSERT(mFile); michael@0: va_list list; michael@0: va_start(list, aFormat); michael@0: nsAutoCString str; michael@0: str.AppendPrintf(aFormat, list); michael@0: va_end(list); michael@0: mSHA1.update(str.get(), str.Length()); michael@0: fwrite(str.get(), 1, str.Length(), mFile); michael@0: } michael@0: void Finish(SHA1Sum::Hash &aHash) michael@0: { michael@0: int fd = fileno(mFile); michael@0: fflush(mFile); michael@0: MozillaUnRegisterDebugFD(fd); michael@0: fclose(mFile); michael@0: mSHA1.finish(aHash); michael@0: mFile = nullptr; michael@0: } michael@0: private: michael@0: FILE *mFile; michael@0: SHA1Sum mSHA1; michael@0: }; michael@0: michael@0: static void RecordStackWalker(void *aPC, void *aSP, void *aClosure) michael@0: { michael@0: std::vector *stack = michael@0: static_cast*>(aClosure); michael@0: stack->push_back(reinterpret_cast(aPC)); michael@0: } michael@0: michael@0: /**************************** Late-Write Observer ****************************/ michael@0: michael@0: /** michael@0: * An implementation of IOInterposeObserver to be registered with IOInterposer. michael@0: * This observer logs all writes as late writes. michael@0: */ michael@0: class LateWriteObserver MOZ_FINAL : public IOInterposeObserver michael@0: { michael@0: public: michael@0: LateWriteObserver(const char* aProfileDirectory) michael@0: : mProfileDirectory(PL_strdup(aProfileDirectory)) michael@0: { michael@0: } michael@0: ~LateWriteObserver() { michael@0: PL_strfree(mProfileDirectory); michael@0: mProfileDirectory = nullptr; michael@0: } michael@0: michael@0: void Observe(IOInterposeObserver::Observation& aObservation); michael@0: private: michael@0: char* mProfileDirectory; michael@0: }; michael@0: michael@0: void LateWriteObserver::Observe(IOInterposeObserver::Observation& aOb) michael@0: { michael@0: #ifdef OBSERVE_LATE_WRITES michael@0: // Crash if that is the shutdown check mode michael@0: if (gShutdownChecks == SCM_CRASH) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: // If we have shutdown mode SCM_NOTHING or we can't record then abort michael@0: if (gShutdownChecks == SCM_NOTHING || !Telemetry::CanRecord()) { michael@0: return; michael@0: } michael@0: michael@0: // Write the stack and loaded libraries to a file. We can get here michael@0: // concurrently from many writes, so we use multiple temporary files. michael@0: std::vector rawStack; michael@0: michael@0: NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0, michael@0: reinterpret_cast(&rawStack), 0, nullptr); michael@0: Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack); michael@0: michael@0: nsPrintfCString nameAux("%s%s%s", mProfileDirectory, michael@0: NS_SLASH, "Telemetry.LateWriteTmpXXXXXX"); michael@0: char *name; michael@0: nameAux.GetMutableData(&name); michael@0: michael@0: // We want the sha1 of the entire file, so please don't write to fd michael@0: // directly; use sha1Stream. michael@0: FILE *stream; michael@0: #ifdef XP_WIN michael@0: HANDLE hFile; michael@0: do { michael@0: // mkstemp isn't supported so keep trying until we get a file michael@0: int result = _mktemp_s(name, strlen(name) + 1); michael@0: hFile = CreateFileA(name, GENERIC_WRITE, 0, nullptr, CREATE_NEW, michael@0: FILE_ATTRIBUTE_NORMAL, nullptr); michael@0: } while (GetLastError() == ERROR_FILE_EXISTS); michael@0: michael@0: if (hFile == INVALID_HANDLE_VALUE) { michael@0: NS_RUNTIMEABORT("Um, how did we get here?"); michael@0: } michael@0: michael@0: // http://support.microsoft.com/kb/139640 michael@0: int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); michael@0: if (fd == -1) { michael@0: NS_RUNTIMEABORT("Um, how did we get here?"); michael@0: } michael@0: michael@0: stream = _fdopen(fd, "w"); michael@0: #else michael@0: int fd = mkstemp(name); michael@0: stream = fdopen(fd, "w"); michael@0: #endif michael@0: michael@0: SHA1Stream sha1Stream(stream); michael@0: michael@0: size_t numModules = stack.GetNumModules(); michael@0: sha1Stream.Printf("%u\n", (unsigned)numModules); michael@0: for (size_t i = 0; i < numModules; ++i) { michael@0: Telemetry::ProcessedStack::Module module = stack.GetModule(i); michael@0: sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(), michael@0: module.mName.c_str()); michael@0: } michael@0: michael@0: size_t numFrames = stack.GetStackSize(); michael@0: sha1Stream.Printf("%u\n", (unsigned)numFrames); michael@0: for (size_t i = 0; i < numFrames; ++i) { michael@0: const Telemetry::ProcessedStack::Frame &frame = michael@0: stack.GetFrame(i); michael@0: // NOTE: We write the offsets, while the atos tool expects a value with michael@0: // the virtual address added. For example, running otool -l on the the firefox michael@0: // binary shows michael@0: // cmd LC_SEGMENT_64 michael@0: // cmdsize 632 michael@0: // segname __TEXT michael@0: // vmaddr 0x0000000100000000 michael@0: // so to print the line matching the offset 123 one has to run michael@0: // atos -o firefox 0x100000123. michael@0: sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset); michael@0: } michael@0: michael@0: SHA1Sum::Hash sha1; michael@0: sha1Stream.Finish(sha1); michael@0: michael@0: // Note: These files should be deleted by telemetry once it reads them. If michael@0: // there were no telemetry runs by the time we shut down, we just add files michael@0: // to the existing ones instead of replacing them. Given that each of these michael@0: // files is a bug to be fixed, that is probably the right thing to do. michael@0: michael@0: // We append the sha1 of the contents to the file name. This provides a simple michael@0: // client side deduplication. michael@0: nsPrintfCString finalName("%s%s", mProfileDirectory, michael@0: "/Telemetry.LateWriteFinal-"); michael@0: for (int i = 0; i < 20; ++i) { michael@0: finalName.AppendPrintf("%02x", sha1[i]); michael@0: } michael@0: PR_Delete(finalName.get()); michael@0: PR_Rename(name, finalName.get()); michael@0: #endif michael@0: } michael@0: michael@0: /******************************* Setup/Teardown *******************************/ michael@0: michael@0: static StaticAutoPtr sLateWriteObserver; michael@0: michael@0: namespace mozilla{ michael@0: michael@0: void InitLateWriteChecks() michael@0: { michael@0: nsCOMPtr mozFile; michael@0: NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); michael@0: if (mozFile) { michael@0: nsAutoCString nativePath; michael@0: nsresult rv = mozFile->GetNativePath(nativePath); michael@0: if (NS_SUCCEEDED(rv) && nativePath.get()) { michael@0: sLateWriteObserver = new LateWriteObserver(nativePath.get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void BeginLateWriteChecks() michael@0: { michael@0: if (sLateWriteObserver) { michael@0: IOInterposer::Register( michael@0: IOInterposeObserver::OpWriteFSync, michael@0: sLateWriteObserver michael@0: ); michael@0: } michael@0: } michael@0: michael@0: void StopLateWriteChecks() michael@0: { michael@0: if (sLateWriteObserver) { michael@0: IOInterposer::Unregister( michael@0: IOInterposeObserver::OpAll, michael@0: sLateWriteObserver michael@0: ); michael@0: // Deallocation would not be thread-safe, and StopLateWriteChecks() is michael@0: // called at shutdown and only in special cases. michael@0: // sLateWriteObserver = nullptr; michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla