2013-01-16 28 views
0

我正在用C語言將用Ruby編寫的Ruby gem移植到帶有FFI的Ruby中。在jRuby但不是MRI中的FFI代碼段錯誤Ruby

當我使用MRI Ruby運行測試時,沒有任何seg-fault。 在jRuby中運行時,出現seg-fault錯誤。

這是在測試我覺得代碼負責:運行sqlany_execute的時候,但只有當傳遞給SET_VALUE的對象是String類的

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只是使用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賽格故障和什麼都可以我做了嗎?

+0

什麼版本的JRuby? JRuby master是否發生這種情況?有可能這是一個JRuby FFI錯誤,可能已經修復。 –

+0

$ ruby​​ -v jruby 1.7.1(1.9.3p327)2012-12-03 30a153b上OpenJDK 64位服務器虛擬機1.7.0_09-icedtea-mockbuild_2012_12_06_11_04-b00 [linux-amd64] 我會嘗試升級到jRuby 1.7.2 – Chris

+1

@SébastienLeCallonnec我已經更新到jRuby-1.7.2和我的ffi到ffi-1.3.1-java.gem。當使用jRuby-1.7.2時,測試仍然會出現seg錯誤 – Chris

回答

1

這個答案可能過於詳細,但我認爲對於那些在將來遇到類似問題的人來說,深入一點會很好。

看起來這是你的問題:

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

當它應該是:

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

在64位系統,字節內存自我[的4..7:價值] [:length]指向可能包含垃圾(因爲malloc不清除它返回的內存),並且當本地代碼在該地址讀取size_t數量時,它將是垃圾,可能指示大於4千兆字節的緩衝區。

例如如果字符串長度真的是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結構並將其用於該長度。 例如

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)的值會告訴你)。

+0

核磁共振紅寶石和jRuby都是64位。感謝您的回答和幫助。 '$紅寶石-v 紅寶石1.9.3p286(2012年10月12日修訂版37165)[x86_64的Linux的] $紅寶石-e「需要 「FFI」;把FFI.type_size(:爲size_t)」 $ RVM使用JRuby的 使用〜/ .rvm /寶石/ JRuby的1.7.1 $紅寶石-v 的JRuby 1.7.1(1.9.3p327) 2012-12-03 30a153b on OpenJDK 64位服務器虛擬機1.7.0_09-icedtea-mockbuild_2013_01_14_23_04-b00 [linux-amd64] $ ruby​​ -e'require「ffi」;把FFI.type_size(:size_t)' ' – Chris

+0

這也可能是你在MRI上只是幸運的,它沒有段錯誤。從malloc()返回的內存在MRI上可能已經清理完畢,因爲啓動過程中內存流失的程度要低得多。 – 2013-01-19 20:08:12