1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/reporter.h"
16 :
17 : #include <DbgHelp.h>
18 :
19 : #include <stdint.h>
20 :
21 : #include <map>
22 : #include <string>
23 :
24 : #include "base/bind.h"
25 : #include "base/bind_helpers.h"
26 : #include "base/file_util.h"
27 : #include "base/logging.h"
28 : #include "base/strings/utf_string_conversions.h"
29 : #include "base/threading/platform_thread.h"
30 : #include "base/time/time.h"
31 : #include "syzygy/kasko/http_agent_impl.h"
32 : #include "syzygy/kasko/minidump.h"
33 : #include "syzygy/kasko/service.h"
34 : #include "syzygy/kasko/upload.h"
35 : #include "syzygy/kasko/upload_thread.h"
36 : #include "syzygy/kasko/version.h"
37 : #include "syzygy/kasko/waitable_timer.h"
38 : #include "syzygy/kasko/waitable_timer_impl.h"
39 :
40 : namespace kasko {
41 :
42 : namespace {
43 :
44 : // The RPC protocol used for receiving dump requests.
45 : const base::char16* const kRpcProtocol = L"ncalrpc";
46 :
47 : // The subdirectory where minidumps are generated.
48 : const base::char16* const kTemporarySubdir = L"Temporary";
49 :
50 : // Uploads a crash report containing the minidump at |minidump_path| and
51 : // |crash_keys| to |upload_url|. Returns true if successful.
52 : bool UploadCrashReport(
53 : const base::string16& upload_url,
54 : const base::FilePath& minidump_path,
55 E : const std::map<base::string16, base::string16>& crash_keys) {
56 E : std::string dump_contents;
57 E : if (!base::ReadFileToString(minidump_path, &dump_contents)) {
58 i : LOG(ERROR) << "Failed to read the minidump file at "
59 : << minidump_path.value();
60 i : return false;
61 : }
62 :
63 : HttpAgentImpl http_agent(
64 E : L"Kasko", base::ASCIIToUTF16(KASKO_VERSION_STRING));
65 E : base::string16 remote_dump_id;
66 E : uint16_t response_code = 0;
67 : if (!SendHttpUpload(&http_agent, upload_url, crash_keys, dump_contents,
68 : Reporter::kMinidumpUploadFilePart, &remote_dump_id,
69 E : &response_code)) {
70 E : LOG(ERROR) << "Failed to upload the minidump file to " << upload_url;
71 E : return false;
72 i : } else {
73 : // TODO(erikwright): Log this report ID somewhere accessible to our client.
74 : // For example, the Windows Event Log.
75 E : LOG(INFO) << "Successfully uploded a crash report. Report ID: "
76 : << remote_dump_id;
77 : }
78 :
79 E : return true;
80 E : }
81 :
82 : // Moves |minidump_path| and |crash_keys_path| to |permanent_failure_directory|.
83 : // The destination filenames have the filename from |minidump_path| and the
84 : // extensions Reporter::kPermanentFailureMinidumpExtension and
85 : // Reporter::kPermanentFailureCrashKeysExtension.
86 : void HandlePermanentFailure(const base::FilePath& permanent_failure_directory,
87 : const base::FilePath& minidump_path,
88 E : const base::FilePath& crash_keys_path) {
89 : base::FilePath minidump_target = permanent_failure_directory.Append(
90 : minidump_path.BaseName().ReplaceExtension(
91 E : Reporter::kPermanentFailureMinidumpExtension));
92 :
93 : // Note that we take the filename from the minidump file, in order to
94 : // guarantee that the two files have matching names.
95 : base::FilePath crash_keys_target = permanent_failure_directory.Append(
96 : minidump_path.BaseName().ReplaceExtension(
97 E : Reporter::kPermanentFailureCrashKeysExtension));
98 :
99 E : if (!base::CreateDirectory(permanent_failure_directory)) {
100 i : LOG(ERROR) << "Failed to create directory at "
101 : << permanent_failure_directory.value();
102 E : } else if (!base::Move(minidump_path, minidump_target)) {
103 i : LOG(ERROR) << "Failed to move " << minidump_path.value() << " to "
104 : << minidump_target.value();
105 E : } else if (!base::Move(crash_keys_path, crash_keys_target)) {
106 i : LOG(ERROR) << "Failed to move " << crash_keys_path.value() << " to "
107 : << crash_keys_target.value();
108 : }
109 E : }
110 :
111 : void GenerateReport(const base::FilePath& temporary_directory,
112 : ReportRepository* report_repository,
113 : base::ProcessId client_process_id,
114 : uint64_t exception_info_address,
115 : base::PlatformThreadId thread_id,
116 : MinidumpType minidump_type,
117 : const char* protobuf,
118 : size_t protobuf_length,
119 E : std::map<base::string16, base::string16> crash_keys) {
120 E : if (!base::CreateDirectory(temporary_directory)) {
121 i : LOG(ERROR) << "Failed to create dump destination directory: "
122 : << temporary_directory.value();
123 i : return;
124 : }
125 :
126 E : base::FilePath dump_file;
127 E : if (!base::CreateTemporaryFileInDir(temporary_directory, &dump_file)) {
128 i : LOG(ERROR) << "Failed to create a temporary dump file.";
129 i : return;
130 : }
131 :
132 E : std::vector<CustomStream> custom_streams;
133 E : if (protobuf && protobuf_length) {
134 : CustomStream custom_stream = {
135 E : Reporter::kProtobufStreamType, protobuf, protobuf_length};
136 E : custom_streams.push_back(custom_stream);
137 : }
138 :
139 : if (!GenerateMinidump(dump_file, client_process_id, thread_id,
140 : exception_info_address, minidump_type,
141 E : custom_streams)) {
142 i : LOG(ERROR) << "Minidump generation failed.";
143 i : base::DeleteFile(dump_file, false);
144 i : return;
145 : }
146 :
147 E : report_repository->StoreReport(dump_file, crash_keys);
148 E : }
149 :
150 : // Implements kasko::Service to capture minidumps and store them in a
151 : // ReportRepository.
152 : class ServiceImpl : public Service {
153 : public:
154 : ServiceImpl(const base::FilePath& temporary_directory,
155 : ReportRepository* report_repository,
156 : UploadThread* upload_thread)
157 : : temporary_directory_(temporary_directory),
158 : report_repository_(report_repository),
159 E : upload_thread_(upload_thread) {}
160 :
161 E : ~ServiceImpl() override {}
162 :
163 : // Service implementation.
164 : void SendDiagnosticReport(
165 : base::ProcessId client_process_id,
166 : uint64_t exception_info_address,
167 : base::PlatformThreadId thread_id,
168 : MinidumpType minidump_type,
169 : const char* protobuf,
170 : size_t protobuf_length,
171 E : const std::map<base::string16, base::string16>& crash_keys) override {
172 : GenerateReport(temporary_directory_, report_repository_, client_process_id,
173 : exception_info_address, thread_id, minidump_type, protobuf,
174 E : protobuf_length, crash_keys);
175 E : upload_thread_->UploadOneNowAsync();
176 E : }
177 :
178 : private:
179 : base::FilePath temporary_directory_;
180 : ReportRepository* report_repository_;
181 : UploadThread* upload_thread_;
182 :
183 : DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
184 : };
185 :
186 : } // namespace
187 :
188 : const base::char16* const Reporter::kPermanentFailureCrashKeysExtension =
189 : L".kys";
190 : const base::char16* const Reporter::kPermanentFailureMinidumpExtension =
191 : L".dmp";
192 : const base::char16* const Reporter::kMinidumpUploadFilePart =
193 : L"upload_file_minidump";
194 : // 0x4B6B is 'Kk'.
195 : const uint32_t Reporter::kProtobufStreamType = 0x4B6B0001;
196 : static_assert(Reporter::kProtobufStreamType > LastReservedStream,
197 : "kProtobufStreamType <= LastReservedStream.");
198 :
199 : // static
200 : scoped_ptr<Reporter> Reporter::Create(
201 : const base::string16& endpoint_name,
202 : const base::string16& url,
203 : const base::FilePath& data_directory,
204 : const base::FilePath& permanent_failure_directory,
205 : const base::TimeDelta& upload_interval,
206 E : const base::TimeDelta& retry_interval) {
207 :
208 : scoped_ptr<WaitableTimer> waitable_timer(
209 E : WaitableTimerImpl::Create(upload_interval));
210 E : if (!waitable_timer) {
211 i : LOG(ERROR) << "Failed to create a timer for the upload process.";
212 i : return scoped_ptr<Reporter>();
213 : }
214 :
215 : scoped_ptr<ReportRepository> report_repository(new ReportRepository(
216 : data_directory, retry_interval, base::Bind(&base::Time::Now),
217 : base::Bind(&UploadCrashReport, url),
218 E : base::Bind(&HandlePermanentFailure, permanent_failure_directory)));
219 :
220 : // It's safe to pass an Unretained reference to |report_repository| because
221 : // the Reporter instance will shut down |upload_thread| before destroying
222 : // |report_repository|.
223 : scoped_ptr<UploadThread> upload_thread = UploadThread::Create(
224 : data_directory, waitable_timer.Pass(),
225 : base::Bind(base::IgnoreResult(&ReportRepository::UploadPendingReport),
226 E : base::Unretained(report_repository.get())));
227 :
228 E : if (!upload_thread) {
229 i : LOG(ERROR) << "Failed to initialize background upload process.";
230 i : return scoped_ptr<Reporter>();
231 : }
232 :
233 : scoped_ptr<Reporter> instance(
234 : new Reporter(report_repository.Pass(), upload_thread.Pass(),
235 E : endpoint_name, data_directory.Append(kTemporarySubdir)));
236 E : if (!instance->service_bridge_.Run()) {
237 i : LOG(ERROR) << "Failed to start the Kasko RPC service using protocol "
238 : << kRpcProtocol << " and endpoint name " << endpoint_name << ".";
239 i : return scoped_ptr<Reporter>();
240 : }
241 :
242 E : instance->upload_thread_->Start();
243 :
244 E : return instance.Pass();
245 E : }
246 :
247 E : Reporter::~Reporter() {}
248 :
249 : void Reporter::SendReportForProcess(
250 : base::ProcessHandle process_handle,
251 : MinidumpType minidump_type,
252 E : const std::map<base::string16, base::string16>& crash_keys) {
253 : GenerateReport(temporary_minidump_directory_, report_repository_.get(),
254 : base::GetProcId(process_handle), NULL, 0, minidump_type, NULL,
255 E : 0, crash_keys);
256 E : upload_thread_->UploadOneNowAsync();
257 E : }
258 :
259 : // static
260 E : void Reporter::Shutdown(scoped_ptr<Reporter> instance) {
261 E : instance->upload_thread_->Stop(); // Non-blocking.
262 E : instance->service_bridge_.Stop(); // Blocking.
263 E : instance->upload_thread_->Join(); // Blocking.
264 E : }
265 :
266 : Reporter::Reporter(scoped_ptr<ReportRepository> report_repository,
267 : scoped_ptr<UploadThread> upload_thread,
268 : const base::string16& endpoint_name,
269 : const base::FilePath& temporary_minidump_directory)
270 : : report_repository_(report_repository.Pass()),
271 : upload_thread_(upload_thread.Pass()),
272 : temporary_minidump_directory_(temporary_minidump_directory),
273 : service_bridge_(
274 : kRpcProtocol,
275 : endpoint_name,
276 : make_scoped_ptr(new ServiceImpl(temporary_minidump_directory_,
277 : report_repository_.get(),
278 E : upload_thread_.get()))) {
279 E : }
280 :
281 : } // namespace kasko
|