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)

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