NaCl学习笔记
Chrome是一个多进程(multi-process)架构的浏览器,相比起Firefox之类的单进程浏览器,其提供额外的完全特性。 Chrome包含一个称为“browser”的主进程,其负责运行UI界面、管理标签页(Tabs)和插件进程,具有当前用户所有的访问权限(文件、网络等),并且能够fork出子进程。
标签页被分配到单独的“renderer”进程中,通常每个domain会共享一个进程。展示进程运行在沙盒(Sandbox)中,不能打开文件、网络。展示进程负责渲染HTML布局、处理页面展示。
插件指外部的二进制文件,插件为浏览器添加额外的功能。插件可以随着浏览器一起安装,例如 Adobe Flash、Adobe Reader,也可以是用户自行安装的。一般来说,现有的大部分插件由于需要网络、文件系统的访问权限,而不能在沙盒内运行,因此Chrome提供了进程外插件机制(out of process plugins),在独立进程中以完整访问权限运行插件,并且使用进程间通信(IPC)机制来与browser、renderer进程通信。
Chrome也支持进程内插件(in process plugins),其运行于renderer进程内部,不需要使用IPC机制进行通信。
NPAPI是一个跨浏览器的插件框架,除了微软使用ActiveX技术以外,其他主要浏览器均支持。Chrome使用进程外插件的方式来支持NPAPI。Chrome 目前已经不再支持NPAPI。
PPAPI是谷歌提出的开源、跨平台插件框架,其主要目的是解决NPAPI的性能、可移植性问题。PPAPI的初期设计曾考虑尽可能保持与NPAPI的接口风格一致性,但是这一设计原则逐渐被放弃。
PPAPI允许C/C++模块与浏览器交互、以安全可移植方式来访问系统级函数。PPAPI不支持操作系统调用,但是它提供了类似的API。
PPAPI支持以下操作:
- 与JavaScript的双向通信
- 进行文件I/O操作
- 播放音频
- 渲染3D图形
Google没有提供自动化安装第三方的PPAPI插件的方式。使用类似下面的命令行参数启动Chrome,可以手工加载PPAPI插件:
1 |
chrome --disable-sandbox --register-pepper-plugins="<plugin_dll_path>;mime/type" |
NaCl是一种在浏览器中安全执行平台无关的、不受信任代码的开源沙盒技术,它允许计算密集型、交互性的实时Web应用有效的利用机器的物理性能,并同时保证安全性。NaCl本质上是一个进程内PPAPI插件。
一个NaCl应用程序包含:JavaScript、HTML、CSS、以及一个NaCl模块。目前NaCl SDK支持的语言主要是C和C++。
NaCl SDK是用于开发NaCl可执行文件(nexe)的工具包。由一系列GNU工具链组成,包括 gcc, binutils、gdb等。
Portable Native Client(PNacl)则是在NaCl的基础上支持硬件架构的无关性,可以在任何支持AOT(ahead-of-time)的平台下运行,必须在Chrome 31+才能支持。Chrome负责把pexe格式的中间代码翻译成客户端本地代码。PNacl可以在不依赖Google Web Store的情况下进行部署。在Chrome://flags中开启Native Client标记可以运行任意NaCl。
应当尽可能使用PNacl,而不是NaCl,除非:
- 程序需要平台相关的指令,例如内联汇编
- 程序使用动态链接,PNacl仅支持与newlib C标准库的naclport静态链接,对glibc的静态链接、以及动态链接尚不支持
- 程序使用了某些PNaCl的LLVM工具链所不支持的GNU扩展
- PNaCl工具链:基于LLVM工具链。使用newlib库,生成单个pexe文件,在运行时,浏览器内置的AOT转换器负责将其转换为本地代码
- NaCl工具链:基于GCC,包含x86_newlib、x86_glibc、arm_newlib三个工具链,生成多个架构相关的nexe文件,并打包到一个应用,浏览器负责决定使用哪个nexe
不能在一个应用里混合使用多个工具链。下表列出这些工具链使用的C标准库、目录名称,其中platform可能是win或者linux
目标架构 | C标准库 | 工具链目录 |
x86 | newlib | toolchain/<platform>_x86_newlib |
x86 | glibc | toolchain/<platform>_x86_glibc |
ARM | newlib | toolchain/<platform>_arm_newlib |
PNaCl | newlib | toolchain/<platform>_pnacl |
- NaCl沙盒确保代码只能通过安全的、白名单中的API来访问系统资源,并且确保代码不能干扰其它浏览器内外代码的运行
- NaCl验证器在运行前静态分析代码,确保只包含允许的代码和数据图式(pattern)
至少包含HTML代码、Manifest文件和NaCl模块3部分内容。
HTML代码中需要包含embed标签,用于定位manifest文件:
1 2 |
<embed width="300" height="150" type="application/x-pnacl" src="helloworld.nmf" name="mygame"> </embed> |
Manifest文件以nmf为扩展名,定义了需要加载的NaCl模块以及选项,形式如下:
1 2 3 |
{ "url": "helloworld.pexe" } |
NaCl模块是编译好的pexe文件,其使用Pepper API作为本地代码与JavaScript、其它浏览器资源的桥梁。
NaCl具有以下缺点:
- 不能直接访问硬件,相比之下WebRTC之类的技术允许访问音视频捕获设备
- 没有完整的OpenGL特性支持
- 不允许原始TCP/UDP
- 其它浏览器厂商对其缺乏兴趣
- 不支持同步化的JavaScript调用,所有NaCl与JavaScript的交互均是异步的
PPAPI (without NaCL)
本文上面已经提到过,PPAPI插件无法方便的安装,只能使用命令行参数
Google Native Messaging
该技术允许Chrome插件与本地程序进行消息交互。
优势:
- 支持运行本地代码,可以进行完整的硬件访问、原生TCP/UDP访问
- 支持从Chrome启动本地程序
劣势:
- 两个单独的安装程序:本地程序的安装、Chrome扩展的安装
- 难以获取视频数据返回给浏览器,不适合密集的数据传输
- 到https://developer.chrome.com/native-client/sdk/download下载SDK
- 解压到D:\CPP\tools\Chromium\nacl_sdk
- 运行以下脚本:
12cd nacl_sdknaclsdk update --force - 安装Python 2.7.x,并添加到PATH环境变量
- 下载SDK并解压到~/CPP/tools/Chromium/nacl_sdk
- 运行脚本:
123456789sudo apt-get install libc6:i386cd ~/CPP/tools/Chromium/nacl_sdk./naclsdk updatevim ~/.bashrc#添加以下内容export PATH="$PATH":/home/alex/CPP/tools/Chromium/depot_toolsexport NACL_SDK_ROOT=/home/alex/Chromium/nacl_sdk/pepper_39 - 如果没有安装Python 2.x,则apt-get安装
环境变量:
NACL_SDK_ROOT=D:\CPP\tools\nacl_sdk\pepper_39
如果安装了Cygwin,需要把Cygwin的bin目录从PATH中排除掉(可以通过Eclipse工程右键,Properties - C/C++ Build Environment设置),否则构建时可能收到类似“/x86_64-nacl-ld: cannot find -lppapi_gles2”的错误
IDE设置(Eclipse CDT):
- File - New - Makefile Project with existing Code,工程名称设置为nacl-helloworld,Existing Code Location定位到准备存放代码的目录,Toolchain for Indexer Settings选择Cross GCC
- 工程点右键 - Properties - C/C++ General - Paths and Symbols - Source Location,删除默认,与Makefile设置保持一致
- 工程点右键 - Properties - C/C++ General - Paths and Symbols - Output Location,删除默认,添加/project-root/***,***可以是glibc、newlib、pnacl等,根据Makefile中设置的工具链(VALID_TOOLCHAINS)来设置
- 工程点右键 - Properties - C/C++ General - Paths and Symbols - Includes - GNU C++,添加:${NACL_SDK_ROOT}/include、${NACL_SDK_ROOT}/toolchain/win_***/x86-64_bc-nacl/include,根据Makefile中设置的工具链(VALID_TOOLCHAINS)来设置
- 工程点右键 - Properties - C/C++ Build,取消勾选“Use default build command”,设置为make.bat
- 工程点右键 - Make Targets - Create,创建名为serve的构建目标
1 |
@%NACL_SDK_ROOT%\tools\make.exe %* |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
CONFIG = Debug VALID_TOOLCHAINS := newlib glibc pnacl win NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../../..) include $(NACL_SDK_ROOT)/tools/common.mk TARGET = hello_world LIBS = ppapi_cpp ppapi CFLAGS = -Wall SOURCES = hello_world.cc $(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS)))) ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG)))) $(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) $(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) else $(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) endif $(eval $(call NMF_RULE,$(TARGET),)) |
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 |
#include <string> #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/var.h" /** * NaCl实例类 * 浏览器端的每一个embed标签对应了一个实例 * 指定embed标签的属性: * src="hello_world.nmf" * type="application/x-pnacl" */ class HelloWorldInstance : public pp::Instance { public: explicit HelloWorldInstance( PP_Instance instance ) : pp::Instance( instance ) { } virtual ~HelloWorldInstance() { } /** * 处理浏览器端通过postMessage()调用传入的消息 * @param var_message 浏览器传入的消息 */ virtual void HandleMessage( const pp::Var& var_message ) { // 处理传入消息 if ( !var_message.is_string() ) return; std::string message = var_message.AsString(); pp::Var var_reply; if ( message == "hello" ) { var_reply = pp::Var( "hello from NaCl" ); PostMessage( var_reply ); } } }; /** * NaCl模块类 */ class HelloWorldModule : public pp::Module { public: HelloWorldModule() : pp::Module() { } virtual ~HelloWorldModule() { } /** * 在模块加载完成后,浏览器即调用此函数创建实例。 * @param in 浏览器端的实例对象句柄 * @return 插件端的实例对象 */ virtual pp::Instance* CreateInstance( PP_Instance instance ) { return new HelloWorldInstance( instance ); } }; namespace pp { /** * 该工厂函数在NaCl模块在加载时被浏览器调用一次,不会多次调用 * 浏览器维持模块的单例 * */ Module* CreateModule() { return new HelloWorldModule(); } } |
1 2 3 4 5 6 7 8 9 |
{ "program": { "portable": { "pnacl-translate": { "url": "hello_world.pexe" } } } } |
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 |
<!DOCTYPE html> <html> <head> <title>hello World</title> <script type="text/javascript"> HelloWorldModule = null; // 全局模块对象 statusText = 'NO-STATUS'; function moduleDidLoad() { HelloWorldModule = document.getElementById( 'hello_world' ); updateStatus( 'SUCCESS' ); HelloWorldModule.postMessage('hello'); } //处理NaCl模块发送的消息 function handleMessage( message_event ) { alert( message_event.data ); } function pageDidLoad() { if ( HelloWorldModule == null ) { updateStatus( 'LOADING...' ); } else { updateStatus(); } } function updateStatus( opt_message ) { if ( opt_message ) statusText = opt_message; var statusField = document.getElementById( 'statusField' ); if ( statusField ) { statusField.innerHTML = statusText; } } </script> </head> <body onload="pageDidLoad()"> <div id="listener"> <script type="text/javascript"> var listener = document.getElementById( 'listener' ); listener.addEventListener( 'load', moduleDidLoad, true ); listener.addEventListener( 'message', handleMessage, true ); </script> <embed id="hello_world" width=0 height=0 src="newlib/Debug/hello_world.nmf" type="application/x-nacl" /> </div> <div> Status <code id="statusField">NO-STATUS</code> </div> </body> </html> |
- Project - Build Project,会生成hello_world.pexe文件
- 鼠标选中工程根目录,Shift + F9,选择serve
- 使用Chrome打开http://localhost:5103/,可以看到弹出消息框,显示来自NaCl模块的消息文本:“hello from NaCl”,运行成功
- 在源代码hello_world.cc的79行,设置一个断点
- 修改Makefile,以Debug方式编译,并设置工具链把newlib置为默认:
12CONFIG = DebugVALID_TOOLCHAINS := newlib glibc pnacl win - Run - Debug Configurations,在C/C++ Remote Application目录下创建新的Debug配置
- Main 选项卡
- Name设置为nacl-helloworld newlib;Project选择nacl-helloworld
- C/C++ Application填写:D:\CPP\projects\eclipse\4.3.2\nacl-helloworld\newlib\Debug\hello_world_x86_64.nexe
- 底部的链接Select Other,点击后选择GDB(DSF) Manual Remote Debugging Launcher
- 点选Disable auto build
- Debugger选项卡
- Stop on startup at设置为:Instance_DidCreate,或者取消勾选
- GDB Debugger选择:%NACL_SDK_ROOT%\toolchain\win_x86_newlib\bin\x86_64-nacl-gdb.exe
- 切换到子选项卡Connection,端口设置为4014
- Source选项卡,点击Add - Path Mapping,Name设置为pepper_mapping,添加一个映射条目:\cygdrive\s\src\out 对应D:\CPP\tools\Chromium\nacl_sdk,可以在调试无法找到源代码时再行设置
- 点击Apply保存设置,然后点击Run,Eclipse Debugger开始等待连接到Chrome
- 在客户端设置以下环境变量:
12345678910rem 重定向stderr、stdoutset NACL_EXE_STDERR=F:\Temp\Chrome\nacl_stderr.logset NACL_EXE_STDOUT=F:\Temp\Chrome\nacl_stdout.logrem 重定向NaCl内部生成的消息,默认至stderrset NACLLOG=F:\Temp\Chrome\nacl.logrem 控制警告、错误信息的显示set NACL_PLUGIN_DEBUG=1rem 1-255,越高则信息越详细set NACL_SRPC_DEBUG=255set NACLVERBOSITY=255 - 运行make serve启动HTTP服务器
- 以参数--no-sandbox --enable-nacl-debug启动Chrome,如果使用ChromiumPortable,可以修改ChromiumPortable.ini:
123[GoogleChromePortable];--vmodule=ppb*=4 --enable-logging=stderr用于记录NaCl模块对Pepper API的调用AdditionalParameters=--no-sandbox --enable-nacl-debug --disable-hang-monitor --vmodule=ppb*=4 --enable-logging=stderr - Chrome暂停运行,Eclipse自动停止在断点处,调试成功
NaCl提供了一种跨平台的类POSIX环境,很多Linux下开源项目都可以在此环境下运行,但是需要特殊的编译过程(port to nacl),在Windows下,需要Cygwin才能编译ports。
- 安装需要的工具
12sudo apt-get install curl sed git cmake texinfo gettext pkg-config autoconf automake libtool xsltprocsudo apt-get install libglib2.0-dev - 安装depot_tools,depot_tools是一个脚本工具包,用于管理代码的签出、review,包含gclient, gcl, git-cl, repo等工具
123456cd ~/CPP/tools/Chromiumgit clone https://chromium.googlesource.com/chromium/tools/depot_tools.gitvim ~/.bashrc#添加export PATH=`pwd`/depot_tools:"$PATH" - 签出naclports源代码
12345cd ~/CPP/tools/Chromiummkdir naclportscd naclportsgclient config --name=src https://chromium.googlesource.com/external/naclports.gitgclient sync - 进入/home/alex/CPP/tools/Chromium/naclports/src,可以看到若干Ports的源代码,每个子目录通常包含以下文件:
- pkg_info:port的描述
- build.sh:用于构建此port的脚本
- nacl.patch:一个可选的补丁文件
- 构建port的工具位于naclports/bin/naclports,执行下面的脚本即可下载、打补丁、构建、安装目标应用或者库
1naclports install <package_dir>注意NaCl模块可以在四种架构下构建(i686,x86_64, arm, pnacl),每次构建只会使用其中一种架构。某些架构下有多个Toolchain(例如x86有newlib、glibc),可以通过环境变量选择:
1NACL_ARCH=i686 TOOLCHAIN=glibc make ffmpeg如果要为某个port编译所有架构下所有工具链的版本,可以运行:
1./make_all.sh ffmpeg头文件、库被安装到对应工具链的目录下,使用这些库不需要额外的-I、-L参数。源码和编译的输出文件位于out/build/<PACKAGE_NAME>目录下。默认的,所有编译都是以RELEASE方式进行的,可以设置环境变量NACL_DEBUG=1来改变此行为,或者给naclports 传递 --debug参数。naclports会优先尝试直接从Google下载二进制包,如果要强制从源代码编译,可以传递--from-source参数
- 使用如下命令构建ffmpeg:
12cd ~/CPP/tools/Chromium/naclports/srcNACL_ARCH=pnacl make ffmpeg
可以下载便携版(Portable)的Chromium:http://sourceforge.net/projects/crportable/
类似的,便携版的Chrome也有,可以到网上搜索并下载。
Leave a Reply