简体   繁体   English

ACCESS_VIOLATION 从 Rust 调用 Btrieve BTRCALL 函数

[英]ACCESS_VIOLATION calling Btrieve BTRCALL function from Rust

I am attempting to call Btrieve (a very old database engine) from Rust.我试图从 Rust 调用 Btrieve(一个非常古老的数据库引擎)。 This is a bit long, but this is my first attempt at FFI from Rust and I wanted to describe everything I have done.这有点长,但这是我第一次从 Rust 尝试 FFI,我想描述我所做的一切。

The Btrieve engine is implemented in a DLL, w3btrv7.dll, which is a 32-bit DLL. Btrieve 引擎是在一个 DLL w3btrv7.dll 中实现的,它是一个 32 位 DLL。 I have made an import library for it using 32-bit MSVC tools (it doesn't come with an official one):我使用 32 位 MSVC 工具为它制作了一个导入库(它没有官方工具):

lib /Def:w3btrv7.def /Out:w3btrv7.lib /Machine:x86

I then installed the 32-bit Rust toolchain stable-i686-pc-windows-msvc and set it as my default.然后我安装了 32 位 Rust 工具链stable-i686-pc-windows-msvc并将其设置为我的默认值。 Bindgen barfs on the official Btrieve headers so I had to make my own. Bindgen 在官方 Btrieve 标头上显示错误,所以我必须自己制作。 Luckily we only need to wrap a single function, BTRCALL .幸运的是,我们只需要包装一个函数BTRCALL

I have this in my wrapper.h:我的 wrapper.h 中有这个:

short int BTRCALL(
    unsigned short  operation,
    void*           posBlock,
    void*           dataBuffer,
    unsigned short* dataLength,
    void*           keyBuffer,
    unsigned char   keyLength,
    char            ckeynum);

I am linking as:我链接为:

println!("cargo:rustc-link-lib=./src/pervasive/w3btrv7");

Which seems to work: the program runs, is a 32-bit exe, and I can see in Process Explorer that it has loaded w3btrv7.dll .这似乎有效:该程序运行,是一个 32 位 exe,我可以在 Process Explorer 中看到它已加载w3btrv7.dll

When I send the header through bindgen I get:当我通过 bindgen 发送标头时,我得到:

extern "C" {
    pub fn BTRCALL(
        operation: ::std::os::raw::c_ushort,
        posBlock: *mut ::std::os::raw::c_void,
        dataBuffer: *mut ::std::os::raw::c_void,
        dataLength: *mut ::std::os::raw::c_ushort,
        keyBuffer: *mut ::std::os::raw::c_void,
        keyLength: ::std::os::raw::c_uchar,
        ckeynum: ::std::os::raw::c_char,
    ) -> ::std::os::raw::c_short;
}

The types and sizes all seem to tally up correctly, and they match a DllImport I have from a C# application which works perfectly:类型和大小似乎都正确匹配,它们与我从 C# 应用程序中获得的 DllImport 相匹配,该应用程序完美运行:

[DllImport("w3btrv7.dll", CharSet = CharSet.Ansi)]
private static extern short BTRCALL(
    ushort operation, // In C#, ushort = UInt16.
    [MarshalAs(UnmanagedType.LPArray, SizeConst = 128)] byte[] posBlock,
    [MarshalAs(UnmanagedType.LPArray)] byte[] dataBuffer,
    ref ushort dataLength,
    [MarshalAs(UnmanagedType.LPArray)] byte[] keyBuffer,
    byte keyLength,     // unsigned byte
    char keyNumber);    // 2 byte char

The keyNumber is slightly different, but I have tried both bytes and shorts in both signed and unsigned variations, and it still doesn't work. keyNumber略有不同,但我尝试了有符号和无符号变体中的字节和短裤,但它仍然不起作用。

Unfortunately when I run my program it blows up after the first call to BTRCALL.不幸的是,当我运行我的程序时,它在第一次调用 BTRCALL 后就崩溃了。 (Well, actually it's when the function that this call is in returns). (嗯,实际上是这个调用所在的函数返回时)。 I've extracted all the params into local variables and checked their types and all looks correct:我已经将所有参数提取到局部变量中并检查了它们的类型,并且看起来都是正确的:

let op: u16 = 0;
let mut pos_block: [u8; 128] = self.pos_block.clone();
let pos_block_ptr: *mut std::ffi::c_void = pos_block.as_mut_ptr() as *mut _;
let mut data_buffer: [u8; 32768] = self.data_buffer.clone();
let data_buffer_ptr: *mut std::ffi::c_void = data_buffer.as_mut_ptr() as *mut _;
let mut data_length: u16 = data_buffer.len() as u16;
let mut key_buffer: [u8; 256] = self.key_buffer.clone();
let key_buffer_ptr: *mut std::ffi::c_void = key_buffer.as_mut_ptr() as *mut _;
let key_length: u8 = 255; //self.key_length;
let key_number: i8 = self.key_number.try_into().unwrap();

let status: i16 = BTRCALL(
    op,
    pos_block_ptr,
    data_buffer_ptr,
    &mut data_length,
    key_buffer_ptr,
    key_length,
    key_number
);

It crashes the program with它使程序崩溃

error: process didn't exit successfully: `target\debug\blah.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

From what I have read, this is probably due to an improper address access.根据我的阅读,这可能是由于地址访问不当造成的。

Indeed, when I put some tracing in to check the variables there is some very interesting behaviour, in that my local variables which are passed by value seem to be getting overwritten.确实,当我进行一些跟踪以检查变量时,有一些非常有趣的行为,因为我的按值传递的局部变量似乎被覆盖了。 The log here just dumps the first 30 bytes of the buffers because the rest is just zeros:此处的日志仅转储缓冲区的前 30 个字节,因为其余部分只是零:

pos_block = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
pos_block_ptr = 0xad6524
data_buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
data_buffer_ptr = 0xad65a8
data_length = 32768
key_buffer = [34, 67, 58, 92, 116, 101, 109, 112, 92, 99, 115, 115, 92, 120, 100, 98, 92, 67, 65, 83, 69, 46, 68, 66, 34, 0, 0, 0, 0, 0]
key_buffer_ptr = 0xade5b0
key_length = 255
key_number = 0

>>>>>>>>>>>>>>> AFTER THE CALL TO BTRCALL:

pos_block = [0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 203, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0]
pos_block_ptr = 0x0
data_buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
data_buffer_ptr = 0x42442e45
data_length = 0
key_buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
key_buffer_ptr = 0x0
key_length = 173
key_number = 0
BTRCALL() returned B_NO_ERROR

Notice pos_block_ptr has been set to 0, among other things.请注意pos_block_ptr已设置为 0 等。 In contrast, a successful execution of the exact same call from the C# code simply writes some data into the first 18 bytes of pos_block and doesn't change any of the other variables.相比之下,从 C# 代码成功执行完全相同的调用只是将一些数据写入pos_block的前 18 个字节,而不会更改任何其他变量。

It's as if it went a bit berserk and just started overwriting memory...就好像它有点发疯了,刚刚开始覆盖内存......

At this point I don't know what to try next.在这一点上,我不知道接下来要尝试什么。

Changing the declaration from extern "C" to extern "stdcall" works:将声明从extern "C"更改为extern "stdcall"有效:

extern "stdcall" {
    pub fn BTRCALL(
        operation: ::std::os::raw::c_ushort,
        posBlock: *mut ::std::os::raw::c_void,
        dataBuffer: *mut ::std::os::raw::c_void,
        dataLength: *mut ::std::os::raw::c_ushort,
        keyBuffer: *mut ::std::os::raw::c_void,
        keyLength: ::std::os::raw::c_uchar,
        ckeynum: ::std::os::raw::c_char,
    ) -> ::std::os::raw::c_short;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM