WebAssembly 系統介面 (WASI)#

穩定性:1 - 實驗性

node:wasi 模組目前未提供一些 WASI 執行時期提供的全面性檔案系統安全性屬性。未來可能會或可能不會實作對安全檔案系統沙盒的完整支援。在此同時,請勿依賴它來執行不可信賴的程式碼。

原始碼: lib/wasi.js

WASI API 提供 WebAssembly 系統介面 規格的實作。WASI 透過一系列類似 POSIX 的函式,讓 WebAssembly 應用程式存取基礎作業系統。

import { readFile } from 'node:fs/promises';
import { WASI } from '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('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 提供一個基於功能的模型,透過此模型,應用程式會提供其自訂的 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)#

嘗試透過呼叫其 _start() 輸出,開始執行 instance 作為 WASI 命令。如果 instance 不包含 _start() 輸出,或如果 instance 包含 _initialize() 輸出,則會擲回例外狀況。

start() 要求 instance 匯出名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會擲回例外。

如果 start() 被呼叫超過一次,則會擲回例外。

wasi.initialize(instance)#

嘗試初始化 instance 作為 WASI 反應器,方法是呼叫其 _initialize() 匯出(如果存在)。如果 instance 包含 _start() 匯出,則會擲回例外。

initialize() 要求 instance 匯出名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會擲回例外。

如果 initialize() 被呼叫超過一次,則會擲回例外。

wasi.wasiImport#

wasiImport 是實作 WASI 系統呼叫 API 的物件。這個物件應該在 WebAssembly.Instance 的實例化期間傳遞為 wasi_snapshot_preview1 匯入。