在移植过程中,遇到了指针崩溃的问题,经过多方查找,终于确定和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)