Index: include/lldb/Target/Thread.h
===================================================================
--- include/lldb/Target/Thread.h
+++ include/lldb/Target/Thread.h
@@ -1164,7 +1164,7 @@
 
   size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames,
                    uint32_t num_frames_with_source,
-                   bool stop_format);
+                   bool stop_format, bool only_stacks = false);
 
   size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame,
                              uint32_t num_frames, bool show_frame_info,
Index: source/Commands/CommandObjectThread.cpp
===================================================================
--- source/Commands/CommandObjectThread.cpp
+++ source/Commands/CommandObjectThread.cpp
@@ -42,7 +42,7 @@
 using namespace lldb_private;
 
 //-------------------------------------------------------------------------
-// CommandObjectThreadBacktrace
+// CommandObjectIterateOverThreads
 //-------------------------------------------------------------------------
 
 class CommandObjectIterateOverThreads : public CommandObjectParsed {
@@ -291,6 +291,122 @@
   CommandOptions m_options;
 };
 
+//-------------------------------------------------------------------------
+// CommandObjectUniqueThreadStacks
+//-------------------------------------------------------------------------
+
+class CommandObjectUniqueThreadStacks : public CommandObjectParsed {
+
+  class UniqueStack {
+
+  public:
+    UniqueStack(std::stack<Address> stackFrames, Thread* thread)
+      : m_stackFrames(stackFrames) {
+      m_threads.push_back(thread);
+    }
+
+    void AddThread(Thread* thread) {
+      m_threads.push_back(thread);
+    }
+
+    const std::vector<Thread*>& GetUniqueThreads() const { 
+      return m_threads;
+    }
+
+    Thread* GetRepresentativeThread() const {
+      return m_threads.front();
+    }
+
+    bool IsEqual(std::stack<Address> stackFrames) const {
+      return stackFrames == m_stackFrames;
+    }
+
+  protected:
+    std::vector<Thread*> m_threads;
+    std::stack<Address> m_stackFrames;
+  };
+
+public:
+  CommandObjectUniqueThreadStacks(CommandInterpreter &interpreter)
+      : CommandObjectParsed(
+            interpreter, "thread unique-stacks",
+            "Show unique call stacks for all threads in the process.",
+            nullptr,
+            eCommandRequiresThread | eCommandTryTargetAPILock |
+            eCommandProcessMustBeLaunched | eCommandProcessMustBePaused)
+    {
+    }
+
+  ~CommandObjectUniqueThreadStacks() override = default;
+
+  bool DoExecute(Args &command, CommandReturnObject &result) override {
+
+    std::vector<UniqueStack> uniqueStacks;
+    Process *process = m_exe_ctx.GetProcessPtr();
+
+    // Iterate over threads, finding unique stack buckets.
+    for (ThreadSP thread_sp : process->Threads()) {
+      Thread *thread = thread_sp.get();
+      BucketThread(thread, uniqueStacks);
+    }
+
+    const uint32_t start_frame = 0;
+    const uint32_t num_frames = UINT32_MAX;
+    const uint32_t num_frames_with_source = 0;// UINT32_MAX;
+    const bool stop_format = true;
+    const bool just_stack = true;
+
+    // Output all of the unique stack frames.
+    Stream &strm = result.GetOutputStream();
+    strm.IndentMore();
+    for (const UniqueStack& stack: uniqueStacks) {
+      // List the common thread ID's
+      strm.Indent("thread(s) ");
+      for (Thread* thread : stack.GetUniqueThreads()) {
+        strm.Printf("#%lu ", thread->GetID());
+      }
+      strm.EOL();
+
+      // List the shared call stack for this set of threads
+      stack.GetRepresentativeThread()->GetStatus(strm, start_frame,
+                                                 num_frames, num_frames_with_source,
+                                                 stop_format, just_stack);
+    }
+    strm.IndentLess();
+
+    return result.Succeeded();
+  }
+
+protected:
+
+  void BucketThread(Thread* thread, std::vector<UniqueStack>& uniqueStacks) {
+    // Grab each frame's address
+    std::stack<Address> stackFrames;
+    const uint32_t frameCount = thread->GetStackFrameCount();
+    for (uint32_t frameIndex = 0; frameIndex < frameCount; frameIndex++)
+    {
+      const Address stackFrameAddress = thread->GetStackFrameAtIndex(frameIndex)->GetFrameCodeAddress();
+      stackFrames.push(stackFrameAddress);
+    }
+
+    // Try to match the threads stack to and existing thread.
+    bool foundMatch = false;
+    for (UniqueStack& uniqueStack : uniqueStacks)
+    {
+      if (uniqueStack.IsEqual(stackFrames)) {
+        foundMatch = true;
+        uniqueStack.AddThread(thread);
+      }
+    }
+
+    // We failed to find an existing unique stack, so create a new one.
+    if (!foundMatch) {
+      UniqueStack newUniqueStack(stackFrames, thread);
+      uniqueStacks.push_back(newUniqueStack);
+    }
+  }
+};
+
 enum StepScope { eStepScopeSource, eStepScopeInstruction };
 
 static OptionEnumValueElement g_tri_running_mode[] = {
@@ -1925,6 +2041,8 @@
                              "thread <subcommand> [<subcommand-options>]") {
   LoadSubCommand("backtrace", CommandObjectSP(new CommandObjectThreadBacktrace(
                                   interpreter)));
+  LoadSubCommand("unique-stacks",
+                 CommandObjectSP(new CommandObjectUniqueThreadStacks(interpreter)));
   LoadSubCommand("continue",
                  CommandObjectSP(new CommandObjectThreadContinue(interpreter)));
   LoadSubCommand("list",
Index: source/Core/Debugger.cpp
===================================================================
--- source/Core/Debugger.cpp
+++ source/Core/Debugger.cpp
@@ -236,7 +236,7 @@
                                      "when displaying thread information."},
     {"thread-stop-format", OptionValue::eTypeFormatEntity, true, 0,
      DEFAULT_THREAD_STOP_FORMAT, nullptr, "The default thread format  "
-                                     "string to usewhen displaying thread "
+                                     "string to use when displaying thread "
                                      "information as part of the stop display."},
     {"use-external-editor", OptionValue::eTypeBoolean, true, false, nullptr,
      nullptr, "Whether to use an external editor or not."},
Index: source/Target/Thread.cpp
===================================================================
--- source/Target/Thread.cpp
+++ source/Target/Thread.cpp
@@ -1912,39 +1912,42 @@
 
 size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
                          uint32_t num_frames, uint32_t num_frames_with_source,
-                         bool stop_format) {
-  ExecutionContext exe_ctx(shared_from_this());
-  Target *target = exe_ctx.GetTargetPtr();
-  Process *process = exe_ctx.GetProcessPtr();
-  size_t num_frames_shown = 0;
-  strm.Indent();
-  bool is_selected = false;
-  if (process) {
-    if (process->GetThreadList().GetSelectedThread().get() == this)
-      is_selected = true;
-  }
-  strm.Printf("%c ", is_selected ? '*' : ' ');
-  if (target && target->GetDebugger().GetUseExternalEditor()) {
-    StackFrameSP frame_sp = GetStackFrameAtIndex(start_frame);
-    if (frame_sp) {
-      SymbolContext frame_sc(
-          frame_sp->GetSymbolContext(eSymbolContextLineEntry));
-      if (frame_sc.line_entry.line != 0 && frame_sc.line_entry.file) {
-        Host::OpenFileInExternalEditor(frame_sc.line_entry.file,
-                                       frame_sc.line_entry.line);
+                         bool stop_format, bool only_stacks) {
+
+  if (!only_stacks) {
+    ExecutionContext exe_ctx(shared_from_this());
+    Target *target = exe_ctx.GetTargetPtr();
+    Process *process = exe_ctx.GetProcessPtr();
+    strm.Indent();
+    bool is_selected = false;
+    if (process) {
+      if (process->GetThreadList().GetSelectedThread().get() == this)
+        is_selected = true;
+    }
+    strm.Printf("%c ", is_selected ? '*' : ' ');
+    if (target && target->GetDebugger().GetUseExternalEditor()) {
+      StackFrameSP frame_sp = GetStackFrameAtIndex(start_frame);
+      if (frame_sp) {
+        SymbolContext frame_sc(
+            frame_sp->GetSymbolContext(eSymbolContextLineEntry));
+        if (frame_sc.line_entry.line != 0 && frame_sc.line_entry.file) {
+          Host::OpenFileInExternalEditor(frame_sc.line_entry.file,
+                                         frame_sc.line_entry.line);
+        }
       }
     }
-  }
 
-  DumpUsingSettingsFormat(strm, start_frame, stop_format);
+    DumpUsingSettingsFormat(strm, start_frame, stop_format);
+  }
 
+  size_t num_frames_shown = 0;
   if (num_frames > 0) {
     strm.IndentMore();
 
     const bool show_frame_info = true;
 
     const char *selected_frame_marker = nullptr;
-    if (num_frames == 1 ||
+    if (num_frames == 1 || only_stacks ||
         (GetID() != GetProcess()->GetThreadList().GetSelectedThread()->GetID()))
       strm.IndentMore();
     else