summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-patches/21-thread-names.patch
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-patches/21-thread-names.patch')
-rw-r--r--toolkit/crashreporter/breakpad-patches/21-thread-names.patch661
1 files changed, 661 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-patches/21-thread-names.patch b/toolkit/crashreporter/breakpad-patches/21-thread-names.patch
new file mode 100644
index 0000000000..ea4dd67cd1
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-patches/21-thread-names.patch
@@ -0,0 +1,661 @@
+diff --git a/src/google_breakpad/common/minidump_format.h b/src/google_breakpad/common/minidump_format.h
+--- a/src/google_breakpad/common/minidump_format.h
++++ b/src/google_breakpad/common/minidump_format.h
+@@ -227,17 +227,18 @@ typedef struct {
+
+ /*
+ * DbgHelp.h
+ */
+
+
+ /* An MDRVA is an offset into the minidump file. The beginning of the
+ * MDRawHeader is at offset 0. */
+-typedef uint32_t MDRVA; /* RVA */
++typedef uint32_t MDRVA; /* RVA */
++typedef uint64_t MDRVA64; /* RVA64 */
+
+ typedef struct {
+ uint32_t data_size;
+ MDRVA rva;
+ } MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */
+
+
+ typedef struct {
+@@ -327,16 +328,18 @@ typedef enum {
+ MD_MISC_INFO_STREAM = 15, /* MDRawMiscInfo */
+ MD_MEMORY_INFO_LIST_STREAM = 16, /* MDRawMemoryInfoList */
+ MD_THREAD_INFO_LIST_STREAM = 17,
+ MD_HANDLE_OPERATION_LIST_STREAM = 18,
+ MD_TOKEN_STREAM = 19,
+ MD_JAVASCRIPT_DATA_STREAM = 20,
+ MD_SYSTEM_MEMORY_INFO_STREAM = 21,
+ MD_PROCESS_VM_COUNTERS_STREAM = 22,
++ MD_IPT_TRACE_STREAM = 23,
++ MD_THREAD_NAMES_STREAM = 24,
+ MD_LAST_RESERVED_STREAM = 0x0000ffff,
+
+ /* Breakpad extension types. 0x4767 = "Gg" */
+ MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */
+ MD_ASSERTION_INFO_STREAM = 0x47670002, /* MDRawAssertionInfo */
+ /* These are additional minidump stream values which are specific to
+ * the linux breakpad implementation. */
+ MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
+@@ -1117,16 +1120,26 @@ typedef struct {
+ * module_path;
+ * message;
+ * signature_string;
+ * backtrace;
+ * message2; */
+ uint8_t data[0];
+ } MDRawMacCrashInfoRecord;
+
++typedef struct __attribute__((packed,aligned(4))) {
++ uint32_t thread_id;
++ MDRVA64 rva_of_thread_name;
++} MDRawThreadName;
++
++typedef struct {
++ uint32_t number_of_thread_names;
++ MDRawThreadName thread_names[0];
++} MDRawThreadNamesList;
++
+ /* This is the maximum supported size for each string in
+ * (MDRawMacCrashInfoRecord).data. If we encounter a string in the
+ * __crash_info section which seems larger than this, that's a sign of data
+ * corruption. */
+ #define MACCRASHINFO_STRING_MAXSIZE 8192
+
+ /* In principle there should only be one or two non-empty __DATA,__crash_info
+ * sections per process. But the __crash_info section is almost entirely
+diff --git a/src/google_breakpad/processor/call_stack.h b/src/google_breakpad/processor/call_stack.h
+--- a/src/google_breakpad/processor/call_stack.h
++++ b/src/google_breakpad/processor/call_stack.h
+@@ -41,21 +41,23 @@
+ // beginning with the innermost callee frame.
+ //
+ // Author: Mark Mentovai
+
+ #ifndef GOOGLE_BREAKPAD_PROCESSOR_CALL_STACK_H__
+ #define GOOGLE_BREAKPAD_PROCESSOR_CALL_STACK_H__
+
+ #include <cstdint>
++#include <string>
+ #include <vector>
+
+ namespace google_breakpad {
+
+ using std::vector;
++using std::string;
+
+ struct StackFrame;
+ template<typename T> class linked_ptr;
+
+ class CallStack {
+ public:
+ CallStack() { Clear(); }
+ ~CallStack();
+@@ -63,29 +65,33 @@ class CallStack {
+ // Resets the CallStack to its initial empty state
+ void Clear();
+
+ const vector<StackFrame*>* frames() const { return &frames_; }
+
+ // Set the TID associated with this call stack.
+ void set_tid(uint32_t tid) { tid_ = tid; }
+ void set_last_error(uint32_t last_error) { last_error_ = last_error; }
++ void set_name(const string& name) { name_ = name; }
+
+ uint32_t tid() const { return tid_; }
+ uint32_t last_error() const { return last_error_; }
++ const string name() const { return name_; }
+
+ private:
+ // Stackwalker is responsible for building the frames_ vector.
+ friend class Stackwalker;
+
+ // Storage for pushed frames.
+ vector<StackFrame*> frames_;
+
+ // The TID associated with this call stack. Default to 0 if it's not
+ // available.
+ uint32_t tid_;
+ // The last error the OS set for this thread (win32's GetLastError())
+ uint32_t last_error_;
++ // The name of this thread, empty if it's not available
++ string name_;
+ };
+
+ } // namespace google_breakpad
+
+ #endif // GOOGLE_BREAKPAD_PROCSSOR_CALL_STACK_H__
+diff --git a/src/google_breakpad/processor/minidump.h b/src/google_breakpad/processor/minidump.h
+--- a/src/google_breakpad/processor/minidump.h
++++ b/src/google_breakpad/processor/minidump.h
+@@ -1197,16 +1197,96 @@ class MinidumpMacCrashInfo : public Mini
+ bool ReadCrashInfoRecord(MDLocationDescriptor location,
+ uint32_t record_start_size);
+ bool Read(uint32_t expected_size);
+
+ string description_;
+ vector<crash_info_record_t> records_;
+ };
+
++// MinidumpThreadName wraps MDRawThreadName
++class MinidumpThreadName : public MinidumpObject {
++ public:
++ ~MinidumpThreadName() override;
++
++ const MDRawThreadName* thread_name() const {
++ if (valid_) {
++ return &thread_name_;
++ }
++
++ return NULL;
++ }
++
++ uint32_t thread_id() const {
++ if (valid_) {
++ return thread_name_.thread_id;
++ }
++
++ return 0;
++ }
++
++ string name() const;
++
++ // Print a human-readable representation of the object to stdout.
++ void Print();
++
++ protected:
++ explicit MinidumpThreadName(Minidump* minidump);
++
++ private:
++ // These objects are managed by MinidumpThreadNameList
++ friend class MinidumpThreadNamesList;
++
++ // This works like MinidumpStream::Read, but is driven by
++ // MinidumpThreadNameList.
++ bool Read(uint32_t expected_size);
++
++ // Reads the thread name. This is done separately from Read to
++ // allow contiguous reading of thread names by MinidumpThreadNameList.
++ bool ReadAuxiliaryData();
++
++ bool valid_;
++ MDRawThreadName thread_name_;
++ const string* name_;
++};
++
++
++// MinidumpThreadNamesList contains all the names for threads in a process
++// in the form of MinidumpThreadNames.
++class MinidumpThreadNamesList : public MinidumpStream {
++ public:
++ ~MinidumpThreadNamesList() override;
++
++ unsigned int name_count() const {
++ return valid_ ? name_count_ : 0;
++ }
++
++ const string GetNameForThreadId(uint32_t thread_id) const;
++
++ // Print a human-readable representation of the object to stdout.
++ void Print();
++
++ protected:
++ explicit MinidumpThreadNamesList(Minidump* minidump_);
++
++ private:
++ friend class Minidump;
++
++ typedef vector<MinidumpThreadName> MinidumpThreadNames;
++
++ static const uint32_t kStreamType = MD_THREAD_NAMES_STREAM;
++
++ bool Read(uint32_t expected_size_) override;
++
++ MinidumpThreadNames* thread_names_;
++ uint32_t name_count_;
++ bool valid_;
++
++ DISALLOW_COPY_AND_ASSIGN(MinidumpThreadNamesList);
++};
+
+ // Minidump is the user's interface to a minidump file. It wraps MDRawHeader
+ // and provides access to the minidump's top-level stream directory.
+ class Minidump {
+ public:
+ // path is the pathname of a file containing the minidump.
+ explicit Minidump(const string& path,
+ bool hexdump=false,
+@@ -1261,16 +1343,17 @@ class Minidump {
+ virtual MinidumpAssertion* GetAssertion();
+ virtual MinidumpSystemInfo* GetSystemInfo();
+ virtual MinidumpUnloadedModuleList* GetUnloadedModuleList();
+ virtual MinidumpMiscInfo* GetMiscInfo();
+ virtual MinidumpBreakpadInfo* GetBreakpadInfo();
+ virtual MinidumpMemoryInfoList* GetMemoryInfoList();
+ MinidumpCrashpadInfo* GetCrashpadInfo();
+ MinidumpMacCrashInfo* GetMacCrashInfo();
++ MinidumpThreadNamesList* GetThreadNamesList();
+
+ // The next method also calls GetStream, but is exclusive for Linux dumps.
+ virtual MinidumpLinuxMapsList *GetLinuxMapsList();
+
+ // The next set of methods are provided for users who wish to access
+ // data in minidump files directly, while leveraging the rest of
+ // this class and related classes to handle the basic minidump
+ // structure and known stream types.
+diff --git a/src/processor/call_stack.cc b/src/processor/call_stack.cc
+--- a/src/processor/call_stack.cc
++++ b/src/processor/call_stack.cc
+@@ -45,11 +45,12 @@ CallStack::~CallStack() {
+ void CallStack::Clear() {
+ for (vector<StackFrame *>::const_iterator iterator = frames_.begin();
+ iterator != frames_.end();
+ ++iterator) {
+ delete *iterator;
+ }
+ tid_ = 0;
+ last_error_ = 0;
++ name_ = "";
+ }
+
+ } // namespace google_breakpad
+diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc
+--- a/src/processor/minidump.cc
++++ b/src/processor/minidump.cc
+@@ -5724,16 +5724,226 @@ MinidumpCrashpadInfo* Minidump::GetCrash
+ return GetStream(&crashpad_info);
+ }
+
+ MinidumpMacCrashInfo* Minidump::GetMacCrashInfo() {
+ MinidumpMacCrashInfo* mac_crash_info;
+ return GetStream(&mac_crash_info);
+ }
+
++MinidumpThreadNamesList* Minidump::GetThreadNamesList() {
++ MinidumpThreadNamesList* thread_names_list;
++ return GetStream(&thread_names_list);
++}
++
++//
++// MinidumpThreadName
++//
++
++
++MinidumpThreadName::MinidumpThreadName(Minidump* minidump)
++ : MinidumpObject(minidump),
++ valid_(false),
++ thread_name_(),
++ name_(NULL) {
++
++}
++
++MinidumpThreadName::~MinidumpThreadName() {
++ ;
++}
++
++void MinidumpThreadName::Print() {
++ if (!valid_) {
++ BPLOG(ERROR) << "MinidumpThreadName cannot print invalid data";
++ return;
++ }
++
++ printf("MDRawThreadName\n");
++ printf(" thread_id = 0x%x\n",
++ thread_name_.thread_id);
++ printf(" rva_of_thread_name = 0x%" PRIx64 "\n",
++ thread_name_.rva_of_thread_name);
++
++ printf(" (name) = \"%s\"\n", name().c_str());
++ printf("\n");
++}
++
++string MinidumpThreadName::name() const {
++ if (!valid_) {
++ BPLOG(ERROR) << "Invalid MinidumpThreadName for name";
++ return "";
++ }
++
++ return *name_;
++}
++
++bool MinidumpThreadName::Read(uint32_t expected_size) {
++
++ delete name_;
++
++ if (expected_size < sizeof(thread_name_)) {
++ BPLOG(ERROR) << "MinidumpThreadName expected size is less than size "
++ << "of struct " << expected_size << " < "
++ << sizeof(thread_name_);
++ return false;
++ }
++
++ if (!minidump_->ReadBytes(&thread_name_, sizeof(thread_name_))) {
++ BPLOG(ERROR) << "MinidumpThreadName cannot read name";
++ return false;
++ }
++
++ if (expected_size > sizeof(thread_name_)) {
++ uint32_t thread_name_bytes_remaining = expected_size - sizeof(thread_name_);
++ off_t pos = minidump_->Tell();
++ if (!minidump_->SeekSet(pos + thread_name_bytes_remaining)) {
++ BPLOG(ERROR) << "MinidumpThreadName unable to seek to end of name";
++ return false;
++ }
++ }
++
++ if (minidump_->swap()) {
++ Swap(&thread_name_.thread_id);
++ uint64_t rva_of_thread_name;
++ memcpy(&rva_of_thread_name, &thread_name_.rva_of_thread_name, sizeof(uint64_t));
++ Swap(&rva_of_thread_name);
++ memcpy(&thread_name_.rva_of_thread_name, &rva_of_thread_name, sizeof(uint64_t));
++ }
++
++ return true;
++}
++
++bool MinidumpThreadName::ReadAuxiliaryData() {
++ // Each thread must have a name string.
++ name_ = minidump_->ReadString(thread_name_.rva_of_thread_name);
++ if (!name_) {
++ BPLOG(ERROR) << "MinidumpThreadName could not read name";
++ valid_ = false;
++ return false;
++ }
++
++ // At this point, we have enough info for the name to be valid.
++ valid_ = true;
++ return true;
++}
++
++//
++// MinidumpThreadNamesList
++//
++
++
++MinidumpThreadNamesList::MinidumpThreadNamesList(Minidump* minidump)
++ : MinidumpStream(minidump),
++ thread_names_(NULL),
++ name_count_(0),
++ valid_(false) {
++ ;
++}
++
++MinidumpThreadNamesList::~MinidumpThreadNamesList() {
++ delete thread_names_;
++}
++
++const string MinidumpThreadNamesList::GetNameForThreadId(uint32_t thread_id) const {
++ if (valid_) {
++ for (unsigned int name_index = 0;
++ name_index < name_count_;
++ ++name_index) {
++ const MinidumpThreadName& thread_name = (*thread_names_)[name_index];
++ if (thread_name.thread_id() == thread_id) {
++ return thread_name.name();
++ }
++ }
++ }
++
++ return "";
++}
++
++void MinidumpThreadNamesList::Print() {
++ if (!valid_) {
++ BPLOG(ERROR) << "MinidumpThreadNamesList cannot print invalid data";
++ return;
++ }
++
++ printf("MinidumpThreadNamesList\n");
++ printf(" name_count = %d\n", name_count_);
++ printf("\n");
++
++ for (unsigned int name_index = 0;
++ name_index < name_count_;
++ ++name_index) {
++ printf("thread_name[%d]\n", name_index);
++
++ (*thread_names_)[name_index].Print();
++ }
++}
++
++bool MinidumpThreadNamesList::Read(uint32_t expected_size) {
++ delete thread_names_;
++ thread_names_ = NULL;
++ name_count_ = 0;
++
++ valid_ = false;
++
++ uint32_t number_of_thread_names;
++ if (!minidump_->ReadBytes(&number_of_thread_names, sizeof(number_of_thread_names))) {
++ BPLOG(ERROR) << "MinidumpThreadNamesList could not read the number of thread names";
++ return false;
++ }
++
++ if (minidump_->swap()) {
++ Swap(&number_of_thread_names);
++ }
++
++ if (expected_size !=
++ sizeof(number_of_thread_names) + (sizeof(MDRawThreadName) * number_of_thread_names)) {
++ BPLOG(ERROR) << "MinidumpThreadNamesList expected_size mismatch " <<
++ expected_size << " != " << sizeof(number_of_thread_names) << " + (" <<
++ sizeof(MDRawThreadName) << " * " << number_of_thread_names << ")";
++ return false;
++ }
++
++ if (number_of_thread_names != 0) {
++ scoped_ptr<MinidumpThreadNames> thread_names(
++ new MinidumpThreadNames(number_of_thread_names,
++ MinidumpThreadName(minidump_)));
++
++ for (unsigned int name_index = 0;
++ name_index < number_of_thread_names;
++ ++name_index) {
++ MinidumpThreadName* thread_name = &(*thread_names)[name_index];
++
++ if (!thread_name->Read(sizeof(MDRawThreadName))) {
++ BPLOG(ERROR) << "MinidumpThreadNamesList could not read name " <<
++ name_index << "/" << number_of_thread_names;
++ return false;
++ }
++ }
++
++ for (unsigned int name_index = 0;
++ name_index < number_of_thread_names;
++ ++name_index) {
++ MinidumpThreadName* thread_name = &(*thread_names)[name_index];
++
++ if (!thread_name->ReadAuxiliaryData()) {
++ BPLOG(ERROR) << "MinidumpThreadNamesList could not read required "
++ "auxiliary data for thread name " <<
++ name_index << "/" << number_of_thread_names;
++ return false;
++ }
++ }
++ thread_names_ = thread_names.release();
++ }
++
++ name_count_ = number_of_thread_names;
++ valid_ = true;
++ return true;
++}
++
+ static const char* get_stream_name(uint32_t stream_type) {
+ switch (stream_type) {
+ case MD_UNUSED_STREAM:
+ return "MD_UNUSED_STREAM";
+ case MD_RESERVED_STREAM_0:
+ return "MD_RESERVED_STREAM_0";
+ case MD_RESERVED_STREAM_1:
+ return "MD_RESERVED_STREAM_1";
+@@ -5772,16 +5982,20 @@ static const char* get_stream_name(uint3
+ case MD_TOKEN_STREAM:
+ return "MD_TOKEN_STREAM";
+ case MD_JAVASCRIPT_DATA_STREAM:
+ return "MD_JAVASCRIPT_DATA_STREAM";
+ case MD_SYSTEM_MEMORY_INFO_STREAM:
+ return "MD_SYSTEM_MEMORY_INFO_STREAM";
+ case MD_PROCESS_VM_COUNTERS_STREAM:
+ return "MD_PROCESS_VM_COUNTERS_STREAM";
++ case MD_IPT_TRACE_STREAM:
++ return "MD_IPT_TRACE_STREAM";
++ case MD_THREAD_NAMES_STREAM:
++ return "MD_THREAD_NAMES_STREAM";
+ case MD_LAST_RESERVED_STREAM:
+ return "MD_LAST_RESERVED_STREAM";
+ case MD_BREAKPAD_INFO_STREAM:
+ return "MD_BREAKPAD_INFO_STREAM";
+ case MD_ASSERTION_INFO_STREAM:
+ return "MD_ASSERTION_INFO_STREAM";
+ case MD_LINUX_CPU_INFO:
+ return "MD_LINUX_CPU_INFO";
+diff --git a/src/processor/minidump_dump.cc b/src/processor/minidump_dump.cc
+--- a/src/processor/minidump_dump.cc
++++ b/src/processor/minidump_dump.cc
+@@ -49,16 +49,17 @@ using google_breakpad::MinidumpUnloadedM
+ using google_breakpad::MinidumpMemoryInfoList;
+ using google_breakpad::MinidumpMemoryList;
+ using google_breakpad::MinidumpException;
+ using google_breakpad::MinidumpAssertion;
+ using google_breakpad::MinidumpSystemInfo;
+ using google_breakpad::MinidumpMiscInfo;
+ using google_breakpad::MinidumpBreakpadInfo;
+ using google_breakpad::MinidumpCrashpadInfo;
++using google_breakpad::MinidumpThreadNamesList;
+
+ struct Options {
+ Options()
+ : minidumpPath(), hexdump(false), hexdump_width(16) {}
+
+ string minidumpPath;
+ bool hexdump;
+ unsigned int hexdump_width;
+@@ -197,16 +198,21 @@ static bool PrintMinidumpDump(const Opti
+ }
+
+ MinidumpCrashpadInfo *crashpad_info = minidump.GetCrashpadInfo();
+ if (crashpad_info) {
+ // Crashpad info is optional, so don't treat absence as an error.
+ crashpad_info->Print();
+ }
+
++ MinidumpThreadNamesList *thread_names_list = minidump.GetThreadNamesList();
++ if (thread_names_list) {
++ thread_names_list->Print();
++ }
++
+ DumpRawStream(&minidump,
+ MD_LINUX_CMD_LINE,
+ "MD_LINUX_CMD_LINE",
+ &errors);
+ DumpRawStream(&minidump,
+ MD_LINUX_ENVIRON,
+ "MD_LINUX_ENVIRON",
+ &errors);
+diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc
+--- a/src/processor/minidump_processor.cc
++++ b/src/processor/minidump_processor.cc
+@@ -173,16 +173,22 @@ ProcessResult MinidumpProcessor::Process
+ }
+
+ MinidumpMemoryList *memory_list = dump->GetMemoryList();
+ if (memory_list) {
+ BPLOG(INFO) << "Found " << memory_list->region_count()
+ << " memory regions.";
+ }
+
++ MinidumpThreadNamesList* thread_names_list = dump->GetThreadNamesList();
++ if (thread_names_list) {
++ BPLOG(INFO) << "Found " << thread_names_list->name_count()
++ << " thread names.";
++ }
++
+ 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, " <<
+@@ -308,16 +314,19 @@ ProcessResult MinidumpProcessor::Process
+ } 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);
+ stack->set_last_error(thread->GetLastError());
++ if (thread_names_list) {
++ stack->set_name(thread_names_list->GetNameForThreadId(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;
+ }
+diff --git a/src/processor/stackwalk_common.cc b/src/processor/stackwalk_common.cc
+--- a/src/processor/stackwalk_common.cc
++++ b/src/processor/stackwalk_common.cc
+@@ -876,35 +876,45 @@ void PrintProcessState(const ProcessStat
+ printf("\n");
+ printf("Application-specific information:\n");
+ printf("%s", process_state.mac_crash_info().c_str());
+ }
+
+ // If the thread that requested the dump is known, print it first.
+ int requesting_thread = process_state.requesting_thread();
+ if (requesting_thread != -1) {
+- printf("\n");
+- printf("Thread %d (%s)\n",
+- requesting_thread,
+- process_state.crashed() ? "crashed" :
+- "requested dump, did not crash");
+- PrintStack(process_state.threads()->at(requesting_thread), cpu,
++ const CallStack* requesting_thread_callstack =
++ process_state.threads()->at(requesting_thread);
++ printf("\n"
++ "Thread %d (%s)",
++ requesting_thread,
++ process_state.crashed() ? "crashed" :
++ "requested dump, did not crash");
++ if (!requesting_thread_callstack->name().empty()) {
++ printf(" - %s", requesting_thread_callstack->name().c_str());
++ }
++ PrintStack(requesting_thread_callstack, cpu,
+ output_stack_contents,
+ process_state.thread_memory_regions()->at(requesting_thread),
+ process_state.modules(), resolver);
+ }
+
+ // Print all of the threads in the dump.
+ int thread_count = process_state.threads()->size();
+ for (int thread_index = 0; thread_index < thread_count; ++thread_index) {
+ if (thread_index != requesting_thread) {
+ // Don't print the crash thread again, it was already printed.
++ const CallStack* callstack = process_state.threads()->at(thread_index);
++ printf("\n"
++ "Thread %d", thread_index);
++ if (!callstack->name().empty()) {
++ printf(" - %s", callstack->name().c_str());
++ }
+ printf("\n");
+- printf("Thread %d\n", thread_index);
+- PrintStack(process_state.threads()->at(thread_index), cpu,
++ PrintStack(callstack, cpu,
+ output_stack_contents,
+ process_state.thread_memory_regions()->at(thread_index),
+ process_state.modules(), resolver);
+ }
+ }
+
+ PrintModules(process_state.modules(),
+ process_state.modules_without_symbols(),