本文介绍在一个较老的环境下编译 V8 引擎,并全静态地链接到既有的游戏服务上。原因是我们需要一个很新的能支持 WebAssembly 的 JS 引擎。经过调研,我们认为使用 V8 是最好的。
游戏服务使用 GCC4.4.6,这是一个非常老的版本,甚至不能完整支持 C++11 标准。而即使是很老版本的 V8 都需要完整的 C++11 支持(GCC 4.8+)。进一步地,最新版本的 V8 需要 C++14 标准的支持:
- v8.h 中出现了诸如
std::remove_cv_t的 C++14 的标准库函数 - C++14标准编译出来的库也没办法直接和原游戏的目标文件进行链接
因此我们的方案是将游戏中对 V8 强依赖的模块升级成 C++14 标准,主要步骤如下:
- 从源码构建 GCC 和 GLIBC
- 从源码构建 V8 静态库 v8_monolith.a
- 将 V8 静态库、libc++、GLIBC 和游戏模块全静态链接
编译GCC和GLIBC
见文章
代码拉取
代码拉取值得单独开一节,因为虽然 V8 代码使用 Git 来管理的,但它却并不是传统的 git clone 的路数,而是需要借助 gclient 这个工具。
gclient,包括 gn 等工具都在 depot_tools 这个包里面,可以通过下面的链接得到这个包
1 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git |
在得到这个包后,还需要将它加到环境路径里面以方便访问
1 | export PATH=`pwd`/depot_tools:"$PATH" |
然后,使用下面的命令拉取
1 | fetch v8 |
如果出现 gclient 拉取问题,可以尝试修改 .gclient 文件如下所示
1 | solutions = [ |
此外,url 还可以尝试取 github 上的地址
1 | https://github.com/chromium/chromium.git |
编译V8
目前V8默认是使用Clang编译的,确实比GCC快很多,但由于我们最后还要和游戏进行链接,所以需要把工具链换成GCC。在这篇wiki中给出了从简单到复杂的不同的编译的流程。我试验下来直接用gn是最为方便的。
修改编译器
V8 默认的编译工具链是 clang,需要将它变成 GCC。首先需要设置 use_custom_libcxx,这个项目如果为 true,就会走 v8 自带的编译工具,这样编译出来如果是 d8 还好,但需要静态库,这样肯定不行。
1 | use_custom_libcxx = false |
然后需要修改 gn args,添加下面的项目,目的是禁用 clang 相关的宏。
1 | is_clang = false |
为了防止 ld.gold 报错,还需要开启下面的选项。
1 | fatal_linker_warnings = false |
但这么做还不够,需要在入口 build/config/BUILDCONFIG.gn 中,修改 Linux 下,is_clang 为 true 的情况下,toolchain为 GCC。否则,还是有一些代码会把 is_clang 的值变回 true。
1 | _default_toolchain = "//build/toolchain/linux:$target_cpu" |
紧接着,就要修改原来的gcc toolchain,将里面的配置替换成我们刚编译得到的GCC9,具体涉及下面的修改
toolchain\gcc_toolchain.gni
这是一个模板文件,我们需要修改GCC的cc、cxx、alink和link_command这几个项目。具体说来,包括:- 设置
cc、cxx和ld到GCC9的bin目录下的对应程序;
这里,ld就指定为g++即可 - 修改
extra_cppflags添加GCC对应的include文件夹的路径; - 对于
cc和cxx两个tool,需要在最后加上-static; - 对于
alink和link_command,需要加上libstdc++.a。
- 设置
config\c++\BUILD.gn
参照toolchain\gcc_toolchain.gni修改cflags_cc和ldflags。
去掉snapshot
snapshot技术是V8为了提高Context的加载速度引入的优化,将V8启动后的内存布局和JS函数预编译好的二进制对象写到专门的文件shapshot_blob.bin和natives_blob.bin中,并在每一次初始化的时候直接加载,以减少重复编译消耗。
修改gn args,添加
1 | v8_use_external_startup_data = false |
生成v8静态库
首先生成ninja构建文件
1 | gn gen out.gn/x64.debug/ |
修改gn args,添加
1 | v8_monolithic = true |
然后使用下面的命令生成静态库,注意后面的-j不能加得太大,因为在编译比如torque的时候GCC会占用比较大的内存,如果并行度很高,可能就编不出来
1 | ninja -C out.gn/x64.debug/ v8_monolith -j2 |
静态链接
通过下面的命令可以静态链接V8的Hello world程序。
1 | $(GCC_ROOT)/bin/g++ samples/bench.cpp -o bench -isystem$(GLIBC_ROOT)/include/ -I. -Iinclude -isystem$(GLIBC) -nodefaultlibs -DV8_COMPRESS_POINTERS -static $(V8_OBJ)/libv8_monolith.a -Wl,--start-group $(GCC_ROOT)/lib64/libstdc++.a $(GLIBC_ROOT)/lib/libpthread.a $(GLIBC_ROOT)/lib/libdl.a $(GLIBC_ROOT)/lib/libm.a $(GLIBC_ROOT)/lib/librt.a $(GCC_GCC)/libgcc.a $(GCC_GCC)/libgcc_eh.a $(GCC_GCC)/libcommon.a $(GLIBC_ROOT)/lib/libc.a -Wl,--end-group |
附注
2558版本
2558版本是一个较老的版本,它是使用Make和GYP来构建的。由于它同样需要移动语义,所以也要C++11的支持。主要涉及下面的修改
build/standalone.gypi
设置$(snapshot)为off。
设置clang%为1。- Hello world链接过程
1
g++ -pthread -fuse-ld=gold -fuse-ld=gold -m64 -m64 -rdynamic -rdynamic -Wl,--threads -Wl,--thread-count=4 -Wl,--threads -Wl,--thread-count=4 -o hello -Wl,--start-group hello-world.o out_libs/libv8_libplatform.a out_libs/libicui18n.a out_libs/libicuuc.a out_libs/libv8_base.a out_libs/libv8_libbase.a out_libs/libicudata.a out_libs/libv8_nosnapshot.a -Wl,--end-group -ldl -lrt