<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kirh_036 (Krimiston)</title>
    <link>https://ruby-china.org/kirh_036</link>
    <description>22岁，是学生</description>
    <language>en-us</language>
    <item>
      <title>使用 MinGW + clang 编译 CRuby 时遇到的问题及其解决</title>
      <description>&lt;p&gt;注：本文提到的 ruby 均为 ruby 3.1.2 版本，源代码的行数以该版本为准。&lt;/p&gt;

&lt;p&gt;最近，当我试图用 msys2 的 clang64 工具链编译 ruby 时，出现了 Segmentation fault：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/kirh_036/6418ef27-52b4-4d20-91f8-e1f32a2804db.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;发生了什么事？我试着用 ucrt64 工具链代替。结果，编译过程很顺利。下面是 &lt;code&gt;make check&lt;/code&gt; 的执行结果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finished tests in 195.106628s, 1.5581 tests/s, 8.6619 assertions/s.
304 tests, 1690 assertions, 17 failures, 0 errors, 11 skips

ruby -v: ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x64-mingw-ucrt]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我尝试在 ucrt64 环境下使用 clang 而不是 gcc 作为编译器。结果，像 clang64 工具链一样，出现了一个 Segmentation fault。这表明，用 clang 编译 ruby 可能会引起一些问题。我试图在 GitHub 上搜索相关问题，但一无所获（不排除我信息查找能力差）。&lt;/p&gt;

&lt;p&gt;我不服气，我开始寻找问题的根源。通过 &lt;code&gt;make -n&lt;/code&gt; ，我找到了发生崩溃的命令行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./miniruby.exe -I/k/Download/ruby-3.1.2/lib -I. -I.ext/common  /k/Download/ruby-3.1.2/ext/extmk.rb --make='make' \
        --command-output=ext/-test-/exts.mk --dest-dir="" --extout=".ext" --ext-build-dir="./ext" --mflags="" --make-flags="" --gnumake=yes --extflags="" --make-flags="MINIRUBY='./miniruby.exe -I/k/Download/ruby-3.1.2/lib -I. -I.ext/common '" --extstatic  \
        -- configure ext/-test-
make[1]: *** [ext/configure-ext.mk:20: ext/-test-/exts.mk] Segmentation fault
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;似乎在 miniruby 执行的时候发生了什么。那么传统艺能，我把优化参数改为 &lt;code&gt;-Og&lt;/code&gt; （当然，并没有解决崩溃问题），并分别用 lldb 和 gdb 进行调试。&lt;/p&gt;

&lt;p&gt;lldb：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(lldb) r
Process 6480 launched: 'K:\Download\ruby-3.1\miniruby.exe' (x86_64)
Process 6480 stopped
* thread #1, stop reason = Exception 0xc0000005 encountered at address 0x7ff6c3b3fd93: Access violation reading location 0x00000017
    frame #0: 0x00007ff6c3b3fd93 miniruby.exe`rb_vm_exec [inlined] rb_ec_tag_state(ec=0xffffffffffffffff) at eval_intern.h:146:33
   143  static inline int
   144  rb_ec_tag_state(const rb_execution_context_t *ec)
   145  {
-&amp;gt; 146      struct rb_vm_tag *tag = ec-&amp;gt;tag;
                                        ^
   147      enum ruby_tag_type state = tag-&amp;gt;state;
   148      tag-&amp;gt;state = TAG_NONE;
   149      rb_ec_vm_lock_rec_check(ec, tag-&amp;gt;lock_rec);
(lldb) bt
* thread #1, stop reason = Exception 0xc0000005 encountered at address 0x7ff6c3b3fd93: Access violation reading location 0x00000017
  * frame #0: 0x00007ff6c3b3fd93 miniruby.exe`rb_vm_exec [inlined] rb_ec_tag_state(ec=0xffffffffffffffff) at eval_intern.h:146:33
    frame #1: 0x00007ff6c3b3fd93 miniruby.exe`rb_vm_exec(ec=0x0000000012070015, mjit_enable_p=false) at vm.c:2209:18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gdb：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) r
Starting program: /k/Download/ruby-3.1/miniruby.exe -I/k/Download/ruby-3.1.2/lib -I. -I.ext/common /k/Download/ruby-3.1.2/ext/extmk.rb --make=make --command-output=ext/-test-/exts.mk --dest-dir= --extout=.ext --ext-build-dir=./ext --mflags= --make-flags= --gnumake=yes --extflags= --make-flags=MINIRUBY=\'./miniruby.exe\ -I/k/Download/ruby-3.1.2/lib\ -I.\ -I.ext/common\ \' --extstatic -- configure ext/-test-
[New Thread 11476.0x63c]
[New Thread 11476.0xb7c]
[New Thread 11476.0x9c8]
[New Thread 11476.0x698]

Thread 1 received signal SIGSEGV, Segmentation fault.
ruby_options (argc=32758, argv=0x17) at K:/Download/ruby-3.1.2/eval.c:117
117     K:/Download/ruby-3.1.2/eval.c: No such file or directory.
(gdb) bt
#0  ruby_options (argc=32758, argv=0x17) at K:/Download/ruby-3.1.2/eval.c:117
#1  0x0000000000000000 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，gdb 指向的 &lt;code&gt;eval.c:117&lt;/code&gt; 的内容是：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EC_EXEC_TAG&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_NONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;EC_EXEC_TAG&lt;/code&gt; 宏的定义在 &lt;code&gt;eval_intern.h:165&lt;/code&gt; ：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define EC_EXEC_TAG() \
    (ruby_setjmp(_tag.buf) ? rb_ec_tag_state(VAR_FROM_MEMORY(_ec)) : (EC_REPUSH_TAG(), 0))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不难发现，两个调试器的矛头都指向 &lt;code&gt;rb_ec_tag_state&lt;/code&gt; 这个函数。根据 lldb 的显示结果，其崩溃的原因似乎是 &lt;code&gt;ec&lt;/code&gt; 是一个无效的指针（甚至不知道为什么是 &lt;code&gt;0xffffffffffffffff&lt;/code&gt;），在获取结构成员时由于访问无效内存而崩溃。但是为什么 &lt;code&gt;ec&lt;/code&gt; 是一个无效的指针？为什么 gcc 的编译没有这个问题？事情开始玄学了起来。&lt;/p&gt;

&lt;p&gt;但问题最终还是解决了。经过全局搜索，我发现调用 &lt;code&gt;rb_ec_tag_state&lt;/code&gt; 只存在于 &lt;code&gt;EC_EXEC_TAG&lt;/code&gt; 宏中。有没有一种可能，是调用 &lt;code&gt;rb_ec_tag_state&lt;/code&gt; 前的判断语句————具体的说是 &lt;code&gt;ruby_setjmp&lt;/code&gt; 的返回值，出问题了呢？&lt;/p&gt;

&lt;p&gt;我开始追踪 &lt;code&gt;ruby_setjmp&lt;/code&gt; 函数，首先在 &lt;code&gt;eval_intern.h:58&lt;/code&gt; 处定位到：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define ruby_setjmp(env) RUBY_SETJMP(env)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;include/x64-mingw-ucrt/ruby/config.h&lt;/code&gt; （编译前生成的一个头文件）中定位到：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define RUBY_SETJMP(env) __builtin_setjmp((void **)(env))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得注意的是 &lt;code&gt;__builtin_&lt;/code&gt; 前缀。百度结果显示，带有这个前缀的函数是 gcc 的内置函数（clang 也支持），是相应标准库函数的优化版本。我怀疑这个函数的行为 clang 与 gcc 的不同，这导致了我在本文开头遇到的问题。&lt;/p&gt;

&lt;p&gt;解决方案：果然用标准库函数才是最稳的。我把 &lt;code&gt;include/x64-mingw-ucrt/ruby/config.h&lt;/code&gt; 中的：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define RUBY_SETJMP(env) __builtin_setjmp((void **)(env))
#define RUBY_LONGJMP(env,val) __builtin_longjmp((void **)(env),val)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换成：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define RUBY_SETJMP(env) setjmp(env)
#define RUBY_LONGJMP(env,val) longjmp(env,val)
#define RUBY_JMP_BUF jmp_buf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并使用 clang64 工具链进行编译。结果，编译过程很顺利。以下是 &lt;code&gt;make check&lt;/code&gt; 的执行结果：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finished tests in 194.228169s, 1.5652 tests/s, 8.7011 assertions/s.
304 tests, 1690 assertions, 17 failures, 0 errors, 11 skips

ruby -v: ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x64-mingw-ucrt]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺便一提，在 ucrt64 环境下，clang 编译也是成功的，但 &lt;code&gt;make check&lt;/code&gt; 的结果有微妙的不同。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finished tests in 194.135740s, 1.5659 tests/s, 8.7001 assertions/s.
304 tests, 1689 assertions, 18 failures, 0 errors, 11 skips

ruby -v: ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x64-mingw-ucrt]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，问题虽然解决，但实际上我并不知其所以然。因为我今后多半不会用到 &lt;code&gt;setjmp&lt;/code&gt; 函数，而且最近忙于毕业找工作，就没有探究导致这个问题的根本原因。希望有能大佬能找出答案。&lt;/p&gt;</description>
      <author>kirh_036</author>
      <pubDate>Thu, 09 Jun 2022 14:34:01 +0800</pubDate>
      <link>https://ruby-china.org/topics/42447</link>
      <guid>https://ruby-china.org/topics/42447</guid>
    </item>
  </channel>
</rss>
