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
@@ -62,26 +62,30 @@ 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; }
 
   uint32_t tid() const { return tid_; }
+  uint32_t last_error() const { return last_error_; }
 
  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_;
 };
 
 }  // 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
@@ -279,16 +279,26 @@ class MinidumpMemoryRegion : public Mini
 class MinidumpThread : public MinidumpObject {
  public:
   virtual ~MinidumpThread();
 
   const MDRawThread* thread() const { return valid_ ? &thread_ : NULL; }
   // GetMemory may return NULL even if the MinidumpThread is valid,
   // if the thread memory cannot be read.
   virtual MinidumpMemoryRegion* GetMemory();
+  // Corresponds to win32's GetLastError function, which records the last
+  // error value set by the OS for this thread. A more useful error message
+  // can be produced by passing this value to FormatMessage:
+  //
+  // https://docs.microsoft.com/windows/win32/debug/retrieving-the-last-error-code
+  //
+  // The value may also be looked up in Microsoft's System Error Codes listing:
+  //
+  // https://docs.microsoft.com/windows/win32/debug/system-error-codes
+  virtual uint32_t GetLastError();
   // GetContext may return NULL even if the MinidumpThread is valid.
   virtual MinidumpContext* GetContext();
 
   // The thread ID is used to determine if a thread is the exception thread,
   // so a special getter is provided to retrieve this data from the
   // MDRawThread structure.  Returns false if the thread ID cannot be
   // determined.
   virtual bool GetThreadID(uint32_t *thread_id) const;
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
@@ -44,11 +44,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;
 }
 
 }  // 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
@@ -1567,16 +1567,76 @@ MinidumpMemoryRegion* MinidumpThread::Ge
   if (!valid_) {
     BPLOG(ERROR) << "Invalid MinidumpThread for GetMemory";
     return NULL;
   }
 
   return memory_;
 }
 
+uint32_t MinidumpThread::GetLastError() {
+  if (!valid_) {
+    BPLOG(ERROR) << "Cannot retrieve GetLastError() from an invalid thread";
+    return 0;
+  }
+
+  if (!thread_.teb) {
+    BPLOG(ERROR) << "Cannot retrieve GetLastError() without a valid TEB pointer";
+    return 0;
+  }
+
+  auto memory = minidump_->GetMemoryList();
+  if (!memory) {
+    BPLOG(ERROR) << "Cannot retrieve GetLastError() without a valid memory list";
+    return 0;
+  }
+
+  auto context = GetContext();
+  if (!context) {
+    BPLOG(ERROR) << "Cannot retrieve GetLastError()'s without a valid context";
+    return 0;
+  }
+
+  uint64_t pointer_width = 0;
+  switch (context_->GetContextCPU()) {
+    case MD_CONTEXT_X86:
+      pointer_width = 4;
+      break;
+    case MD_CONTEXT_AMD64:
+    case MD_CONTEXT_ARM64:
+      pointer_width = 8;
+      break;
+    default:
+      BPLOG(ERROR) << "GetLastError() isn't implemented for this CPU type yet";
+      return 0;
+  }
+
+  auto region = memory->GetMemoryRegionForAddress(thread_.teb);
+  if (!region) {
+    BPLOG(ERROR) << "GetLastError()'s memory isn't mapped in this minidump";
+    return 0;
+  }
+
+  // The TEB is opaque but we know the value we want lives at this offset
+  // from reverse engineering.
+  uint64_t offset = pointer_width * 13;
+  uint32_t error = 0;
+  if (!region->GetMemoryAtAddress(thread_.teb + offset, &error)) {
+    BPLOG(ERROR) << "GetLastError()'s memory isn't mapped in this minidump";
+    return 0;
+  }
+
+  if (minidump_->swap()) {
+    Swap(&error);
+  }
+
+  return error;
+}
+
+
 
 MinidumpContext* MinidumpThread::GetContext() {
   if (!valid_) {
     BPLOG(ERROR) << "Invalid MinidumpThread for GetContext";
     return NULL;
   }
 
   if (!context_) {
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
@@ -301,16 +301,17 @@ 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());
     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;
   }
