0

我正在使用 FFI 将用 C 编写的 Ruby gem 移植到 Ruby。

当我使用 MRI Ruby 运行测试时,没有任何段错误。在 jRuby 中运行时,出现段错误。

这是我认为负责的测试中的代码:

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 更复杂。我将只关注字符串特定的部分。这是原始的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 GB。

例如,如果字符串长度确实是 15 个字节,则设置低 4 位,高 60 位应全为零。

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

如果只设置了高 32 位中的一位,那么您将获得 > 4 GB 的值

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

这将是 4294967311 字节的长度。

解决它的一种方法是定义一个 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

为什么它只在 JRuby 上出现段错误,而不是在 MRI 上?也许 MRI 是一个 32 位的可执行文件(打印 FFI.type_size(:size_t) 的值会告诉你)。

于 2013-01-18T05:24:46.803 回答