ALIX / AMD Geode 上的 nodejs 运行 voyage linux 导致“无效机器指令”

the*_*ole 5 x86 debian-based embedded-linux node.js debian-jessie

以下调查的结果是:最近的 Node.js 无法移植到 AMD Geode(或其他非 SSE x86)处理器!

\n\n

我深入研究了代码,并陷入了 ia32 汇编器实现中,该实现将 SSE/SSE2 指令深度集成到其代码中(宏、宏、宏……)。主要后果是,由于缺乏更新的指令集扩展,您无法在 AMD geode 处理器上运行最新版本的 Node.js。回退到 387 算术仅适用于 node.js 代码,但不适用于它所依赖的 javascript V8 编译器实现。调整 V8 以支持非 SSE x86 处理器是一件痛苦且需要付出大量努力的事情。

\n\n

如果有人提供相反的证据,我会很高兴听到;-)

\n\n

调查历史

\n\n

我有一个正在运行的 ALIX.2D13 ( https://www.pcengines.ch ),它有一个 AMD Geode LX 作为主处理器。它运行 voyage linux,这是一个基于 debian jessi 的发行版,适用于资源受限的嵌入式设备。

\n\n
     root@voyage:~# cat /proc/cpuinfo \n     processor       : 0\n     vendor_id       : AuthenticAMD\n     cpu family      : 5\n     model           : 10\n     model name      : Geode(TM) Integrated Processor by AMD PCS\n     stepping        : 2\n     cpu MHz         : 498.004\n     cache size      : 128 KB\n     physical id     : 0\n     siblings        : 1\n     core id         : 0\n     cpu cores       : 1\n     apicid          : 0\n     initial apicid  : 0\n     fdiv_bug        : no\n     f00f_bug        : no\n     coma_bug        : no\n     fpu             : yes\n     fpu_exception   : yes\n     cpuid level     : 1\n     wp              : yes\n     flags           : fpu de pse tsc msr cx8 sep pge cmov clflush mmx mmxext 3dnowext 3dnow 3dnowprefetch vmmcall\n     bugs            : sysret_ss_attrs\n     bogomips        : 996.00\n     clflush size    : 32\n     cache_alignment : 32\n     address sizes   : 32 bits physical, 32 bits virtual\n
Run Code Online (Sandbox Code Playgroud)\n\n

当我按照https://nodejs.org/en/download/package-manager/上的说明安装 nodejs 8.x 时,我收到一些“无效的机器指令”(不确定是否正确,但从德语错误输出翻译而来)。当我下载 32 位 x86 的二进制文件以及手动编译它时,也会发生这种情况。

\n\n

deps/v8/gypfiles/toolchain.gypi在回答以下问题后,我通过删除-msse2和添加来更改编译器标志-march=geode -mtune=geode。现在我得到了同样的错误,但有堆栈跟踪:

\n\n
root@voyage:~/GIT/node# ./node\n\n\n#\n# Fatal error in ../deps/v8/src/ia32/assembler-ia32.cc, line 109\n# Check failed: cpu.has_sse2().\n#\n\n==== C stack trace ===============================\n\n    ./node(v8::base::debug::StackTrace::StackTrace()+0x12) [0x908df36]\n    ./node() [0x8f2b0c3]\n    ./node(V8_Fatal+0x58) [0x908b559]\n    ./node(v8::internal::CpuFeatures::ProbeImpl(bool)+0x19a) [0x8de6d08]\n    ./node(v8::internal::V8::InitializeOncePerProcessImpl()+0x96) [0x8d8daf0]\n    ./node(v8::base::CallOnceImpl(int*, void (*)(void*), void*)+0x35) [0x908bdf5]\n    ./node(v8::internal::V8::Initialize()+0x21) [0x8d8db6d]\n    ./node(v8::V8::Initialize()+0xb) [0x86700a1]\n    ./node(node::Start(int, char**)+0xd3) [0x8e89f27]\n    ./node(main+0x67) [0x846845c]\n    /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb74fc723]\n    ./node() [0x846a09c]\nUng\xc3\xbcltiger Maschinenbefehl\nroot@voyage:~/GIT/node#\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果你现在查看这个文件,你会发现以下内容

\n\n
... [line 107-110]\nvoid CpuFeatures::ProbeImpl(bool cross_compile) {\nbase::CPU cpu;\nCHECK(cpu.has_sse2());  // SSE2 support is mandatory.\nCHECK(cpu.has_cmov());  // CMOV support is mandatory.\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

我评论了该行,但仍然是“Ung\xc3\xbcltiger Maschinenbefehl”(无效的机器指令)。

\n\n

这就是gdb ./node显示的内容(已执行run):

\n\n
root@voyage:~/GIT/node# gdb ./node\nGNU gdb (Debian 7.7.1+dfsg-5) 7.7.1\n[...]\nThis GDB was configured as "i586-linux-gnu".\n[...]\nReading symbols from ./node...done.\n(gdb) run\nStarting program: /root/GIT/node/node \n[Thread debugging using libthread_db enabled]\nUsing host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".\n[New Thread 0xb7ce2b40 (LWP 29876)]\n[New Thread 0xb74e2b40 (LWP 29877)]\n[New Thread 0xb6ce2b40 (LWP 29878)]\n[New Thread 0xb64e2b40 (LWP 29879)]\n\nProgram received signal SIGILL, Illegal instruction.\n0x287a23c0 in ?? ()\n(gdb) \n
Run Code Online (Sandbox Code Playgroud)\n\n

我认为,有必要使用调试符号进行编译......

\n\n
make clean\nmake CFLAGS="-g"\n
Run Code Online (Sandbox Code Playgroud)\n\n

没有机会解决所有 SSE/SSE2 问题...放弃!请参阅我最上面的部分

\n

Pet*_*des 2

结论:在 x86 上运行时,node.js + V8 通常需要 SSE2。

V8 端口页面上:x87(未正式支持)

如果需要,请联系/抄送 CL 中的 x87 团队。为此,请使用邮件列表 v8-x87-ports.at.googlegroups.com。

JavaScript 通常需要浮点(每个数字变量都是浮点,并且使用整数数学只是一种优化),因此可能很难避免 V8 实际上发出 FP 数学指令。

V8 目前设计为始终JIT,而不是解释。当它仍在分析时,或者当它遇到使它“去优化”的东西时,它就会开始/回退到 JITing 未优化的机器代码。

有人努力向 V8 添加解释器,但这可能没有帮助,因为解释器本身将使用 TurboFan JIT 后端编写。它的目的并不是让 V8 可移植到目前不知道如何进行 JIT 的架构。


疯狂的想法:node.js在软件模拟层(如Intel 的 SDEqemu-user )之上运行,可以在仅支持 x87​​ 的 x86 CPU 上使用 SSE/SSE2 模拟 x86。它们使用动态翻译,因此对于不使用任何 SSE 指令的代码来说可能会以接近本机的速度运行。

这可能很疯狂,因为 Node.js + V8 可能有一些虚拟内存技巧,可能会混淆模拟层。不过,我想这qemu应该足够强大。


下面留下的原始答案作为调查其他程序此类问题的通用指南。(提示:grep Makefiles 等 for-msse或,或者在构建时-msse2检查编译器命令行是否有)。pgrep -a gcc


cpuinfo说它有 CMOV,这是 686 (ppro / p6) 功能。 这表示Geode 支持i686。与“普通”CPU 相比,缺少的是 SSE2,它在某些最新的编译器版本中默认启用-m32(32 位模式)。

不管怎样,你应该做的是使用进行编译-march=geode -O3,这样 gcc 或 clang 将使用你的 CPU 支持的所有内容,但仅此而已。

-O3 -msse2 -march=geode会告诉 gcc 它可以使用 Geode 支持的所有内容以及SSE2,因此您需要删除任何-msse-msse2选项,或-mno-sse在它们后面添加。 在 Node.js 中,deps/v8/gypfiles/toolchain.gypi正在设置-msse2.


使用-march=geodeimply -mtune=geode,它会影响不涉及使用新指令的代码生成选择,因此幸运的话,您的二进制文件将比您仅用于-mno-sse控制指令集内容而不覆盖 的情况运行得更快-mtune=generic。(如果您在 geode 上构建,则可以使用-march=native,这应该与使用 相同-march=geode。)


另一种可能性是问题指令位于 JIT 编译的 Javascript 函数中。

Node.js 使用 V8。我快速进行了谷歌搜索,但没有找到任何关于告诉 V8 不要假设 SSE/SSE2 的内容。如果它没有浮点的后备代码生成策略(x87 指令),那么您可能必须完全禁用 JIT 并使其在解释器模式下运行。(速度较慢,因此这可能是一个问题。)

但希望 V8 表现良好,并在 JITing 之前检查支持哪些指令集。


您应该通过运行来检查 gdb /usr/bin/node,看看哪里出了问题。 在 GDB 命令行中键入run my_program.js以启动程序。(首次启动 gdb 时,无法将 args 传递给 node.js。当您启动时,必须从 gdb 内部指定 args run。)

如果引发 SIGILL 的指令的地址位于映射到文件的内存区域中(/proc/pid/maps如果 gdb 没有告诉您,请查看),这会告诉您哪个提前编译的可执行文件或库负责。用 重新编译它-march=geode

如果它位于匿名内存中,则很可能是 JIT 编译器的输出。

当程序收到SIGILL时GDB停止时会打印指令地址。您还可以查看(32 位模式指令指针)print $ip的当前值。EIP