Rust 团队很高兴宣布 Rust 的新版本 1.24.1。Rust 是一种系统编程语言,专注于安全、速度和并发。
如果您之前已通过 rustup 安装了 Rust,获取 Rust 1.24.1 就像
$ rustup update stable
如果您还没有,您可以从我们网站上的相应页面获取 rustup
,并查看 GitHub 上1.24.1 的详细发布说明。
1.24.1 稳定版中的内容
在 1.24.0 中发现了一些小错误,这些错误共同导致了这次发布。
更改的简要概述
- 通过 FFI 解除分配时不要中止(这恢复了 1.24.0 中添加的行为)
- 为 Windows 上的链接器参数生成 UTF-16 文件
- 使错误索引生成器再次工作
- 如果需要更新,Cargo 将在 Windows 7 上发出警告。
如果您的代码继续构建,那么唯一可能影响您的问题是解除分配问题。我们计划在 1.25 或 1.26 中恢复此行为,具体取决于新策略的顺利程度。
有了这些,让我们深入了解细节!
通过 FFI 解除分配时不要中止
TL;DR:1.24.0 中的新行为破坏了 rlua
箱,并且正在恢复。如果您已经更改了代码以利用 1.24.0 中的行为,则现在需要恢复它。虽然我们仍然计划最终引入此行为,但我们将以更缓慢的速度和新的实现策略推出它。
引用1.24 宣布
这里还有一个我们想谈论的更改:未定义行为。Rust 通常努力最大限度地减少未定义行为,在安全代码中没有未定义行为,在不安全代码中尽可能少地出现未定义行为。您可以在 panic! 跨越 FFI 边界时调用 UB 的一个区域。换句话说,这
extern "C" fn panic_in_ffi() {
panic!("Test");
}
这行不通,因为 panic! 的工作机制必须与“C”ABI 的工作方式相协调,在本例中,或在其他示例中的任何其他 ABI。
在 Rust 1.24 中,此代码现在将中止而不是产生未定义行为。
如上所述,这导致了破坏。它始于针对 rlua
箱提交的错误。rlua
是一个提供 Rust 和Lua 编程语言 之间的高级绑定的包。
旁注:
rlua
由Chucklefish维护,Chucklefish 是一家来自伦敦的游戏开发工作室,正在使用 Rust。Lua 是一种非常流行的用于扩展和编写游戏脚本的语言。我们非常关心生产中的 Rust 用户,因此处理这个问题对 Rust 团队来说是一个非常高的优先级。
在 Windows 上,并且仅在 Windows 上,任何尝试处理来自 Lua 的错误都将简单地中止。这使得 rlua
无法使用,因为 Lua 中的任何错误都会导致您的程序崩溃。
深入研究后,找到了罪魁祸首:setjmp
/longjmp
。这些函数由 C 标准库提供,作为处理错误的一种方式。您首先调用 setjmp
,然后在稍后的某个时间点调用 longjmp
。当您这样做时,控制流将返回到您之前调用 setjmp
的位置。这通常用作实现异常的一种方式,有时甚至用作协程。Lua 的实现使用 setjmp
/longjmp
来实现异常
与 C++ 或 Java 不同,C 语言不提供异常处理机制。为了缓解这种困难,Lua 使用来自 C 的 setjmp 机制,这会导致类似于异常处理的机制。(如果您使用 C++ 编译 Lua,则不难更改代码以使用真正的异常。)
问题是:当一些 C 代码 setjmp
/longjmp
穿过 Rust 堆栈帧时会发生什么?因为丢弃检查和借用检查对这种控制流方式一无所知,所以如果您 longjmp
穿过一个 Rust 堆栈帧,该帧在其堆栈上有任何不是 Copy
的类型,则会导致未定义行为。但是,如果跳转完全发生在 C 中,这应该可以正常工作。这就是 rlua
的管理方式:对 Lua 的每次调用都用 lua_pcall
包装
但是,当您为 Lua 编写库函数时,有一种标准方法来处理错误。每当 C 函数检测到错误时,它只需调用
lua_error
(或者更好的是luaL_error
,它格式化错误消息,然后调用lua_error
)。lua_error
函数清除 Lua 中需要清除的任何内容,并跳回到发出该执行的lua_pcall
,并将错误消息传递过去。
因此,问题变成了:为什么这会中断?为什么它会在 Windows 上中断?
当我们最初谈论 setjmp
/longjmp
时,这里没有突出显示一个关键短语。它在这里
深入研究后,找到了罪魁祸首:
setjmp
/longjmp
。这些函数由 C 标准库提供,作为处理错误的一种方式。
这些函数不是 C 语言的一部分,而是标准库的一部分。这意味着平台作者实现这些函数,并且它们的实现可能不同。
Windows 有一个称为 SEH 的概念,它是“结构化异常处理”的缩写。Windows 使用 SEH 来实现 setjmp
/longjmp
,因为 SEH 的整个理念是拥有统一的错误处理。出于类似的原因,C++ 异常使用 SEH,Rust panic 也使用 SEH。
在我们能够整理出正在发生的事情的确切细节之前,让我们看看 rlua
的工作原理。rlua
有一个内部函数 protect_lua_call
,用于调用 Lua。使用它看起来像这样
protect_lua_call(self.state, 0, 1, |state| {
ffi::lua_newtable(state);
})?;
也就是说,protect_lua_call
接受一些参数,其中一个是闭包。此闭包传递给 lua_pcall
,它捕获由传递给它的代码(即该闭包)抛出的任何 longjmp
。
考虑上面的代码,并想象 lua_newtable
这里可以调用 longjmp
。以下是应该发生的事情
protect_lua_call
接受我们的闭包,并将其传递给lua_pcall
。lua_pcall
调用setjmp
来处理任何错误,并调用我们的闭包。- 在我们的闭包中,
lua_newtable
出现错误,并调用longjmp
。 - 最初的
lua_pcall
使用它之前调用的setjmp
捕获longjmp
。 - 每个人都很高兴。
但是,protect_lua_call
的实现将我们的闭包转换为 extern fn
,因为这是 Lua 所需的。因此,在 1.24.0 中的更改中,它设置了一个 panic 处理程序,该处理程序将导致中止。换句话说,代码现在有点像这样伪代码
protect_lua_call(self.state, 0, 1, |state| {
let result = panic::catch_unwind(|| {
ffi::lua_newtable(state);
});
if result.is_err() {
process::abort();
}
})?;
早些时候,在讨论 setjmp
/longjmp
时,我们说在 Rust 代码中使用它的问题是它不处理 Rust 析构函数。因此,在除 Windows 之外的所有平台上,上面的 catch_unwind
诡计实际上被忽略了,因此一切正常。但是,在 Windows 上,由于 setjmp
/longjmp
和 Rust panic 都使用 SEH,因此 longjmp
被“捕获”,并运行新的中止代码!
这里的解决方案是生成中止处理程序,但以 longjmp
不会触发它的方式生成。目前还不确定这是否会进入 Rust 1.25;如果着陆顺利,我们可能会回溯,否则,此功能将在 1.26 中恢复。
为 Windows 上的链接器参数生成 UTF-16 文件
TL;DR:rustc
在某些边缘情况下停止为某些 Windows 用户工作。如果它一直为您工作,那么您不会受到此错误的影响。
与之前的错误(非常复杂且难以理解)形成对比,此错误的影响很简单:如果您在调用 rustc
的目录中具有非 ASCII 路径,则在 1.24 中,它将错误地报错,并显示类似以下内容的消息
致命错误 LNK1181:无法打开输入文件
导致它的 PR,#47507,对最终导致问题的行为有一个很好的解释
在生成链接器时,rustc 历来以超出命令行大小的 OS 限制而闻名,尤其是在 Windows 上。这在增量编译中尤其如此,因为每次编译可能会有数十个目标文件。编译器目前具有用于检测生成失败并改为通过文件传递参数的逻辑,但这种失败检测仅在进程实际生成失败时才会触发。
但是,在生成该文件时,我们做错了。正如文档所述
响应文件和 DEF 文件可以是带有 BOM 的 UTF-16,也可以是 ANSI。
我们提供了一个 UTF-8 编码的文件,没有BOM。因此,修复方法很简单:生成一个带有 BOM 的 UTF-16 文件。
使错误索引生成器再次工作
TL;DR:在某些情况下,使用 Rust 1.24.0 构建 Rust 1.24.0 会失败。如果您没有自己构建 Rust,那么您不会受到此错误的影响。
在为各种 Linux 发行版打包 Rust 时,发现使用 1.24 构建 1.24 会失败。这是由错误的路径引起的,导致某些元数据无法正确生成。
由于此问题并不特别有趣,并且只影响一小部分人,他们现在应该都知道,因此我们不会进一步详细说明。要了解更多信息,请查看问题和由此产生的讨论。
如果需要更新,Cargo 将在 Windows 7 上发出警告。
TL;DR:如果您使用的是未应用安全修复程序的旧版 Windows,Cargo 无法从 crates.io 获取索引。如果您使用的是较新的 Windows 或已修补的 Windows,那么您不会受到此错误的影响。
2017 年 2 月,GitHub 宣布他们将停止支持弱加密标准。一年后的 2018 年 2 月,弃用期结束,支持被移除。总的来说,这是一件好事。
Cargo 使用 GitHub 存储 Crates.io(我们的包仓库)的索引。它还使用 `libgit2` 进行 `git` 操作。`libgit2` 使用 WinHTTP 发起 HTTP 请求。作为操作系统的一部分,其功能集取决于您使用的操作系统。
本节使用“Windows 7”表示“Windows 7、Windows Server 2018 和 Windows Server 2012”,因为这样更简洁。以下内容适用于这三个版本的 Windows。
Windows 7 于 2016 年 6 月 发布了更新,涉及 TLS。在打补丁之前,Windows 7 默认使用 TLS 1.0。此更新允许应用程序原生使用 TLS 1.1 或 1.2。
如果您的系统没有收到该更新,那么您仍然在使用 TLS 1.0。这意味着访问 GitHub 会开始失败。
`libgit2` 创建了一个修复程序,使用 `WinHTTP` API 请求 TLS 1.2。在 master 分支上,我们已经 更新了修复程序,但对于 1.24.1 稳定版,我们 发布了一个警告,建议他们升级 Windows 版本。虽然 `libgit2` 修复程序可以回溯,但我们认为代码更改范围对于点版本来说太大,尤其是在问题不影响已打补丁的系统的情况下。