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 : #include "syzygy/reorder/reorderer.h"
16 :
17 : #include "base/file_util.h"
18 : #include "base/stringprintf.h"
19 : #include "base/utf_string_conversions.h"
20 : #include "base/values.h"
21 : #include "base/json/json_reader.h"
22 : #include "base/json/string_escape.h"
23 : #include "syzygy/block_graph/block_graph.h"
24 : #include "syzygy/common/defs.h"
25 : #include "syzygy/common/syzygy_version.h"
26 : #include "syzygy/core/json_file_writer.h"
27 : #include "syzygy/core/serialization.h"
28 : #include "syzygy/pdb/omap.h"
29 : #include "syzygy/pe/find.h"
30 : #include "syzygy/pe/metadata.h"
31 : #include "syzygy/pe/pe_file.h"
32 : #include "syzygy/pe/pe_utils.h"
33 :
34 : namespace reorder {
35 :
36 : using block_graph::BlockGraph;
37 : using trace::parser::Parser;
38 : using base::ListValue;
39 : using base::DictionaryValue;
40 : using base::Value;
41 :
42 : namespace {
43 :
44 : const char kCommentKey[] = "comment";
45 : const char kMetadataKey[] = "metadata";
46 : const char kSectionsKey[] = "sections";
47 : const char kSectionIdKey[] = "id";
48 : const char kSectionNameKey[] = "name";
49 : const char kSectionCharacteristicsKey[] = "characteristics";
50 : const char kBlocksKey[] = "blocks";
51 :
52 : bool OutputTrailingBlockComment(const BlockGraph::Block* block,
53 E : core::JSONFileWriter* json_file) {
54 E : DCHECK(block != NULL);
55 E : DCHECK(json_file != NULL);
56 :
57 E : if (!json_file->pretty_print())
58 E : return true;
59 :
60 : std::string comment = base::StringPrintf(
61 : "%s(%s)",
62 : BlockGraph::BlockTypeToString(block->type()),
63 E : block->name().c_str());
64 :
65 E : if (!json_file->OutputTrailingComment(comment))
66 i : return false;
67 :
68 E : return true;
69 E : }
70 :
71 : bool OutputBlockSpec(const Reorderer::Order::BlockSpec& block_spec,
72 E : core::JSONFileWriter* json_file) {
73 : // TODO(rogerm): Stop referring to block->addr() and take the address space
74 : // as an input parameter.
75 E : DCHECK(json_file != NULL);
76 : // TODO(rogerm): Flesh out support for synthesizing new blocks.
77 E : DCHECK(block_spec.block != NULL);
78 :
79 : // If no basic-block RVAs are given then the entire block is to be
80 : // used and we can just just the block address.
81 E : if (block_spec.basic_block_offsets.empty()) {
82 E : if (!json_file->OutputInteger(block_spec.block->addr().value()))
83 i : return false;
84 E : if (!OutputTrailingBlockComment(block_spec.block, json_file))
85 i : return false;
86 E : return true;
87 : }
88 :
89 : // Otherwise, we output a pair (two element list) comprising the block
90 : // address and the list of basic-block RVAs.
91 :
92 : // Open the outer list.
93 E : if (!json_file->OpenList())
94 i : return false;
95 :
96 : // Output the block address.
97 : if (!json_file->OutputInteger(block_spec.block->addr().value()) ||
98 E : !OutputTrailingBlockComment(block_spec.block, json_file)) {
99 i : return false;
100 : }
101 :
102 : // Open the inner list.
103 E : if (!json_file->OpenList())
104 i : return false;
105 :
106 : // Output the basic block RVAs.
107 : Reorderer::Order::OffsetVector::const_iterator it =
108 E : block_spec.basic_block_offsets.begin();
109 E : for (; it != block_spec.basic_block_offsets.end(); ++it) {
110 E : if (!json_file->OutputInteger(*it))
111 i : return false;
112 E : }
113 :
114 : // Close the inner list.
115 E : if (!json_file->CloseList())
116 i : return false;
117 :
118 : // Close the outer list.
119 E : if (!json_file->CloseList())
120 i : return false;
121 :
122 E : return true;
123 E : }
124 :
125 : // Serializes a block list to JSON.
126 : bool OutputSectionSpec(const Reorderer::Order::SectionSpec& section_spec,
127 E : core::JSONFileWriter* json_file) {
128 E : DCHECK(json_file != NULL);
129 :
130 : // Open the section specification dictionary.
131 E : if (!json_file->OpenDict())
132 i : return false;
133 :
134 : // If the section has an ID in the original image, output the ID.
135 E : if (section_spec.id != Reorderer::Order::SectionSpec::kNewSectionId) {
136 : if (!json_file->OutputKey(kSectionIdKey) ||
137 E : !json_file->OutputInteger(section_spec.id)) {
138 i : return false;
139 : }
140 : }
141 :
142 : // Output the section metadata.
143 : if (!json_file->OutputKey(kSectionNameKey) ||
144 : !json_file->OutputString(section_spec.name) ||
145 : !json_file->OutputKey(kSectionCharacteristicsKey) ||
146 E : !json_file->OutputInteger(section_spec.characteristics)) {
147 i : return false;
148 : }
149 :
150 : // Open the block spec list.
151 : if (!json_file->OutputKey(kBlocksKey) ||
152 E : !json_file->OpenList()) {
153 i : return false;
154 : }
155 :
156 : // Output each of the block specifications.
157 : Reorderer::Order::BlockSpecVector::const_iterator it =
158 E : section_spec.blocks.begin();
159 E : for (; it != section_spec.blocks.end(); ++it) {
160 E : if (!OutputBlockSpec(*it, json_file))
161 i : return false;
162 E : }
163 :
164 : // Close the block spec list.
165 E : if (!json_file->CloseList())
166 i : return false;
167 :
168 : // Close the section spec dictionary.
169 E : if (!json_file->CloseDict())
170 i : return false;
171 :
172 E : return true;
173 E : }
174 :
175 : bool LoadBlockSpec(const pe::ImageLayout& image,
176 : const Value* block_value,
177 E : Reorderer::Order::BlockSpec* block_spec) {
178 E : DCHECK(block_value != NULL);
179 E : DCHECK(block_spec != NULL);
180 :
181 E : block_spec->block = NULL;
182 E : block_spec->basic_block_offsets.clear();
183 :
184 : // If the block value is a single integer, then we use the entire block.
185 : // Otherwise, we evaluate the value as a pair (represented as a list)
186 : // where the first element is the address and the second element is a list
187 : // of basic-block RVAs.
188 E : int address = 0;
189 E : const ListValue* rva_list = NULL;
190 E : if (!block_value->GetAsInteger(&address)) {
191 E : const ListValue* pair = NULL;
192 : if (!block_value->GetAsList(&pair) ||
193 : !pair->GetInteger(0, &address) ||
194 E : !pair->GetList(1, &rva_list)) {
195 i : LOG(ERROR) << "Invalid entry for block specification.";
196 i : return false;
197 : }
198 : }
199 :
200 : // Resolve the referenced block.
201 E : core::RelativeAddress rva(address);
202 E : const BlockGraph::Block* block = image.blocks.GetBlockByAddress(rva);
203 E : if (block == NULL) {
204 i : LOG(ERROR) << "Block address not found in decomposed image: "
205 : << address;
206 i : return false;
207 : }
208 :
209 : // Read in the basic_block offsets.
210 E : if (rva_list != NULL && !rva_list->empty()) {
211 E : block_spec->basic_block_offsets.reserve(rva_list->GetSize());
212 E : for (size_t i = 0; i < rva_list->GetSize(); ++i) {
213 E : int offset = 0;
214 E : if (!rva_list->GetInteger(i, &offset)) {
215 i : LOG(ERROR) << "Unexpected value for basic-block offset #" << i
216 : << " of " << address << " [" << block->name() << "].";
217 i : block_spec->basic_block_offsets.clear();
218 i : return false;
219 : }
220 : if (offset < 0 ||
221 E : static_cast<BlockGraph::Size>(offset) >= block->size()) {
222 i : LOG(ERROR) << "Offset " << offset << " falls outside block range [0-"
223 : << (block->size() - 1) << "] for " << block->name();
224 i : return false;
225 : }
226 E : block_spec->basic_block_offsets.push_back(offset);
227 E : }
228 : }
229 :
230 E : block_spec->block = block;
231 E : return true;
232 E : }
233 :
234 : bool LoadSectionSpec(const pe::ImageLayout& image,
235 : const DictionaryValue* section_value,
236 : Reorderer::Order::SectionSpec* section_spec,
237 E : std::set<size_t>* seen_section_ids) {
238 E : DCHECK(section_value != NULL);
239 E : DCHECK(section_spec != NULL);
240 E : DCHECK(seen_section_ids != NULL);
241 :
242 : // Some keys we'll refer to multiple times below.
243 E : const std::string section_id_key(kSectionIdKey);
244 E : const std::string section_name_key(kSectionNameKey);
245 E : const std::string section_characteristics_key(kSectionCharacteristicsKey);
246 E : const std::string blocks_key(kBlocksKey);
247 :
248 : // Get the section id, if given.
249 E : int tmp_section_id = Reorderer::Order::SectionSpec::kNewSectionId;
250 : if (section_value->HasKey(section_id_key) &&
251 E : !section_value->GetInteger(section_id_key, &tmp_section_id)) {
252 i : LOG(ERROR) << "Invalid value for " << section_id_key << ".";
253 i : return false;
254 : }
255 :
256 : // Lookup the original section by id, if the id was given. Populate the
257 : // section metadata based on the original section info. The other keys
258 : // will be inspected below to see if any of the metadata needs to be
259 : // over-ridden.
260 E : section_spec->id = tmp_section_id;
261 E : if (section_spec->id != Reorderer::Order::SectionSpec::kNewSectionId) {
262 : // Lookup the section in the original image layout.
263 E : if (section_spec->id < 0 || section_spec->id > image.sections.size()) {
264 i : LOG(ERROR) << "Invalid section id: " << section_spec->id << ".";
265 i : return false;
266 : }
267 :
268 : // Make sure this section id does not already exist.
269 E : if (!seen_section_ids->insert(section_spec->id).second) {
270 i : LOG(ERROR) << "Section ID " << section_spec->id << " redefined.";
271 i : return false;
272 : }
273 :
274 : // Copy the metadata into the section spec.
275 E : section_spec->name = image.sections[section_spec->id].name;
276 : section_spec->characteristics =
277 E : image.sections[section_spec->id].characteristics;
278 : }
279 :
280 : // Possibly over-ride the section name.
281 : if (section_value->HasKey(section_name_key) &&
282 E : !section_value->GetString(section_name_key, §ion_spec->name)) {
283 i : LOG(ERROR) << "Invalid value for " << section_name_key << ".";
284 i : return false;
285 : }
286 :
287 : // Make sure we've got a section name. This may have come from either the
288 : // the original image (via the section id) or explicitly from the section
289 : // name key.
290 E : if (section_spec->name.empty()) {
291 i : LOG(ERROR) << "Missing a value for the section name. Either a valid "
292 : << section_id_key << " or valid " << section_name_key
293 : << " is required.";
294 i : return false;
295 : }
296 :
297 : // Possibly over-ride the section characteristics. The characteristics are
298 : // required if the section was not given by id.
299 : if (section_spec->id == Reorderer::Order::SectionSpec::kNewSectionId ||
300 E : section_value->HasKey(section_characteristics_key)) {
301 E : int tmp_characteristics = 0;
302 : if (!section_value->GetInteger(section_characteristics_key,
303 E : &tmp_characteristics)) {
304 i : LOG(ERROR) << "Missing or invalid value for "
305 : << section_characteristics_key << ".";
306 i : return false;
307 : }
308 E : section_spec->characteristics = tmp_characteristics;
309 : }
310 :
311 : // Get the list of block specifications.
312 E : const ListValue* blocks = NULL;
313 E : if (!section_value->GetList(blocks_key, &blocks)) {
314 i : LOG(ERROR) << "Invalid or missing value for " << blocks_key << ".";
315 i : return false;
316 : }
317 :
318 E : if (!blocks->empty()) {
319 : // Populate the block spec vector.
320 E : section_spec->blocks.resize(blocks->GetSize());
321 E : ListValue::const_iterator block_it = blocks->begin();
322 E : for (size_t block_idx = 0; block_idx != blocks->GetSize(); ++block_idx) {
323 E : const Value* block_value = NULL;
324 E : if (!blocks->Get(block_idx, &block_value)) {
325 i : LOG(ERROR) << "Failed to access item " << block_idx << ".";
326 i : return false;
327 : }
328 :
329 E : if (!LoadBlockSpec(image, block_value, §ion_spec->blocks[block_idx]))
330 i : return false;
331 E : }
332 : }
333 :
334 E : return true;
335 E : }
336 :
337 : } // namespace
338 :
339 : const size_t Reorderer::Order::SectionSpec::kNewSectionId = ~1;
340 :
341 : Reorderer::Reorderer(const FilePath& module_path,
342 : const FilePath& instrumented_path,
343 : const TraceFileList& trace_files,
344 : Flags flags)
345 : : playback_(module_path, instrumented_path, trace_files),
346 : flags_(flags),
347 : code_block_entry_events_(0),
348 E : order_generator_(NULL) {
349 E : }
350 :
351 E : Reorderer::~Reorderer() {
352 E : }
353 :
354 : bool Reorderer::Reorder(OrderGenerator* order_generator,
355 : Order* order,
356 : PEFile* pe_file,
357 E : ImageLayout* image) {
358 E : DCHECK(order_generator != NULL);
359 E : DCHECK(order != NULL);
360 :
361 E : DCHECK(order_generator_ == NULL);
362 E : order_generator_ = order_generator;
363 :
364 E : bool success = ReorderImpl(order, pe_file, image);
365 :
366 E : order_generator_ = NULL;
367 :
368 E : return success;
369 E : }
370 :
371 : bool Reorderer::ReorderImpl(Order* order,
372 : PEFile* pe_file,
373 E : ImageLayout* image) {
374 E : DCHECK(order != NULL);
375 E : DCHECK(order_generator_ != NULL);
376 :
377 E : if (!parser_.Init(this)) {
378 i : LOG(ERROR) << "Failed to initialize call trace parser.";
379 :
380 i : return false;
381 : }
382 :
383 E : if (!playback_.Init(pe_file, image, &parser_))
384 i : return false;
385 :
386 E : if (playback_.trace_files().size() > 0) {
387 E : LOG(INFO) << "Processing trace events.";
388 E : if (!parser_.Consume())
389 i : return false;
390 :
391 E : if (code_block_entry_events_ == 0) {
392 i : LOG(ERROR) << "No events originated from the given instrumented DLL.";
393 i : return false;
394 : }
395 : }
396 :
397 E : if (!CalculateReordering(order))
398 i : return false;
399 :
400 E : return true;
401 E : }
402 :
403 E : bool Reorderer::CalculateReordering(Order* order) {
404 E : DCHECK(order != NULL);
405 E : DCHECK(order_generator_ != NULL);
406 :
407 E : LOG(INFO) << "Calculating new order.";
408 : if (!order_generator_->CalculateReordering(*playback_.pe_file(),
409 : *playback_.image(),
410 : (flags_ & kFlagReorderCode) != 0,
411 : (flags_ & kFlagReorderData) != 0,
412 E : order))
413 i : return false;
414 :
415 : order->comment = base::StringPrintf("Generated using the %s.",
416 E : order_generator_->name().c_str());
417 :
418 E : return true;
419 E : }
420 :
421 E : void Reorderer::OnProcessEnded(base::Time time, DWORD process_id) {
422 : // Notify the order generator.
423 E : if (!order_generator_->OnProcessEnded(process_id, UniqueTime(time))) {
424 i : parser_.set_error_occurred(true);
425 i : return;
426 : }
427 :
428 : // Cleanup the local record for process_id.
429 E : ignore_result(matching_process_ids_.erase(process_id));
430 E : }
431 :
432 : // CallTraceEvents implementation.
433 : void Reorderer::OnFunctionEntry(base::Time time,
434 : DWORD process_id,
435 : DWORD thread_id,
436 E : const TraceEnterExitEventData* data) {
437 E : DCHECK(data != NULL);
438 :
439 : const BlockGraph::Block* block = playback_.FindFunctionBlock(process_id,
440 E : data->function);
441 :
442 E : if (block == NULL) {
443 i : parser_.set_error_occurred(true);
444 i : return;
445 : }
446 :
447 : // Get the time of the call. Since batched function calls come in with the
448 : // same time stamp, we rely on their relative ordering and UniqueTime's
449 : // incrementing ID to maintain relative order.
450 E : UniqueTime entry_time(time);
451 :
452 : // If this is the first call of interest by a given process, send an
453 : // OnProcessStarted event.
454 E : if (matching_process_ids_.insert(process_id).second) {
455 E : if (!order_generator_->OnProcessStarted(process_id, entry_time)) {
456 i : parser_.set_error_occurred(true);
457 i : return;
458 : }
459 : }
460 :
461 E : ++code_block_entry_events_;
462 : if (!order_generator_->OnCodeBlockEntry(block,
463 : block->addr(),
464 : process_id,
465 : thread_id,
466 E : entry_time)) {
467 i : parser_.set_error_occurred(true);
468 : return;
469 : }
470 E : }
471 :
472 : void Reorderer::OnBatchFunctionEntry(base::Time time,
473 : DWORD process_id,
474 : DWORD thread_id,
475 E : const TraceBatchEnterData* data) {
476 : // Explode the batch event into individual function entry events.
477 E : TraceEnterExitEventData new_data = {};
478 E : for (size_t i = 0; i < data->num_calls; ++i) {
479 E : new_data.function = data->calls[i].function;
480 E : OnFunctionEntry(time, process_id, thread_id, &new_data);
481 E : }
482 E : }
483 :
484 : bool Reorderer::Order::SerializeToJSON(const PEFile& pe,
485 : const FilePath &path,
486 E : bool pretty_print) const {
487 E : file_util::ScopedFILE file(file_util::OpenFile(path, "wb"));
488 E : if (file.get() == NULL)
489 i : return false;
490 E : core::JSONFileWriter json_file(file.get(), pretty_print);
491 E : return SerializeToJSON(pe, &json_file);
492 E : }
493 :
494 : bool Reorderer::Order::SerializeToJSON(const PEFile& pe,
495 E : core::JSONFileWriter* json_file) const {
496 E : DCHECK(json_file != NULL);
497 :
498 : // Open the top-level dictionary and the metadata dictionary.
499 E : if (!json_file->OpenDict())
500 i : return false;
501 :
502 : // Output the filecomment.
503 : if (!json_file->OutputKey(kCommentKey) ||
504 E : !json_file->OutputString(comment)) {
505 i : return false;
506 : }
507 :
508 : // Output metadata.
509 E : PEFile::Signature orig_sig;
510 E : pe.GetSignature(&orig_sig);
511 E : pe::Metadata metadata;
512 : if (!metadata.Init(orig_sig) ||
513 : !json_file->OutputKey(kMetadataKey) ||
514 E : !metadata.SaveToJSON(json_file)) {
515 i : return false;
516 : }
517 :
518 : // Open list of sections.
519 : if (!json_file->OutputKey(kSectionsKey) ||
520 E : !json_file->OpenList()) {
521 i : return false;
522 : }
523 :
524 : // Output the individual block lists.
525 E : SectionSpecVector::const_iterator it = sections.begin();
526 E : for (; it != sections.end(); ++it) {
527 E : const SectionSpec& section_spec = *it;
528 E : if (section_spec.blocks.empty())
529 i : continue;
530 :
531 E : if (!OutputSectionSpec(section_spec, json_file))
532 i : return false;
533 E : }
534 :
535 : // Close the list of sections.
536 E : if (!json_file->CloseList())
537 i : return false;
538 :
539 : // Close the outermost dictionary.
540 E : if (!json_file->CloseDict())
541 i : return false;
542 :
543 E : return true;
544 E : }
545 :
546 : bool Reorderer::Order::LoadFromJSON(const PEFile& pe,
547 : const ImageLayout& image,
548 E : const FilePath& path) {
549 E : std::string file_string;
550 E : if (!file_util::ReadFileToString(path, &file_string)) {
551 i : LOG(ERROR) << "Unable to read order file to string";
552 i : return false;
553 : }
554 :
555 : // Read in the JSON file. It should be a dictionary.
556 E : const DictionaryValue* outer_dict = NULL;
557 E : scoped_ptr<Value> value(base::JSONReader::Read(file_string, false));
558 E : if (value.get() == NULL || !value->GetAsDictionary(&outer_dict)) {
559 i : LOG(ERROR) << "Order file does not contain a valid JSON dictionary.";
560 i : return false;
561 : }
562 :
563 : // Load the metadata from the order file, and ensure it is consistent with
564 : // the signature of the module the ordering is being applied to.
565 E : pe::Metadata metadata;
566 E : PEFile::Signature pe_sig;
567 E : pe.GetSignature(&pe_sig);
568 E : const DictionaryValue* metadata_dict = NULL;
569 : if (!outer_dict->GetDictionary(kMetadataKey, &metadata_dict) ||
570 : !metadata.LoadFromJSON(*metadata_dict) ||
571 E : !metadata.IsConsistent(pe_sig)) {
572 i : LOG(ERROR) << "Missing, invalid, or inconsistent " << kMetadataKey << ".";
573 i : return false;
574 : }
575 :
576 : // Load the comments field.
577 : if (outer_dict->HasKey(kCommentKey) &&
578 E : !outer_dict->GetString(kCommentKey, &comment)) {
579 i : LOG(ERROR) << "Invalid " << kCommentKey << " value. Must be a string.";
580 i : return false;
581 : }
582 :
583 : // Grab the sections list.
584 E : const ListValue* order = NULL;
585 E : if (!outer_dict->GetList(kSectionsKey, &order)) {
586 i : LOG(ERROR) << "Missing or invalid " << kSectionsKey << ".";
587 i : return false;
588 : }
589 :
590 : // Allocate the expected number of sections.
591 E : sections.clear();
592 :
593 : // Iterate through the elements of the list. They should each be dictionaries
594 : // representing a single section. We'll also track the sections descriptions
595 : // we have already seen.
596 E : std::set<size_t> seen_section_ids;
597 E : sections.resize(order->GetSize());
598 E : for (size_t index = 0; index < order->GetSize(); ++index) {
599 E : const DictionaryValue* section = NULL;
600 E : if (!order->GetDictionary(index, §ion)) {
601 i : LOG(ERROR) << "Item " << index << "of " << kSectionsKey
602 : << " list is not a dictionary.";
603 i : return false;
604 : }
605 :
606 E : if (!LoadSectionSpec(image, section, §ions[index], &seen_section_ids))
607 i : return false;
608 E : }
609 :
610 E : return true;
611 E : }
612 :
613 : bool Reorderer::Order::GetOriginalModulePath(const FilePath& path,
614 E : FilePath* module) {
615 E : std::string file_string;
616 E : if (!file_util::ReadFileToString(path, &file_string)) {
617 E : LOG(ERROR) << "Unable to read order file to string.";
618 E : return false;
619 : }
620 :
621 E : scoped_ptr<Value> value(base::JSONReader::Read(file_string, false));
622 E : if (value.get() == NULL || value->GetType() != Value::TYPE_DICTIONARY) {
623 i : LOG(ERROR) << "Order file does not contain a valid JSON dictionary.";
624 i : return false;
625 : }
626 : const DictionaryValue* outer_dict =
627 E : reinterpret_cast<const DictionaryValue*>(value.get());
628 :
629 E : std::string metadata_key("metadata");
630 E : const DictionaryValue* metadata_dict = NULL;
631 E : if (!outer_dict->GetDictionary(metadata_key, &metadata_dict)) {
632 i : LOG(ERROR) << "Order dictionary must contain 'metadata'.";
633 i : return false;
634 : }
635 :
636 E : pe::Metadata metadata;
637 E : if (!metadata.LoadFromJSON(*metadata_dict))
638 i : return false;
639 :
640 E : *module = FilePath(metadata.module_signature().path);
641 :
642 E : return true;
643 E : }
644 :
645 : Reorderer::UniqueTime::UniqueTime()
646 : : time_(),
647 : id_(0) {
648 : }
649 :
650 : Reorderer::UniqueTime::UniqueTime(const UniqueTime& other)
651 : : time_(other.time_),
652 E : id_(other.id_) {
653 E : }
654 :
655 : Reorderer::UniqueTime::UniqueTime(const base::Time& time)
656 : : time_(time),
657 E : id_(next_id_++) {
658 E : }
659 :
660 E : Reorderer::UniqueTime& Reorderer::UniqueTime::operator=(const UniqueTime& rhs) {
661 E : time_ = rhs.time_;
662 E : id_ = rhs.id_;
663 E : return *this;
664 E : }
665 :
666 E : int Reorderer::UniqueTime::compare(const UniqueTime& rhs) const {
667 E : if (time_ < rhs.time_)
668 i : return -1;
669 E : if (time_ > rhs.time_)
670 i : return 1;
671 E : if (id_ < rhs.id_)
672 E : return -1;
673 E : if (id_ > rhs.id_)
674 E : return 1;
675 E : return 0;
676 E : }
677 :
678 : size_t Reorderer::UniqueTime::next_id_ = 0;
679 :
680 : } // namespace reorder
|