1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <algorithm>
8 
9 #include "mozilla/IOInterposer.h"
10 #include "mozilla/PoisonIOInterposer.h"
11 #include "mozilla/ProcessedStack.h"
12 #include "mozilla/SHA1.h"
13 #include "mozilla/Scoped.h"
14 #include "mozilla/StaticPtr.h"
15 #include "mozilla/Telemetry.h"
16 #include "mozilla/Unused.h"
17 #include "nsAppDirectoryServiceDefs.h"
18 #include "nsDirectoryServiceUtils.h"
19 #include "nsLocalFile.h"
20 #include "nsPrintfCString.h"
21 #include "mozilla/StackWalk.h"
22 #include "plstr.h"
23 #include "prio.h"
24 
25 #ifdef XP_WIN
26 #  define NS_SLASH "\\"
27 #  include <fcntl.h>
28 #  include <io.h>
29 #  include <stdio.h>
30 #  include <stdlib.h>
31 #  include <sys/stat.h>
32 #  include <windows.h>
33 #else
34 #  define NS_SLASH "/"
35 #endif
36 
37 #include "LateWriteChecks.h"
38 
39 /*************************** Auxiliary Declarations ***************************/
40 
41 static MOZ_THREAD_LOCAL(int) tlsSuspendLateWriteChecks;
42 
SuspendingLateWriteChecksForCurrentThread()43 bool SuspendingLateWriteChecksForCurrentThread() {
44   if (!tlsSuspendLateWriteChecks.init()) {
45     return true;
46   }
47   return tlsSuspendLateWriteChecks.get() > 0;
48 }
49 
50 // This a wrapper over a file descriptor that provides a Printf method and
51 // computes the sha1 of the data that passes through it.
52 class SHA1Stream {
53  public:
SHA1Stream(FILE * aStream)54   explicit SHA1Stream(FILE* aStream) : mFile(aStream) {
55     MozillaRegisterDebugFILE(mFile);
56   }
57 
Printf(const char * aFormat,...)58   void Printf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3) {
59     MOZ_ASSERT(mFile);
60     va_list list;
61     va_start(list, aFormat);
62     nsAutoCString str;
63     str.AppendVprintf(aFormat, list);
64     va_end(list);
65     mSHA1.update(str.get(), str.Length());
66     mozilla::Unused << fwrite(str.get(), 1, str.Length(), mFile);
67   }
Finish(mozilla::SHA1Sum::Hash & aHash)68   void Finish(mozilla::SHA1Sum::Hash& aHash) {
69     int fd = fileno(mFile);
70     fflush(mFile);
71     MozillaUnRegisterDebugFD(fd);
72     fclose(mFile);
73     mSHA1.finish(aHash);
74     mFile = nullptr;
75   }
76 
77  private:
78   FILE* mFile;
79   mozilla::SHA1Sum mSHA1;
80 };
81 
RecordStackWalker(uint32_t aFrameNumber,void * aPC,void * aSP,void * aClosure)82 static void RecordStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP,
83                               void* aClosure) {
84   std::vector<uintptr_t>* stack =
85       static_cast<std::vector<uintptr_t>*>(aClosure);
86   stack->push_back(reinterpret_cast<uintptr_t>(aPC));
87 }
88 
89 /**************************** Late-Write Observer  ****************************/
90 
91 /**
92  * An implementation of IOInterposeObserver to be registered with IOInterposer.
93  * This observer logs all writes as late writes.
94  */
95 class LateWriteObserver final : public mozilla::IOInterposeObserver {
96   using char_type = mozilla::filesystem::Path::value_type;
97 
98  public:
LateWriteObserver(const char_type * aProfileDirectory)99   explicit LateWriteObserver(const char_type* aProfileDirectory)
100       : mProfileDirectory(NS_xstrdup(aProfileDirectory)) {}
~LateWriteObserver()101   ~LateWriteObserver() {
102     free(mProfileDirectory);
103     mProfileDirectory = nullptr;
104   }
105 
106   void Observe(
107       mozilla::IOInterposeObserver::Observation& aObservation) override;
108 
109  private:
110   char_type* mProfileDirectory;
111 };
112 
Observe(mozilla::IOInterposeObserver::Observation & aOb)113 void LateWriteObserver::Observe(
114     mozilla::IOInterposeObserver::Observation& aOb) {
115   if (SuspendingLateWriteChecksForCurrentThread()) {
116     return;
117   }
118 
119 #ifdef DEBUG
120   MOZ_CRASH();
121 #endif
122 
123   // If we can't record then abort
124   if (!mozilla::Telemetry::CanRecordExtended()) {
125     return;
126   }
127 
128   // Write the stack and loaded libraries to a file. We can get here
129   // concurrently from many writes, so we use multiple temporary files.
130   std::vector<uintptr_t> rawStack;
131 
132   MozStackWalk(RecordStackWalker, nullptr, /* maxFrames */ 0, &rawStack);
133   mozilla::Telemetry::ProcessedStack stack =
134       mozilla::Telemetry::GetStackAndModules(rawStack);
135 
136   nsTAutoString<char_type> nameAux(mProfileDirectory);
137   nameAux.AppendLiteral(NS_SLASH "Telemetry.LateWriteTmpXXXXXX");
138   char_type* name = nameAux.BeginWriting();
139 
140   // We want the sha1 of the entire file, so please don't write to fd
141   // directly; use sha1Stream.
142   FILE* stream;
143 #ifdef XP_WIN
144   HANDLE hFile;
145   do {
146     // mkstemp isn't supported so keep trying until we get a file
147     _wmktemp_s(char16ptr_t(name), NS_strlen(name) + 1);
148     hFile = CreateFileW(char16ptr_t(name), GENERIC_WRITE, 0, nullptr,
149                         CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
150   } while (GetLastError() == ERROR_FILE_EXISTS);
151 
152   if (hFile == INVALID_HANDLE_VALUE) {
153     MOZ_CRASH("Um, how did we get here?");
154   }
155 
156   // http://support.microsoft.com/kb/139640
157   int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
158   if (fd == -1) {
159     MOZ_CRASH("Um, how did we get here?");
160   }
161 
162   stream = _fdopen(fd, "w");
163 #else
164   int fd = mkstemp(name);
165   if (fd == -1) {
166     MOZ_CRASH("mkstemp failed");
167   }
168   stream = fdopen(fd, "w");
169 #endif
170 
171   SHA1Stream sha1Stream(stream);
172 
173   size_t numModules = stack.GetNumModules();
174   sha1Stream.Printf("%u\n", (unsigned)numModules);
175   for (size_t i = 0; i < numModules; ++i) {
176     mozilla::Telemetry::ProcessedStack::Module module = stack.GetModule(i);
177     sha1Stream.Printf("%s %s\n", module.mBreakpadId.get(),
178                       NS_ConvertUTF16toUTF8(module.mName).get());
179   }
180 
181   size_t numFrames = stack.GetStackSize();
182   sha1Stream.Printf("%u\n", (unsigned)numFrames);
183   for (size_t i = 0; i < numFrames; ++i) {
184     const mozilla::Telemetry::ProcessedStack::Frame& frame = stack.GetFrame(i);
185     // NOTE: We write the offsets, while the atos tool expects a value with
186     // the virtual address added. For example, running otool -l on the the
187     // firefox binary shows
188     //      cmd LC_SEGMENT_64
189     //      cmdsize 632
190     //      segname __TEXT
191     //      vmaddr 0x0000000100000000
192     // so to print the line matching the offset 123 one has to run
193     // atos -o firefox 0x100000123.
194     sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset);
195   }
196 
197   mozilla::SHA1Sum::Hash sha1;
198   sha1Stream.Finish(sha1);
199 
200   // Note: These files should be deleted by telemetry once it reads them. If
201   // there were no telemetry runs by the time we shut down, we just add files
202   // to the existing ones instead of replacing them. Given that each of these
203   // files is a bug to be fixed, that is probably the right thing to do.
204 
205   // We append the sha1 of the contents to the file name. This provides a simple
206   // client side deduplication.
207   nsAutoString finalName(u"Telemetry.LateWriteFinal-"_ns);
208   for (int i = 0; i < 20; ++i) {
209     finalName.AppendPrintf("%02x", sha1[i]);
210   }
211   RefPtr<nsLocalFile> file = new nsLocalFile(nameAux);
212   file->RenameTo(nullptr, finalName);
213 }
214 
215 /******************************* Setup/Teardown *******************************/
216 
217 static mozilla::StaticAutoPtr<LateWriteObserver> sLateWriteObserver;
218 
219 namespace mozilla {
220 
InitLateWriteChecks()221 void InitLateWriteChecks() {
222   nsCOMPtr<nsIFile> mozFile;
223   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
224   if (mozFile) {
225     PathString nativePath = mozFile->NativePath();
226     if (nativePath.get()) {
227       sLateWriteObserver = new LateWriteObserver(nativePath.get());
228     }
229   }
230 }
231 
BeginLateWriteChecks()232 void BeginLateWriteChecks() {
233   if (sLateWriteObserver) {
234     IOInterposer::Register(IOInterposeObserver::OpWriteFSync,
235                            sLateWriteObserver);
236   }
237 }
238 
StopLateWriteChecks()239 void StopLateWriteChecks() {
240   if (sLateWriteObserver) {
241     IOInterposer::Unregister(IOInterposeObserver::OpAll, sLateWriteObserver);
242     // Deallocation would not be thread-safe, and StopLateWriteChecks() is
243     // called at shutdown and only in special cases.
244     // sLateWriteObserver = nullptr;
245   }
246 }
247 
PushSuspendLateWriteChecks()248 void PushSuspendLateWriteChecks() {
249   if (!tlsSuspendLateWriteChecks.init()) {
250     return;
251   }
252   tlsSuspendLateWriteChecks.set(tlsSuspendLateWriteChecks.get() + 1);
253 }
254 
PopSuspendLateWriteChecks()255 void PopSuspendLateWriteChecks() {
256   if (!tlsSuspendLateWriteChecks.init()) {
257     return;
258   }
259   int current = tlsSuspendLateWriteChecks.get();
260   MOZ_ASSERT(current > 0);
261   tlsSuspendLateWriteChecks.set(current - 1);
262 }
263 
264 }  // namespace mozilla
265