ProcessResult MinidumpProcessor::Process()

in src/processor/minidump_processor.cc [80:328]


ProcessResult MinidumpProcessor::Process(
    Minidump *dump, ProcessState *process_state) {
  assert(dump);
  assert(process_state);

  process_state->Clear();

  const MDRawHeader *header = dump->header();
  if (!header) {
    BPLOG(ERROR) << "Minidump " << dump->path() << " has no header";
    return PROCESS_ERROR_NO_MINIDUMP_HEADER;
  }
  process_state->time_date_stamp_ = header->time_date_stamp;

  bool has_process_create_time =
      GetProcessCreateTime(dump, &process_state->process_create_time_);

  bool has_cpu_info = GetCPUInfo(dump, &process_state->system_info_);
  bool has_os_info = GetOSInfo(dump, &process_state->system_info_);

  uint32_t dump_thread_id = 0;
  bool has_dump_thread = false;
  uint32_t requesting_thread_id = 0;
  bool has_requesting_thread = false;

  MinidumpBreakpadInfo *breakpad_info = dump->GetBreakpadInfo();
  if (breakpad_info) {
    has_dump_thread = breakpad_info->GetDumpThreadID(&dump_thread_id);
    has_requesting_thread =
        breakpad_info->GetRequestingThreadID(&requesting_thread_id);
  }

  MinidumpException *exception = dump->GetException();
  if (exception) {
    process_state->crashed_ = true;
    has_requesting_thread = exception->GetThreadID(&requesting_thread_id);

    process_state->crash_reason_ = GetCrashReason(
        dump, &process_state->crash_address_);
  }

  // This will just return an empty string if it doesn't exist.
  process_state->assertion_ = GetAssertion(dump);

  MinidumpModuleList *module_list = dump->GetModuleList();

  // Put a copy of the module list into ProcessState object.  This is not
  // necessarily a MinidumpModuleList, but it adheres to the CodeModules
  // interface, which is all that ProcessState needs to expose.
  if (module_list) {
    process_state->modules_ = module_list->Copy();
    process_state->shrunk_range_modules_ =
        process_state->modules_->GetShrunkRangeModules();
    for (unsigned int i = 0;
         i < process_state->shrunk_range_modules_.size();
         i++) {
      linked_ptr<const CodeModule> module =
          process_state->shrunk_range_modules_[i];
      BPLOG(INFO) << "The range for module " << module->code_file()
                  << " was shrunk down by " << HexString(
                      module->shrink_down_delta()) << " bytes. ";
    }
  }

  MinidumpUnloadedModuleList *unloaded_module_list =
      dump->GetUnloadedModuleList();
  if (unloaded_module_list) {
    process_state->unloaded_modules_ = unloaded_module_list->Copy();
  }

  MinidumpMemoryList *memory_list = dump->GetMemoryList();
  if (memory_list) {
    BPLOG(INFO) << "Found " << memory_list->region_count()
                << " memory regions.";
  }

  MinidumpThreadList *threads = dump->GetThreadList();
  if (!threads) {
    BPLOG(ERROR) << "Minidump " << dump->path() << " has no thread list";
    return PROCESS_ERROR_NO_THREAD_LIST;
  }

  BPLOG(INFO) << "Minidump " << dump->path() << " has " <<
      (has_cpu_info            ? "" : "no ") << "CPU info, " <<
      (has_os_info             ? "" : "no ") << "OS info, " <<
      (breakpad_info != NULL   ? "" : "no ") << "Breakpad info, " <<
      (exception != NULL       ? "" : "no ") << "exception, " <<
      (module_list != NULL     ? "" : "no ") << "module list, " <<
      (threads != NULL         ? "" : "no ") << "thread list, " <<
      (has_dump_thread         ? "" : "no ") << "dump thread, " <<
      (has_requesting_thread   ? "" : "no ") << "requesting thread, and " <<
      (has_process_create_time ? "" : "no ") << "process create time";

  bool interrupted = false;
  bool found_requesting_thread = false;
  unsigned int thread_count = threads->thread_count();

  // Reset frame_symbolizer_ at the beginning of stackwalk for each minidump.
  frame_symbolizer_->Reset();

  for (unsigned int thread_index = 0;
       thread_index < thread_count;
       ++thread_index) {
    char thread_string_buffer[64];
    snprintf(thread_string_buffer, sizeof(thread_string_buffer), "%d/%d",
             thread_index, thread_count);
    string thread_string = dump->path() + ":" + thread_string_buffer;

    MinidumpThread *thread = threads->GetThreadAtIndex(thread_index);
    if (!thread) {
      BPLOG(ERROR) << "Could not get thread for " << thread_string;
      return PROCESS_ERROR_GETTING_THREAD;
    }

    uint32_t thread_id;
    if (!thread->GetThreadID(&thread_id)) {
      BPLOG(ERROR) << "Could not get thread ID for " << thread_string;
      return PROCESS_ERROR_GETTING_THREAD_ID;
    }

    thread_string += " id " + HexString(thread_id);
    BPLOG(INFO) << "Looking at thread " << thread_string;

    // If this thread is the thread that produced the minidump, don't process
    // it.  Because of the problems associated with a thread producing a
    // dump of itself (when both its context and its stack are in flux),
    // processing that stack wouldn't provide much useful data.
    if (has_dump_thread && thread_id == dump_thread_id) {
      continue;
    }

    MinidumpContext *context = thread->GetContext();

    if (has_requesting_thread && thread_id == requesting_thread_id) {
      if (found_requesting_thread) {
        // There can't be more than one requesting thread.
        BPLOG(ERROR) << "Duplicate requesting thread: " << thread_string;
        return PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS;
      }

      // Use processed_state->threads_.size() instead of thread_index.
      // thread_index points to the thread index in the minidump, which
      // might be greater than the thread index in the threads vector if
      // any of the minidump's threads are skipped and not placed into the
      // processed threads vector.  The thread vector's current size will
      // be the index of the current thread when it's pushed into the
      // vector.
      process_state->requesting_thread_ = process_state->threads_.size();

      found_requesting_thread = true;

      if (process_state->crashed_) {
        // Use the exception record's context for the crashed thread, instead
        // of the thread's own context.  For the crashed thread, the thread's
        // own context is the state inside the exception handler.  Using it
        // would not result in the expected stack trace from the time of the
        // crash. If the exception context is invalid, however, we fall back
        // on the thread context.
        MinidumpContext *ctx = exception->GetContext();
        context = ctx ? ctx : thread->GetContext();
      }
    }

    // If the memory region for the stack cannot be read using the RVA stored
    // in the memory descriptor inside MINIDUMP_THREAD, try to locate and use
    // a memory region (containing the stack) from the minidump memory list.
    MinidumpMemoryRegion *thread_memory = thread->GetMemory();
    if (!thread_memory && memory_list) {
      uint64_t start_stack_memory_range = thread->GetStartOfStackMemoryRange();
      if (start_stack_memory_range) {
        thread_memory = memory_list->GetMemoryRegionForAddress(
           start_stack_memory_range);
      }
    }
    if (!thread_memory) {
      BPLOG(ERROR) << "No memory region for " << thread_string;
    }

    // Use process_state->modules_ instead of module_list, because the
    // |modules| argument will be used to populate the |module| fields in
    // the returned StackFrame objects, which will be placed into the
    // returned ProcessState object.  module_list's lifetime is only as
    // long as the Minidump object: it will be deleted when this function
    // returns.  process_state->modules_ is owned by the ProcessState object
    // (just like the StackFrame objects), and is much more suitable for this
    // task.
    scoped_ptr<Stackwalker> stackwalker(
        Stackwalker::StackwalkerForCPU(process_state->system_info(),
                                       context,
                                       thread_memory,
                                       process_state->modules_,
                                       process_state->unloaded_modules_,
                                       frame_symbolizer_));

    scoped_ptr<CallStack> stack(new CallStack());
    if (stackwalker.get()) {
      if (!stackwalker->Walk(stack.get(),
                             &process_state->modules_without_symbols_,
                             &process_state->modules_with_corrupt_symbols_)) {
        BPLOG(INFO) << "Stackwalker interrupt (missing symbols?) at "
                    << thread_string;
        interrupted = true;
      }
    } else {
      // Threads with missing CPU contexts will hit this, but
      // don't abort processing the rest of the dump just for
      // one bad thread.
      BPLOG(ERROR) << "No stackwalker for " << thread_string;
    }
    stack->set_tid(thread_id);
    process_state->threads_.push_back(stack.release());
    process_state->thread_memory_regions_.push_back(thread_memory);
  }

  if (interrupted) {
    BPLOG(INFO) << "Processing interrupted for " << dump->path();
    return PROCESS_SYMBOL_SUPPLIER_INTERRUPTED;
  }

  // If a requesting thread was indicated, it must be present.
  if (has_requesting_thread && !found_requesting_thread) {
    // Don't mark as an error, but invalidate the requesting thread
    BPLOG(ERROR) << "Minidump indicated requesting thread " <<
        HexString(requesting_thread_id) << ", not found in " <<
        dump->path();
    process_state->requesting_thread_ = -1;
  }

  // Exploitability defaults to EXPLOITABILITY_NOT_ANALYZED
  process_state->exploitability_ = EXPLOITABILITY_NOT_ANALYZED;

  // If an exploitability run was requested we perform the platform specific
  // rating.
  if (enable_exploitability_) {
    scoped_ptr<Exploitability> exploitability(
        Exploitability::ExploitabilityForPlatform(dump,
                                                  process_state,
                                                  enable_objdump_));
    // The engine will be null if the platform is not supported
    if (exploitability != NULL) {
      process_state->exploitability_ = exploitability->CheckExploitability();
    } else {
      process_state->exploitability_ = EXPLOITABILITY_ERR_NOENGINE;
    }
  }

  BPLOG(INFO) << "Processed " << dump->path();
  return PROCESS_OK;
}