无法传递新类型包装的不安全C结构而不会导致非法指令

时间:2015-02-05 03:42:02

标签: rust ffi

我正在处理包装不安全的FFI层here,并且遇到了一个非常奇怪的问题。 (每晚最新)

extern crate cql_ffi;
use std::ffi::CString;

#[allow(missing_copy_implementations)]
pub struct CassCluster(pub cql_ffi::CassCluster);

fn main() {

    let cluster = &mut CassCluster(unsafe{*cql_ffi::cass_cluster_new()});
    println!("trying method 1");
    let result1 = method1();

    println!("trying method 2");
    let result2 = method2(cluster);
}

pub fn method1() {
    let cluster = &mut CassCluster(unsafe{*cql_ffi::cass_cluster_new()});    
    let result = unsafe{cql_ffi::cass_cluster_set_contact_points(&mut cluster.0,  CString::from_slice("127.0.0.1".as_bytes()).as_ptr() as *const i8)};
}

pub fn method2(cluster: &mut CassCluster) {
    let result = unsafe{cql_ffi::cass_cluster_set_contact_points(&mut cluster.0,  CString::from_slice("127.0.0.1".as_bytes()).as_ptr() as *const i8)};
}

请注意,方法1和方法2仅在群集是传入fn还是在其内部创建时有所不同。

运行时:

trying method 1
trying method 2
Illegal instruction

无论是否调用method1,method2总是因非法指令而失败。

valgrind产生的堆栈跟踪可能很有趣:

trying method 2
==19145== Invalid write of size 8
==19145==    at 0x6A10ACF: std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==19145==    by 0x4F78367: std::list<std::string, std::allocator<std::string> >::_M_insert(std::_List_iterator<std::string>, std::string const&) (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x4F77E13: std::list<std::string, std::allocator<std::string> >::push_back(std::string const&) (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x4F8AE69: cass_cluster_set_contact_points (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x10DDC3: method2::h2b76fca37ae2e2878ba (test.rs:25)
==19145==    by 0x10DA1B: main::hc39cc26c65e20849maa (test.rs:13)
==19145==    by 0x11C198: rust_try_inner (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x11C185: rust_try (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x11988C: rt::lang_start::hd3d7c7415c447b9fdBB (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x10DC04: main (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

1 个答案:

答案 0 :(得分:2)

我认为你是从错误的角度来看这个 - 你不应该取消引用C库返回给你的指针。你实际上甚至不知道指向物体的构成是什么!

相反,您应该只是保持指针并将其传递回期望它的函数。我已经采取了一些代码并对其进行了重组。在这里,我们有一个名为Cluster的结构,它将拥有cass_cluster_new返回的指针。我们创建了一个小例子方法,对集群做了一些有趣的事情,我们还处理了在完成Cluster时释放资源的问题。

请注意,此Cluster结构实际占用空间,而不是您当前拥有的空Cluster枚举。空枚举占用空间,因此它将以有趣的方式进行优化。但是,您实际上需要将指针保留在某处

另一件事是我们只是将库中的返回指针视为c_void。这是因为我们永远不会取消引用它,所以我们只把它当作一个不透明的句柄。

#![feature(libc)]

extern crate libc;

use libc::{c_void,c_int};

extern "C" {
    pub fn cass_cluster_new() -> *mut c_void;
    pub fn cass_cluster_free(cluster: *mut c_void);
    pub fn cass_cluster_set_port(cluster: *mut c_void, port: c_int); // ignoring return code
}

struct Cluster(*mut c_void);

impl Cluster {
    fn new() -> Cluster {
        Cluster(unsafe { cass_cluster_new() })
    }

    // N.B. Ports are better represented as u16! 
    fn set_port(&mut self, port: i32) {
        unsafe { cass_cluster_set_port(self.0, port) }
    }
}

impl Drop for Cluster {
    fn drop(&mut self) {
        unsafe { cass_cluster_free(self.0) }
    }
}

fn main() {
    let mut cluster = Cluster::new();
    cluster.set_port(5432);

    // cluster is automatically dropped when it goes out of scope
}

Playpen