Hey Rust community!
I'm having trouble re-assigning pointer from null (set on the Rust side) to a new valid address (on the C side).
In short, I have a Rust struct that exists in a share library, which I would love to exposed to my C main program through FFI.
Here's the small Rust library contains the static struct, out_rust_stdout_plugin
. Notice that the proxy
is a null pointer and _head
is a struct that only contains null pointers:
use std::ffi::c_void;
use std::os::raw::{c_int, c_char};
use std::ptr;
// #[repr(C)] pub struct Foo { _private: [u8; 0] }
#[repr(C)]
#[derive(Debug)]
pub struct embedded_list {
pub prev: *mut embedded_list,
pub next: *mut embedded_list,
}
#[repr(C)]
#[derive(Debug)]
pub struct simple_s {
pub name: *const c_char,
pub type_: c_int,
pub proxy: *mut c_void,
pub _head: embedded_list,
pub cb_init: Option<
extern "C" fn() -> c_int
>,
}
#[export_name = "out_rust_stdout_plugin"]
pub static OUT_STDOUT2_PLUGIN: simple_s = simple_s {
name: "strrr\0".as_ptr() as *const c_char,
type_: 20,
proxy: ptr::null_mut(),
cb_init: Some(plugin_init),
_head: embedded_list {
prev: ptr::null_mut(),
next: ptr::null_mut(),
},
};
extern "C" fn plugin_init() -> c_int {
println!("init called!!");
32
}
// Globally mutable struct variable is inherently unsafe. So
// we have to convince the Rust compiler that the simple_s
// type is thread-safe (uphold from the C main program).
unsafe impl Sync for simple_s {}
Here's the tiny C program that loads the symbox, out_rust_stdout_plugin
from the Rust share library. It then tries to rebind output->_head.prev
or output->proxy
to new addresses. Both ended up with segmentation faults:
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
struct embedded_list {
struct embedded_list *prev, *next;
};
struct simple_s {
char *name;
int type;
void *proxy;
struct embedded_list _head;
int (*cb_init) ();
};
int main() {
void *handle;
handle = dlopen("/usr/local/lib/librust_to_c.so", RTLD_LAZY);
if (!handle) {
printf("handle is null\n");
return -1;
}
void *s;
s = dlsym(handle, "out_rust_stdout_plugin");
if (dlerror() != NULL) {
printf("dlsym is null\n");
return -1;
}
if (!s) {
printf("can not load plugin\n");
dlclose(handle);
return -1;
}
struct simple_s *output;
output = (struct simple_s *) s;
printf("output->name: %s\n", output->name);
printf("output->name ptr: %p\n", output->name);
printf("output->type: %d\n", output->type);
printf("output->cb_init: %p\n", output->cb_init);
printf("output->proxy: %p\n", output->proxy);
printf("output->_head: %p\n", output->_head);
printf("&output->_head: %p\n", &output->_head);
printf("cb_init execution: %d\n", (output->cb_init)());
// attempt to rebind the pointer
struct embedded_list *embedded_l_ptr = (struct embedded_list*) calloc(1, sizeof(struct embedded_list));
printf("valid embedded: %p\n", embedded_l_ptr);
fflush(stdout);
output->_head.prev = embedded_l_ptr; // seg fault!
printf("reassigned!\n");
// the following will also segfault when the pointer assignment:
// output->_head.prev = embedded_l_ptr;
// is commented out:
//
// int i = 3;
// output->proxy = &i;
printf("probably fail\n");
fflush(stdout);
dlclose(handle);
return 0;
}
Sorry if this is super obvious, I've been banging my head all day on this and has not been able to figure out why.