我正在使用FFI将用C编写的Ruby gem移植到Ruby。
当我使用MRI Ruby运行测试时,没有任何seg-fault。 在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;
在我的港口,这成了:
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
我的问题是:是什么导致jRuby出现故障,我该怎么办呢?
答案 0 :(得分:1)
这个答案可能过于详细,但我认为对于那些在未来遇到类似问题的人来说,深入探讨一下会很好。
看起来这是你的问题:
self[:value][:length].write_int(length)
应该是什么时候:
self[:value][:length].write_ulong(length)
在64位系统上,内存自身[:value] [:length]的字节4..7指向可能包含垃圾(因为malloc不会清除它返回的内存),并且当本机代码读取时该地址的size_t数量,它将是垃圾,可能表示大于4千兆字节的缓冲区。
e.g。如果字符串长度实际为15个字节,则将设置较低的4位,并且较高的60应该全部为零。
bit 0 1 2 3 4 32 63
+---+---+---+---+---+ ~ +---+ ~ +---+
| 1 | 1 | 1 | 1 | 0 | ~ | 0 | ~ | 0 |
+---+---+---+---+---+ ~ +---+ ~ +---+
如果只设置了高32位中的一位,那么你得到一个> 4千兆字节值
bit 0 1 2 3 4 32 63
+---+---+---+---+---+ ~ +---+ ~ +---+
| 1 | 1 | 1 | 1 | 0 | ~ | 1 | ~ | 0 |
+---+---+---+---+---+ ~ +---+ ~ +---+
,其长度为4294967311字节。
解决这个问题的一种方法是定义一个SizeT结构并将其用于长度。 e.g。
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 ::指针:
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)的值会告诉你)。