1 : // Copyright 2012 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 : // This file implements the TraceFileWriter and TraceFileWriterFactory classes
16 : // which provide an implementation and factory, respectively, for the default
17 : // buffer consumer used by the call trace service.
18 :
19 : #include "syzygy/trace/service/trace_file_writer.h"
20 :
21 : #include <time.h>
22 :
23 : #include "base/atomicops.h"
24 : #include "base/bind.h"
25 : #include "base/file_util.h"
26 : #include "base/logging.h"
27 : #include "base/stringprintf.h"
28 : #include "base/files/file_path.h"
29 : #include "base/memory/scoped_ptr.h"
30 : #include "sawbuck/common/com_utils.h"
31 : #include "syzygy/common/align.h"
32 : #include "syzygy/common/buffer_writer.h"
33 : #include "syzygy/common/path_util.h"
34 : #include "syzygy/trace/protocol/call_trace_defs.h"
35 : #include "syzygy/trace/service/buffer_pool.h"
36 : #include "syzygy/trace/service/session.h"
37 : #include "syzygy/trace/service/trace_file_writer_factory.h"
38 :
39 : namespace trace {
40 : namespace service {
41 :
42 : namespace {
43 :
44 : base::FilePath GenerateTraceFileName(const Session* session,
45 E : const base::FilePath& trace_directory) {
46 E : DCHECK(session != NULL);
47 E : DCHECK(!trace_directory.empty());
48 :
49 E : const ProcessInfo& client = session->client_info();
50 :
51 : // We use the current time to disambiguate the trace file, so let's look
52 : // at the clock.
53 E : time_t t = time(NULL);
54 E : struct tm local_time = {};
55 E : ::localtime_s(&local_time, &t);
56 :
57 : // Construct the trace file path from the program being run, the current
58 : // timestamp, and the process id.
59 : return trace_directory.Append(base::StringPrintf(
60 : L"trace-%ls-%4d%02d%02d%02d%02d%02d-%d.bin",
61 : client.executable_path.BaseName().value().c_str(),
62 : 1900 + local_time.tm_year,
63 : 1 + local_time.tm_mon,
64 : local_time.tm_mday,
65 : local_time.tm_hour,
66 : local_time.tm_min,
67 : local_time.tm_sec,
68 E : client.process_id));
69 E : }
70 :
71 : bool OpenTraceFile(const base::FilePath& file_path,
72 E : base::win::ScopedHandle* file_handle) {
73 E : DCHECK(!file_path.empty());
74 E : DCHECK(file_handle != NULL);
75 :
76 : // Create a new trace file.
77 : base::win::ScopedHandle new_file_handle(
78 : ::CreateFile(file_path.value().c_str(),
79 : GENERIC_READ | GENERIC_WRITE,
80 : FILE_SHARE_DELETE | FILE_SHARE_READ,
81 : NULL, /* lpSecurityAttributes */
82 : CREATE_ALWAYS,
83 : FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING,
84 E : NULL /* hTemplateFile */));
85 E : if (!new_file_handle.IsValid()) {
86 i : DWORD error = ::GetLastError();
87 i : LOG(ERROR) << "Failed to open '" << file_path.value()
88 : << "' for writing: " << com::LogWe(error) << ".";
89 i : return false;
90 : }
91 :
92 E : file_handle->Set(new_file_handle.Take());
93 :
94 E : return true;
95 E : }
96 :
97 E : bool GetBlockSize(const base::FilePath& path, size_t* block_size) {
98 : wchar_t volume[MAX_PATH];
99 :
100 E : if (!::GetVolumePathName(path.value().c_str(), volume, arraysize(volume))) {
101 i : DWORD error = ::GetLastError();
102 i : LOG(ERROR) << "Failed to get volume path name: " << com::LogWe(error)
103 : << ".";
104 i : return false;
105 : }
106 :
107 E : DWORD sectors_per_cluster = 0;
108 E : DWORD bytes_per_sector = 0;
109 E : DWORD free_clusters = 0;
110 E : DWORD total_clusters = 0;
111 :
112 : if (!::GetDiskFreeSpace(volume, §ors_per_cluster, &bytes_per_sector,
113 E : &free_clusters, &total_clusters)) {
114 i : DWORD error = ::GetLastError();
115 i : LOG(ERROR) << "Failed to get volume info: " << com::LogWe(error) << ".";
116 i : return false;
117 : }
118 :
119 E : *block_size = bytes_per_sector;
120 E : return true;
121 E : }
122 :
123 : bool WriteTraceFileHeader(HANDLE file_handle,
124 : const Session* session,
125 E : size_t block_size) {
126 E : DCHECK(file_handle != INVALID_HANDLE_VALUE);
127 E : DCHECK(session != NULL);
128 E : DCHECK(block_size != 0);
129 :
130 E : const ProcessInfo& client = session->client_info();
131 :
132 : // Make sure we record the path to the executable as a path with a drive
133 : // letter, rather than using device names.
134 E : base::FilePath drive_path;
135 : if (!common::ConvertDevicePathToDrivePath(client.executable_path,
136 E : &drive_path)) {
137 i : return false;
138 : }
139 :
140 : // Allocate an initial buffer to which to write the trace file header.
141 E : std::vector<uint8> buffer;
142 E : buffer.reserve(32 * 1024);
143 :
144 : // Skip past the fixed sized portion of the header and populate the variable
145 : // length fields.
146 E : common::VectorBufferWriter writer(&buffer);
147 : if (!writer.Consume(offsetof(TraceFileHeader, blob_data)) ||
148 : !writer.WriteString(drive_path.value()) ||
149 : !writer.WriteString(client.command_line) ||
150 E : !writer.Write(client.environment.size(), &client.environment[0])) {
151 i : return false;
152 : }
153 :
154 : // Go back and populate the fixed sized portion of the header.
155 E : TraceFileHeader* header = reinterpret_cast<TraceFileHeader*>(&buffer[0]);
156 : ::memcpy(&header->signature,
157 : &TraceFileHeader::kSignatureValue,
158 E : sizeof(header->signature));
159 E : header->server_version.lo = TRACE_VERSION_LO;
160 E : header->server_version.hi = TRACE_VERSION_HI;
161 E : header->timestamp = ::GetTickCount();
162 E : header->process_id = client.process_id;
163 E : header->block_size = block_size;
164 E : header->module_base_address = client.exe_base_address;
165 E : header->module_size = client.exe_image_size;
166 E : header->module_checksum = client.exe_checksum;
167 E : header->module_time_date_stamp = client.exe_time_date_stamp;
168 E : header->os_version_info = client.os_version_info;
169 E : header->system_info = client.system_info;
170 E : header->memory_status = client.memory_status;
171 E : header->header_size = buffer.size();
172 :
173 : // Align the heeader buffer up to the block size.
174 E : writer.Align(block_size);
175 :
176 : // Commit the header page to disk.
177 E : DWORD bytes_written = 0;
178 : if (!::WriteFile(file_handle, &buffer[0], buffer.size(), &bytes_written,
179 E : NULL) || bytes_written != buffer.size() ) {
180 i : DWORD error = ::GetLastError();
181 i : LOG(ERROR) << "Failed writing trace file header: " << com::LogWe(error)
182 : << ".";
183 i : return false;
184 : }
185 :
186 E : return true;
187 E : }
188 :
189 : } // namespace
190 :
191 : TraceFileWriter::TraceFileWriter(MessageLoop* message_loop,
192 : const base::FilePath& trace_directory)
193 : : message_loop_(message_loop),
194 : trace_file_path_(trace_directory), // Will mutate to filename on Open().
195 E : block_size_(0) {
196 E : DCHECK(message_loop != NULL);
197 E : DCHECK(!trace_directory.empty());
198 E : }
199 :
200 E : bool TraceFileWriter::Open(Session* session) {
201 E : DCHECK(session != NULL);
202 E : DCHECK(!trace_file_handle_.IsValid());
203 :
204 E : if (!file_util::CreateDirectory(trace_file_path_)) {
205 i : LOG(ERROR) << "Failed to create trace directory: '"
206 : << trace_file_path_.value() << "'.";
207 i : return false;
208 : }
209 :
210 : // Append the trace file name onto the trace file directory we stored on
211 : // construction.
212 E : trace_file_path_ = GenerateTraceFileName(session, trace_file_path_);
213 :
214 : // Open the trace file.
215 E : base::win::ScopedHandle temp_handle;
216 E : if (!OpenTraceFile(trace_file_path_, &temp_handle)) {
217 i : LOG(ERROR) << "Failed to open trace file: '"
218 : << trace_file_path_.value() << "'.";
219 i : return false;
220 : }
221 :
222 : // Figure out how big a physical block is on the disk.
223 E : if (!GetBlockSize(trace_file_path_, &block_size_)) {
224 i : LOG(ERROR) << "Failed to determine the trace file block size.";
225 i : return false;
226 : }
227 :
228 : // Write the trace file header.
229 E : if (!WriteTraceFileHeader(temp_handle, session, block_size_)) {
230 i : LOG(ERROR) << "Failed to write trace file header.";
231 i : return false;
232 : }
233 :
234 E : trace_file_handle_.Set(temp_handle.Take());
235 :
236 E : return true;
237 E : }
238 :
239 E : bool TraceFileWriter::Close(Session* /* session */) {
240 E : return true;
241 E : }
242 :
243 E : bool TraceFileWriter::ConsumeBuffer(Buffer* buffer) {
244 E : DCHECK(buffer != NULL);
245 E : DCHECK(buffer->session != NULL);
246 E : DCHECK(message_loop_ != NULL);
247 E : DCHECK(trace_file_handle_.IsValid());
248 :
249 : message_loop_->PostTask(FROM_HERE,
250 : base::Bind(&TraceFileWriter::WriteBuffer,
251 : this,
252 : scoped_refptr<Session>(buffer->session),
253 E : base::Unretained(buffer)));
254 :
255 E : return true;
256 E : }
257 :
258 E : size_t TraceFileWriter::block_size() const {
259 E : return block_size_;
260 E : }
261 :
262 : void TraceFileWriter::WriteBuffer(Session* session,
263 E : Buffer* buffer) {
264 E : DCHECK(session != NULL);
265 E : DCHECK(buffer != NULL);
266 E : DCHECK_EQ(session, buffer->session);
267 E : DCHECK_EQ(Buffer::kPendingWrite, buffer->state);
268 E : DCHECK_EQ(MessageLoop::current(), message_loop_);
269 E : DCHECK(trace_file_handle_.IsValid());
270 :
271 : // Parse the record prefix and segment header;
272 : volatile RecordPrefix* prefix =
273 E : reinterpret_cast<RecordPrefix*>(buffer->data_ptr);
274 : volatile TraceFileSegmentHeader* header =
275 E : reinterpret_cast<volatile TraceFileSegmentHeader*>(prefix + 1);
276 :
277 : // Let's not trust the client to stop playing with the buffer while
278 : // we're writing. Whatever the length is now, is what we'll use.
279 E : size_t segment_length = header->segment_length;
280 E : const size_t kHeaderLength = sizeof(*prefix) + sizeof(*header);
281 E : if (segment_length > 0) {
282 : size_t bytes_to_write = common::AlignUp(kHeaderLength + segment_length,
283 E : block_size_);
284 : if (prefix->type != TraceFileSegmentHeader::kTypeId ||
285 : prefix->size != sizeof(TraceFileSegmentHeader) ||
286 : prefix->version.hi != TRACE_VERSION_HI ||
287 E : prefix->version.lo != TRACE_VERSION_LO) {
288 i : LOG(WARNING) << "Dropped buffer: invalid segment header.";
289 E : } else if (bytes_to_write > buffer->buffer_size) {
290 i : LOG(WARNING) << "Dropped buffer: bytes written exceeds buffer size.";
291 i : } else {
292 : // Commit the buffer to disk.
293 : // TODO(rogerm): Use overlapped I/O.
294 E : DCHECK(bytes_to_write != 0);
295 E : DWORD bytes_written = 0;
296 : if (!::WriteFile(trace_file_handle_,
297 : buffer->data_ptr,
298 : bytes_to_write,
299 : &bytes_written,
300 : NULL) ||
301 E : bytes_written != bytes_to_write) {
302 i : DWORD error = ::GetLastError();
303 i : LOG(ERROR) << "Failed writing to '" << trace_file_path_.value()
304 : << "': " << com::LogWe(error) << ".";
305 : }
306 : }
307 : }
308 :
309 : // It's entirely possible for this buffer to be handed out to another client
310 : // and for the service to be forcibly shutdown before the client has had a
311 : // chance to even touch the buffer. In that case, we'll end up writing the
312 : // buffer again. We clear the RecordPrefix and the TraceFileSegmentHeader so
313 : // that we'll at least see the buffer as empty and write nothing.
314 : ::memset(buffer->data_ptr, 0,
315 E : sizeof(RecordPrefix) + sizeof(TraceFileSegmentHeader));
316 :
317 E : session->RecycleBuffer(buffer);
318 E : }
319 :
320 : } // namespace service
321 : } // namespace trace
|