WebAssembly 系統介面 (WASI)#

穩定性:1 - 實驗性

node:wasi 模組目前不提供某些 WASI 執行時所提供的全面檔案系統安全屬性。未來可能會也可能不會實現對安全檔案系統沙箱的完全支援。在此期間,請勿依賴它來執行不受信任的程式碼。

原始碼: lib/wasi.js

WASI API 提供了 WebAssembly 系統介面規範的實現。WASI 透過一組類 POSIX 函式,讓 WebAssembly 應用程式能夠訪問底層作業系統。

import { readFile } from 'node:fs/promises';
import { WASI } from 'node:wasi';
import { argv, env } from 'node:process';

const wasi = new WASI({
  version: 'preview1',
  args: argv,
  env,
  preopens: {
    '/local': '/some/real/path/that/wasm/can/access',
  },
});

const wasm = await WebAssembly.compile(
  await readFile(new URL('./demo.wasm', import.meta.url)),
);
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());

wasi.start(instance);'use strict';
const { readFile } = require('node:fs/promises');
const { WASI } = require('node:wasi');
const { argv, env } = require('node:process');
const { join } = require('node:path');

const wasi = new WASI({
  version: 'preview1',
  args: argv,
  env,
  preopens: {
    '/local': '/some/real/path/that/wasm/can/access',
  },
});

(async () => {
  const wasm = await WebAssembly.compile(
    await readFile(join(__dirname, 'demo.wasm')),
  );
  const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());

  wasi.start(instance);
})();

要執行上面的示例,請建立一個名為 demo.wat 的新 WebAssembly 文字格式檔案。

(module
    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
    ;; The function signature for fd_write is:
    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
    (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))

    ;; Write 'hello world\n' to memory at an offset of 8 bytes
    ;; Note the trailing newline which is required for the text to appear
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; Creating a new io vector within linear memory
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string

        (call $fd_write
            (i32.const 1) ;; file_descriptor - 1 for stdout
            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
        )
        drop ;; Discard the number of bytes written from the top of the stack
    )
) 

使用 wabt.wat 編譯為 .wasm

wat2wasm demo.wat 

安全#

WASI 提供了一個基於能力(capabilities-based)的模型,透過該模型為應用程式提供其自定義的 envpreopensstdinstdoutstderrexit 能力。

Node.js 當前的威脅模型不像某些 WASI 執行時那樣提供安全的沙箱環境。

雖然支援這些能力特性,但它們在 Node.js 中並不構成一個安全模型。例如,檔案系統沙箱可以透過各種技術被繞過。該專案正在探索未來是否可以新增這些安全保證。

類: WASI#

WASI 類提供了 WASI 系統呼叫 API 以及用於處理基於 WASI 的應用程式的附加便利方法。每個 WASI 例項代表一個獨立的環境。

new WASI([options])#

  • options <Object>
    • args <Array> 一個字串陣列,WebAssembly 應用程式會將其視為命令列引數。第一個引數是 WASI 命令本身的虛擬路徑。預設值: []
    • env <Object> 一個類似於 process.env 的物件,WebAssembly 應用程式會將其視為其環境。預設值: {}
    • preopens <Object> 此物件表示 WebAssembly 應用程式的本地目錄結構。preopens 的字串鍵被視為檔案系統內的目錄。preopens 中相應的值是這些目錄在主機上的真實路徑。
    • returnOnExit <boolean> 預設情況下,當 WASI 應用程式呼叫 __wasi_proc_exit() 時,wasi.start() 會返回指定的退出碼,而不是終止程序。將此選項設定為 false 將導致 Node.js 程序以指定的退出碼退出。預設值: true
    • stdin <integer> 在 WebAssembly 應用程式中用作標準輸入的檔案描述符。預設值: 0
    • stdout <integer> 在 WebAssembly 應用程式中用作標準輸出的檔案描述符。預設值: 1
    • stderr <integer> 在 WebAssembly 應用程式中用作標準錯誤的檔案描述符。預設值: 2
    • version <string> 請求的 WASI 版本。目前唯一支援的版本是 unstablepreview1。此選項是必需的。

wasi.getImportObject()#

返回一個匯入物件,如果除了 WASI 提供的匯入之外不需要其他 WASM 匯入,則可以將其傳遞給 WebAssembly.instantiate()

如果版本 unstable 被傳入建構函式,它將返回

{ wasi_unstable: wasi.wasiImport } 

如果版本 preview1 被傳入建構函式,或者沒有指定版本,它將返回

{ wasi_snapshot_preview1: wasi.wasiImport } 

wasi.start(instance)#

嘗試透過呼叫 instance_start() 匯出來開始執行該例項作為 WASI 命令。如果 instance 不包含 _start() 匯出,或者如果 instance 包含 _initialize() 匯出,則會丟擲異常。

start() 要求 instance 匯出一個名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會丟擲異常。

如果 start() 被呼叫多次,則會丟擲異常。

wasi.initialize(instance)#

嘗試透過呼叫 instance_initialize() 匯出來將其初始化為 WASI 反應器(reactor),如果該匯出存在的話。如果 instance 包含 _start() 匯出,則會丟擲異常。

initialize() 要求 instance 匯出一個名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會丟擲異常。

如果 initialize() 被呼叫多次,則會丟擲異常。

wasi.finalizeBindings(instance[, options])#

在不呼叫 initialize()start() 的情況下,為 instance 設定 WASI 主機繫結。當 WASI 模組在子執行緒中例項化以線上程間共享記憶體時,此方法非常有用。

finalizeBindings() 要求 instance 匯出一個名為 memoryWebAssembly.Memory,或者使用者在 options.memory 中指定一個 WebAssembly.Memory 物件。如果 memory 無效,則會丟擲異常。

start()initialize() 會在內部呼叫 finalizeBindings()。如果 finalizeBindings() 被呼叫多次,則會丟擲異常。

wasi.wasiImport#

wasiImport 是一個實現 WASI 系統呼叫 API 的物件。在例項化 WebAssembly.Instance 期間,應將此物件作為 wasi_snapshot_preview1 匯入來傳遞。