Unable to read memory? C++内存怎么不安全的

背景:周三同事跟我说在客户现场发生一件很奇怪的事情,我们的程序(C#)在执行调用客户C++代码模块之后,只要循环调用100次,程序必然崩溃,而且无法try-catch异常,Event Log也没什么有效的提示。这个C++方法功能也很简单:就是C#传入一组数据,C++计算,然后返回结果。C++程序之前被其他语言调用过,从未出现过问题。

一开始我没太在意,只是建议检查一下传入/传出参数,和内存释放相关代码应该就可以解决,再不然就添加异常捕获代码,看下真正异常是什么。

第二天(周四),比往常早出门,还想着应该是第一个到公司,谁知刚进门就看到昨天问问题的同事已然在座位上,过去一问,问题还是没有解决。

努力过的方向:

  1. 由于不是每次都会出现异常,所以添加了log,记录每次执行步骤,然后用于判断出现异常代码行。失败:因为每次异常位置都不一样
  2. 尝试更改 DllImport 参数。失败,异常依然有
  3. 尝试使用 HandleProcessCorruptedStateExceptionsAttribute 捕获异常,期待异常信息能给一点提示。失败:并没什么异常信息

C# 代码:

csharp 复制代码
// 输入:input_1,input_2
// 输出:output/err_1/err_2
[DllImport("C_plus_plus.dll", EntryPoint = "2a64cf1c-a94c-43b0-942d-870fa679bfe7")]
public extern static byte Calc_By_C_plus_plus(IntPtr output, IntPtr err_1, IntPtr err_2, IntPtr input_1, IntPtr input_2);    

// 调用
var gc_output = GCHandle.Alloc(new double[10], GCHandleType.Pinned);
var output_ptr = gc_output.AddrOfPinnedObject();     
// 输出数组,长度==1?? 
var gc_err_1 = GCHandle.Alloc(new double[1], GCHandleType.Pinned);
var err_1_ptr = gc_err_1.AddrOfPinnedObject();  
var gc_err_2 = GCHandle.Alloc(new double[1], GCHandleType.Pinned);
var err_2_ptr = gc_err_2.AddrOfPinnedObject();  
// ...
var gc_input_1 = GCHandle.Alloc(input_1, GCHandleType.Pinned);
var input_1_ptr = gc_output.AddrOfPinnedObject();  
try
{
 int result = Calc_By_C_plus_plus(output_ptr, err_1_ptr, err_2_ptr, input_1_ptr, input_1_p);
 var s_1_o = new int[10]
 for (int i = 0; i < 10; i++)
 {
  s_1_o[i] = Marshal.ReadInt32(step_1_out_ptr, 4 * i);
 }
}
finally
{
 gc_output.Free();
 gc_err_1.Free();
 gc_err_2.Free();
 // ...
 gc_input_1.Free();
}

C++Header

C++ 复制代码
CALC_API signed char Calc_Step_1(int *ouput, double *err1, double *err2, double *input_1, double *input_2);

C#代码看上去并没什么问题,输入两个double类型数组(input_1,intput_2),计算之后返回一个 int数组(output)和两个double类型数组(err_1,err_2),看到输出err数组长度都是1,感觉比较奇怪,但客户说其他语言调用就是这么定义的没什么问题。只能写个Cnosole程序Debug一下了。

Console程序运行起来之后执行一次调用,没出现异常,计算结果也正确。于是添加for 100次循环,直接运行,竟然出现了 IndexOutOfRangeException!

怀疑是内存释放问题,但是 IntPtr没办法提供更多内存信息。于是改了一下 C# 代码,使用数组直接传参

csharp 复制代码
// [In]/[Out]
[DllImport("C_plus_plus.dll", EntryPoint = "2a64cf1c-a94c-43b0-942d-870fa679bfe7")]
public extern static int Calc_By_C_plus_plus([Out] int[] result, [Out] double[] err_1, [Out] double[] err_2, [In] double[] src_1, [In] double[] src_2)       
       
 // 调用
 public static int Calc(int[] output, double[] input_1, double[] input_2)
 {
     var calc_output = new int[10];
     var err1 = new double[1];
     var err2 = new double[1];

     var result = Calc_By_C_plus_plus(calc_output, err1, err2, input_1, input_2);
     Array.Copy(step1out, step_1_out, step_1_out.Length);
     return result;
 } 

这次异常信息变成了:ExecutionEngineException,多次执行,每次出现的位置都不一样,奇了怪了,更奇怪的是异常还会出现在数组定义的地方,肯定是内存问题没错了!

开始单步调试,发现执行C++代码之后,鼠标移到 err2 数组时竟然没法预览,添加到 watch 窗口之后在 value 列显示 'Unable to read memory'

开始有点意思了,为什么是 err2,而不是其他变量呢,再次检查代码,发现输入数组(input_1,input_2),输出数组(output)的长度(Array.Length)都是10,而 err1/err2 数组长度却是 1,难道是传入数组 err1 数组长度不够,C++代码写到 err2 数组里面了,导致 err2 数组没法读取?

将 err1/err2 数组长度改为10,单步调试。果然,这次 err2 可以正常读取,之前判断是对的!对C++内存不安全有了切身的体会。

将循环次数改为一百万次,五分钟之后程序正常结束,问题就此解决!

本文使用 markdown.com.cn 排版

相关推荐
格林威16 小时前
C# 相机Burst模式图像采集:使用相机内存配合OpenCvSharp和Halcon实现短时间的高速采集的方法
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
J总裁的小芒果17 小时前
参数过多-分批传参
c#
典典分享指南17 小时前
短视频分镜提示词:让 AI 出片的产品口播
java·c#·bash·composer·symfony
Z59981784119 小时前
c#软件开发学习笔记--WPF(Canvas、数据绑定、MVVM模式、值转换器)
笔记·学习·c#
淡海水20 小时前
05-03-栈队列-PriorityQueue-TElement-TPriority-NET6优先队列语义与四叉堆实现
服务器·前端·c#·priorityqueue·clr·telement
leonkay1 天前
C# 特性(Attribute)——【1】基础讲解
开发语言·青少年编程·面试·c#·.net·个人开发
CSDN_RTKLIB1 天前
数据库七大符号表
c#
leonkay1 天前
C# 特性(Attribute)——【2】工业设备参数框架设计
经验分享·面试·架构·c#·学习方法·设计
2501_906565121 天前
小创 · 智能助手
开发语言·c#
datalover2 天前
【无标题】
c#·linq