0

Cで書かれたRuby gemをFFIでRubyに移植しています。

MRI Ruby を使用してテストを実行すると、セグ フォールトはありません。jRuby で実行すると、seg-fault が発生します。

これは、責任があると思われるテストのコードです。

if type == Date or type == DateTime then
  assert_nil param.set_value(value.strftime("%F %T"));
else
  assert_nil param.set_value(value);
end
@api.sqlany_bind_param(stmt, 0, param)
puts "\n#{param.inspect}"

#return if String === value or Date === value or DateTime === value
assert_succeeded @api.sqlany_execute(stmt)

セグメンテーション違反は、sqlany_execute の実行時に発生しますが、set_value に渡されたオブジェクトがクラス String である場合にのみ発生します。

sqlany_execute は、FFI の attach_function メソッドを使用するだけです。

param.set_value はより複雑です。String 固有の部分だけに焦点を当てます。これが元のCコードです

case T_STRING:
    s_bind->value.length = malloc(sizeof(size_t));
    length = RSTRING_LEN(val);
    *s_bind->value.length = length;
    s_bind->value.buffer = malloc(length);
    memcpy(s_bind->value.buffer, RSTRING_PTR(val), length);
    s_bind->value.type = A_STRING;
    break;

https://github.com/in4systems/sqlanywhere/blob/db25e7c7a2d5c855ab3899eacbc7a86b91114f53/ext/sqlanywhere.c#L1461

私のポートでは、これは次のようになりました。

when String
  self[:value][:length] = SQLAnywhere::LibC.malloc(FFI::Type::ULONG.size)
  length = value.bytesize
  self[:value][:length].write_int(length)
  self[:value][:buffer] = SQLAnywhere::LibC.malloc(length + 1)
  self[:value][:buffer_size] = length + 1

  ## Don't use put_string as that includes the terminating null
  # value.each_byte.each_with_index do |byte, index|
  # self[:value][:buffer].put_uchar(index, byte)
  # end
  self[:value][:buffer].put_string(0, value)
  self[:value][:type] = :string

https://github.com/in4systems/sqlanywhere/blob/e49099a4e6514169395523391f57d2333fbf7d78/lib/bind_param.rb#L31

私の質問は、jRuby がセグメンテーション フォールトを引き起こしている原因と、それに対して何ができるかということです。

4

1 に答える 1

1

この答えはおそらく過度に詳細ですが、将来同様の問題に遭遇する人のために少し深く掘り下げるのは良いことだと思いました。

これがあなたの問題だったようです:

self[:value][:length].write_int(length)

あるべきだったとき:

self[:value][:length].write_ulong(length)

64ビットシステムでは、メモリself [:value] [:length]のバイト4..7がガベージを含んでいた可能性があり(mallocは返されるメモリをクリアしないため)、ネイティブコードがsize_tの量を読み取る場合そのアドレスでは、ガベージになり、4ギガバイトを超えるバッファを示している可能性があります。

たとえば、文字列の長さが実際に15バイトの場合、下位4ビットが設定され、上位60ビットはすべてゼロになります。

bit   0   1   2   3   4      32       63
    +---+---+---+---+---+ ~ +---+ ~ +---+
    | 1 | 1 | 1 | 1 | 0 | ~ | 0 | ~ | 0 |
    +---+---+---+---+---+ ~ +---+ ~ +---+

その上位32ビットの1ビットだけが設定されている場合、4ギガバイトを超える値が得られます

bit   0   1   2   3   4      32       63
    +---+---+---+---+---+ ~ +---+ ~ +---+
    | 1 | 1 | 1 | 1 | 0 | ~ | 1 | ~ | 0 |
    +---+---+---+---+---+ ~ +---+ ~ +---+

これは4294967311バイトの長さになります。

これを修正する1つの方法は、SizeT構造体を定義し、それを長さに使用することです。例えば

class SizeT < FFI::Struct
  layout :value, :size_t
end

self[:value][:length] = SQLAnywhere::LibC.malloc(SizeT.size)
length = value.bytesize
SizeT.new(self[:value][:length])[:value] = length

または、モンキーパッチFFI :: Pointer:

class FFI::Pointer
  if FFI.type_size(:size_t) == 4
    def write_size_t(val)
      write_int(val)
    end
  else
    def write_size_t(val)
      write_long_long(val)
    end
  end
end

なぜそれはMRIではなくJRubyでのみセグフォールトしたのですか?MRIは32ビットの実行可能ファイルだったのかもしれません(FFI.type_size(:size_t)の値を出力するとわかります)。

于 2013-01-18T05:24:46.803 に答える