ACCESS_VIOLATION 从 Rust 调用 Btrieve BTRCALL 函数
ACCESS_VIOLATION calling Btrieve BTRCALL function from Rust
我正在尝试从 Rust 调用 Btrieve(一个非常古老的数据库引擎)。
这有点长,但这是我第一次尝试使用 Rust 和 FFI
我想描述我所做的一切。
Btrieve 引擎是在 DLL 中实现的,w3btrv7.dll,这是一个
32 位 DLL。我已经使用 32 位 MSVC 工具为它创建了一个导入库
(官方没有):
lib /Def:w3btrv7.def /Out:w3btrv7.lib /Machine:x86
然后我安装了 32 位 Rust 工具链 stable-i686-pc-windows-msvc
并将其设置为我的默认值。官方 Btrieve 上的 Bindgen barfs headers
所以我不得不自己做。幸运的是我们只需要包装一个函数,
BTRCALL
.
我的wrapper.h里有这个:
short int BTRCALL(
unsigned short operation,
void* posBlock,
void* dataBuffer,
unsigned short* dataLength,
void* keyBuffer,
unsigned char keyLength,
char ckeynum);
我链接为:
println!("cargo:rustc-link-lib=./src/pervasive/w3btrv7");
这似乎有效:程序 运行s 是一个 32 位 exe,我可以
在 Process Explorer 中看到它已加载 w3btrv7.dll
.
当我通过 bindgen 发送 header 时,我得到:
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;
}
类型和大小似乎都正确,而且匹配
我从 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
keyNumber
略有不同,但我已经尝试了有符号和无符号变体中的字节和短裤,但它仍然不起作用。
不幸的是,当我 运行 我的程序在第一次调用后就崩溃了
到 BTRCALL。 (好吧,实际上是这个调用所在的函数
returns)。我已经将所有参数提取到局部变量中并进行了检查
他们的类型和所有看起来都是正确的:
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
);
它使程序崩溃
error: process didn't exit successfully: `target\debug\blah.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
据我了解,这可能是由于地址访问不当造成的。
确实,当我进行一些跟踪以检查变量时,有一些非常有趣的行为,因为我
按值传递的局部变量似乎被覆盖了。这里的日志只是转储第一个
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
注意 pos_block_ptr
已设置为 0,等等。相反,成功的执行
来自 C# 代码的完全相同的调用只是将一些数据写入 pos_block
的前 18 个字节
并且不改变任何其他变量。
好像有点发疯,刚开始覆盖内存...
此时我不知道接下来要尝试什么。
将声明从 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;
}
我正在尝试从 Rust 调用 Btrieve(一个非常古老的数据库引擎)。 这有点长,但这是我第一次尝试使用 Rust 和 FFI 我想描述我所做的一切。
Btrieve 引擎是在 DLL 中实现的,w3btrv7.dll,这是一个 32 位 DLL。我已经使用 32 位 MSVC 工具为它创建了一个导入库 (官方没有):
lib /Def:w3btrv7.def /Out:w3btrv7.lib /Machine:x86
然后我安装了 32 位 Rust 工具链 stable-i686-pc-windows-msvc
并将其设置为我的默认值。官方 Btrieve 上的 Bindgen barfs headers
所以我不得不自己做。幸运的是我们只需要包装一个函数,
BTRCALL
.
我的wrapper.h里有这个:
short int BTRCALL(
unsigned short operation,
void* posBlock,
void* dataBuffer,
unsigned short* dataLength,
void* keyBuffer,
unsigned char keyLength,
char ckeynum);
我链接为:
println!("cargo:rustc-link-lib=./src/pervasive/w3btrv7");
这似乎有效:程序 运行s 是一个 32 位 exe,我可以
在 Process Explorer 中看到它已加载 w3btrv7.dll
.
当我通过 bindgen 发送 header 时,我得到:
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;
}
类型和大小似乎都正确,而且匹配 我从 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
keyNumber
略有不同,但我已经尝试了有符号和无符号变体中的字节和短裤,但它仍然不起作用。
不幸的是,当我 运行 我的程序在第一次调用后就崩溃了 到 BTRCALL。 (好吧,实际上是这个调用所在的函数 returns)。我已经将所有参数提取到局部变量中并进行了检查 他们的类型和所有看起来都是正确的:
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
);
它使程序崩溃
error: process didn't exit successfully: `target\debug\blah.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
据我了解,这可能是由于地址访问不当造成的。
确实,当我进行一些跟踪以检查变量时,有一些非常有趣的行为,因为我 按值传递的局部变量似乎被覆盖了。这里的日志只是转储第一个 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
注意 pos_block_ptr
已设置为 0,等等。相反,成功的执行
来自 C# 代码的完全相同的调用只是将一些数据写入 pos_block
的前 18 个字节
并且不改变任何其他变量。
好像有点发疯,刚开始覆盖内存...
此时我不知道接下来要尝试什么。
将声明从 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;
}