我有一个程序,该程序应该通过一个自定义的API来控制电机控制器,该API打开COM端口、读取输出和写入命令。该程序在Linux和Windows上通过终端运行时都能正常工作。为了验证代码的功能,它被直接放在树莓派上,并从命令终端运行。同样,它运行正常,没有崩溃。然而,对于我们正在做的事情,我们需要能够使代码在树莓pi上作为S函数Simulink块运行。Simulink块在Windows和Linux上的Matlab上正确编译,使用模拟函数执行时运行代码没有问题。问题是当试图在硬件上运行Simulink块时。一旦被推到pi,代码将为几个命令运行,但随后将使用free(): invalid pointer error
崩溃。完整的回编译跟踪如下:
Top model targets built:
Model Action Rebuild Reason
=========================================================================================================================
RWComs_model_OLDER Code generated and compiled Dependency RWComs_Sfunc.mexa64 of S-function RWComs_Sfunc has changed.
1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 24.625s
A run-time error is encountered when running External mode simulation on the Raspberry Pi hardware. This usually occurs when a hardware resource, such as a web camera or an audio card, is not available or configured incorrectly. The log file, /home/pi/MATLAB_ws/R2020b/RWComs_model_OLDER.log, storing model diagnostic information on the Raspberry Pi hardware has the following content: **** Starting the application ****
Opening port: '/dev/ttyACM0'...succeeded.
Initializing port......done.
Detecting device version...v2.1.
free(): invalid pointer
我不确定如何进行,因为从所有其他测试来看,代码似乎可以工作,这让我认为这是Simulink或Raspberry Pi的问题。我尝试过注释掉代码行的调试方法,但它并没有真正将问题缩小到除了用树莓派阅读或写作之外的任何事情。虽然代码在调用device->IssueCommand()
函数时看起来崩溃了,但device->Connect()
对IssueCommand函数有一个隐式调用,它成功了,所以我不认为该函数有错。有人遇到过类似的问题吗?如果需要,我可以包含Device.cpp文件,但我认为问题不在那里(同样,因为代码在其他地方都能正常工作(。
注1:代码中没有明确使用free()
注2:圆周率是复盆子圆周率4
Simulink S函数包装器:
/*
* Include Files
*
*/
#if defined(MATLAB_MEX_FILE)
#include "tmwtypes.h"
#include "simstruc_types.h"
#else
#include "rtwtypes.h"
#endif
/* %%%-SFUNWIZ_wrapper_includes_Changes_BEGIN --- EDIT HERE TO _END */
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include "Device.h"
#include "ErrorCodes.h"
/* %%%-SFUNWIZ_wrapper_includes_Changes_END --- EDIT HERE TO _BEGIN */
#define u_width 1
#define y_width 1
/*
* Create external references here.
*
*/
/* %%%-SFUNWIZ_wrapper_externs_Changes_BEGIN --- EDIT HERE TO _END */
/* %%%-SFUNWIZ_wrapper_externs_Changes_END --- EDIT HERE TO _BEGIN */
/*
* Start function
*
*/
void RWComs_Sfunc_Start_wrapper(void **pW,
const real_T *comPort, const int_T p_width0)
{
/* %%%-SFUNWIZ_wrapper_Start_Changes_BEGIN --- EDIT HERE TO _END */
Device* device = new Device();
string str = "";
pW[0] = device;
//Sets com port depending on operating system
#ifdef __linux__
char port[] = "/dev/ttyACM";
#else
char port[] = R"(\.COM)";
#endif
char integer_string[100];
sprintf(integer_string, "%d", (int)comPort[0]);
strcat(port, integer_string);
#ifndef MATLAB_MEX_FILE
device->Connect(port);
device->IssueCommand("!", "MG", "", 1, str, true);
device->IssueCommand("", R"(/"d=",":"?bs 1_?a 1_?V 2_# 10_)", "", 1, str, true);
#endif
/* %%%-SFUNWIZ_wrapper_Start_Changes_END --- EDIT HERE TO _BEGIN */
}
/*
* Output function
*
*/
void RWComs_Sfunc_Outputs_wrapper(const real_T *cmd,
real_T *rpm,
real_T *current,
real_T *voltage,
void **pW,
const real_T *comPort, const int_T p_width0)
{
/* %%%-SFUNWIZ_wrapper_Outputs_Changes_BEGIN --- EDIT HERE TO _END */
Device* device = (Device*) pW[0];
string str = "";
device->IssueCommand("!", "G " + to_string(cmd[0]), "", 0, str, false);
returnData rtn = device->readRpmVoltageCurrent(str);
rpm[0] = rtn.rpm;
current[0] = rtn.cur / 10.0;
voltage[0] = rtn.volt / 10.0;
/* %%%-SFUNWIZ_wrapper_Outputs_Changes_END --- EDIT HERE TO _BEGIN */
}
/*
* Terminate function
*
*/
void RWComs_Sfunc_Terminate_wrapper(void **pW,
const real_T *comPort, const int_T p_width0)
{
/* %%%-SFUNWIZ_wrapper_Terminate_Changes_BEGIN --- EDIT HERE TO _END */
printf("I'm in Terminaten");
Device* device = (Device*) pW[0];
#ifndef MATLAB_MEX_FILE
device->Disconnect();
delete(device);
#endif
/* %%%-SFUNWIZ_wrapper_Terminate_Changes_END --- EDIT HERE TO _BEGIN */
}
程序在[…]时正常运行。再次,它正常运行,没有崩溃。一旦被推到pi,代码将为几个命令运行,但随后将崩溃〔…〕
所有与未定义行为(UB(一致。UB最阴险的伎俩是按照你的期望行事,直到最不方便的时候。像Valgrind这样的工具可以检测到一些类型的UB,否则这些UB会一直处于休眠状态,直到您的演示文稿、客户运行它等等。
Valgrind可以检测到的一个例子是缓冲区溢出。当您分配某种类型的数组,并在分配的数组末尾之后写入时,就会发生这种情况。例如,您可能有一个char
数组——让我们任意地称它为port
——它是用字符串文字初始化的,因此它有足够的空间容纳可见字符和null终止符。不过,它不是字符串文字,因此可以修改其内容。现在,有人可能会使用这个可修改的数组,并尝试向其添加一些内容,这个过程有时被称为str
ing concat
enation。好吧,这将失败得很惨,因为数组没有容纳额外字符的空间。然而,它可能";功能正确";因为没有崩溃的要求。也许它所覆盖的恰好是另一个char
数组,一个有足够空间的数组,比方说有一百个字符,所以不存在硬内存冲突。
然后你为另一个系统编译,这个系统颠倒了一些事情。现在,在port
之后,内存中的下一个不再是另一个char
数组,而是包含指针的某个对象,让我们称之为str
。因此,现在缓冲区溢出不再覆盖普通的旧数据,而是覆盖str
。这严重扰乱了str
的内部记账,使其对任何操作都不稳定。繁荣崩溃
Valgrind能帮上什么忙?Valgrind可能会强制变量之间进行填充。这样,任何溢出都会在覆盖另一个对象之前写入填充字节。向填充中写入表示发生了不好的事情,Valgrind可以通知您代码中发生这种情况的位置。
再说一遍,这都是理论上的。一旦UB发生,任何事情都可能发生。这确实假设您已经完成了类似strcat(port, __)
的操作,而port
没有空间进行串联。我相信,如果我在不久的将来检查你的代码,那里就不会有这样的东西了。所以,也许这只是一个漫步。