munmap_chunk:在rust生成的动态库中使用函数的C程序中的无效指针



我在Rust中有一个函数。它接收一个*const c_char并将其转换成一个字符串。

#[no_mangle]
pub extern "C" fn listen(addr: *const c_char) {
unsafe {
let addr_str = {
let cstr = CStr::from_ptr(addr);
let bytes = cstr.to_bytes();
String::from_raw_parts(bytes.as_ptr() as *mut u8, bytes.len(), bytes.len())
};
}
}

在C程序中,我从命令行获取地址。

#include "xxx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// input: address
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage %s <listen_addr>n", argv[0]);
return -1;
}
const char *addr = argv[1];
listen(addr);
return 0;
}

将抛出错误munmap_chunk(): invalid pointer。但是,如果我选择将地址存储在堆上,程序就不会抛出错误。

int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage %s <listen_addr>n", argv[0]);
return -1;
}
const char *addr = argv[1];
int addr_len = strlen(addr) + 1;
char *addr_buffer = (char *)malloc(addr_len);
strcpy(addr_buffer, addr);
listen(addr_buffer);
return 0;
}

为什么把地址放在堆栈上不能工作,而放在堆上可以工作?

为什么你把地址放在堆栈上不能工作,但如果你把它放在堆上就可以工作?

因为这个:

String::from_raw_parts(bytes.as_ptr() as *mut u8, bytes.len(), bytes.len())

是完全不健全的,在两种情况下:

  1. String是一个拥有的类型,当它超出作用域时,它的内容被释放,但在第一种情况下,因为它几乎肯定不是直接分配的(只是隐式地作为堆栈的一部分——在大多数情况下,尽管这实际上并不能保证),当你要求它释放什么是,就其而言,不是一个有效的指针(这是一个最好的情况)。

  2. 即使您将字符串移动到"堆"它仍然是不可靠的,因为不能保证你的C和Rust运行时使用相同的分配器,你不能用一个分配器释放另一个分配器的分配。

两个版本都是不健全的,因为Rust要求String的缓冲区为UTF8,但是没有验证。

整个事情绝对令人困惑,因为有一个CStr::to_str方法,它确实正确地做了所有正确的事情(因此它甚至不安全)。

from_raw_parts()的文档告诉我们

buf处的内存需要先前由标准库使用的相同分配器分配,并且要求对齐恰好为1。

buf的所有权被有效地转移到String对象,然后该对象可以随意释放、重新分配或更改指针所指向的内存内容。

在这里,您以这种方式创建了一个String,但是一旦它被删除,它就会尝试释放内存区域。在你的第一次尝试中,你直接引用argv[],它在堆栈上,释放肯定会失败。也许,在你的第二次尝试中,分配方法(偶然地)与Rust所期望的兼容,但没有什么能保证这一点。