浏览器相关知识

  • Chrome-v8pwn属于浏览器的pwn。接下来对浏览器做一个比较系统比较全面的了解
  • Chrome就是我们经常说的谷歌浏览器,本质上是一个网页浏览器,该浏览器是由谷歌公司开发的。而Chrome里面的JavaScript解释器被称为v8,一开始主要做的pwn题就是面向v8
  • 接下来介绍一下主流的JS引擎。
引擎 开发者 主要应用 编译方式 备注
V8 Google Chrome、Node.js JIT(TurboFan + lgnition) 速度快,广泛用于服务器端
SpiderMonkey Mozilla Firefox JIT(IonMonkey) 早期JS引擎,支持WebAssembly
JavaScriptCore(JSC) Apple Safari、WebKit JIT(Nitro) 适用于macOS/iOS
Chakra Microsoft 旧版Edge、IE JIT Edge现已经改用V8
Hermes Meta React Native AOT 专注移动端优化
QuickJS Fabrice Bellard 嵌入式设备 解释执行(无 JIT) 轻量级,支持ES2020
  • 而解释器这个的实现也就是使用底层语言去解释执行另一种语言,在这里是使用C++语言来解释JavaScript语言

环境搭建

编译最新版本

  • (注:如果是在打比赛现学就请看编译之前版本)

  • 这边需要手动编译源码,chrome里面的JavaScript解释器被称为v8

  • 我们先要下载一个源码,这个源码被称为v8,而v8经过编译后的文件被称为d8。根据编译的可选项,可以编译出debug版本或者release版本,一般两个版本都编译出来

  • 还需要下载两个编译v8源码的工具depot_toolsninja

    • depot_tools:是用来得到v8源码(也就是使用这个工具去下载v8源码,而不是直接使用git去拉取源码)
    • ninja:用来编译v8
1
2
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
git clone https://github.com/ninja-build/ninja.git
  • 然后将这两个工具添加进环境变量,将depot_tools添加环境变量(注意添加环境变量的时候需要使用绝对路径)
1
echo 'export PATH=$PATH:"/home/myheart/CTF/pwn/chrome_v8_pwn/depot_tools"' >> ~/.bashrc
  • ninja添加环境变量:在添加ninja为环境变量之前先要使用./configure.py编译ninja
1
2
3
4
5
cd ninja
./configure.py --bootstrap
cd ..
echo 'export PATH=$PATH:"/home/myheart/CTF/pwn/chrome_v8_pwn/ninja"' >> ~/.bashrc
source ~/.bashrc
  • 接下来就是使用depot_tools去下载源码
1
2
fetch v8
cd v8
  • 然后准备依赖和编译v8
    • gclient sync:v8项目的所有依赖项(注意旧版的源码可能会出现Python版本问题)
    • tools/dev/v8gen.py x64.debug:传递给v8gen.py一个参数,表示生成x64架构生成的调试版本
    • ninja -C out.gn/x64.debug :编译项目
1
2
3
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug

注意:在gclient sync命令执行的时候可能会出现代理问题,执行成功后会在这个文件中生成文件。

image-20250214200010144

注意:在ninja -C out.gn/x64.debug 这个命令就是开始编译了,编译时间比较久,很吃CPU

image-20250214202103840

回退版本与加载补丁

  • 在打比赛的时候,需要对相应的版本进行调试,这就导致了我们需要编译指定的版本。在比赛中我们得到的v8不一定是最新版本,我们之前编译的版本是V8 version 13.5.0 ,假如我们比赛的时候v8的版本为v8 version 13.3版本,这时我们就要回退版本。
  • 我们现在已经使用fetch v8,将远程的v8源码版本为v8 version 13.5.0给拉取到本地了。

image-20250218145114643

  • 如果我们修改了这个源码,我们就可以使用git diff > my_changes.diff,就会生成一个my_changes.diff文件,这个文件之后有用。
  • 如果我们想要回退到指定的v8版本,这时就需要输入如下命令,用于查看v8的版本:
1
git tag

image-20250218145510100

  • 要切换版本就需要输入如下命令:
1
2
3
git checkout tags/10.0.1 -b v8-10.0.1
//也就是git checkout tags/指定版本 -b v8-指定版本

动态调试

  • js有两种动态调试的方式

    • 第一种使用的是d8内部的API调试,但是调试并没有调试到寄存器内存这么底层。
    • 第二种就是使用d8配合gdb进行调试,这种调试就会涉及到寄存器内存
  • 对于第一种方式,具体介绍一下V8内部的API

API 作用
console.log(%HasFastProperties({})); 检查对象是否使用 Fast Properties
console.log(%GetOptimizationStatus(foo)); 获取 foo 函数的优化状态
console.log(%HasInlinedFunctionCode(bar)); 查看 bar 是否被内联
%CollectGarbage(); 触发垃圾回收
console.log(%GetHiddenClass(obj)); 获取对象的隐藏类信息

d8调试

  • 我们先创建一个test.js文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Foo(property_num,element_num) {
//添加可索引属性
for (let i = 0; i < element_num; i++) {
this[i] = `element${i}`
}
//添加常规属性
for (let i = 0; i < property_num; i++) {
let ppt = `property${i}`
this[ppt] = ppt
}
}
var bar = new Foo(10,10)
console.log(%HasFastProperties(bar));
delete bar.property2
console.log(%HasFastProperties(bar));
  • 然后使用d8 test.js --allow-natives-syntax即可进行调试,使用--allow-natives-syntax就可以调用V8API,这样就可以输出一些调试信息,这样就可以输出调试信息。

image-20250215004347798

  • 还可以这样进行调试,类似于交互式Shell效果调试。先输入命令../v8/out.gn/x64.debug/d8 --allow-natives-syntax
  • 这样我们就可以进入d8的交互式界面

image-20250215004504171

  • 然后我们就可以进行一边编写代码一边调试

image-20250215004650130

gdb调试

  • 我们先创建一个test.js文件
1
2
3
4
5
var num = 10;
var username = "iwen";
var age = 20;
var people_zhangsan = "张三";
console.log(age);
  • 使用gdb调试,就要进行如下操作
1
gdb ../v8/out.gn/x64.debug/d8
  • 然后在gdb的内部输入命令,就可以调试了
1
pwndbg> set args --allow-natives-syntax test2.js

image-20250215005354891

image-20250215005454750

  • v8源码中就可以v8自带的调试JS代码的gdb插件,我们先进入/path/to/v8/tools目录,然后在这个目录下可以找到gdbinit文件,这样就可以使用如下命令:
1
cp gdbinit ~/.gdbinit_v8
  • 之后编辑~/.gdbinit,添加如下文件:
1
source ~/.gdbinit_v8

image-20250218143830509

  • 这样我们调试的时候就会出现对应的源码

image-20250218144212624

  • 这里也介绍一下~/.gdbinit_v8中的一些调试命令
    • job命令:用于可视化显示JavaScript对象的内存结构。
    • telescope命令:查看内存数据

image-20250218151701532

image-20250218151743753

基础知识

JIT编译初步了解

  • 这里先介绍一下一些基础知识。Chrome V8目前演变成如下解释过程:

  • 当我们编写一个JS代码,使用V8去执行这个JS代码:

    • 首先我们会通过解析器V8JS代码解析为抽象语法树。
    • 然后会通过解释器对抽象语法树进行解释,将JS代码转换为字节码,一边解释一边执行,并且解释器会记录特定代码片段的运行次数
    • 当运行次数超过某个阈值,该段代码就会被记为热代码,并且将运行时的信息反馈给优化编译器
    • 优化编译器根据反馈信息,优化并编译字节码,生成优化后的机器码,这样再次执行这个代码的时候就会执行相应的机器码。
  • 上面的技术就被称为JIT(及时编译技术)

    • 其中解析器的源码在v8/src/parsing
    • 解释器的源码在v8/src/interpreter
    • 优化编译器源码在v8/src/compiler或者v8/src/maglev

JS常用的类

数组Array

关于Chrome-pwn的题型

  • Chrome-pwn题型有两种

    • 第一种:一般就是对v8进行一些修改,人为制造出一个漏洞,然后给出.diff文件
    • 第二种:直接用CVE出题
  • 对于第二种就是看CVE漏洞在哪,或者一步一步去牢。接下来重点分析第一种题型

  • 对于第一种题型,出题人先会对源码进行修改,然后编写.diff文件。而这个.diff文件,是github主要用于显示代码变更,是Git版本控制系统的一部分。所以给出.diff文件,我们就可以从.diff文件中看出出题人所修改的地方,从而发现并利用漏洞。接下来就以2019StarCTF oob这题给的.diff为例子,对.diff的一些进行分析

  • 下面就是该题给的.diff文件,接下来逐句解释一下.diff文件的每行代码的意思

    • 前四行是Git Diff格式的头部信息,用于描述对比的文件修改信息
      • diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc:这里就表明了对源码中/src/bootstrapper.cc这个文件做了修改
        • diff --git是Git生成的差异比较标识
        • a/src/bootstrapper.cc是修改前的文件,其中a/表示修改前的文件。
        • b/src/bootstrapper.cc是修改后的文件,b/表示修改后的文件
      • index b027d36..ef1002f 100644:表示哈希和文件权限模式,b027d36修改前的哈希、ef1002f修改后的哈希,100644文件权限模式。这里的哈希仅仅只是被修改文件修改前后的哈希
      • 第三行和第四行--- a/src/bootstrapper.cc+++ b/src/bootstrapper.cc,表示修改前和修改后的文件路径
    • @@ -1668,6 +1668,8 @@表示改文件改动的地方:表示改动的位置和行号,从这边我们就可以得知对源码修改了2
      • ``-1668,6-表示旧代码,而1668,6表示1668行的位置往下6`行;
      • +1668,8-表示新代码,而1668,8表示1668行的位置往下8行;
    • 之后的@@ -1668,6 +1668,8 @@之后从第6行到第14行就是展现修改后具体的代码,而有两行开头有+就表示是新添加的代码
  • .diff文件的剩余部分是对其它源码文件进行修改,就不做详细介绍了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc

@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",



diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);



diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(ArrayOob) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \




diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();

// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:

魔改V8

  • 参考这篇博客:chrome v8 pwn 学习 (1) | CoLin’s BLOG

  • 对于第一种类型的出题方式,就需要对v8的源码进行魔改,这里需要理解一下JavaScriptC++,这样会更容易理解,不过现在有AI会方便许多。

  • 对于V8的开发不熟悉的需要多花费一点时间去理解和尝试,这里就先从总体开始了解,如何修改V8的源码编译好的d8中新增加一个全局函数MyFunc()。这个MyFunc()函数的主要功能就是接收用户传入的参数(可以是字符串的数字浮点数整数),返回的是传入的参数加上100的结果,这个结果的数据类型默认为整型或者是浮点型

  • 接下来就先给出我修改源码后的.diff文件,根据上面初步了解了.diff文件,我们从.diff文件这边可以了解到开发V8流程的其中一小部分,也就是为V8增加一个全局函数。

  • 接下来是我修改源码后使用git diff > my_changes.diff ,从这里可以了解到我们增加一个全局函数需要对源码的什么位置进行修改,接下来说明一下,具体修改了哪些文件的代码

    • path/to/v8/BUILD.bazel
    • path/to/v8/BUILD.gn
    • path/to/v8/src/builtins/builtins-definitions.h
    • path/to/v8/src/init/bootstrapper.cc
    • path/to/v8/src/compiler/turbofan-typer.cc
    • 注意:从BUILD.bazelBUILD.gn,我们可以了解到我们还新建了一个文件,该文件为path/to/v8/src/builtins/builtins-myfunc.cc
  • 可以先尝试一下根据.diff文件不用git命令自己尝试修改源码,然后编译,成功添加MyFunc()这个全局函数。也可以根据后面的操作去尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
diff --git a/BUILD.bazel b/BUILD.bazel
index a3c2438a030..3cd7b9c1630 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1367,6 +1367,7 @@ filegroup(
"src/builtins/builtins-global.cc",
"src/builtins/builtins-internal.cc",
"src/builtins/builtins-json.cc",
+ "src/builtins/builtins-myfunc.cc",
"src/builtins/builtins-number.cc",
"src/builtins/builtins-object.cc",
"src/builtins/builtins-promise.h",



diff --git a/BUILD.gn b/BUILD.gn
index ba32e344142..a4e9e463898 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -5289,6 +5289,7 @@ v8_source_set("v8_base_without_compiler") {
"src/builtins/builtins-internal.cc",
"src/builtins/builtins-intl.cc",
"src/builtins/builtins-json.cc",
+ "src/builtins/builtins-myfunc.cc",
"src/builtins/builtins-number.cc",
"src/builtins/builtins-object.cc",
"src/builtins/builtins-reflect.cc",




diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index c163334d7db..15e4b42fea3 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -133,6 +133,7 @@ namespace internal {
IF_TSAN(TFC, TSANRelaxedStore32IgnoreFP, TSANStore) \
IF_TSAN(TFC, TSANRelaxedStore32SaveFP, TSANStore) \
IF_TSAN(TFC, TSANRelaxedStore64IgnoreFP, TSANStore) \
+ CPP(MyFunc, kDontAdaptArgumentsSentinel) \
IF_TSAN(TFC, TSANRelaxedStore64SaveFP, TSANStore) \
IF_TSAN(TFC, TSANSeqCstStore8IgnoreFP, TSANStore) \
IF_TSAN(TFC, TSANSeqCstStore8SaveFP, TSANStore) \





diff --git a/src/compiler/turbofan-typer.cc b/src/compiler/turbofan-typer.cc
index 1b09c0f020e..998f9555256 100644
--- a/src/compiler/turbofan-typer.cc
+++ b/src/compiler/turbofan-typer.cc
@@ -1849,6 +1849,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::NonInternal();
}
switch (function.shared(t->broker()).builtin_id()) {
+ case Builtin::kMyFunc:
+ return Type::Number();
case Builtin::kMathRandom:
return Type::PlainNumber();
case Builtin::kMathFloor:





diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index d285b4e7e42..1c273e0f332 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -2276,6 +2276,10 @@ void Genesis::InitializeGlobal(DirectHandle<JSGlobalObject> global_object,
native_context()->set_security_token(*global_object);

Factory* factory = isolate_->factory();
+
+ { // -- M y F u n c
+ SimpleInstallFunction(isolate_, global_object, "MyFunc", Builtin::kMyFunc, 1, kDontAdapt);
+ }

{ // -- C o n t e x t
DirectHandle<Map> meta_map(native_context()->meta_map(), isolate());
@@ -2768,6 +2772,7 @@ void Genesis::InitializeGlobal(DirectHandle<JSGlobalObject> global_object,
DONT_ENUM);
native_context()->set_global_parse_int_fun(*parse_int_fun);

+
// Install Number constants
const double kMaxValue = 1.7976931348623157e+308;
const double kMinValue = 5e-324;
@@ -7253,5 +7258,7 @@ char* Bootstrapper::RestoreState(char* from) {
// Called when the top-level V8 mutex is destroyed.
void Bootstrapper::FreeThreadResources() { DCHECK(!IsActive()); }

+
+
} // namespace internal
} // namespace v8
  • 这里也给出参考博客中的myfunc.cc中的具体代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "src/builtins/builtins-utils-inl.h"
namespace v8 {
namespace internal {

BUILTIN(MyFunc) {
HandleScope scope(isolate);
Handle<Object> value = args.atOrUndefined(isolate, 1);

// 判断参数是否为基本类型
if (IsJSPrimitiveWrapper(*value)) {
value = handle(Cast<JSPrimitiveWrapper>(value)->value(), isolate);
}

// 判断参数是否为数字
if (!IsNumber(*value)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kArgumentIsNotUndefinedOrInteger,
isolate->factory()->NewStringFromAsciiChecked(
"My.Func"),
isolate->factory()->Number_string()));
}

// 将Object转换为浮点数
double const value_number = Object::NumberValue(*value);

return *isolate->factory()->NewNumber(value_number + 100);
}

}
}
  • 修改完编译后就可以得到我们自定义的一个全局函数

image-20250217195916950

  • 我们就先来介绍一下添加全局函数要修改的这些文件

相关文件

  • 下面均为V8 version 13.5.0的源码

BUILD.bazel和BUILD.gn

  • 用于定义如何构建、编译和链接 V8 项目的各个模块和文件。
  • 也就是这些文件用于要构建、编译和链接的V8项目的源文件,所以我们在src中新建一个文件,这时我们就要将这个文件的路径写入,到这两个文件中,这样我们自定义的功能才能被编译

image-20250217213331952

image-20250217213348299

builtins-definitions.h

  • builtins-definitions.h,文件:这个文件主要就是定义JavaScript的内置类型、方法与函数,包括基本类型,比如:整数浮点数布尔值数组字符串
  • 例如下面的代码定义了一些数组的操作:比如ArrayPop,就是对数组的操作。

image-20250217210529358

1
2
3
4
5
6
7
8
9
10
11
12
13
// 源码:488行-499行
/* ES6 #sec-array.prototype.pop */ \
CPP(ArrayPop, kDontAdaptArgumentsSentinel) \
TFJ(ArrayPrototypePop, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.push */ \
CPP(ArrayPush, kDontAdaptArgumentsSentinel) \
TFJ(ArrayPrototypePush, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.shift */ \
CPP(ArrayShift, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.unshift */ \
CPP(ArrayUnshift, kDontAdaptArgumentsSentinel) \
/* Support for Array.from and other array-copying idioms */ \
TFS(CloneFastJSArray, NeedsContext::kYes, kSource) \
  • 在源码中有CPPTFJTFS这三个宏定义,还有ASM()等宏定义。接下来简单介绍一下前三个宏定义。我们编写内置函数是使用CPP编写,编写其具体功能
  • CPPTFJTFS这三个宏定义主要决定的是这个函数使用的是编译执行还是解释执行,他们是决定了内建函数如何在 V8 引擎内部执行(编译执行、解释执行、JIT 优化、辅助优化等)
  • 然后介绍一下这个文件中的相关参数:
    • CPP宏定义中的相关参数:

      • ArrayPop
      • kDontAdaptArgumentsSentinel
    • TFJ宏定义相关参数:

      • ArrayPrototypePush
      • kDontAdaptArgumentsSentinel
    • TFS宏定义相关参数:

      • CloneFastJSArray
      • NeedsContext::kYes
      • kSource

bootstrapper.cc

  • 初始化和引导 V8 引擎的运行。它是 V8 启动过程中的核心部分之一,负责执行引擎的初始化和配置工作。
  • 在这里我们就分析一个比较重要的方法:我们从源码可以看到这个方法从2269行到5051行,占这个文件非常大一部分。
1
2
void Genesis::InitializeGlobal(DirectHandle<JSGlobalObject> global_object,
DirectHandle<JSFunction> empty_function)
  • 我们对数组一些操作的实现,会这这个方法中的里面进行初始化的配置,这样一些名称等都会被初始化,我们才能通过像这样arr.pop()关键字方法使用该功能,这边的{}并不代表函数,只是将一些列操作或者方法给集合在一起,这样就更方便查找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{    
......

SimpleInstallFunction(isolate_, proto, "findIndex",
Builtin::kArrayPrototypeFindIndex, 1, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "findLast",
Builtin::kArrayPrototypeFindLast, 1, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "findLastIndex",
Builtin::kArrayPrototypeFindLastIndex, 1, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "lastIndexOf",
Builtin::kArrayPrototypeLastIndexOf, 1, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "pop", Builtin::kArrayPrototypePop,
0, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "push", Builtin::kArrayPrototypePush,
1, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "reverse",
Builtin::kArrayPrototypeReverse, 0, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "shift",
Builtin::kArrayPrototypeShift, 0, kDontAdapt);
SimpleInstallFunction(isolate_, proto, "unshift",
Builtin::kArrayPrototypeUnshift, 1, kDontAdapt);

......
}
  • 接下来介绍一下SimpleInstallFunction相关参数的具体含义:
    • isolate_
    • proto
    • "findIndex"
    • Builtin::kArrayPrototypeFindLastIndex
    • kDontAdapt

turbofan-typer.cc

  • 这里简单介绍一下turbofanturbofan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码。

  • 这个文件算是一个编译器优化,就添加如下形式就行:

1
2
3
4
5
// 第1851行
switch (function.shared(t->broker()).builtin_id()) {
// 在上面这个switch中添加下面内容:
case Builtin::kMyFunc:
return Type::Number();
  • 接下来介绍一下具体这个函数的具体含义:
    • function.shared(t->broker()).builtin_id()
    • Builtin::kMyFunc
    • Type::Number()

添加MyFunc()全局函数

新建全局函数文件

  • 在创建全局函数的时候我们会在这个文件目录下创建这样的文件src/builtins/builtins-xxx.cc,这里面编写的就是这个函数具体实现的功能
  • 所以我们就先创建一个src/builtins/builtins-myfunc.cc文件,在文件中写入如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "src/builtins/builtins-utils-inl.h"

namespace v8 {
namespace internal {

BUILTIN(MyFunc) {
HandleScope scope(isolate);
Handle<Object> value = args.atOrUndefined(isolate, 1);

// 判断参数是否为基本类型
if (IsJSPrimitiveWrapper(*value)) {
value = handle(Cast<JSPrimitiveWrapper>(value)->value(), isolate);
}

// 判断参数是否为数字
if (!IsNumber(*value)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kArgumentIsNotUndefinedOrInteger,
isolate->factory()->NewStringFromAsciiChecked(
"My.Func"),
isolate->factory()->Number_string()));
}

// 将Object转换为浮点数
double const value_number = Object::NumberValue(*value);

return *isolate->factory()->NewNumber(value_number + 100);
}

}
}

添加源文件

  • 然后我们在BUILD.bazelBUILD.gn这两个文件中添加我们新建的文件,如果是在开发中最好是按照顺序添加,这样会保证顺序不会乱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//BUILD.bazel
@@ -1367,6 +1367,7 @@ filegroup(
"src/builtins/builtins-global.cc",
"src/builtins/builtins-internal.cc",
"src/builtins/builtins-json.cc",
+ "src/builtins/builtins-myfunc.cc",
"src/builtins/builtins-number.cc",
"src/builtins/builtins-object.cc",
"src/builtins/builtins-promise.h",
BUILD.gn
//
@@ -5289,6 +5289,7 @@ v8_source_set("v8_base_without_compiler") {
"src/builtins/builtins-internal.cc",
"src/builtins/builtins-intl.cc",
"src/builtins/builtins-json.cc",
+ "src/builtins/builtins-myfunc.cc",
"src/builtins/builtins-number.cc",
"src/builtins/builtins-object.cc",
"src/builtins/builtins-reflect.cc",
  • 然后在这个文件builtins-definitions.h中添加
1
CPP(MyFunc, kDontAdaptArgumentsSentinel)      

image-20250217222825643

初始化函数

  • 然后在这个文件中bootstrapper.cc,添加如下代码,注意这边需要在
1
2
3
4
5
6
7
8
//注意需要再这个方法里面添加
void Genesis::InitializeGlobal(DirectHandle<JSGlobalObject> global_object,
DirectHandle<JSFunction> empty_function) {
{ // -- M y F u n c
SimpleInstallFunction(isolate_, global_object, "MyFunc", Builtin::kMyFunc, 1, kDontAdapt);
}

}

image-20250217223223065

设置函数优化

  • 最后在这个文件中添加turbofan-typer.cc如下代码,需要再switch内部中加入:
1
2
3
switch (function.shared(t->broker()).builtin_id()) {
case Builtin::kMyFunc:
return Type::Number();

image-20250217223541411

  • 这些都添加完之后就可以编译源码了,编译后就可以使用自定义的内置函数MyFunc()

image-20250217231653794

例题