diff --git a/src/llnode.cc b/src/llnode.cc
index d24fdc4c..204ac64d 100644
--- a/src/llnode.cc
+++ b/src/llnode.cc
@@ -136,7 +136,8 @@ bool BacktraceCmd::DoExecute(SBDebugger d, char** cmd,
       lldb::SBMemoryRegionInfo info;
       if (target.GetProcess().GetMemoryRegionInfo(pc, info).Success() &&
           info.IsExecutable() && info.IsWritable()) {
-        result.Printf("  %c frame #%u: 0x%016" PRIx64 " <builtin>\n", star, i, pc);
+        result.Printf("  %c frame #%u: 0x%016" PRIx64 " <builtin>\n", star, i,
+                      pc);
         continue;
       }
     }
diff --git a/src/llscan.cc b/src/llscan.cc
index 9ebc3e94..63988e18 100644
--- a/src/llscan.cc
+++ b/src/llscan.cc
@@ -617,6 +617,14 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs(
       result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", str.raw(),
                     type_name.c_str(), "<Second>", search_value_.raw());
     }
+  } else if (repr == v8->string()->kThinStringTag) {
+    v8::ThinString thin_str(str);
+    v8::String actual = thin_str.Actual(err);
+    if (err.Success() && actual.raw() == search_value_.raw()) {
+      std::string type_name = thin_str.GetTypeName(err);
+      result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", str.raw(),
+                    type_name.c_str(), "<Actual>", search_value_.raw());
+    }
   }
   // Nothing to do for other kinds of string.
 }
@@ -694,6 +702,14 @@ void FindReferencesCmd::ReferenceScanner::ScanRefs(v8::String& str,
       references = llscan.GetReferencesByValue(second.raw());
       references->push_back(str.raw());
     }
+  } else if (repr == v8->string()->kThinStringTag) {
+    v8::ThinString thin_str(str);
+    v8::String actual = thin_str.Actual(err);
+
+    if (err.Success()) {
+      references = llscan.GetReferencesByValue(actual.raw());
+      references->push_back(str.raw());
+    }
   }
   // Nothing to do for other kinds of string.
 }
diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc
index d36cec53..ff68c812 100644
--- a/src/llv8-constants.cc
+++ b/src/llv8-constants.cc
@@ -334,6 +334,7 @@ void String::Load() {
   kConsStringTag = LoadConstant("ConsStringTag");
   kSlicedStringTag = LoadConstant("SlicedStringTag");
   kExternalStringTag = LoadConstant("ExternalStringTag");
+  kThinStringTag = LoadConstant("ThinStringTag");
 
   kLengthOffset = LoadConstant("class_String__length__SMI");
 }
@@ -362,6 +363,9 @@ void SlicedString::Load() {
   kOffsetOffset = LoadConstant("class_SlicedString__offset__SMI");
 }
 
+void ThinString::Load() {
+  kActualOffset = LoadConstant("class_ThinString__actual__String");
+}
 
 void FixedArrayBase::Load() {
   kLengthOffset = LoadConstant("class_FixedArrayBase__length__SMI");
@@ -518,6 +522,7 @@ void Frame::Load() {
   kConstructFrame = LoadConstant("frametype_ConstructFrame");
   kJSFrame = LoadConstant("frametype_JavaScriptFrame");
   kOptimizedFrame = LoadConstant("frametype_OptimizedFrame");
+  kStubFrame = LoadConstant("frametype_StubFrame");
 }
 
 
diff --git a/src/llv8-constants.h b/src/llv8-constants.h
index 04a46d23..3cc4e273 100644
--- a/src/llv8-constants.h
+++ b/src/llv8-constants.h
@@ -248,6 +248,7 @@ class String : public Module {
   int64_t kConsStringTag;
   int64_t kSlicedStringTag;
   int64_t kExternalStringTag;
+  int64_t kThinStringTag;
 
   int64_t kLengthOffset;
 
@@ -297,6 +298,16 @@ class SlicedString : public Module {
   void Load();
 };
 
+class ThinString : public Module {
+ public:
+  MODULE_DEFAULT_METHODS(ThinString);
+
+  int64_t kActualOffset;
+
+ protected:
+  void Load();
+};
+
 class FixedArrayBase : public Module {
  public:
   MODULE_DEFAULT_METHODS(FixedArrayBase);
@@ -441,6 +452,7 @@ class Frame : public Module {
   int64_t kConstructFrame;
   int64_t kJSFrame;
   int64_t kOptimizedFrame;
+  int64_t kStubFrame;
 
  protected:
   void Load();
diff --git a/src/llv8-inl.h b/src/llv8-inl.h
index 47281eea..8d4a0d51 100644
--- a/src/llv8-inl.h
+++ b/src/llv8-inl.h
@@ -272,6 +272,8 @@ ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String);
 ACCESSOR(SlicedString, Parent, sliced_string()->kParentOffset, String);
 ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi);
 
+ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String);
+
 ACCESSOR(FixedArrayBase, Length, fixed_array_base()->kLengthOffset, Smi);
 
 inline std::string OneByteString::ToString(Error& err) {
@@ -319,6 +321,16 @@ inline std::string SlicedString::ToString(Error& err) {
   return tmp.substr(offset.GetValue(), length.GetValue());
 }
 
+inline std::string ThinString::ToString(Error& err) {
+  String actual = Actual(err);
+  if (err.Fail()) return std::string();
+
+  std::string tmp = actual.ToString(err);
+  if (err.Fail()) return std::string();
+
+  return tmp;
+}
+
 inline int64_t FixedArray::LeaData() const {
   return LeaField(v8()->fixed_array()->kDataOffset);
 }
diff --git a/src/llv8.cc b/src/llv8.cc
index 20ecb06d..ec18c305 100644
--- a/src/llv8.cc
+++ b/src/llv8.cc
@@ -2,7 +2,6 @@
 
 #include <algorithm>
 #include <cinttypes>
-#include <algorithm>
 
 #include "llv8-inl.h"
 #include "llv8.h"
@@ -43,6 +42,7 @@ void LLV8::Load(SBTarget target) {
   two_byte_string.Assign(target, &common);
   cons_string.Assign(target, &common);
   sliced_string.Assign(target, &common);
+  thin_string.Assign(target, &common);
   fixed_array_base.Assign(target, &common);
   fixed_array.Assign(target, &common);
   oddball.Assign(target, &common);
@@ -90,8 +90,7 @@ double LLV8::LoadDouble(int64_t addr, Error& err) {
 std::string LLV8::LoadBytes(int64_t length, int64_t addr, Error& err) {
   uint8_t* buf = new uint8_t[length + 1];
   SBError sberr;
-  process_.ReadMemory(addr, buf,
-                      static_cast<size_t>(length), sberr);
+  process_.ReadMemory(addr, buf, static_cast<size_t>(length), sberr);
   if (sberr.Fail()) {
     err = Error::Failure("Failed to load V8 raw buffer");
     delete[] buf;
@@ -268,6 +267,8 @@ std::string JSFrame::Inspect(bool with_args, Error& err) {
       return "<internal>";
     } else if (value == v8()->frame()->kConstructFrame) {
       return "<constructor>";
+    } else if (value == v8()->frame()->kStubFrame) {
+      return "<stub>";
     } else if (value != v8()->frame()->kJSFrame &&
                value != v8()->frame()->kOptimizedFrame) {
       err = Error::Failure("Unknown frame marker");
@@ -957,6 +958,11 @@ std::string String::ToString(Error& err) {
     return std::string("(external)");
   }
 
+  if (repr == v8()->string()->kThinStringTag) {
+    ThinString thin(this);
+    return thin.ToString(err);
+  }
+
   err = Error::Failure("Unsupported string representation");
   return std::string();
 }
@@ -1111,9 +1117,9 @@ std::string JSArrayBuffer::Inspect(InspectOptions* options, Error& err) {
 
   char tmp[128];
   snprintf(tmp, sizeof(tmp),
-           "<ArrayBuffer: backingStore=0x%016" PRIx64 ", byteLength=%d",
-           data, byte_length);
-  
+           "<ArrayBuffer: backingStore=0x%016" PRIx64 ", byteLength=%d", data,
+           byte_length);
+
   std::string res;
   res += tmp;
   if (options->detailed) {
@@ -1156,7 +1162,8 @@ std::string JSArrayBufferView::Inspect(InspectOptions* options, Error& err) {
   int byte_offset = static_cast<int>(off.GetValue());
   char tmp[128];
   snprintf(tmp, sizeof(tmp),
-           "<ArrayBufferView: backingStore=0x%016" PRIx64 ", byteOffset=%d, byteLength=%d",
+           "<ArrayBufferView: backingStore=0x%016" PRIx64
+           ", byteOffset=%d, byteLength=%d",
            data, byte_offset, byte_length);
 
   std::string res;
diff --git a/src/llv8.h b/src/llv8.h
index 0e27fcbb..cd11b158 100644
--- a/src/llv8.h
+++ b/src/llv8.h
@@ -221,6 +221,15 @@ class SlicedString : public String {
   inline std::string ToString(Error& err);
 };
 
+class ThinString : public String {
+ public:
+  V8_VALUE_DEFAULT_METHODS(ThinString, String)
+
+  inline String Actual(Error& err);
+
+  inline std::string ToString(Error& err);
+};
+
 class HeapNumber : public HeapObject {
  public:
   V8_VALUE_DEFAULT_METHODS(HeapNumber, HeapObject)
@@ -405,7 +414,6 @@ class JSArrayBuffer : public HeapObject {
   inline bool WasNeutered(Error& err);
 
   std::string Inspect(InspectOptions* options, Error& err);
-  
 };
 
 class JSArrayBufferView : public HeapObject {
@@ -477,6 +485,7 @@ class LLV8 {
   constants::TwoByteString two_byte_string;
   constants::ConsString cons_string;
   constants::SlicedString sliced_string;
+  constants::ThinString thin_string;
   constants::FixedArrayBase fixed_array_base;
   constants::FixedArray fixed_array;
   constants::Oddball oddball;
@@ -503,6 +512,7 @@ class LLV8 {
   friend class TwoByteString;
   friend class ConsString;
   friend class SlicedString;
+  friend class ThinString;
   friend class HeapNumber;
   friend class JSObject;
   friend class JSArray;
diff --git a/test/fixtures/inspect-scenario.js b/test/fixtures/inspect-scenario.js
index e53a1ee6..4c61e316 100644
--- a/test/fixtures/inspect-scenario.js
+++ b/test/fixtures/inspect-scenario.js
@@ -8,6 +8,13 @@ let outerVar = 'outer variable';
 
 exports.holder = {};
 
+function makeThin(a, b) {
+  var str = a + b;
+  var obj = {};
+  obj[str];  // Turn the cons string into a thin string.
+  return str;
+}
+
 function closure() {
 
   function Class() {
@@ -28,6 +35,9 @@ function closure() {
   c.hashmap['cons-string'] =
       'this could be a bit smaller, but v8 wants big str.';
   c.hashmap['cons-string'] += c.hashmap['cons-string'];
+  c.hashmap['internalized-string'] = 'foobar';
+  // This thin string points to the previous 'foobar'.
+  c.hashmap['thin-string'] = makeThin('foo', 'bar');
   c.hashmap['array'] = [true, 1, undefined, null, 'test', Class];
   c.hashmap['long-array'] = new Array(20).fill(5);
   c.hashmap['array-buffer'] = new Uint8Array(
diff --git a/test/frame-test.js b/test/frame-test.js
index 69861344..f4f047fc 100644
--- a/test/frame-test.js
+++ b/test/frame-test.js
@@ -18,7 +18,7 @@ tape('v8 stack', (t) => {
     // FIXME(bnoordhuis) This can fail with versions of lldb that don't
     // support the GetMemoryRegions() API; llnode won't be able to identify
     // V8 builtins stack frames, it just prints them as anonymous frames.
-    lines = lines.filter((s) => !/<builtin>/.test(s));
+    lines = lines.filter((s) => !/<builtin>|<stub>/.test(s));
     const eyecatcher = lines[0];
     const adapter = lines[1];
     const crasher = lines[2];
diff --git a/test/inspect-test.js b/test/inspect-test.js
index 657cd1a4..0fe2bdc0 100644
--- a/test/inspect-test.js
+++ b/test/inspect-test.js
@@ -47,6 +47,7 @@ tape('v8 inspect', (t) => {
 
   let regexp = null;
   let cons = null;
+  let thin = null;
   let arrowFunc = null;
   let array = null;
   let longArray = null;
@@ -112,6 +113,11 @@ tape('v8 inspect', (t) => {
     t.ok(consMatch, '.cons-string ConsString property');
     cons = consMatch[1];
 
+    const thinMatch = lines.match(
+        /.thin-string=(0x[0-9a-f]+):<String: "foobar">/);
+    t.ok(thinMatch, '.thin-string ThinString property');
+    thin = thinMatch[1];
+
     sess.send(`v8 inspect ${regexp}`);
     sess.send(`v8 inspect -F ${cons}`);
   });
@@ -141,6 +147,15 @@ tape('v8 inspect', (t) => {
         -1,
         '--string-length truncates the string');
 
+    sess.send(`v8 inspect ${thin}`);
+  });
+
+  sess.linesUntil(/">/, (lines) => {
+    lines = lines.join('\n');
+    t.ok(
+      /0x[0-9a-f]+:<String: "foobar">/.test(lines),
+      'thin string content');
+
     sess.send(`v8 inspect ${array}`);
   });