GCC 11

David C

Active member
I'm moving code from T3.2 to T4.1 and have come across a few peculiarities. I've reverted to the Teensyduino 1.53 to "fix" it, but I was wondering how I might resolve the issue before it becomes critical.

I can't post the whole code because it's too long.

But this is one problem (after compiling OK)

screenshot.png


The value of retrySBDIX before the "if" statement is correctly shown as 0, so the the second Serial.println should not execute. Except that it does (along with everything else) and shows a value 1.

How can this be? What do I have to do to fix it (aside from use 1.53)? The code does work perfectly when built with 1.53.

Could it possibly be an optimisation thing?

Thanks!
 
Is that variable being modified, say, from an interrupt or something? I’m throwing darts here because we don’t know what the code looks like, but what happens if you also make the variable volatile?
 
No, it's not modified from an interrupt. It's declared globally.

The point is that it works with 1.53 (and whatever version of GCC that uses) but not with GCC 11.
 
Hard to see and diagnose with so little info.

are any compiler warnings shown? If you have not already done so, go into preferences and set to show all warnings.
 
We switched from gcc 5.4 to gcc 11.3 starting with Teensyduino 1.58. So if this really is a problem with the compiler, you should be able to reproduce it with versions 1.54 to 1.57.

You could also try different compiler optimization levels with Tools > Optimize.

If it really is a compiler problem, nobody will be able to help without code to reproduce the issue. You'll need to whittle down your program to something reasonably sized and sharable.

I'm pretty skeptical it's a compiler bug. We've encountered missed optimization bugs before, and in fact we still use -O2 as the default rather than -O3 because of missed optimization bugs in the older versions. But those confirmed compiler bugs still always produced correct results, just slowly. Maybe this case really is the first ever in Teensy's 14 year history where a compiler bug gives a wrong result, but I kinda doubt that. However, it is really truly is a compiler bug, the only way to actually prove it is by sharing the exact code source code which reproduces the problem.
 
Again, just another guess without being able to look at your complete code: what is after the code snippet that is shown ?? The second instance of Serial.println(retrySBDIX); is terminated with a semicolon, but there is also an opening brace following that line (which actually will do nothing). Is it possible that you have another Serial.println() call somewhere after the one terminated with a semicolon that is actually printing the '1' result that you are seeing. I'd recommend that you add command(s) to print some kind of identifying text before you print variable contents in order to be able to uniquely & definitively identify what is being printed & from where.

Mark J Culross
KD5RXT
 
Like kd5rxt-mark said, you might be getting a value from elsewhere. One thing that has helped me is realizing that modern Arduino has printf. So, it can be as simple as
Code:
Serial.printf("retrySBDIX: %u\n", retrySBDIX);
so that you're sure what you're looking at.
 
When i get unexpected behavior like this then it’s nearly always stack overflow. Stack pointer overwriting global variables. Try tools that keep track of how much stack space is left over.

In setup() write a fixed pattern like 0x12345678 in all RAM bytes reserved for the stack. Then check with a call, before and after the suspicious code, if stack ever got pushed so much that all 0x12345678 values got overwritten.
 
We switched from gcc 5.4 to gcc 11.3 starting with Teensyduino 1.58. So if this really is a problem with the compiler, you should be able to reproduce it with versions 1.54 to 1.57.

You could also try different compiler optimization levels with Tools > Optimize.

If it really is a compiler problem, nobody will be able to help without code to reproduce the issue. You'll need to whittle down your program to something reasonably sized and sharable.

I'm pretty skeptical it's a compiler bug. We've encountered missed optimization bugs before, and in fact we still use -O2 as the default rather than -O3 because of missed optimization bugs in the older versions. But those confirmed compiler bugs still always produced correct results, just slowly. Maybe this case really is the first ever in Teensy's 14 year history where a compiler bug gives a wrong result, but I kinda doubt that. However, it is really truly is a compiler bug, the only way to actually prove it is by sharing the exact code source code which reproduces the problem.
To be clear, the issue starts at 1.58 and is not present in the earlier versions.
 
When i get unexpected behavior like this then it’s nearly always stack overflow. Stack pointer overwriting global variables. Try tools that keep track of how much stack space is left over.

In setup() write a fixed pattern like 0x12345678 in all RAM bytes reserved for the stack. Then check with a call, before and after the suspicious code, if stack ever got pushed so much that all 0x12345678 values got overwritten.
That's s good idea. Do you have any tips about finding out which RAM bytes are reserved and how to write to them?
 
I've tried fast, faster and fastest so far, and the result is the same.

Is there a place where I can download 1.57? I have only 1.53.

I'm attaching the output with all warnings on, an extract of the serial interface, and a longer extract of the code.

I'll try the suggestion to examine the stack.
 

Attachments

  • code extract.txt
    926 bytes · Views: 21
  • serial output.txt
    412 bytes · Views: 18
  • verbose complier output.txt
    103.7 KB · Views: 11
I found some code on this post to examine the memory.

https://forum.pjrc.com/index.php?threads/memory-status-and-monitoring.32401/

I inserted it thus:

C:
        Serial.printf("retrySBDIX1: %u\n", retrySBDIX);
        if (retrySBDIX)
        {
            report_ram();
            ram.run();

            Serial.printf("retrySBDIX2: %u\n", retrySBDIX);

            if (distressMode)
            {
                loadDistressMessage();
            }

And this is the result:

Code:
retrySBDIX2: 0
[014.8]  Trying transmit again, attempt#1
[014.8]  Signal strength = 0
retrySBDIX1: 0
==== memory report ====
free: 44 Kb (68.9%)
stack: 2 Kb (3.1%)
heap: 2 Kb (3.9%)

Interestingly, although the value of the boolean retrySBDIX variable is 0, the report_ram etc is still executed, but the value after reprort_ram is still 0.
 
With thanks to KurtE I downloaded and installed 1.57 (with Arduino 1.8.19).

Without making any code changes, the issue does NOT arise.
 
Without making any code changes, the issue does NOT arise.

I understand you're suffering a non-working program and that's a painful situation. I want to help you. So does everyone else here.

Yours is not the first code we've seen break by updating to gcc 11. For example, in the core library and Wire & SPI libraries we've been using constexpr constructors on Serial, Serial1, Serial2, Wire, Wire1, SPI, SPI1, etc. The idea was for these commonly used classes to be static initialized at startup, so they could be used from constructors of other classes. It worked fine with gcc 5.4. That broke in 1.58 because of the update to gcc 11.3 (and the problem has come up several times on this forum where people's programs using those in C++ constructors no longer worked). It's been fixed in 1.59-beta. It would be the easy answer to blame the compiler, but the situation turned out to be much more complex. After much investigation and discussion, it turns out the C++ standards don't actually say constexpr constructor gives static initialization. With the way the code was written, gcc 5.4 happened to initialize everything at compile time. But gcc 11.3 didn't, and according to the C++ language specs how the class gets initialized is undefined behavior. We "fixed" the problem by changing the constructor parameters from pointers to integers. But even that could break on future compilers, but at least for now it's been carefully tested with gcc 11.3 to get the results we want. Turns out there isn't any way to guarantee this behavior with C++14 or C++17, but C++20 adds a "constinit" feature for exactly this scenario.

Whether your program is suffering from a similar situation, I don't know because fully analyzing the problem is impossible with small code fragments that can't be compiled to reproduce the problem. But if I were to guess, my money would be on some sort of buffer overflow in either your report_ram() or ram.run() functions which overwrites part of the stack to corrupt your retrySBDIX variable.

We've also seen buffer overflow bugs many times on this forum. They manifest is peculiar ways, sometimes even happening and vanishing with the smallest of code changes because the compiler allocates variables in a different order or keep more in registers or spills more onto the stack depending on the surrounding code. Sometimes, almost beyond belief, a program can run seemingly error free for quite some time before a small buffer overflow overwriting some variable actually manifests as an observable problem! Some buffer overflow bugs lay undiscovered for a very long time, because they overwrite a byte or word that wasn't actually used. Compilers align variables to word boundaries, so often programs have many small "holes" in their memory usage that are never actually accessed, which can allow that sort of bug to remain unknown long after the code is thought to be error-free. Obviously a change between major versions of the compiler would be expected to compile code differently where the layout of your variables in static memory and on the stack could be completely different, even though you haven't changed a single line of your program.

This is why code to reproduce the problem is so important. I want to help you resolve this. So does everyone else here. And in the highly unlikely case it really is a compiler bug, of course I want to truly get to the bottom of such a problem. But that's impossible, because we don't have the source code to reproduce the problem.

Please put some effort into whittling down your program into something sharable that reproduces the problem.
 
Last edited:
OK.

I have whittled it down such that the project made for T3.2 with v1.58/Arduino 1.8.19 compiles but has the same issue when it runs. The files are attached.

The forum does not allow upload of .h files (!)), so they're renamed .ino just for upload.
 

Attachments

  • ConAir.ino
    2.2 KB · Views: 19
  • CP.ino
    24.6 KB · Views: 20
  • Iridium.ino
    22.1 KB · Views: 20
  • cp (2).ino
    10 KB · Views: 20
  • RamMonitor.ino
    7.3 KB · Views: 19
You could always do it like this.
cp.h
Code:
#pragma once

// from old comms proc
// cp.h

/*
RockBlock on Serial1
DataVault Serial 2
conair serial 3

Inbound Message format: type|length|payload
Outbound:    RBStatus|length|payload type|payload

*/

#define RockBlock Serial1
#define DataVault Serial2

HardwareSerial* modemSerial;

// message types
#define OUTBOUND_MESSAGE 0x10
#define QUEUE_LENGTH 0x01
#define REQUEST_MODEM_STATE 0x02
#define SENDING_MESSAGE 0x03
#define INBOUND_MESSAGE 0x04
#define SENT_MESSAGE 0x05
#define CP_WAITING 0x06
#define DEVICE_STATUS 0x07
#define TRANSMISSION_ERROR 0x08
#define CONAIR_DATA 0x20
#define I2C_BUS_POWER_OFF 0x30
#define I2C_BUS_IS_ON 0x31
#define I2C_BUS_POWER_ON 0x32
#define AVIONICS_POWER_OFF 0x40
#define AVIONICS_POWER_ON 0x41
#define CLEAR_QUEUE 0x32
#define TEXT_MESSAGE 0x70
#define MODEM_TYPE 0xA0
#define DATE_TIME 0xA1
#define GPS_DATA 0xA2
#define MODEM_CN 0xA3
#define REQUEST_IMEI 0xf0
#define CP_BOOT 0xD0

#define AUX_SCAN_TIME 100 // analog scan interval

// main processor timeout
#define MAIN_PROC_TIMEOUT 40000 // 40 secs
boolean noMainProc = false;

// statuses
#define NO_MODEM 0x01
#define MODEM_OK 0x02

byte RBstatus;
#define RESET_MP_COMMS 0xaa

//IridiumSBD isbd(RockBlock, -1);

// commands for debugging
byte recIndex;
char serialrecbyte, strMonCmd[250];

// command list
const char monCmnds[] = "RBstop\0RBstart\0RBMM\0RBclearq\0RBLD";
const int monComndpositions[] = { 0, 7, 15, 20, 29 };

#define maxCommands 5

#define cmdRBstop 0
#define cmdRBstart 1
#define cmdRBMM 2
#define cmdRBclearq 3
#define cmdRBLD 4

static const int ledPin = 13;

const byte LF = 10;
const byte CR = 13;

char msgToSend[50];
int msgIndex = 0, msgcount = 0;
byte recbyte;
boolean sending;

long oldMillis, oldMicros;

// RockBlock
#define MAX_RBQLENGTH 100
#define MAX_MSG_LENGTH 200
#define MIN_MSG_LENGTH 20

//boolean useRockBlock = false, RBsending = false, RBpresent = false;
//int RBtransInterval = 60; // ****************************************
//int RBCheckInterval;
//int RBOutMessageCount; // *********************
//byte RBOutMessage[MAX_MSG_LENGTH], RBQueue[MAX_RBQLENGTH][MAX_MSG_LENGTH], RBMsgLengths[MAX_RBQLENGTH];
//int RBwriteIndex = 0, RBreadIndex = 0;
boolean distressMessage;
int DVIndex = 0, inCount = 0;
byte msgtype, DVMsgType;

int sigQuality;

int DVMessageNo;
uint8_t conaircoverindex;
int32_t maxval;

boolean sendConAirData = false;

#define MAX_RX_SIZE 255
#define MESSAGE_RECEIVED 0x04
#define SYS_DISTRESS 0x55
#define GPS_POS 0x60
#define MSG_RECEIVED 0x60
#define AC_ENG 0x31
#define MAX_RETRIES 10

int retries;

//boolean RBavailable = false;
byte RBInMessage[MAX_RX_SIZE];
size_t RBInBufferSize;
size_t txSize;
//boolean distressState = false;
//boolean cancelSending = false;
byte distressType;
byte DMRef[2];

// things for conair
#define GC_SERIAL Serial3

#define CP_I2C 0x11
#define CONAIR_SEND_VOLUME 0x10
#define CONAIR_SEND_ALL 0x11
#define CONAIR_FIELDS 11
#define CONAIR_DATA_BYTES 23
#define CONAIR_REC_BUFFER_SIZE 5
#define CONAIR_TIMEOUT 5000
#define CONAIR_MIN_HOPPER 80
#define CONAIR_MAX_HOPPER 800
#define CONAIR_RETRY_INTERVAL 50

const double conM = 0.09;  // same as datavault defaults
const double conC = 45;
int iGals;

#define CR 13

byte conairMsgType, sendConairData = false, conairrecbyte, buffer[20];

byte conAirData[CONAIR_DATA_BYTES + 1];
int16_t rawData[CONAIR_FIELDS] = { 11,1052,2164,2165,1477,-1,-5,-4,-2,20,1237 };
unsigned long  noOfChars, noOfMsgs;

boolean ConAirGatesOpen = false;
boolean gateControllerPresent = true;
int ConAirIndex;

const char conAirCoverLabels[][8] = { "200/0.5","200/1.0","200/2.0","200/3.0","200/MAX","267/0.5","267/1.0","267/2.0","267/3.0","267/MAX",
"400/0.5","400/1.0","400/2.0","400/3.0","400/4.0","400/5.0","400/MAX","800/0.5","800/1.0","800/2.0","800/3.0","800/4.0","800/5.0","800/6.0","800/MAX","CON-N/A" };
char strConAirCover[10];


// dc iridium stuff
#define iridiumSerial Serial1


// buffer
#define IRIDIUM_BUFFER_LENGTH 300
#define OUT_BUFFER_LENGTH 200
#define IN_BUFFER_LENGTH 300

char iridiumBuffer[IRIDIUM_BUFFER_LENGTH];
uint8_t iridiumOutBuffer[OUT_BUFFER_LENGTH];
uint8_t iridiumInBuffer[IN_BUFFER_LENGTH];

int iridiumBufferIndex, outboundMsgLength, inboundMsgLength;

// iridium states
byte modemState;
byte modemTimeOut;
byte sendingState;
byte signalStrength, signalStrengthCount;
#define MIN_SIGNAL_STRENGTH 2
#define SIGNAL_STRENGTH_INTERVAL 30

int SBDIXattempts = 1;
uint16_t SBDIXresponse[6];
#define MO_STATUS 0
#define MOMSN 1
#define MT_STATUS 2
#define MTMSN 3
#define MT_LENGTH 4
#define MT_QUEUED 5

boolean SBDIXResponseError, inBoundMessage, messageSent, signalStrengthAvailable, retrySBDIX;
boolean waitingForGoodSignal; // used to hold sending
boolean messageReadyToSend = false;

const struct {
    byte WAITING = 0;
    byte WAIT_FOR_AT = 1;
    byte WAIT_FOR_ATE1 = 2;
    byte WAIT_FOR_ATD0 = 3;
    byte WAIT_FOR_ATK0 = 4;
    byte WAIT_FOR_SBDWB_READY = 5;
    byte WAIT_FOR_SBDWB_RECEIPT = 6;
    byte WAIT_FOR_SBDIX = 7;
    byte WAIT_FOR_SBDRB = 8;
    byte WAIT_FOR_SBDD0 = 9;
    byte WAIT_FOR_CSQ = 10;
    byte WAIT_FOR_CSQ_RETRY = 11;
    byte POWER_TOGGLE = 12;
    byte WAIT_FOR_POWER_UP = 13;
    byte WAIT_FOR_IMEI = 14;
} iridiumState;

// timeouts
const struct {
    byte WAIT_FOR_AT = 10;
    byte WAIT_FOR_SBDWD_READY = 5;
    byte WAIT_FOR_SBDWB_RECEIPT = 5;
    byte WAIT_FOR_SBDIX = 60;
    byte WAIT_FOR_SBDRB = 10;
    byte WAIT_FOR_SBDD0 = 10;
    byte WAIT_FOR_CSQ = 60; // manual says up to 50
    byte WAIT_FOR_CSQ_RETRY = 10; // seconds between retries when sending
    byte FIRST_CSQ_TEST = 2;
    byte WAIT_FOR_IMEI = 5;
} iridiumTimeOut;

// responses
const struct {
    byte GOOD = 1;
    byte SBDIX_PROTOCOL_ERROR = 2;
} iridiumResponse;

// sending state
const struct {
    byte WAITING_FOR_MESSAGE = 1;
    byte WAITING_FOR_GOOD_SIGNAL = 2;
    byte SENDING = 3;
} iridiumSendingState;

// state
boolean modemAvailable = false;

// queue
#define MAX_MESSAGE_SIZE 250 // = 4 * 64
#define MAX_QUEUE_SIZE 20
uint8_t queueLength, msgQueue[MAX_QUEUE_SIZE][MAX_MESSAGE_SIZE + 5], queueWriteIndex, queueReadIndex, lastMessageWriteIndex;

// queue sizing
// msg bytes
// + 1 for msg length
// + 2 for rockair start bytes
// + 2 for rockair number

// message
uint8_t msgLength;
uint8_t msgContent[MAX_MESSAGE_SIZE];

// RockAir 400
//#define rockAirSerial Serial3 // *****************************************

// buffer
#define IN_BUFFER_LENGTH 300
char RockAirInBuffer[IN_BUFFER_LENGTH], V2Buffer[IN_BUFFER_LENGTH], SXBuffer[IN_BUFFER_LENGTH];
int RockAirBufferIndex, RockAirMsgRef, V2BufferIndex, V2MsgRef, SXBufferIndex, SXMsgRef;
float RockAirSpeed = 0.0, V2Speed = 0.0;;

// V2Track
//#define V2TrackSerial Serial1

// External GPS
int32_t encLat, encLon;
uint32_t  encGPSTime, gpstime;
uint16_t gpsspeed, gpstrack, gpsaltitude;
uint8_t gpshdop, encGPSFixType, gpssats, gpspdop;
int hh, mm, ss, dd, yy, mth, secs;

uint16_t sentMsgRef, statusQueryRef;
int8_t statusRequestMsgIndex = -1;

#define TP_MESSAGE_TYPE 0x31
#define TP_MANUFACTURER 0x02

byte encBytes[300];

// RockAir states
const struct {
    byte WAITING = 0;
    byte WAITING_OK_FOR_BYTES = 1;
    byte WAITING_FOR_REF_NO = 2;
    byte WAITING_FOR_STATUS = 3;
} rockAirStates;

byte rockAirState = 0, emTimeOut = 0;
byte msgBytes[300];

#define KEEP_ALIVE_SECS 10
byte emKeepAlive = KEEP_ALIVE_SECS;
byte V2KeepAlive = KEEP_ALIVE_SECS;

int rockAirTickCount = 0;

// timeouts
const struct {
    byte WAITING_OK_FOR_BYTES = 5;
    byte WAITING_FOR_STATUS = 5;
} rockAirTimeOuts;

boolean measureResponse, lastMessageSent;

// distress mode
#define DISTRESS_ACTION_CODE 23;
byte DA[50], DC[50], LP[50], inMsgType, DAMsgRef[2];

const struct {
    byte OFF = 0;
    byte SENDING_DA = 1;
    byte SENDING_DC = 2;
    byte SENDING_LP = 3;
} distress;

byte distressState;

boolean distressMode = false, DASent = false, DCReceived = false;

// V2TrackStates
const struct {
    byte READY_FOR_MSG = 0;
    byte WAITING_GPS = 1;
    byte WAITING_FOR_MSG_ID = 2;
    byte WAITING_FOR_MSGSTATUS = 3;
    byte WAITING_FOR_DISTRESS_ACK = 4;
    byte WAITING_INBOUND_COUNT = 5;
    byte WAITING_INBOUND_MSG_ID = 6;
    byte WAITING_INBOUND_MSG_TEXT = 7;
    byte WAITING_DISMISS = 8;
    byte WAITING_STATE_ACK = 9;

} V2States;

#define QUEUE_CHECK_TIME 5
#define AC_STATE_SECS 10

byte V2State = 0, V2TimeOut = 0, queueCheckSecs, queueCheckIndex = 0, sendACStateSecs = AC_STATE_SECS;

char V2IDPrefix[20], V2Prefixes[5][20], V2InboundMsgId[20], V2InboundMessage[250];

#define V2_INBOUND_CHECK_INTERVAL 60
uint8_t V2InboundCheckSecs = V2_INBOUND_CHECK_INTERVAL;

#define MAX_V2_REQUESTS 5
uint8_t V2Requests = MAX_V2_REQUESTS;

uint64_t V2Prefix = 0;
char V2IDs[20][20];

boolean V2unknown = false, V2Ready = false, V2QEmpty = true, V2CancelAtStart = false;
uint8_t V2QueuedMessages = 0;

// timeouts
const struct {
    byte WAITING_FOR_MSG_ID = 5;
    byte WAITING_GPS = 5;
    byte WAITING_FOR_MSGSTATUS = 5;
    byte WAITING_FOR_DISTRESS_ACK = 5;
    byte WAITING_INBOUND_COUNT = 5;
    byte WAITING_INBOUND_MSG_ID = 5;
    byte WAITING_INBOUND_MSG_TEXT = 5;
    byte WAITING_DISMISS = 5;
    byte WAITING_STATE_ACK = 5;
} V2TimeOuts;

// states
uint8_t aircraftStates;
uint startDelay = 0;

// spidertracks
//#define SXSerial Serial3

// sx states
const struct {
    byte WAITING_FOR_BOOT = 0;
    byte READY_FOR_MESSAGE = 1;
    byte WAITING_FOR_MSG_ACK = 2;
    byte WAITING_SOS_ACK = 3;
    byte WAITING_SOS_ACTIVATE = 4;

} SXStates;

byte SXState, SXTimeout, SXProtocolVersion;
#define SX_DEFAULT_TIMEOUT 5

int SXQueueSize = 0, testCount = 0, SXTickCount = 0, csRetries = 0;

boolean SXReady = false, SXQEmpty, SXDebug = true, gpsNoFix = true;

// rtc clock to use epoch time
boolean RTCset = false;
time_t RTCTime;

char strSXFixType[10], strEpoch[20];

int dummySpeed = 0;

// for distance check
float fLat, fLon, lastLat, lastLon, fVal;

// handle a sx nak
boolean nacked = false;
boolean gotAnotherBoot = false;

// log output
boolean logOutput = true;
 
I have whittled it down such that the project made for T3.2 with v1.58/Arduino 1.8.19 compiles but has the same issue when it runs.

I compiled and uploaded your program to a Teensy 3.2. This is what I get in the Arduino Serial Monitor:

Code:
[000.6]  Firmware version 81
[000.6]  I2C power ON
[000.6]  I2C monitoring OFF
started Iridium
sent boot on start
started

Maybe it needs certain hardware connected to demonstrate the problem?

Or maybe I need to do something after it starts running, to cause it to reproduce the issue?

(also noticed many compiler warnings.... might be worthwhile to address those)
 
Even though I have no idea how to reproduce the problem using the code given, I spent a few minutes reading through the code anyway.

The stuff in RamMonitor.ino looks like the sort of code which could have all sorts of compiler-version-specific assumptions unwittingly built in. Here's a copy for casual reading:

C++:
// Teensy 3.x RAM Monitor
// copyright by Adrian Hunt (c) 2015 - 2016
//
// simplifies memory monitoring;  providing both "raw"
// memory information  and with frequent  calls to the
// run() function, adjusted information with simulated
// stack allocations. memory is also monitored for low
// memory state and stack and heap crashes.
//
// raw memory information methods:
//
//     int32_t unallocated() const;
//   calculates space between  heap and current stack.
//   will return negitive if heap and stack currently
//   overlap, corruption is very likely.
//
//     uint32_t stack_used() const;
//   calculates the current stack size.
//
//     uint32_t heap_total() const;
//   return the heap size.
//
//     uint32_t heap_used() const;
//   returns allocated heap.
//
//     uint32_t heap_free() const;
//   returns unused heap.
//
//     int32_t free() const;
//   calculates total free ram; unallocated and unused
//   heap. note that this uses the current stack size.
//
//     uint32_t total() const;
//   returns total physical ram.
//
// extended memory information.  These methods require
// the  RamMonitor  object to  be initialized  and the
// run() method called regularly.
//
//     uint32_t stack_total() const;
//   returns the  memory required for  the stack. this
//   is determind by historical stack usage.
//
//     int32_t stack_free() const;
//   returns stack  space that can be used  before the
//   stack grows and total size is increased.
//
//     int32_t adj_unallocd() const;
//   calculates  unallocated  memory,  reserving space
//   for the stack.
//
//     int32_t adj_free() const;
//   calculates  total  free  ram  by  using  adjusted
//   unallocated and unused heap.
//
//     bool warning_lowmem() const;
//     bool warning_crash() const;
//   return warning states: low memory is flagged when
//   adjusted unallocated memory is below a set value.
//   crash is flagged when  reserved stack space over-
//   laps heap and there is a danger of corruption.
//
//     void initialize();
//   initializes the RamMonitor  object enabling stack
//   monitoring and the extended information methods.
//
//     void run();
//   detects stack growth and updates memory warnings.
//   this function must be called regulary.
//
// when using the extended memory information methods,
// a single  RamMonitor  object  should be  create  at
// global level.  two static  constants define  values
// that control stack allocation step size and the low
// memory  warning level.  these values  are in bytes.
// the stack allocation step must be divisable by 4.
//
//     static const uint16_t STACKALLOCATION;
//     static const uint16_t LOWMEM;
//

#ifndef RAMMONITOR_H
#define RAMMONITOR_H "1.0"

#include <malloc.h>
#include <inttypes.h>

extern int* __brkval;   // top of heap (dynamic ram): grows up towards stack
extern char _estack;    // bottom of stack, top of ram: stack grows down towards heap

class RamMonitor {
private:
  typedef uint32_t MemMarker;
  typedef uint8_t  MemState;

  // user defined consts
  static const uint16_t  STACKALLOCATION    = 1024;  // stack allocation step size: must be 32bit boundries, div'able by 4
  static const uint16_t  LOWMEM             = 4096;  // low memory warning: 4kb (less than between stack and heap)

  // internal consts
  static const uint32_t  HWADDRESS_RAMSTART =
#if defined(__MK20DX256__)
                                        0x1FFF8000;  // teensy 3.1 (? 3.2 ?)
#elif defined(__MKL26Z64__)
                                        0x????????;  // teensy LC
#else
                                        0x1FFFE000;  // teensy 3.0
#endif
  static const MemMarker MEMMARKER    = 0x524D6D6D;  // chars RMmm ... Ram Monitor memory marker
  static const uint16_t  MARKER_STEP  = STACKALLOCATION / sizeof(MemMarker);

  static const MemState msOk          = 0;
  static const MemState msLow         = 1;
  static const MemState msCrash       = 2;

  MemMarker* _mlastmarker;    // last uncorrupted memory marker
  MemState   _mstate;         // detected memory state

  void _check_stack() {
    int32_t free;

    // skip markers already comsumed by the stack
    free = ((char*) &free) - ((char*) _mlastmarker);
    if(free < 0) {
      int32_t steps;

      steps = free / STACKALLOCATION; // note steps will be negitive
      if(free % STACKALLOCATION)
        --steps;

      _mlastmarker += MARKER_STEP * steps;
    };

    // check last marker and move if corrupted
    while((*_mlastmarker != MEMMARKER) && (_mlastmarker >= (MemMarker*) __brkval))
      _mlastmarker -= MARKER_STEP;
  };
public:
  int32_t unallocated() const { char tos; return &tos - (char*) __brkval; };  // calcs space between heap and stack (current): will be negitive if heap/stack crash
  uint32_t stack_used() const { char tos; return &_estack - &tos; };          // calcs stack size (current): grows into unallocated
  uint32_t heap_total() const { return mallinfo().arena; };                   // returns heap size: grows into unallocated
  uint32_t heap_used() const { return mallinfo().uordblks; };                 // returns heap allocated
  uint32_t heap_free() const { return mallinfo().fordblks; };                 // returns free heap

  int32_t free() const { return unallocated() + heap_free(); };               // free ram: unallocated and unused heap
  uint32_t total() const { return &_estack - (char*) HWADDRESS_RAMSTART; };   // physical ram

  // these functions (along with initialize and run)
  // create the ellusion of stack allocation.
  uint32_t stack_total() {                                                  // uses memory markers to "alloc" unallocated
    _check_stack();
    return &_estack - (char*) _mlastmarker;
  };

  int32_t stack_free() {                                                    // calc stack usage before next marker corruption
    char tos;

    _check_stack();
    return &tos - (char*) _mlastmarker;
  };

  int32_t adj_unallocd() {                                                  // calcs space between heap and "alloc'd" stack: will be negitive if heap/stack crash
    _check_stack();
    return ((char*) _mlastmarker) - (char*) __brkval;
  };

  int32_t adj_free() { return adj_unallocd() + heap_free(); };              // free ram: unallocated and unused heap

  bool warning_lowmem() const { return (_mstate & msLow); };        // returns true when unallocated memory is < LOWMEM
  bool warning_crash() const { return (_mstate & msCrash); };       // returns true when stack is in danger of overwriting heap

  void initialize() {
    MemMarker* marker = (MemMarker*) &_estack;      // top of memory
    int32_t    size;
    int32_t    steps;

    // skip current stack;
    size = &_estack - (char*) &marker; // current stack size: marker address is tos
    steps = size / STACKALLOCATION;
    if(size % STACKALLOCATION)
      ++steps;

    marker -= MARKER_STEP * steps;

    // record current top of stack
    _mlastmarker = marker;
    _mstate = msOk;

    // mark unused ram between top of stack and top of heap
    while(marker >= (MemMarker*) __brkval) {
      *marker = MEMMARKER;                 // write memory marker
      marker -= MARKER_STEP;
    };
  };

  void run() {
    int32_t unallocd = adj_unallocd();   // calls _check_stack() internally

    if(unallocd < 0)
      _mstate = msCrash | msLow;
    else if(unallocd < LOWMEM)
      _mstate = msLow;
    else
      _mstate = msOk;
  };

};

#endif
 
Back
Top