I have a program that is supposed to control a motor controller through a custom API that opens COM ports, reads outputs, and writes commands. The program functions correctly when run via terminal on both Linux and Windows. To verify functionality of the code, it was placed directly on a raspberry pi and run from the command terminal. Again, it functioned correctly with no crashes. However, for what we are doing, we need to be able to make the code run in as an S-function Simulink block on a raspberry pi. The Simulink block compiles correctly on Matlab on both Windows and Linux and runs the code with no problem when executed with the simulation function. The issue is when trying to run the Simulink block on the hardware. Once pushed to the pi, the code will function for a couple of commands, but then will crash with a free(): invalid pointer error
. The full compile back-trace is below:
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
I am unsure how to proceed, because from all other testing, the code seems to work, which makes me think it is a Simulink or Raspberry Pi issue. I have tried the commenting out lines of code method of debugging, but it did not really narrow down the problem to anything other than reading or writing with the Raspberry Pi. Although it looks like the code crashes on the call to device->IssueCommand()
function, the device->Connect()
has an implicit call to the IssueCommand function which succeeds, so I do not believe that function is at fault either. Has anyone ever run into a similar issue? I can include the Device.cpp file if necessary, but I do not think the issue is in there (again, since the code functions correctly everywhere else).
Note 1: there are no explicit uses of free()
in the code
Note 2: The pi is a Raspberry Pi 4
Simulink S-Function Wrapper:
/*
* 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 Terminate\n");
Device* device = (Device*) pW[0];
#ifndef MATLAB_MEX_FILE
device->Disconnect();
delete(device);
#endif
/* %%%-SFUNWIZ_wrapper_Terminate_Changes_END --- EDIT HERE TO _BEGIN */
}
The program functions correctly when [...] Again, it functioned correctly with no crashes. Once pushed to the pi, the code will function for a couple of commands, but then will crash [...]
All consistent with undefined behavior (UB). The most insidious trick that UB has up its sleeve is acting as you expect until the most inconvenient time. Tools like Valgrind can detect some types of UB that would otherwise lie dormant until your presentation, until your customer runs it, etc.
One example of something Valgrind can detect is a buffer overrun. This happens when you allocate an array of some sort, and write beyond the end of what you allocated. For example, you might have a char
array – let's arbitrarily call it port
– that is initialized with a string literal, so it has exactly enough room for the seen characters plus the null terminator. It is not the string literal, though, so it is possible to modify its contents. Now, someone might take this modifiable array and try to append something to it, a procedure sometimes called str
ing concat
enation. Well, this will fail miserably because the array has no room for the additional characters. However, it might "function correctly" because there is no requirement to crash. Perhaps what it overwrites happens to be another char
array, one with plenty of space, let's say a hundred characters, so there is no hard memory violation.
Then you compile for another system, and this system reverses a few things. Now the next thing in memory after port
is no longer another char
array, but some object that contains a pointer, let's call it str
. So now instead of overwriting plain old data, the buffer overrun overwrites str
. This royally messes up the internal bookkeeping of str
, making it unstable for any operations. Boom! Crash!
How could Valgrind help? Valgrind could force padding between the variables. This way any overrun would write to padding bytes before overwriting another object. Writing to the padding signals something bad happened, and Valgrind could inform you where in your code this occurred.
Then again, this is all theoretical. Once UB occurs, anything could happen. And this does assume you've done something like strcat(port, __)
when port
has no space available for the concatenation. I'm sure that if I check your code in the near future, there will be nothing like this there. So maybe this is just a ramble.