WebAssembly 中的内存(以及为什么它比你想象的更安全)

这是 3 部分系列文章中的第 2 篇

  1. 使用 JavaScript 创建 WebAssembly 模块实例
  2. WebAssembly 中的内存(以及为什么它比你想象的更安全)
  3. WebAssembly 表导入……它们是什么?

WebAssembly 中的内存工作方式与 JavaScript 中略有不同。在 WebAssembly 中,你可以直接访问原始字节……这会让有些人担心。但实际上它比你想象的更安全。

什么是内存对象?

当 WebAssembly 模块被实例化时,它需要一个内存对象。你可以创建一个新的 WebAssembly.Memory 并将该对象传入。或者,如果你不这样做,会自动创建一个内存对象并将其附加到该实例。

JS 引擎在内部所做的全部工作只是创建一个 ArrayBuffer(我在另一篇文章中解释了这一点)。ArrayBuffer 是一个 JavaScript 对象,JS 对它有引用。JS 为你分配内存。你告诉它你需要多少内存,它就会创建一个相应大小的 ArrayBuffer。

React.js requesting a new memory object and JS engine creating one

数组的索引可以被视为内存地址。如果你稍后需要更多内存,可以执行称为“增长”的操作来使数组变大。

将 WebAssembly 的内存视为一个 ArrayBuffer(作为 JavaScript 中的一个对象)有两个作用

  1. 它使在 JS 和 WebAssembly 之间传递值变得容易
  2. 它有助于使内存管理更安全

在 JS 和 WebAssembly 之间传递值

由于这只是一个 JavaScript 对象,这意味着 JavaScript 也可以在该内存的字节中进行查找。因此,通过这种方式,WebAssembly 和 JavaScript 可以共享内存并在彼此之间传递值。

他们不使用内存地址,而是使用数组索引来访问每个盒子。

例如,WebAssembly 可以将一个字符串放在内存中。它会将其编码成字节……

WebAssembly robot putting string "Hello" through decoder ring

……然后将这些字节放入数组中。

WebAssembly robot putting bytes into memory

然后它会将第一个索引(一个整数)返回给 JavaScript。这样 JavaScript 就可以提取这些字节并使用它们。

WebAssembly robot returning index of first byte in string

现在,大多数 JavaScript 并不了解如何直接处理字节。因此,你需要在 JavaScript 方面使用一些东西(就像你在 WebAssembly 方面所做的那样),它可以将字节转换成更实用的值(如字符串)。

在某些浏览器中,你可以使用 TextDecoderTextEncoder API。或者你可以在 .js 文件中添加辅助函数。例如,像 Emscripten 这样的工具可以添加编码和解码辅助函数。

JS engine pulling out bytes, and React.js decoding them

所以这是 WebAssembly 内存只是一个 JS 对象的第一个好处。WebAssembly 和 JavaScript 可以通过内存直接来回传递值。

使内存访问更安全

WebAssembly 内存只是一个 JavaScript 对象还有另一个好处:安全。它通过帮助防止浏览器级别的内存泄漏并提供内存隔离来使事情更安全。

内存泄漏

正如我在关于内存管理的文章中提到的,当你管理自己的内存时,你可能会忘记清除它。这会导致系统内存不足。

如果 WebAssembly 模块实例可以直接访问内存,如果它忘记在超出范围之前清除该内存,那么浏览器可能会出现内存泄漏。

但由于内存对象只是一个 JavaScript 对象,它本身由垃圾回收器跟踪(即使它的内容没有被跟踪)。

这意味着当与内存对象关联的 WebAssembly 实例超出范围时,整个内存数组就会被垃圾回收。

Garbage collector cleaning up memory object

内存隔离

当人们听到 WebAssembly 使你可以直接访问内存时,他们可能会有点紧张。他们认为恶意 WebAssembly 模块可能会进入并访问它不应该访问的内存。但事实并非如此。

ArrayBuffer 的边界提供了一个边界。它是 WebAssembly 模块可以直接访问的内存的限制。

Red arrows pointing to the boundaries of the memory object

它可以直接访问该数组内部的字节,但它无法看到该数组边界之外的任何内容。

例如,任何其他位于内存中的 JS 对象(如 window 全局对象)都无法被 WebAssembly 访问。这对安全性非常重要。

在 WebAssembly 中出现加载或存储操作时,引擎会进行数组边界检查以确保地址位于 WebAssembly 实例的内存内。

如果代码试图访问超出边界的地址,引擎将抛出一个异常。这会保护剩余的内存。

WebAssembly trying to store out of bounds and being rejected

所以这就是内存导入。在下一篇文章中,我们将看看另一种使事情更安全的导入……表导入.

关于 Lin Clark

Lin 在 Mozilla 的高级开发部门工作,专注于 Rust 和 WebAssembly。

更多 Lin Clark 的文章……


6 条评论

  1. Ari Ogoke

    您好,Clark 女士

    我偶然发现了你的作品——特别是用卡通解释技术概念。我喜欢我看到的一切,我会回来探索!

    2017 年 7 月 20 日 19:50

  2. Duc Nguyen

    特别感谢这篇文章!感谢信息

    2017 年 7 月 21 日 18:21

  3. Shamim Ahmmed

    谢谢。

    2017 年 7 月 22 日 00:30

  4. Gabrielle

    谢谢,还有来自巴黎的问候!

    2017 年 7 月 23 日 12:52

  5. Ang

    谢谢!
    你的文章非常有趣且有帮助!

    Wasm 中的所有字符串和数组数据是否都存储在 ArrayBuffer 中,无论它们是否被传递到 JavaScript?
    我们是否可以通过 JavaScript 访问它们?

    2017 年 7 月 24 日 20:11

    1. Lin Clark

      是的,WebAssembly 代码中的加载和存储操作会从内存对象(即 ArrayBuffer)中读取和写入。因此,即使数据没有被传递到 JavaScript,它也会存储在 ArrayBuffer 中。JavaScript 确实可以访问内存对象及其中的所有字节。

      2017 年 7 月 25 日 17:34

本文的评论已关闭。