I have a C/C++ application which uses libmodbus (GitHub - stephane/libmodbus: A Modbus library for Linux, Mac OS, FreeBSD and Windows) to read Modbus TCP inputs.
I read the values of each of the 4 Modbus input types (coils, discrete inputs, holding registers, input registers) each into their own vector of a Data wrapper class that I use to package the raw Modbus input values and add things like timestamp and quality.
I then try to concatenate the 4 separate vectors into 1 vector containing all of the Data to return from from my function, but I am getting the error:
myapplication[2066] | | *** Error in `myapplication’: corrupted double-linked list: 0x000702b0 ***
My function looks like this:
std::vector<Data> ModbusTcpClient::GetData ()
{
// Read all of the Modbus input types
std::vector<Data> coils = ReadCoils();
std::vector<Data> discreteInputs = ReadDiscreteInputs();
std::vector<Data> holdingRegisters = ReadHoldingRegisters();
std::vector<Data> inputRegisters = ReadInputRegisters();
// Package all of the data into a single vector to return
std::vector<Data> all;
// The corrupted double-linked list error is thrown after this call to reserve
all.reserve(coils.size() + discreteInputs.size() + holdingRegisters.size() + inputRegisters.size());
// Never get here
all.insert(all.end(), coils.begin(), coils.end());
all.insert(all.end(), discreteInputs.begin(), discreteInputs.end());
all.insert(all.end(), holdingRegisters.begin(), holdingRegisters.end());
all.insert(all.end(), inputRegisters.begin(), inputRegisters.end());
return all;
}
So I figured I would remove the call to reserve the vector space in advance, and see if I could get the code to work with just the inserts.
So I changed it to look like this:
std::vector<Data> ModbusTcpClient::GetData ()
{
// Read all of the Modbus input types
std::vector<Data> coils = ReadCoils();
std::vector<Data> discreteInputs = ReadDiscreteInputs();
std::vector<Data> holdingRegisters = ReadHoldingRegisters();
std::vector<Data> inputRegisters = ReadInputRegisters();
// Package all of the data into a single vector to return
std::vector<Data> all;
//all.reserve(coils.size() + discreteInputs.size() + holdingRegisters.size() + inputRegisters.size());
// The first call to insert executes just fine
all.insert(all.end(), coils.begin(), coils.end());
// The corrupted double-linked list error is thrown after this second call to insert
all.insert(all.end(), discreteInputs.begin(), discreteInputs.end());
// Never get here
all.insert(all.end(), holdingRegisters.begin(), holdingRegisters.end());
all.insert(all.end(), inputRegisters.begin(), inputRegisters.end());
return all;
}
I have tried a number of things, such as changing the function to manually iterate over each vector and push_back each Data object one at a time, rather than insert, same problem though (not surprisingly).
I also tried to change all of the functions to return Data* arrays instead of std::vector, and tried manually copying the arrays into one big destination array, but I also got the same error.
I am a bit at a loss now, because there isn’t much information out there on this corrupted double-linked list error.
All of the references I can find on it suggest to look for double free / delete statements on the same pointer in your code, but that doesn’t apply here.
Also, my Data class is very simple with all value type members, and no dynamic allocation, if that matters.
Here is a snippet which contains the Data class and the Modbus read functions for each input type:
#include <cstring>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <sys/time.h>
#include <string>
#include <vector>
#include <modbus.h>
class Data
{
public:
enum InputType
{
Coil = 0,
DiscreteInput = 1,
HoldingRegister = 2,
InputRegister = 3,
None = 4
};
enum Quality
{
Good = 0,
Bad = 1,
Unset = 2
};
Data(InputType clientInputType,
int clientIndex,
std::string timestamp,
double value,
Quality quality);
InputType GetClientInputType () const;
int GetClientIndex () const;
std::string GetTimestamp () const;
long GetTimestampTicks () const;
double GetValue () const;
int GetQuality () const;
private:
InputType m_clientInputType = InputType::None;
int m_clientIndex = -1;
std::string m_timestamp = "";
double m_value = -1;
Quality m_quality = Quality::Unset;
};
std::vector<Data> GetData();
std::vector<Data> ReadCoils();
std::vector<Data> ReadDiscreteInputs();
std::vector<Data> ReadHoldingRegisters();
std::vector<Data> ReadInputRegisters();
const int NUM_COILS = 8;
const int NUM_DISCRETE_INPUTS = 8;
const int NUM_HOLDING_REGISTERS = 8;
const int NUM_INPUT_REGISTERS = 8;
static modbus_t* ctx;
int main()
{
std::vector<Data> data = GetData();
return 0;
}
Data::Data(Data::InputType clientInputType,
int clientIndex,
std::string timestamp,
double value,
Data::Quality quality)
{
m_clientInputType = clientInputType;
m_clientIndex = clientIndex;
m_timestamp = timestamp;
m_value = value;
m_quality = quality;
}
Data::InputType Data::GetClientInputType() const
{
return m_clientInputType;
}
int Data::GetClientIndex() const
{
return m_clientIndex;
}
std::string Data::GetTimestamp() const
{
return m_timestamp;
}
double Data::GetValue() const
{
return m_value;
}
int Data::GetQuality() const
{
return m_quality;
}
std::string ToString(int x)
{
int length = snprintf(NULL, 0, "%d", x);
char* buf = new char[length + 1];
snprintf(buf, length + 1, "%d", x);
std::string str( buf );
delete[] buf;
return str;
}
std::string GetSystemTimestamp()
{
const int count = 24;
const char* format = "%Y-%m-%dT%H:%M:%S";
char buffer[count];
int millisec;
struct tm* tm_info;
struct timeval tv;
gettimeofday(&tv, NULL);
// Round to nearest millisec
millisec = lrint(tv.tv_usec / 1000.0);
// Allow for rounding up to nearest second
if (millisec >= 1000)
{
millisec -= 1000;
tv.tv_sec++;
}
tm_info = localtime(&tv.tv_sec);
std::strftime(buffer, count, format, tm_info);
std::string timestamp = std::string(buffer) + std::string(".");
if(millisec < 100)
timestamp += std::string("0");
timestamp += ToString(millisec) + std::string("Z");
return timestamp;
}
std::vector<Data> GetData ()
{
// Read all of the Modbus input types
std::vector<Data> coils = ReadCoils();
std::vector<Data> discreteInputs = ReadDiscreteInputs();
std::vector<Data> holdingRegisters = ReadHoldingRegisters();
std::vector<Data> inputRegisters = ReadInputRegisters();
// Package all of the data into a single vector to return
std::vector<Data> all;
all.reserve(coils.size() + discreteInputs.size() + holdingRegisters.size() + inputRegisters.size());
all.insert(all.end(), coils.begin(), coils.end());
all.insert(all.end(), discreteInputs.begin(), discreteInputs.end());
all.insert(all.end(), holdingRegisters.begin(), holdingRegisters.end());
all.insert(all.end(), inputRegisters.begin(), inputRegisters.end());
return all;
}
std::vector<Data> ReadCoils()
{
uint8_t coils[NUM_COILS];
std::memset(coils, 0, NUM_COILS * sizeof(uint8_t));
// Make sure Modbus TCP context is connected
int rc = modbus_connect(ctx);
if(rc == -1)
return std::vector<Data>();
int rc = modbus_read_bits(ctx, 0, NUM_COILS, coils);
if(rc == -1)
return std::vector<Data>();
else if(rc != NUM_COILS)
return std::vector<Data>();
std::string timestamp = GetSystemTimestamp();
std::vector<Data> data;
data.reserve(NUM_COILS);
for(int i = 0; i < NUM_COILS; i++)
{
double value = modbus_get_byte_from_bits(coils, i, 1);
data.push_back(Data(Data::InputType::Coil, i, timestamp, value, Data::Quality::Good));
}
return data;
}
std::vector<Data> ReadDiscreteInputs()
{
uint8_t discreteInputs[NUM_DISCRETE_INPUTS];
std::memset(discreteInputs, 0, NUM_DISCRETE_INPUTS * sizeof(uint8_t));
// Make sure Modbus TCP context is connected
int rc = modbus_connect(ctx);
if(rc == -1)
return std::vector<Data>();
rc = modbus_read_input_bits(ctx, 0, NUM_DISCRETE_INPUTS, discreteInputs);
if(rc == -1)
return std::vector<Data>();
else if(rc != NUM_DISCRETE_INPUTS)
return std::vector<Data>();
std::string timestamp = GetSystemTimestamp();
std::vector<Data> data;
data.reserve(NUM_DISCRETE_INPUTS);
for(int i = 0; i < NUM_DISCRETE_INPUTS; i++)
{
double value = modbus_get_byte_from_bits(discreteInputs, i, 1);
data.push_back(Data(Data::InputType::DiscreteInput, i, timestamp, value, Data::Quality::Good));
}
return data;
}
std::vector<Data> ReadHoldingRegisters()
{
uint16_t holdingRegisters[NUM_HOLDING_REGISTERS];
std::memset(holdingRegisters, 0, NUM_HOLDING_REGISTERS * sizeof(uint16_t));
// Make sure Modbus TCP context is connected
int rc = modbus_connect(ctx);
if(rc == -1)
return std::vector<Data>();
rc = modbus_read_registers(ctx, 0, NUM_HOLDING_REGISTERS, holdingRegisters);
if(rc == -1)
return std::vector<Data>();
else if(rc != NUM_HOLDING_REGISTERS)
return std::vector<Data>();
std::string timestamp = GetSystemTimestamp();
std::vector<Data> data;
data.reserve(NUM_HOLDING_REGISTERS);
for(int i = 0; i < NUM_HOLDING_REGISTERS; i++)
{
data.push_back(Data(Data::InputType::HoldingRegister, i, timestamp, holdingRegisters[i], Data::Quality::Good));
}
return data;
}
std::vector<Data> ReadInputRegisters()
{
uint16_t inputRegisters[NUM_INPUT_REGISTERS];
std::memset(inputRegisters, 0, NUM_INPUT_REGISTERS * sizeof(uint16_t));
// Make sure Modbus TCP context is connected
int rc = modbus_connect(ctx);
if(rc == -1)
return std::vector<Data>();
rc = modbus_read_input_registers(ctx, 0, NUM_INPUT_REGISTERS, inputRegisters);
if(rc == -1)
return std::vector<Data>();
else if(rc != NUM_INPUT_REGISTERS)
return std::vector<Data>();
std::string timestamp = GetSystemTimestamp();
std::vector<Data> data;
data.reserve(NUM_INPUT_REGISTERS);
for(int i = 0; i < NUM_INPUT_REGISTERS; i++)
{
data.push_back(Data(Data::InputType::InputRegister, i, timestamp, inputRegisters[i], Data::Quality::Good));
}
return data;
}
The only other requirement for the snippet above to run should be to compile and link to the libmodbus library.
I circumvented this problem by switching to using Date** dynamic arrays instead of std::vector, however I have also encountered this problem a couple other times doing seemingly unrelated things such as writing to a log file (maybe the logging API I am using uses a standard data structure which has similar problems to what it appears vector has).
I have ran the same exact code (sans Legato proprietary stuff like COMPONENT_INIT) with zero issues on both Windows and Ubuntu as simple executable applications.
Has anyone experienced behavior like this?