You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
➜ v8 git:(035982c6dd) gn args --list out/x64.release | more
action_pool_depth
Current value (from the default) = -1
From //build/toolchain/BUILD.gn:11
Pool for non goma tasks.
added_rust_stdlib_libs
Current value (from the default) = []
From //build/config/rust.gni:44
Any extra std rlibs in your Rust toolchain, relative to the standard
Rust toolchain. Typically used with 'use_unverified_rust_toolchain' = true
also_build_ash_chrome
Current value (from the default) = false
From //build/config/chromeos/ui_mode.gni:26
Setting this to true when building linux Lacros-chrome will cause it to
*also* build linux ash-chrome in a subdirectory using an alternate
toolchain.
Don't set this unless you're sure you want it, because it'll double your build time.also_build_lacros_chrome Current value (from the default) = false From //build/config/chromeos/ui_mode.gni:30 Setting this to true when building linux ash-chrome will cause it to *also* build linux Lacros-chrome in a subdirectory using an alternate toolchain.also_build_lacros_chrome_for_architecture Current value (from the default) = "" From //build/config/chromeos/ui_mode.gni:35 ...
// First argument in list is the accessor class, the second argument is the// accessor ElementsKind, and the third is the backing store class. Use the// fast element handler for smi-only arrays. The implementation is currently// identical. Note that the order must match that of the ElementsKind enum for// the |accessor_array[]| below to work.
#defineELEMENTS_LIST(V) \
V(FastPackedSmiElementsAccessor, PACKED_SMI_ELEMENTS, FixedArray) \
V(FastHoleySmiElementsAccessor, HOLEY_SMI_ELEMENTS, FixedArray) \
V(FastPackedObjectElementsAccessor, PACKED_ELEMENTS, FixedArray) \
V(FastHoleyObjectElementsAccessor, HOLEY_ELEMENTS, FixedArray) \
V(FastPackedDoubleElementsAccessor, PACKED_DOUBLE_ELEMENTS, \
...
注意到注释里的smi-only arrays,搜一下v8相关的smi arrays,找到了Elements kinds in V8,把文章简单过一下
Doubles, for floating-point numbers and integers that cannot be represented as a Smi.
Regular elements, for values that cannot be represented as Smi or doubles.
对于element的kind转换
V8 assigns an elements kind to each array.
The elements kind of an array is not set in stone — it can change at runtime. In the earlier example, we transitioned from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS.
Elements kind transitions can only go from specific kinds to more general kinds.
除了packed arrays还存在holey arrays,简单举例
constarray=[1,2,3,4.56,'x'];// elements kind: PACKED_ELEMENTSarray.length;// 5array[9]=1;// array[5] until array[8] are now holes// elements kind: HOLEY_ELEMENTS
针对packed和holey
The most common elements kinds come in PACKED and HOLEY flavors.
Operations on packed arrays are more efficient than operations on holey arrays.
Elements kinds can transition from PACKED to HOLEY flavors.
对于两者之间的关系
在文章中,除了说了elements,还顺带提到了JavaScript的properties
Objects have properties that map to values, whereas arrays have indices that map to elements.
Array-indexed properties are stored in a separate elements store.
Named properties are stored in the properties store.
Elements and properties can either be arrays or dictionaries.
Each JavaScript object has a HiddenClass associated that keeps information about the object shape.
在JavaScript Object中,其具体结构
对于hidden class的变化
The basic assumption about HiddenClasses is that objects with the same structure — e.g. the same named properties in the same order — share the same HiddenClass. To achieve that we use a different HiddenClass when a property gets added to an object.
// The InternalElementsAccessor is a helper class to expose otherwise protected// methods to its subclasses. Namely, we don't want to publicly expose methods// that take an entry (instead of an index) as an argument.classInternalElementsAccessor : publicElementsAccessor {
public:
InternalIndex GetEntryForIndex(Isolate* isolate, JSObject holder,
FixedArrayBase backing_store,
size_t index) override = 0;
PropertyDetails GetDetails(JSObject holder, InternalIndex entry) override = 0;
};
继续
// Abstract base class for handles that can operate on objects with differing// ElementsKinds.classElementsAccessor {
public:ElementsAccessor() = default;
virtual~ElementsAccessor() = default;
ElementsAccessor(const ElementsAccessor&) = delete;
ElementsAccessor& operator=(const ElementsAccessor&) = delete;
// Returns a shared ElementsAccessor for the specified ElementsKind.static ElementsAccessor* ForKind(ElementsKind elements_kind) {
DCHECK_LT(static_cast<int>(elements_kind), kElementsKindCount);
return elements_accessors_[elements_kind];
}
// Checks the elements of an object for consistency, asserting when a problem// is found.virtualvoidValidate(JSObject obj) = 0;
...
V8_WARN_UNUSED_RESULT virtual Maybe<uint32_t> Unshift(
Handle<JSArray> receiver, BuiltinArguments* args,
uint32_t unshift_size) = 0;
V8_WARN_UNUSED_RESULT virtual MaybeHandle<Object> Pop(
Handle<JSArray> receiver) = 0;
V8_WARN_UNUSED_RESULT virtual MaybeHandle<Object> Shift(
Handle<JSArray> receiver) = 0;
// 头文件intmain(int argc, char* argv[]) {
// Initialize V8.v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::Context> context = v8::Context::New(isolate);
// Enter the context for compiling and running the hello world script.
v8::Context::Scope context_scope(context);
{
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source =
v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'");
// Compile the source code.
v8::Local<v8::Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
v8::String::Utf8Value utf8(isolate, result);
printf("%s\n", *utf8);
}
{
// Use the JavaScript API to generate a WebAssembly module.//// |bytes| contains the binary format for the following module://// (func (export "add") (param i32 i32) (result i32)// get_local 0// get_local 1// i32.add)//constchar csource[] = R"( let bytes = new Uint8Array([ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b ]); let module = new WebAssembly.Module(bytes); let instance = new WebAssembly.Instance(module); instance.exports.add(3, 4);)";
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source =
v8::String::NewFromUtf8Literal(isolate, csource);
// Compile the source code.
v8::Local<v8::Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
// Convert the result to a uint32 and print it.uint32_t number = result->Uint32Value(context).ToChecked();
printf("3 + 4 = %u\n", number);
}
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::DisposePlatform();
delete create_params.array_buffer_allocator;
return0;
}
voidV8::Initialize() {
AdvanceStartupState(V8StartupState::kV8Initializing);
CHECK(platform_);
// Update logging information before enforcing flag implications.
FlagValue<bool>* log_all_flags[] = {&FLAG_turbo_profiling_log_builtins,
...
&FLAG_log_ic,
&FLAG_log_maps};
// 一系列的log设置if (FLAG_log_all) {
...
}
FlagList::EnforceFlagImplications();
if (FLAG_predictable && FLAG_random_seed == 0) {
// Avoid random seeds in predictable mode.
FLAG_random_seed = 12347;
}
// 一些flag判断处理,包括turbo优化等
...
// The --jitless and --interpreted-frames-native-stack flags are incompatible// since the latter requires code generation while the former prohibits code// generation.CHECK(!FLAG_interpreted_frames_native_stack || !FLAG_jitless);
base::OS::Initialize(FLAG_hard_abort, FLAG_gc_fake_mmap);
if (FLAG_random_seed) {
GetPlatformPageAllocator()->SetRandomMmapSeed(FLAG_random_seed);
GetPlatformVirtualAddressSpace()->SetRandomSeed(FLAG_random_seed);
}
if (FLAG_print_flag_values) FlagList::PrintValues();
// Initialize the default FlagList::Hash.FlagList::Hash();
// Before initializing internals, freeze the flags such that further changes// are not allowed. Global initialization of the Isolate or the WasmEngine// already reads flags, so they should not be changed afterwards.if (FLAG_freeze_flags_after_init) FlagList::FreezeFlags();
#if defined(V8_ENABLE_SANDBOX)
// If enabled, the sandbox must be initialized first.GetProcessWideSandbox()->Initialize(GetPlatformVirtualAddressSpace());
CHECK_EQ(kSandboxSize, GetProcessWideSandbox()->size());
#endif
#if defined(V8_USE_PERFETTO)
if (perfetto::Tracing::IsInitialized()) TrackEvent::Register();
#endifIsolateAllocator::InitializeOncePerProcess();
Isolate::InitializeOncePerProcess();
#if defined(USE_SIMULATOR)
Simulator::InitializeOncePerProcess();
#endifCpuFeatures::Probe(false);
ElementsAccessor::InitializeOncePerProcess();
Bootstrapper::InitializeOncePerProcess();
CallDescriptors::InitializeOncePerProcess();
#if V8_ENABLE_WEBASSEMBLY
wasm::WasmEngine::InitializeOncePerProcess();
#endif// V8_ENABLE_WEBASSEMBLYExternalReferenceTable::InitializeOncePerProcess();
AdvanceStartupState(V8StartupState::kV8Initialized);
}
if (FLAG_random_seed) {
GetPlatformPageAllocator()->SetRandomMmapSeed(FLAG_random_seed);
GetPlatformVirtualAddressSpace()->SetRandomSeed(FLAG_random_seed);
}
继续
// 惰性初始化DEFINE_LAZY_LEAKY_OBJECT_GETTER(PageAllocatorInitializer,
GetPageAllocatorInitializer)
// We will attempt allocation this many times. After each failure, we call// OnCriticalMemoryPressure to try to free some memory.const int kAllocationTries = 2;
} // namespace
v8::PageAllocator* GetPlatformPageAllocator() {
DCHECK_NOT_NULL(GetPageAllocatorInitializer()->page_allocator());
returnGetPageAllocatorInitializer()->page_allocator();
}
v8::VirtualAddressSpace* GetPlatformVirtualAddressSpace() {
#if defined(LEAK_SANITIZER)
static base::LeakyObject<base::LsanVirtualAddressSpace> vas(
std::make_unique<base::VirtualAddressSpace>());
#elsestatic base::LeakyObject<base::VirtualAddressSpace> vas;
#endifreturn vas.get();
}
VirtualAddressSpace::VirtualAddressSpace()
: VirtualAddressSpaceBase(OS::CommitPageSize(), OS::AllocatePageSize(),
kNullAddress,
std::numeric_limits<uintptr_t>::max(),
PagePermissions::kReadWriteExecute) {
#if V8_OS_WIN
// On Windows, this additional step is required to lookup the VirtualAlloc2// and friends functions.OS::EnsureWin32MemoryAPILoaded();
#endif// V8_OS_WINDCHECK(bits::IsPowerOfTwo(page_size()));
DCHECK(bits::IsPowerOfTwo(allocation_granularity()));
DCHECK_GE(allocation_granularity(), page_size());
DCHECK(IsAligned(allocation_granularity(), page_size()));
}
其调用了父类VirtualAddressSpaceBase的构造函数
classVirtualAddressSpaceBase
: public NON_EXPORTED_BASE(::v8::VirtualAddressSpace) {
public:using VirtualAddressSpace::VirtualAddressSpace;
private:friend VirtualAddressSubspace;
// Called by a subspace during destruction. Responsible for freeing the// address space reservation and any other data associated with the subspace// in the parent space.virtualvoidFreeSubspace(VirtualAddressSubspace* subspace) = 0;
};
classVirtualAddressSpace {
public:using Address = uintptr_t;
VirtualAddressSpace(size_t page_size, size_t allocation_granularity,
Address base, size_t size,
PagePermissions max_page_permissions)
: page_size_(page_size),
allocation_granularity_(allocation_granularity),
base_(base),
size_(size),
max_page_permissions_(max_page_permissions) {}
virtual~VirtualAddressSpace() = default;
/** * The page size used inside this space. Guaranteed to be a power of two. * Used as granularity for all page-related operations except for allocation, * which use the allocation_granularity(), see below. * * \returns the page size in bytes.*/size_tpage_size() const { return page_size_; }
/** * The granularity of page allocations and, by extension, of subspace * allocations. This is guaranteed to be a power of two and a multiple of the * page_size(). In practice, this is equal to the page size on most OSes, but * on Windows it is usually 64KB, while the page size is 4KB. * * \returns the allocation granularity in bytes.*/size_tallocation_granularity() const { return allocation_granularity_; }
...
#if defined(V8_ENABLE_SANDBOX)
// If enabled, the sandbox must be initialized first.GetProcessWideSandbox()->Initialize(GetPlatformVirtualAddressSpace());
CHECK_EQ(kSandboxSize, GetProcessWideSandbox()->size());
#endif
class V8_EXPORT_PRIVATE Sandbox {
public:
// 沙盒中的内存分配
// +- ~~~ -+---------------------------------------- ~~~ -+- ~~~ -+
// | 32 GB | (Ideally) 1 TB | 32 GB |
// | | | |
// | Guard | 4 GB : ArrayBuffer backing stores, | Guard |
// | Region | V8 Heap : WASM memory buffers, and | Region |
// | (front) | Region : any other sandboxed objects. | (back) |
// +- ~~~ -+----------------+----------------------- ~~~ -+- ~~~ -+
// ^ ^
// base end
// < - - - - - - - - - - - size - - - - - - - - - - >
// < - - - - - - - - - - - - - reservation_size - - - - - - - - - - - - >
// 使用默认构造函数
Sandbox() = default;
Sandbox(const Sandbox&) = delete;
Sandbox& operator=(Sandbox&) = delete;
/**
* Initializes this sandbox.
*
* This will allocate the virtual address subspace for the sandbox inside the
* provided virtual address space. If a subspace of the required size cannot
* be allocated, this method will insted initialize this sandbox as a
* partially-reserved sandbox. In that case, a smaller virtual address space
* reservation will be used and an EmulatedVirtualAddressSubspace instance
* will be created on top of it to back the sandbox. If not enough virtual
* address space can be allocated for even a partially-reserved sandbox, then
* this method will fail with an OOM crash.
*/
void Initialize(v8::VirtualAddressSpace* vas);
....
默认构造函数完成初始化后,调用初始化函数来完成真正的初始化,这部分主要完成地址和一些地址防护设置
boolSandbox::Initialize(v8::VirtualAddressSpace* vas, size_t size,
bool use_guard_regions) {
CHECK(!initialized_);
CHECK(base::bits::IsPowerOfTwo(size));
CHECK(vas->CanAllocateSubspaces());
size_t reservation_size = size;
if (use_guard_regions) {
reservation_size += 2 * kSandboxGuardRegionSize;
}
Address hint = RoundDown(vas->RandomPageAddress(), kSandboxAlignment);
...
if (use_guard_regions) {
Address front = reservation_base_;
Address back = end_;
// These must succeed since nothing was allocated in the subspace yet.CHECK(address_space_->AllocateGuardRegion(front, kSandboxGuardRegionSize));
CHECK(address_space_->AllocateGuardRegion(back, kSandboxGuardRegionSize));
}
initialized_ = true;
InitializeConstants();
DCHECK(!is_partially_reserved());
...
}
// 填充数据voidSandbox::InitializeConstants() {
#ifdef V8_SANDBOXED_POINTERS
// Place the empty backing store buffer at the end of the sandbox, so that any// accidental access to it will most likely hit a guard page.
constants_.set_empty_backing_store_buffer(base_ + size_ - 1);
#endif
}
staticvoidProbe(bool cross_compile) {
static_assert(NUMBER_OF_CPU_FEATURES <= kBitsPerInt);
if (initialized_) return;
initialized_ = true;
ProbeImpl(cross_compile);
}
// x64平台指令集判断voidCpuFeatures::ProbeImpl(bool cross_compile) {
// Only use statically determined features for cross compile (snapshot).if (cross_compile) return;
#if V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64
base::CPU cpu;
CHECK(cpu.has_sse2()); // SSE2 support is mandatory.CHECK(cpu.has_cmov()); // CMOV support is mandatory.if (cpu.has_sse42()) SetSupported(SSE4_2);
if (cpu.has_sse41()) SetSupported(SSE4_1);
if (cpu.has_ssse3()) SetSupported(SSSE3);
if (cpu.has_sse3()) SetSupported(SSE3);
if (cpu.has_avx() && cpu.has_osxsave() && OSHasAVXSupport()) {
SetSupported(AVX);
if (cpu.has_avx2()) SetSupported(AVX2);
if (cpu.has_fma3()) SetSupported(FMA3);
...
voidMemoryProtectionKey::InitializeMemoryProtectionKeySupport() {
// Flip {pkey_initialized} (in debug mode) and check the new value.DCHECK_EQ(true, pkey_initialized = !pkey_initialized);
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// PKU was broken on Linux kernels before 5.13 (see// https://lore.kernel.org/all/[email protected]/).// A fix is also included in the 5.4.182 and 5.10.103 versions ("x86/fpu:// Correct pkru/xstate inconsistency" by Brian Geffon <[email protected]>).// Thus check the kernel version we are running on, and bail out if does not// contain the fix.structutsname uname_buffer;
CHECK_EQ(0, uname(&uname_buffer));
int kernel, major, minor;
// Conservatively return if the release does not match the format we expect.if (sscanf(uname_buffer.release, "%d.%d.%d", &kernel, &major, &minor) != 3) {
return;
}
bool kernel_has_pkru_fix =
kernel > 5 || (kernel == 5 && major >= 13) || // anything >= 5.13
(kernel == 5 && major == 4 && minor >= 182) || // 5.4 >= 5.4.182
(kernel == 5 && major == 10 && minor >= 103); // 5.10 >= 5.10.103if (!kernel_has_pkru_fix) return;
// Try to find the pkey functions in glibc.void* pkey_alloc_ptr = dlsym(RTLD_DEFAULT, "pkey_alloc");
if (!pkey_alloc_ptr) return;
// If {pkey_alloc} is available, the others must also be available.void* pkey_free_ptr = dlsym(RTLD_DEFAULT, "pkey_free");
void* pkey_mprotect_ptr = dlsym(RTLD_DEFAULT, "pkey_mprotect");
void* pkey_get_ptr = dlsym(RTLD_DEFAULT, "pkey_get");
void* pkey_set_ptr = dlsym(RTLD_DEFAULT, "pkey_set");
CHECK(pkey_free_ptr && pkey_mprotect_ptr && pkey_get_ptr && pkey_set_ptr);
pkey_alloc = reinterpret_cast<pkey_alloc_t>(pkey_alloc_ptr);
pkey_free = reinterpret_cast<pkey_free_t>(pkey_free_ptr);
pkey_mprotect = reinterpret_cast<pkey_mprotect_t>(pkey_mprotect_ptr);
pkey_get = reinterpret_cast<pkey_get_t>(pkey_get_ptr);
pkey_set = reinterpret_cast<pkey_set_t>(pkey_set_ptr);
#endif
}
v8初始化分析
环境搭建
这部分比较简单拉取最新的
v8
代码,并编译测试拉取代码,过程也比较简单
上述是官方编译方法,在没有问题的情况下,如果想要自定义一些编译参数,打开
out/x64.release/args.gn
,写入一下内容,这里这么做,主要方便调试,会自动编译v8 samples
下的文件然后重新编译
具体可以使用哪些编译参数,可以使用
gn
获取编译完成,就可以看到如下文件
如果对
samples
或者别的文件进行了更改,直接运行ninja
也只会重新编译链接改动的文件,不会重新全量编译整个v8
,速度非常快,便于测试。简单调试及elements分析
根据参考中的文章,我们先从
v8 samples
里面的shell.cc
的代码看起,来看一下elements
的处理过程,然后边用lldb
调试生成的v8_shell
跟踪
Initialize
,这里会有多个Initialize
,直接通过源码不一定能找到,直接lldb
下断点即可在使用
lldb
调试器前,先载入chromium
调试器的初始化脚本tools/lldb_commands.py
,其增加了部分便于调试的指令回头来看源码,
InitializeOncePerProcess
实现继续
注意到注释里的
smi-only arrays
,搜一下v8
相关的smi arrays
,找到了Elements kinds in V8
,把文章简单过一下JavaScript
会优化纯数字索引的对象属性,这类属性会使用Array
构造,并命名该类属性为elements
,其中elements
有几种kind
Small integers, also known as Smi.
Doubles, for floating-point numbers and integers that cannot be represented as a Smi.
Regular elements, for values that cannot be represented as Smi or doubles.
对于
element
的kind
转换V8 assigns an elements kind to each array.
The elements kind of an array is not set in stone — it can change at runtime. In the earlier example, we transitioned from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS.
Elements kind transitions can only go from specific kinds to more general kinds.
除了
packed arrays
还存在holey arrays
,简单举例针对
packed
和holey
The most common elements kinds come in PACKED and HOLEY flavors.
Operations on packed arrays are more efficient than operations on holey arrays.
Elements kinds can transition from PACKED to HOLEY flavors.
对于两者之间的关系
在文章中,除了说了
elements
,还顺带提到了JavaScript
的properties
Objects have properties that map to values, whereas arrays have indices that map to elements.
提到了
Objects
的properties
,这两个构成了简单的Javascript Objects
,所以再来看看这个内容basic JavaScript object
如下:这种是比较简单的
JavaScript Objects
,但是properties
不可能一直这么简单,所以需要更加复杂的结构来记录更加复杂的properties
Array-indexed properties are stored in a separate elements store.
Named properties are stored in the properties store.
Elements and properties can either be arrays or dictionaries.
Each JavaScript object has a HiddenClass associated that keeps information about the object shape.
在
JavaScript Object
中,其具体结构对于
hidden class
的变化根据上面的描述,对于
hardden class
比较形象的解释可以看一下这个V8 hidden class and inline cache ppt,说的比较简单,但是解释
hidden class
比较形象Objects with the same structure (same properties in the same order) have the same HiddenClass
By default every new named property added causes a new HiddenClass to be created.
Adding array-indexed properties does not create new HiddenClasses.
对于
JavaScript Object
,除了上面说到的properties/elements
,还有一种in-object properties
,这种类型直接存储在对象中,所以在查找过程中,速度会特别快,但是因为对象内存空间限制,这种in-object properties
是预先初始化就固定的阶段结论
到这里就差不多简单说明了
JavaScript Object
有关的elements/properties/in-object properties
,所以再回过头来看一下其中一个elements kind
的定义,就像文章说的,它有很多种kinds
,来看其中一个FastPackedSmiElementsAccessor
这个类的定义很有意思,子类类型作为模板参数传给基类,这里用到了一种叫做
CRPT
的方法,父类模板会根据子类传入的参数类型,调用对应子类的方法,与c++
的虚函数有点类似,但是会比虚函数的开销更小。继续跟
继续
继续
到这个函数中,终于找到了
elements
的各种操作实现,比如pop/push/slice
等等继续,这里使用到了
nested class
,主要用于访问控制继续
这个是
elements
的最顶层的抽象基类,定义了elements
的一些基本操作elements
的整个定义及实现过程到这里,我们搭建了
v8
的测试环境,并且简单跟了一下elements
的实现流程,下一部分具体分析v8
的执行过程v8执行过程
我们来看一下最简单的
hello-world.cc
来了解一下v8的整体运行流程v8初始化
每个初始化调用都简单跟踪一下
snapshot
初始化v8
使用了一个快捷方式来加快速度,将先前准备好的快照直接反序列化到堆中以获得初始化的上下文。在普通台式计算机上,这可以将创建上下文的时间从 40 毫秒缩短到不到 2 毫秒。在普通手机上,这可能意味着 270 毫秒和 10 毫秒之间的差异。嵌入v8
的Chrome
以外的应用程序可能需要的不仅仅是普通的JavaScript
。许多在启动时加载的额外的库脚本,在“实际”应用程序运行之前。例如,一个基于v8
的简单TypeScript VM
必须在启动时加载TypeScript
编译器,以便将TypeScript
源代码即时转换为JavaScript
,所以snapshot
在对提高v8
运行速度核心功能点在
Load
函数中功能是将
snapshot
的文件读入结构v8::StartupData
platform
相关初始化hello-world.cc
由两行代码完成首先来看一下
v8::platform::NewDefaultPlatform
跟进
DefaultPlatform
基本包含了平台用到的所有数据,比如线程池,内存分配等信息
主要看一下,它显示声明的构造函数具体实现,因为基本重要的数据均在这里初始化
再重点看一下
v8::base::PageAllocator
的构造,因为这个PageAllocator
以后会多次出现PageAllocator
构造函数,主要是根据平台确定其两个成员变量数值,这里用lldb
简单调试一下,看看mac
下这个数值是多少allocate_page_size_
在mac
下是4096
platform
其他变量初始化差不多,主要就是一些平台相关的变量初始化,部分数据初始化由它下一句v8::V8::InitializePlatform(platform.get())
实现。这里面的内容还有很多,比如
task
初始化,调用栈跟踪初始化处理等等,比如跟PageAllocator
一起初始化的v8::TracingController
,task
的DefaultWorkerThreadsTaskRunner
,这部分在有需要的时候再回头来看梳理一下调用关系
v8::V8::Initialize
初始化中间涉及到多次的模板实现,直接代码跟踪会有点绕,直接利用
lldb
跟踪一下到这里算是真正进入
v8
初始化过程,来具体分析一下该函数干了哪些事V8::Initialize
主要涉及以下内容因为我用的是
mac
调试,所以调用的是platform-posix.cc
中的初始化函数,功能比较简单,设置了一下标志位这里的初始化是平台相关的,直接看代码如果不对的话,就用调试器调试一下即可
继续,这部分主要涉及
v8
内存分配等相关操作内存分配
继续
有关于惰性初始化的宏
DEFINE_LAZY_LEAKY_OBJECT_GETTER
根据宏定义展开
GetPageAllocatorInitializer
其中
PageAllocatorInitializer
page_allocator_
来源于V8::GetCurrentPlatform
platform_
变量即上面platform
初始化相关内容再来看一下新的内存分配管理器
GetPlatformVirtualAddressSpace()->SetRandomSeed(FLAG_random_seed);
还是按照上面的步骤,找到最后的构造函数
其调用了父类
VirtualAddressSpaceBase
的构造函数其继承了
v8::VirtualAddressSpace
,这里注意区分上面的v8::base::VirtualAddressSpace
继续看一下
v8::VirtualAddressSpace
,它几乎包含了我们在虚拟化地址过程中的所有数据这个类内容比较多,但是如果是功能类开发用到的机会不多,如果想要写
v8
漏洞利用,那建议好好熟悉一下这部分的内容。我在参考文档中,给出了这部分内容的部分官方内存设计文档,有需要可以多看看,这里就先说这么多。开始下一个分析前,先对上面两个部分的调用关系做一下简单的梳理
继续分析
V8::Initialize
的沙盒模式,这个需要flags
来启用它,如果是用fuzz
来测试浏览器,可以考虑暂时关闭该flags
因为沙盒这部分对于安全方面来说比较重要,我们简单跟一下里面的初始化逻辑,具体的沙盒操作,初始化还不会涉及
延迟载入
Sandbox
进入
Sandbox
定义默认构造函数完成初始化后,调用初始化函数来完成真正的初始化,这部分主要完成地址和一些地址防护设置
isolate
初始化isolate
是什么,isolate
是一个JavaScript
的代码执行环境(JavaScript execution environment
)跟进
主要完成了一些地址空间的初始化,为将来的空间分配做准备,比较重要的是这个
GetProcessWidePtrComprCage
它也是通过宏定义载入
跟一下
VirtualMemoryCage
可以看一下它的具体空间分配构成
可以发现这部分跟
sandbox
的空间分配基本是一样的其实
sandbox
的地址空间就是VirtualMemoryCage
的具体化。v8
的内存分配为什么要设计的这么复杂?主要的原因有两个,一个是节省空间,一个是增强安全性。
节省空间
现在普遍使用的机器是64位,跟32位机器相比,cpu能够访问的内存地址扩大了,同样的在32位机器下,一个指针只需要占用4个字节,在64位就需要占用8个字节。所以chrome为了节省指针所用的空间,引入了
Pointer Compression
,即将64位指针分为两个部分,一个部分存放指针基址,另一部分存放其偏移地址(offset
),如果是smi(small integer)
,则直接存放其值,最后用一个标识位来表示值还是偏移。增强安全性
在正常的
v8
漏洞利用过程中,是通过内存破坏(oob rw/uaf/type confusion/off by one
)去读取或者写入某个地址,以达到执行恶意代码的目的,为了缓解这部分的漏洞利用,chrome
在Pointer Compression
的基础上,引入了Cage Pointer
的概念,将所有需要分配地址(compressed pointer
)集中在某个区域,比如下图的1 TB
空间,基址只能是该空间的某一处,并且在前后端位置各预留32 GB
的保护空间,这样在cage
内的地址,无论如何调整offset
,也不可能访问到其他地址。回到
InitializeOncePerProcess
跟进
Heap::InitializeOncePerProcess
跟进
HeapAllocator::InitializeOncePerProcess
可以发现该函数的作用,根据启动参数设置
gc_interval
,默认值为-1
跟进
MemoryAllocator::InitializeOncePerProcess
该函数的作用是设置两个变量的值
commit_page_size_
: 在platform
初始化已经介绍过,如果未定义则根据平台设置,macOS
目前是4096
commit_page_size_bits_: 根据
commit_page_size_
计算2
的指数值,4096
对应的应该是12
调用逻辑
回到
V8::Initialize
,继续这部分代码主要是关于
v8
各个指令集的模拟器的准备,可以在x64
平台调试arm/arm64/riscv64/mips/mips64
等平台代码,不需要底层硬件的支持,因为不涉及真正的代码解释,所以在初始化过程中比较简单。继续分析
CpuFeatures::Probe(false)
主要包含对cpu支持的指令集判断,会cpu支持的指令集不同,生成对应的指令集代码。
继续分析
Bootstrapper::InitializeOncePerProcess()
这部分主要是实现了v8扩展的初始化加载,主要包括
跟一下
v8::RegisterExtension
的实现通过
RegisteredExtension
实现了一个扩展链表其中一个
extension
初始化过程ExternalizeStringExtension
可以发现,过程相对比较简单,是一些相关数据的初始化
继续
CallDescriptors::InitializeOncePerProcess
其中最主要的是
INTERFACE_DESCRIPTOR_LIST
宏,宏展开后大概是这样的该部分最主要的功能是设置了一些函数调用规定,比如参数个数,寄存器入站顺序等,实现类在
CallInterfaceDescriptor
中继续
WasmEngine webassembly
初始化继续
base::MemoryProtectionKey::InitializeMemoryProtectionKeySupport
主要是pku支持的判断,并在支持的情况下,设置相关分配、释放等函数。
继续
ExternalReferenceTable::InitializeOncePerProcess
v8相关漏洞
v8正则表达式漏洞:
https://bugs.chromium.org/p/chromium/issues/detail?id=1307610
https://www.anquanke.com/post/id/276964
参考
The text was updated successfully, but these errors were encountered: