ALIX / AMD Geode 上的 nodejs 运行 航行 linux 导致 "invalid machine instruction"

nodejs on ALIX / AMD Geode running voyage linux leads to "invalid machine instruction"

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

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

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

调查历史

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

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

当我按照 https://nodejs.org/en/download/package-manager/ 上的说明安装 nodejs 8.x 时,我得到了一些 "invalid machine instruction"(不确定是否正确,但翻译自德语错误输出)。当我下载 32 位 x86 的二进制文件以及手动编译它时,也会发生这种情况。

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

root@voyage:~/GIT/node# ./node


#
# Fatal error in ../deps/v8/src/ia32/assembler-ia32.cc, line 109
# Check failed: cpu.has_sse2().
#

==== C stack trace ===============================

    ./node(v8::base::debug::StackTrace::StackTrace()+0x12) [0x908df36]
    ./node() [0x8f2b0c3]
    ./node(V8_Fatal+0x58) [0x908b559]
    ./node(v8::internal::CpuFeatures::ProbeImpl(bool)+0x19a) [0x8de6d08]
    ./node(v8::internal::V8::InitializeOncePerProcessImpl()+0x96) [0x8d8daf0]
    ./node(v8::base::CallOnceImpl(int*, void (*)(void*), void*)+0x35) [0x908bdf5]
    ./node(v8::internal::V8::Initialize()+0x21) [0x8d8db6d]
    ./node(v8::V8::Initialize()+0xb) [0x86700a1]
    ./node(node::Start(int, char**)+0xd3) [0x8e89f27]
    ./node(main+0x67) [0x846845c]
    /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb74fc723]
    ./node() [0x846a09c]
Ungültiger Maschinenbefehl
root@voyage:~/GIT/node#

如果您现在查看此文件,您会发现以下内容

... [line 107-110]
void CpuFeatures::ProbeImpl(bool cross_compile) {
base::CPU cpu;
CHECK(cpu.has_sse2());  // SSE2 support is mandatory.
CHECK(cpu.has_cmov());  // CMOV support is mandatory.
...

我评论了该行,但仍然是 "Ungültiger Maschinenbefehl"(无效的机器指令)。

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

root@voyage:~/GIT/node# gdb ./node
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
[...]
This GDB was configured as "i586-linux-gnu".
[...]
Reading symbols from ./node...done.
(gdb) run
Starting program: /root/GIT/node/node 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
[New Thread 0xb7ce2b40 (LWP 29876)]
[New Thread 0xb74e2b40 (LWP 29877)]
[New Thread 0xb6ce2b40 (LWP 29878)]
[New Thread 0xb64e2b40 (LWP 29879)]

Program received signal SIGILL, Illegal instruction.
0x287a23c0 in ?? ()
(gdb) 

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

make clean
make CFLAGS="-g"

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

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

On the V8 ports page: x87 (not officially supported)

Contact/CC the x87 team in the CL if needed. Use the mailing list v8-x87-ports.at.googlegroups.com for that purpose.

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

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

an effort to add an interpreter to V8,但这可能无济于事,因为解释器本身将使用 TurboFan JIT 后端编写。它不打算使 V8 可移植到它目前不知道如何对其进行 JIT 的架构。


疯狂的想法:运行 node.js 在软件仿真层 (如 Intel's SDE or maybe qemu-user)之上,可以用 SSE/SSE2 在 x86 CPU 上仅支持 x87​​。他们使用动态翻译,因此对于不使用任何 SSE 指令的代码,可能 运行 以接近本机的速度。

这可能很疯狂,因为 node.js + V8 可能有一些虚拟内存技巧可能会混淆仿真层。不过,我猜 qemu 应该足够稳健。


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


你的 cpuinfo 说它有 CMOV,这是一个 686 (ppro / p6) 特性。 This says 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=geode 意味着 -mtune=geode,这会影响不涉及使用新指令的代码生成选择,所以幸运的是你的二进制文件将 运行 比你简单地使用 -mno-sse 来控制指令集的东西而不覆盖 -mtune=generic。 (如果你在 geode 上构建,你可以使用 -march=native,这应该与使用 -march=geode 相同。)


另一种可能性是问题指令在 Javascript 函数中,这些函数是 JIT 编译的。

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

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


你应该通过运行ning gdb /usr/bin/node检查一下,看看是哪里出了问题。在GDB命令行输入run my_program.js启动该程序。 (当你第一次启动 gdb 时,你不能将 args 传递给 node.js。当你 run 时,你必须从 gdb 内部指定 args。)

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

重新编译它

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

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