Rust FFI with C++: Works Perfectly on Ubuntu, But Fails with Segmentation Fault in Alpine Container When Transmitting Parameters

Hi, my problem is that FFI works on ubuntu but doesn't work on alpine. I develop my algorithm on Ubuntu, and once I implement a new feature, I package it into an Alpine container(based on rust:alpine3.22). I implement the solver in C++, and the server is built using Rust. The server receives network requests and then calls the C++ solver to process them.

I used to use CLI, and my solver on Alpine worked fine. But today, I replaced the CLI with FFI. The Rust server transmits C-style strings as parameters to the C++ solve function. It works fine on Ubuntu, successfully solving the problem and printing the solution. However, when I package the program into the Alpine container, it runs successfully until it transmits the parameters to the C++ function. My program is able to print "calc begin", but then it doesn't proceed. Then it throw Segmentation fault instead of print "read problem". So I suspect the issue occurs when transmitting the parameters.

Rust

use crate::request_data::ToProblem;
use crate::response::response_v3::ResponseV3;
use crate::CliError;
use crate::request_data::request_data_v3::RequestDataV3;
use crate::response::Response;
use libc::{c_char, c_void};
use std::ffi::{CStr, CString};

unsafe extern "C" {
    fn solve(problem_string: *const c_char, config_string: *const c_char)->*mut c_void;
    fn get_c_str(result:*mut c_void)->*const c_char;
    fn free_result(result:*mut c_void);
}

pub async fn handle_request(data: RequestDataV3) -> Result<warp::reply::Json, warp::Rejection> {
    println!("start handle");
    let problem_string=serde_json::to_string(&data.to_problem()).unwrap();
    let config_string=serde_json::to_string(&data.config).unwrap();
    let problem_cstr = CString::new(problem_string).unwrap();
    let config_cstr = CString::new(config_string).unwrap();
    println!("{:?}",problem_cstr);
    println!("{:?}",config_cstr);
    let solution;
    println!("calc begin");
    unsafe {
        let result=solve(problem_cstr.as_ptr(), config_cstr.as_ptr());
        println!("calc end");
        solution = CStr::from_ptr(get_c_str(result)).to_string_lossy().into_owned();
        println!("get string");
        free_result(result);
        println!("free");
    }
    let solution_json = serde_json::from_str(&solution);
    if let Err(e) = solution_json{
        return Err(warp::reject::custom(CliError(e.to_string())));
    }
    let solution_json=solution_json.unwrap();
    let response = Response {
        solution:solution_json,
    };
    let response_v3=ResponseV3::from_response(&data, response);
    Ok(warp::reply::json(&response_v3))
}

C++

#include <solver.hpp>

extern "C"{
    struct Result{
        std::string result;
    };

    Result* solve(const char * problem_string,const char * config_string){

        std::cout<<"read problem"<<std::endl;
        SolverConfig config(json::parse(config_string));
        json problem_json=json::parse(problem_string);
        Problem problem(problem_json);

        std::cout<<"solve"<<std::endl;
        Solver solver(&problem,config);
        Solution solution=solver.solve();

        std::cout<<"return"<<std::endl;
        Result* result=new Result();
        result->result=solution.to_json().dump();
        return result;
    }

    const char* get_c_str(Result* result) {
        return result->result.c_str();
    }

    void free_result(Result* result) {
        delete result;
    }
}

it's probably a linkage issue. is your C++ library dynamically linked?

if that was the case, build your C++ library as static library and try again.

by default, for the *-musl target, rust links the libc statically, where the dynamic linker is disabled.

I've been trying to static link, but haven't been successful so far. When static linking, it will "undefined reference to `operator new(unsigned long)". Perhaps I need musl version stdc++.

better to create a -sys crate for the ffi bindings, then you can use a build script to add the necessary linker flags. e.g. to add libstdc++ to the static linked libraries:

// build.rs
fn main() {
    println!("cargo::rustc-link-lib=static=stdc++");
}

note: it will also work if you add the directive in any crate's build script, including the main application, but creating a separate -sys crate is cleaner and more idiomatic.

Thank you for your suggestion, after linking statically, it works perfectly. Though the image size has grown from 20MB to 60MB :sob:.

Oh, sorry — the image size actually got smaller. I forgot I had compiled in debug mode before. After switching from CLI to FFI and dropping the argparse library, it shrank from 20MB to 17MB in release mode.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.