diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/crashreporter/breakpad-patches/21-thread-names.patch | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/breakpad-patches/21-thread-names.patch')
-rw-r--r-- | toolkit/crashreporter/breakpad-patches/21-thread-names.patch | 661 |
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(), |