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