在OSX系统下调试C#访问libleveldb失败问题
我之前在OSX上做C#调用leveldb的PInvoke时,也踩过一模一样的坑——Windows和Linux跑起来毫无问题,到了OSX就抛出tcmalloc.cc:331] Attempt to free invalid pointer的崩溃。这个问题的核心原因是内存分配器不兼容,下面是我亲测有效的解决方案:
1. 重新编译leveldb,禁用tcmalloc
LevelDB在OSX环境下默认会链接Google的tcmalloc内存分配器,但.NET运行时(CoreCLR或Mono)用的是系统原生的malloc/free。当你在C#里用.NET的内存释放方法(比如Marshal.FreeHGlobal)去释放由tcmalloc分配的指针,或者反过来,就会触发这个无效指针的错误。
解决办法是编译leveldb时强制禁用tcmalloc:
# 进入leveldb源码目录 mkdir build && cd build # 用CMake指定禁用tcmalloc cmake -DLEVELDB_DISABLE_TCMALLOC=ON .. # 编译生成库 make
编译完成后,把新生成的libleveldb.dylib替换掉原来的库,确保C#程序加载的是这个无tcmalloc依赖的版本。
2. 严格对齐PInvoke的内存管理逻辑
LevelDB的很多API会返回自己分配的内存(比如字符串、迭代器等),这些内存必须用LevelDB提供的释放函数来清理,绝对不能用.NET的内存释放方法。
比如,你需要在Native类里补充LevelDB的内存释放函数签名:
internal static class Native { // 其他LevelDB API的PInvoke签名... [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern void leveldb_free(IntPtr ptr); }
之后在C#代码里,凡是调用LevelDB获取的指针,用完后都要调用Native.leveldb_free来释放,比如:
IntPtr dbNamePtr = Marshal.StringToHGlobalAnsi("test.db"); try { // 调用LevelDB的API使用dbNamePtr... } finally { // 这里不能用Marshal.FreeHGlobal,要用leveldb_free Native.leveldb_free(dbNamePtr); }
3. 验证OSX库的依赖关系
编译完成后,可以用OSX的otool工具检查库的依赖,确认tcmalloc已经被排除:
otool -L libleveldb.dylib
输出里如果看不到libtcmalloc相关的条目,就说明编译是成功的。
如果还是有问题,建议用lldb附加到你的C#进程,查看崩溃时的完整调用栈,这样能更精准地定位是哪一个API的内存释放逻辑出了问题。
内容的提问来源于stack exchange,提问作者ixje

