v8 8.0以上版本中,V8_COMPRESS_POINTERS引發的崩潰

在移植過程中,遇到了指針崩潰的問題,經過多方查找,終於確定和V8_COMPRESS_POINTERS導致的問題。

V8_COMPRESS_POINTERS是v8新加入的功能。可以節省不少內存佔用。如果設置v8_enable_pointer_compression = false就會關閉。

如果關閉了該功能,程序可以正常運行。

實際上,該功能只會在64位上開啓。因爲該功能是將64位指針壓縮爲32位指針。

基本原理

v8的對象,包括js對象等,很多都是在heap上分配的,這些對象稱爲heap對象。heap是一段連續的內存。這樣,v8對象包含的子對象,他們的指針與v8對象的指針距離都很近,所以如果已知heap對象的地址,那麼子對象只需要知道一部分地址即可。

如:一個 heap_object對象,關聯一個內部的 map對象,從heap_object取map的地址。

假設 :

heap_object addr = 0x790804030c

子成員map 壓縮後地址是 0x08040328

在64位下,將地址分爲高32位低32位,那麼map最終地址是取heap_object 的高32位,與map的低32位結合在一起,就可以了:

map最終地址: 0x7908040328 紅色部分是heap_object的高32位,藍色部分是map的壓縮地址

這樣才能得到一個完整的地址。

注:v8充分的利用了地址來表示對象的類型。地址總是4字節對齊的,故此,v8將最低3位利用起來,表示不同的數據類型:

在 include/v8-internal.h 中定義的

 37 // Tag information for HeapObject.      
 38 const int kHeapObjectTag = 1;                                            
 39 const int kWeakHeapObjectTag = 3;                                               
 40 const int kHeapObjectTagSize = 2;                                 
 41 const intptr_t kHeapObjectTagMask = (1 << kHeapObjectTagSize) - 1;

出現問題以及原因分析

當我們對一個 Local result 對象,調用 result->IsUndefined() 的時候,出現了coredump崩潰,崩潰時,顯示的地址通常是 0x8040328 這樣的地址。根據上面的原理介紹,這應該是由於沒有加上heap對象的高32位地址導致的

那麼,爲什麼會出現這種情況?

根據我們的分析,result->IsUndefined()調用,實際上是調用了Value::IsUndefined() 函數。該函數是一個inline函數,在v8.h中定義。由QuickIsUndefined 函數實現。QuickIsUndefined也是一個inline函數:

bool Value::QuickIsUndefined() const {
  typedef internal::Address A;  // A 即 uintptr_t
  typedef internal::Internals I;
  // Value本身其實是用HandleScope生成的一個槽指針,相當於對heap對象的引用
  A obj = *reinterpret_cast<const A*>(this); 
  // 對低3位進行判斷,看看是否是一個heap對象
  if (!I::HasHeapObjectTag(obj)) return false;
  // GetInstanceType引起了對象的異常
  if (I::GetInstanceType(obj) != I::kOddballType) return false;
  return (I::GetOddballKind(obj) == I::kUndefinedOddballKind);
}

GetInstanceType的定義在v8-internal.h中,也是一個inline函數:

  V8_INLINE static int GetInstanceType(const internal::Address obj) {
    typedef internal::Address A;
    // 獲取map地址,在這個函數中,沒有對壓縮指針進行處理
    A map = ReadTaggedPointerField(obj, kHeapObjectMapOffset);
    // 訪問內部的成員變量,這是出現崩潰
    return ReadRawField<uint16_t>(map, kMapInstanceTypeOffset);
  }

ReadTaggedPointerField函數出現了問題,他也定義在v8-internal中,也是一個inline函數:

  V8_INLINE static internal::Address ReadTaggedPointerField(
      internal::Address heap_object_ptr, int offset) {
#ifdef V8_COMPRESS_POINTERS
    //這裏,第一步,取得壓縮指針。即指針的低32位
    uint32_t value = ReadRawField<uint32_t>(heap_object_ptr, offset);
    //從heap_object取得地址的高32位
    internal::Address root = GetRootFromOnHeapAddress(heap_object_ptr);
    // 組合在一起,形成新指針。
    return root + static_cast<internal::Address>(static_cast<uintptr_t>(value));
#else
    return ReadRawField<internal::Address>(heap_object_ptr, offset);
#endif
  }

至此,出現的原因是宏V8_COMPRESS_POINTERS沒有打開。確切的說,是V8內部的代碼打開了宏V8_COMPRESS_POINTERS,而使用者的代碼沒有打開

爲什麼?

很簡單,這些都是inline函數,inline函數是直接在使用者的代碼中展開的,因爲在編譯使用者的代碼時沒有打開V8_COMPRESS_POINTERS導致的。

解決方案

很簡單,加上V8_COMPRESS_POINTERS宏即可。

當然,v8提供了對應的支持,在v8根目錄的BUILD.gn中定義了:

# This config should only be applied to code using V8 and not any V8 code
# itself.
config("external_config") {
  defines = []
  configs = [ ":v8_header_features" ]
  if (is_component_build) {
    defines += [ "USING_V8_SHARED" ]
  }
  include_dirs = [
    "include",
    "$target_gen_dir/include",
  ]
}

看註釋,就知道,這是v8留給使用者的。

我們在自己的BUILD.gn中加上

confgis += ["//:external_config"]

且慢。。。。 加上這個,會連帶加上宏 V8_IMMINENT_DEPRECATION_WARNINGS,該宏導致Handle這個句柄不能使用。在v8.h:

#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS)
// Handle is an alias for Local for historical reasons.
template <class T>
using Handle = Local<T>;
#endif

Handle是一個將要被廢棄的對象。一些基於老的v8編寫的程序,都會使用Handle。現在需要使用Local代替了。如果老代碼中使用了Handle,就會導致問題。所以,需要關閉該宏:在args.gn配置中加入 **v8_imminent_deprecation_warnings = false **即可。(v8_imminent_deprecation_warnings默認爲true)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章