1. Excecption handling

In C++ (and Java, C# etc) languages the concept of try-catch-throw is established (''Exception handling''). This is a some more better concept than testing return values of each calling routine or testing errno like it is usual in C. The advantage of try-catch-throw is: Focus on the intrinsically algorithmus. Only in that source where an error may be expected and should be tested for safety operation, the conditions should be tested and a throw should be invoked if the algorithmus does not mastered the situation. And, on the opposite side, in sources where any error in any deeper level may be expected and it is able to handle, a try and catch is proper to write. All levels between try and throw do not regard any exception handling, they are simple. This is the '''advantage in comparison to the ''error return'' concept''' whereby any level of operation need to test the error return value.

The necessity of handling error return values in C will be often staggered to a later time, because the core algorithm should be fistly programmed and tested. Then, in later time, the consideration of all possible error situations is too much effort to program, it won’t be done considering the time line for development …​

Therefore the try-catch-throw concept is helpfull.

2. Never execute faulty instructions, test input arguments

@date=2019-04

In the following example a routine gets an arrayIndex. The software may be well tested, the arrayIndex has a valid value anytime, so the desire of developer. Nevertheless it should be tested! The test may only be omitted, it the calculation time is very expensive in this case and the routine is defined as static or private in C++. The static definition ensures that only a call in the same source is possible (or included sources, pay attention).

//called level:
int excutesub(..., int arrayIndex, ...) {
  if(arrayIndex > sizeArray || arrayIndex < 0) {
    THROW(IndexOutOfBoundsException, "illegal index", arrayIndex, -1);
    arrayIndex = sizeArray -1;
    faultyData = true;
  }
  myArray[arrayIndex] = ... //access only with correct index!

The THROW statement either invokes throw of C++, longjmp or write a log entry.

In a developer test phase C++ throw should be available while testing the system on a PC platform. Hence the error in runtime is detected, messaged and fixed. For a cheap target system, C++ may not be available or to expensive and longjmp does not work, there is no sensible possibility for error handling. Such an error may be really unexpected, but it is not possible to exclude.

For this case a 'avoidance strategy' is used: The THROW writes only a centralized information for example only one error store, or in a short buffer, and continue. The faulty value is set to a valid value for the safety of working of the system (not a valid value for the correct functionality, it is not possible). The system runs furthermore. The data may be designated as faulty. The system should run furthermore (it should not reseted or such), because other functionality may be work proper and should be work, and a data access for debugging is possible. The error message from THROW can be detected in a maintenance phase.

3. Debugging on runtime

The old approach is: ''Debug any algorithm till it is error-free''. That approach cannot be satisfied if the algorithms are complex, the error causing situations are variegated and the time to test is limited.

A better approach may be: ''Run the system under all possible situations''. It is not possible to debug step by step in all such situations.

Therefore the ''debugging on runtime'' is the proper approach:

  • Log all error occurences, but don’t abort.

  • Log the situation additionally.

  • Have enoug information to elaborate the situation and reason afterwards.

  • Access to the internal states of the target where the software runs to explore the state of the target.

4. Three possibilities of THROW in emC, C++-throw, longjmp and message

The emC programming style knows three kinds of using TRY-CATCH-THROW using macros. The user sources itself are not need to adapt for this kinds. The macros are adapted it. See Implementation.

  • Full try-catch-throw needs C++ compilation, uses the try throw catch keywords of C++ and can handle so named asynchron exceptions (faulty memory access). It is able to use especially on test of the application on PC, but also for rich target systems. From C++ only the simple throw and catch(…​) is used. The sophisticated C++ exception possibilities are not used and not recommended. Keep it simple.

  • The try-catch-throw concept is able to use also in C or C++ using the longjmp mechanism. It is proper and seems to be the default approach of error handling since 1970, but it was not often published for that. Some comments and using notes to the setjmp.h are confused. A proper description may be found in pubs.opengroup.org/onlinepubs/009695399/functions/longjmp.html. Some compiler for special processors do not implement longjmp in a proper kind. It is a compiler mistake, but users don’t ask for that. Hence the longjmp concept unfortunately is not able to use. But often longjmp is preferred for exception handling for embedded programming. See remark to destructors.

  • Messaging and avoidance strategy: If a program is well tested there is a residual risk that the program executes a THROW. The THROW only stores an error message, and the algorithm is continued. The algorithm should contain statements to set the safety for running the system. Data can be faulty. See example in the chapter above.

In C++ language the way from throw to catch invokes all destructors of data of all calling levels. longjmp may be used in C++ instead throw-catch, because it is faster than throw and sometimes for embedded processors the C++-try-catch is not available or needs to much time for calling organization. Then all destructors should be empty. That should be a concept. In C and also in Java the destructor concept is not known. Destructors are used in C++ applications for closing things, which are opened in the constructor. In Java instead a finally block does this work:

try {
  open a resource;
  doSomething which may be thrown;
}
finally {
  close the resource;
}

There is no catch, it means an exception is catched in an level above. But the finally block is processed anyway. That is the replacement for the destructor rule of C++.

The same can be done with the TRY-FINALLY macros of the emC exception handling.

If a constructor allocates memory for composites and the destructor frees it, of course it does not work. But allocated memory is disallowed often for embedded. In Java the allocation is possible in the constructor, because there is the Garbage Collector which frees the memory.

Generally the THROW can use FILE and LINE in the message to mark the occurrence in source. The CATCH can contain a stacktrace report from TRY to the `THROW`ing routine. The stacktrace is known from Java, it is a proper instrument for searching the cause.

5. Pattern to write TRY-CATCH-FINALLY-THROW for portable programming

Sources should be tested well on a PC platform where try-catch-throw of C++ is available. Then, without changes, they should run on a target platform where a C-compiler does not have this feature or less footprint is available, and the sources are tested well on the other hand.

5.1. The pattern

The pattern to write sources for that approach is the following one:

void anyOperation() {
  STACKTRC_ENTRY("anyOperation");
  float result;
  TRY {
     //an algorithm which expects errors on calling level
     result = anyOperation();
   }_TRY
   CATCH(Exception, exc) {
     printStackTrace_ExceptionJc(exc, _thCxt);
     log_ExceptionJc(exc, __FILE__, __LINE__);
     //alternate handling on error to continue the operation
     result = 0.0f;
   }
   FINALLY {
     //handling anytime, also if the execption is not catched.
   }
   END_TRY;  //throws an uncatched execption to a higher level.
   //continue outside try
   STACKTRACE_LEAVE;
 }
float anyOperation() {
  STACKTRC_TENTRY("testThrow");
  //...
  CALLINE; throwingOperation();
  STACKTRC_LEAVE; return val;
}
void throwingOperation() {
  STACKTRC_TENTRY("testThrow");
  //any algorithm which
  if(ix >= ARRAYLEN_emC(thiz->array)) { //checks conditions
    THROW_s0(IndexOutOfBoundsException, "msg", ix);
    ix = 0;  //replacement strategy
  }
  STACKTRC_LEAVE
}
  • All or the most operations should use STACKTRCE_ENTRY("name") and STACKTRC_LEAVE. With this the ''Stacktrace'' is stored and available for the error report outside of the step-by-step debugger. Operations should not implement this, it is ok, then the Stacktrace is not stored but the system runs nevertheless.

  • Macros TRY{ …​ }_TRY CATCH(…​){ } END_TRY are used for build the blocks. This macros are defined in different ways for the appropriate situations. See below.

  • The macro THROW either throws the exception to continue execution in the CATCH block of any calling level, or it logs only the situation (because try-catch-throw is not available). The replacement strategy after THROW is not used if the try-catch-throw mechanism is available. Then it throws really. But for a simple execution with a C compiler the replacement strategy is the fall-back.

  • The CATCH block is entered for the specified exception. If the exception is not catched, it is forwarded on execution the macro END_TRY to the superior level.

  • The CALLINE macro stores the number of that line in the stacktrace entry.

There are some situations:

  • Test on PC with using CATCH. It helps for elaborately tests to exclude error situations caused from programming errors.

  • Running on target with using CATCH (C++ compiler available or using longjmp). The CATCH block may log errors, does not print a Stacktrace, but continue the execution.

  • Test on PC without CATCH without Exception handling, as end-test.

  • Running on target without CATCH with the fallback strategy after THROW.

The following ideas are basically:

  • The software should be tested as soon as possible. It isn’t able to exclude all software errors.

  • For the residual probability of software errors the target should be run as soon as possible. It means on unexpected errors proper fall-back have to be existent. A ready-to-use software must not stop working and reporting and error if it is possible that it can run furthermore with less disadvantages.

  • Errors on ready-to-use software should be logged internally to detect and fixed it later, if possible.

  • The TRY-CATCH-THROW approach should not be used for expected errors (for example 'file not found'). Such situations should be catched by proper return values of functions.

5.2. Controlling the behavior which strategy of exception handling is used

It depends on the applstdef_emC.h header file which should used in any source of the application. This file can define one of the three compiler switches.

//Choose one of the three Exception handling variants.
#define DEF_Exception_NO
//#define DEF_Exception_longjmp
//#define DEF_Exception_TRYCpp

Additionally and indepentently it can be choosen whether a stacktrace should be used.

//If set, without complex thread context, without Stacktrace
#define DEF_ThreadContext_SIMPLE

The Stacktrace is able to use for exception messages in conclusion with DEF_Exception_NO too, it is recommended anyway on PC-test in the development phase.

THROW with DEF_Exception_NO forces invocation of log_ExceptionJc(…​) to write a log. A possible implementation of this routine is contained in emc/source/appl_emC/LogException_emC.c which can be implemented in the given form in a simple target.

5.3. Assembly an error message

The minimal requirement to a logged error is:

  • An error number

  • Maybe at least one value from the error situation (for example the value of a faulty index)

  • The source file and the line number of the THROW statement. The last one helps to detect the source context of the error event.

A textual value may be a nice to have and maybe an effort on small footprint processors. Therefore it is possible to write such source code fragments in conditionally compiling parts. On the other hand it is a important hint on debugging on runtime (not step by step).

All variants of exception behavior supports an error message which is located in the stack of the throwing level.

  • If the log_ExceptionJc(…​) is used, the text is copied from the stack location to static locations of the error log area, or maybe copied to a telegram which is sent via communication to another unit with a file system.

  • If TRY-CATCH is used, the error message is copied to the ThreadContext area, which is available for this approach. In the END_TRY block this location is freed. It means, the exception message is correct stored in the CATCH environment. If the log_ExceptionJc(…​) is used in the CATCH-Block, it is copied too, and the ThreadContext heap is able to free.

Example:

if(faulty) {
  char msg[40] = {0};
  snprintf(msg, sizeof(msg), "faulty index:%d for value %f", ix, val);
  THROW_s0(IndexOutOfBoundsException, msg, ix);

The exception message is prepared using sprintf in the stack area. The THROW_s0 assures that the msg is copied in a safely memory.

6. Array index faulties, IndexOutOfBoundsException

The simple usage of arrays in C (and in C++) is very simple and vulnerable:

int myArray[5];
//
*(myArray + ix) = value;

That was the intension of C comming from assembler language thinking. myArray is an address in a register or variable, and the access to elements should be able to write very simple here with a pointer arithmetic. In the time of the 1970th, programming was done on paper with reliability. Of course the ix should in range 0 to 4. The pointer arithmetic in the writing style above was conceptional in that time, the arithmetic was reproducible as machine level instructions.

At the present time the same instructions should be write of course as

myArray[ix] = value;

That is more expressive in source code. The identifier myArray is similar a pointer type int* and an array indentifier. That is a syntactically disadvantage to C and C++. In other languages as Java myArray is never a pointer, it is only an identifier to an array.

For simple C and C++ the index is not tested, it is used as given. If ix is <0 or >4 in this example, faulty memory locations can be disturbed. It can be other data, a stack frame to return from a subroutine, control data of heap locations, or a virtual pointer in class instances. All of that may force difficult findable mistakes. The array indexed write access and some pointer arithmetics are the most sensitive parts of a program which may provoke faulties.

For example in Java pointer arithmetic is not possible and array index accesses are always secured. Whereby the effort to do that is optimized. The JIT (just in time compiler) to translate bytecode to machine code detects environment conditions and will not check all array indices if they are safe, for example comming from constant values. Hence Java is fast and safe.

In comparison to C++ there is an effort in user programming (which is done automatically by the JIT compiler in Java): Indices should be tested before doing array write access. This test can be done via static code analyzes, or in Runtime.

The C++11-standard offers a possibility:

#include <array>
//...
std::array<float,5> myArray;
//...
myArray[ix] = value;

It seems be similar as in C, only the array definition is a little bit modified, using a template mechanism in C++. The access to the array is safe. But a faulty index does not throw an catchable exception. Hence the error is not detected while testing. The other disadvantage is: Some embedded platforms does not support C++11 in the year 2020. A reason for that may be: Most of new features of C++ are for PC application programming, not for embedded. Ask the compiler developer for embedded platforms.

The proper mechanism does not presumed a C++11-Standard. It runs in a Standard C++ of the 1990th too:

template<typename T, int n>
class Array_emC {
 T array[n+1];
 public: T& operator[](uint ix) {
   if(ix < n) return array[ix];
   else {
     THROW_s0n(ArrayIndexOutOfBoundsException, "", ix, n);
     return array[n];
   }
 }
 public: T& uncheckedAccess(uint ix) { return array[ix]; }
};

This is a class which implements a secured array access. it is used as:

Array_emC<float, 5> array;
//...
array[ix] = value;

It is similar as in C++11 but it is able to use on all platforms with C++ compilation. And it throws an Exception if it is activated, respectively it writes to an replacement location without disturbing data, if exception handling is not present.

array.uncheckedAccess(0) = 234;

This is the fast and unchecked variant which should only be used if the index range is known.

This Array_emC class is defined in <emC/Base/Array_emC.h> and can be used for C compilation too, than without check, for well tested simple C deployments, which are tested with C++ compilation on PC platform. It uses macros for compatible usage in C and C++ and offers a class with variable array size, for dynamic data.

For C++ compilation the variant without Exception writes to a safety position. The C variant does not check the index, it is only for well tested software.

See source <emC/Base/Array_emC.h>.

7. Thread context data, stacktrace

The Thread context is a memory area which is thread specific. For a simple controller which executes its core algorithm in a hardware interrupt any hardware interrupt handling and the main loop has a thread context. In a multithreading operation system the thread context is associated to any thread.

To find the correspond jmp_buf for a longjmp it is very simple to use a global data cell. But that is not possible for multithreading or if interrupt routines used the TRY-THROW-CATCH. Hence this structure data are found in the thread context. The same is for the exception object which cannot create in the heap on demand because allocated data may be non-desirable for fast realtime long running embedded software. Exception objects cannot be created in the stack too because on forwarding an exception to a calling level (second THROW inside a CATCH block) the exception object is used in a stack level above.

Last not least the thread context is able to use for a threadlocal heap.

7.1. Getting the pointer to the ThreadContext

If an operation uses

void myOperation(...) {
  STACKTRC_ENTRY("myOperation");
  ....

which is necessary for the usage of the ''Stacktrace'' concept respectively for a Stacktrace entry of this routine, a local variable

struct ThreadContext_emC_t* _thCxt = getCurrent_ThreadContext_emC();

is available initialized with the pointer to the current ThreadContext. The same is done if the operation has an argument

void myOperation(..., ThCxt* _thCxt) {
  STACKTRC_TENTRY("myOperation");
  ....

The ThCxt is a short form of struct ThreadContext_emC_t per #define. This second form needs this special argument to the subroutine, but the ThreadContext is given immediately.

How the STACKTRC_ENTRY macro gets the ThreadContext reference. In emC/Exception_emC.h is defined:

#define STACKTRC_ENTRY(NAME) \
  ThCxt* _thCxt = getCurrent_ThreadContext_emC();  STACKTRC_TENTRY(NAME)

The implementation of getCurrent_ThreadContext_emC() depends on the OSAL implementation for the application and the operation system:

  • For a multithread operation system on large hardware ressources, especially for Windows/Linux the ThreadContext_emC is a part of the OSAL-ThreadContext which is necessary to organize the threads on OSAL level. Therefore the getCurrent_ThreadContext_emC() is implemented in the appropriate os_thread.c.

  • If especially a System with a simple CPU hasn’t a multithread operation system a very simple and fast implementation is possible, see src_emC/emC_srcApplSpec/SimpleNumCNoExc/ThreadContextInterrTpl.c.

    • Any hardware interrupt (which do the work) has a static data area for its 'thread context'.

    • The main loop has its own 'thread context'.

    • There is one global static singleton pointer to the current used ThreadContext_emC*, which can be accessed immediately, one machine operation.

    • Because the interrupts are not preemptive one another, only a higher priority interrupt can interrupt a lower one and the main loop, the following mechanism set the global static singleton ThreadContext_emC* pointer:

    • on start of any interrupt the current pointer value is stored in the interrupt itself stack locally and the ThreadContext_emC address of that interrupt is set instead.

    • on end of the interrupt the stored value of the interrupted level is restored. That is one machine instruction (or two, if the pointer is not stored in a register).

    • It is a cheap and fast mechanism to support the ThreadContext_emC concept.

      /**Structure for ThreadContexts for Main and 2 Interrupts. */
      typedef struct ThCxt_Application_t {
       /**The pointer to the current ThreadContext. */
       ThreadContext_emC_s* currThCxt;
       ThreadContext_emC_s thCxtMain;
       ThreadContext_emC_s thCxtIntr1;
       ThreadContext_emC_s thCxtIntr2;
      }ThCxt_Application_s;
      /** public static definition*/
      ThCxt_Application_s thCxtAppl_g = { &thCxtAppl_g.thCxtMain, { 0 }, { 0 }, { 0 } };
      /**A template how to use. */
      void interrupt_handler(...) {
       ThreadContext_emC_s* thCxtRestore = thCxtAppl_g.currThCxt;
       thCxtAppl_g.currThCxt = &thCxtAppl_g.thCxtIntr1;
       //the statements of the Interrupt
       thCxtAppl_g.currThCxt = thCxtRestore;
       //end of interrupt
      }

Because the interrupt saves the current pointer and restores it, the mechanism is safe also if the other interrupt routine interrupts exact between the 2 statements, get current and set new one. In such a system the exception handling can be established in the interrupt too, it is useful if the algorithm in the interrupt may have throwing necessities.

For such a system the routine

ThreadContext_emC_s* getCurrent_ThreadContext_emC  ()
{
 return thCxtAppl_g.currThCxt;
}

is very simple. The ThreadContext is always the current one stored in the global cell.

7.2. Content of the ThreadContext_emC

@ident=ThCxtData

For the content of the OS_ThreadContext to manage threads see the OSAL-specific implementation of os_thread.c. This chapter only describes the ThreadContext for the user’s level.

The following definition is from emC/Base/ExcThreadCxt_emC.h. The Headerfile contains comments of course, they are shorten here for a short overview:

typedef struct ThreadContext_emC_t {

The first element refers a memory area for the threadlocal heap. See next chapter Threadlocal heap. It is a simple concept only for shortly stored informations.

#ifdef DEF_ThreadContextHeap_emC
UserBufferInThCxt_s threadheap;
#endif

The BlockHeap is another Mechanism for safe non-fragmented dynamic memory, especially for events. See [TODO]. It is possible to associate such an BlockHead thread-specific.

#ifdef USE_BlockHeap_emC
/**It is the heap, where block heap allocations are provided in this thread. */
struct BlockHeap_emC_T* blockHeap;
#endif
//
/**The known highest address in the stack.
* It is the address of the _struct ThreadContext_emC_t* pointer
* of the first routine, which creates the Thread context.
*/
MemUnit* topmemAddrOfStack;
//
/**This is the maximal found value of the stack size which is evaluated on
 * [[getCurrentStackDepth_ThreadContext_emC(...)]] . */
int stacksizeMax;
//
/**Number of and index to the current exception instance*/
int zException, ixException;
//
/**Up to NROF_ExceptionObjects (default 4) for nested Exception. */
ExceptionJc exception[4];
//
/**Reference to the current TryObject in Stack.
 * It is possible to access in deeper stack frames.
 * This reference is removed for the outer stack frames.
 */
TryObjectJc* tryObject;
//

The data for the StacktraceThreadContext are the last one. Because it is an embedded struct and the definition is static, the number of elements for the Stacktrace can be changed for larger applications by offering a larger memory area. To assert and check that, the pointer to the ThreadContext_emC_s is combined with the size in a MemC struct, see [TODO]. It will be faulty to calculate the sizeof(ThreadContext_emC_s) if there are more elements. The Stacktrace is defined as (see [TODO]):

 #ifdef DEF_ThreadContextStracktrc_emC
 /**Data of the Stacktrace if this concept is used. */
 StacktraceThreadContext_emC_s stacktrc;
 //IMPORTANT NOTE: The element stacktrc have to be the last
 //because some additional StackEntryJc may be added on end.
 #endif
 //
} ThreadContext_emC_s;

An element of the Stacktrace is defined as following, see next chapter.

typedef struct StacktraceThreadContext_emC_t
{
 uint32 zEntries;
 int32 maxNrofEntriesStacktraceBuffer;
 StacktraceElementJc entries[100];
} StacktraceThreadContext_emC_s;

7.3. Stacktrace as information on exception

A stacktrace gives additional information. If an exception occurs, the information which routine causes it, and from which it was called is an important information to search the reason. This stacktrace mechanism is well known in Java language:

Error script file not found: test\TestCalculatorExpr.jzTc
 at org.vishia.jztxtcmd.JZtxtcmd.execute(JZtxtcmd.java:543)
 at org.vishia.jztxtcmd.JZtxtcmd.smain(JZtxtcmd.java:340)
 at org.vishia.jztxtcmd.JZtxtcmd.main(JZtxtcmd.java:282)

The Stacktrace information may be the most important hint if an error occurs on usage, not in test with debugger. For C language and the ''emC Exception handling'' this concept is available too:

IndexOutOfBoundsException: faulty index:10 for value 2.000000: 10=0x0000000A
 at testThrow (src\TestNumericSimple.c:121)
 at testTryLevel2 (src\TestNumericSimple.c:107)
 at testTry (src\TestNumericSimple.c:86)
 at main (src\TestNumericSimple.c:38)

In generally the necessary information about the stack trace can be stored in the stack itself. The entries are located in the current stack level, and the entries are linked backward with a reference to the parent stacklevel. But that concept has some disadvantages:

  • It requires an additional argument for each operation (C-function): The pointer to the previous stack entry. It means, all routines from the user’s sources should be subordinated to that concept. They should be changed. That is not the concept of emC style, which is: ''It shouldn’t be necessary to change sources.''

  • If the stack itself is corrupt because any failure in software, the stacktrace cannot be back traced, because the references between the stacktrace entries may be corrupt too. This is hardly in debugging too.

  • The linked queue of stacktrace entries should be correct. If a STACKTRC_LEAVE operation was forgotten to write in the software, an entrie in a no more existing stack area remain in the queue. That is corrupt. The system is too sensitive.

  • The linked queue can only be traced from knowledge off the current stack area. It cannot traced from another thread maybe by a debug access on the stopped execution of the thread. The last one may be necessary for some error situation for debugging.

Therefore the Stacktrace is organized in an extra independent memory area which is static or static after allocation on startup. Its address can be known system wide especially for debugging. This memory is referenced by the ThreadContext memory area which is thread specific and therewith treadsafe.

The ''ThreadContext'' concept is a concept of the emC software style which is necessary to hold information about the ''stack trace'' for ''exception handling''. Additonally, the ThreadContext provide a mechanism to allocate shortly used dynamic memory, see chapter Threadlocal heap.

7.4. What is happen on the first STACKTRACE_ENTRY("main")

@ident=mainOsInit

For a System with a OSAL layer for adaption of a multithread operation system, before start of main() nothing is done. The first invocation of getCurrent_ThreadContext_emC) (see chapter Thread context) detects that all is uninitialized and initializes firstly the multithread environment. This is shon on code snippet from emc/sourceSpecials/osal_Windows32/os_thread.c:

ThreadContext_emC_s* getCurrent_ThreadContext_emC  ()
{
 OS_ThreadContext* os_thCxt = getCurrent_OS_ThreadContext();
 if(os_thCxt == null){ //only on startup in main without multithreading
   init_OSAL();        //call only one time
   os_thCxt = getCurrent_OS_ThreadContext();  //repeat it
   if (os_thCxt == null) {
     os_FatalSysError(-1, "init_OSAL failed, no ThreadConect", 0,0);
     return null;
   }
 }
 return &os_thCxt->userThreadContext;  //it is a embedded struct inside the whole ThreadContext.
}

Of course the getCurrent_OS_ThreadContext() returns null (it invokes TlsGetValue(1) from the Windows-API). bOSALInitialized == false too, hence firstly the OSAL will be initalized. That may be a more complex routine, with some API- and/or Operation System invocations for some Mutex etc.

The advantage to do that on start of main is: A debugging starts at main usually. Another possibility may be: Initializing of the OSAL level with a initializer on a static variable.

7.5. A threadlocal heap for short used dynamic memory especially for String preparation

This is only a indirect topic of Exception handling, but often Strings should be assembled with several informations for logging or for exception messages.

Dynamic memory is a basicly problem for embedded long running systems:

  • If dynamic memory is managed from an ordinary heap concept (like in standard-C/C++, using malloc or new), then for long-running applications there is a fragmentation problem. Therefore often for such applications usage of dynamic memory is prohibited.

  • But dynamic memory is nice to have often for a short time to prepare string messages for example for communication telegrams, for logging, or for events.

Without dynamic memory and without the ThreadContext_emC there are two ways to solve such problems:

  • a) Provide a static memory. It can be a part of the instance data of a module (defined in a struct or C++-class), or pure static. The last one may cause faulties if the module is instanciated more as one time, used in a multithreading system, but has only one static memory for such things:

    //strongly not recommended:
    const char* myLogPreparer(...) { //prepares and returns a log message
      static char buffer[100];  //it is static
      snprintf(buffer, 100, ... //prepare
      return buffer;   //that is ok, because it is static.

*+ It is not recommended because this module may be used more as one time and confuses with the only singleton memory.

//more practice, possible:
typedef struct MyData_t {
  char buffer[100];   //one per instance! That's the advantage.
  ... }
void myLogPreparer(Mydata* thiz,...) {
  snprintf(thiz->buffer, sizeof(thiz->buffer),...
  • b) Provide the memory for preparation in the Stack area:

    void logger(...) {
      char buffer[100];  //in stack!
      myLogPreparer(buffer, sizeof(buffer), ...); //deliver the stack local pointer.
    ....
    void myLogPreparer(char* buffer, int zBuffer, ...) {
      snprintf(buffer, zBuffer, ...);

*+ The danger of that programming is: The called routine could store the pointer persistently, that is a stupid failure.

Another disadvantage for both approaches are: The length of the buffer is dedicated out of the routine, which determines the content. That causes unflexibility.

Using dynamic memory it is more simple:

char const* myLogPreparer(...) { //prepares and returns a log message
  char* buffer = (char*)malloc(mySize);  //it is static
  snprintf(buffer, mySize, ... //prepare
  return buffer;   //that is ok, because it is allocated.

The calling level should know that the returned pointer should be freed!

But - The usage of dynamic memory may be prohibited.

The ThreadContext provides a mechanism for dynamic memory only for shortly usage and small sizes which solves that problem:

char const* myLogPreparer(...) { //prepares and returns a log message
  STACKTRC_ENTRY("myLogPreparer");   //_thCxt is available
  MemC memb = getUserBuffer_ThreadContext_emC(mySize, "identString", _thCxt);
  char* buffer = PTR_MemC(memb, char);
  snprintf(buffer, mySize, ... //prepare
  STACKTRC_RETURN buffer;   //that is ok, because it is non in stack.
}

The calling routine should invoke:

char const* msg = myLogPreparer(...args for logging...)
free_MemC(msg);

The free_MemC(…​) routine checks where the memory is allocated. It frees it correctly for the ThreadContext heap. The freeing should be done immediately in the thread.

If more as one buffer are used from ThreadContext, but all of them are freed in the reverse (or another) order, after freeing the whole ThreadContext heaap is free and therefore not fragmented. The ThreadContext heap is only intended for short-term use.

8. How does it works - Different implementation of the TRY-CATCH-THROW macros

.

The macros for exception handling are defined in emC/Base/Exception_emC.h:

#define TRY \
{if(_thCxt == null) { _thCxt = getCurrent_ThreadContext_emC(); } \
 TryObjectJc tryObject = {0}; \
 TryObjectJc* tryObjectPrev = _thCxt->tryObject; _thCxt->tryObject = &tryObject; \
 int32 excNrCatchTest = 0; \
 CALLINE; \
 Exception_TRY
/**Write on end of a TRY-Block the followed macro: */
#define _TRY \
 Exception_CATCH { \
   _thCxt->tryObject = tryObjectPrev; \
   if(_thCxt->exception[_thCxt->ixException].exceptionNr == 0) {/*system Exception:*/ \
     _thCxt->exception[_thCxt->ixException].exceptionNr = ident_SystemExceptionJc;  \
     _thCxt->exception[_thCxt->ixException].exceptionMsg = z_StringJc("System exception"); \
   }  \
   excNrCatchTest = _thCxt->exception[_thCxt->ixException].exceptionNr; \
   if(false) { /*opens an empty block, closed on first CATCH starts with }*/
//end of CATCH before: remove _ixStacktrace_ entries of the deeper levels.
//Note: Till end of catch the stacktrace of the throw level is visible.
#define CATCH(EXCEPTION, EXC_OBJ) \
     RESTORE_STACKTRACE_DEEPNESS  \
   } else if((excNrCatchTest & mask_##EXCEPTION##Jc)!= 0) \
   { ExceptionJc* EXC_OBJ = &_thCxt->exception[_thCxt->ixException]; \
     excNrCatchTest = 0; //do not check it a second time
#define FINALLY \
     RESTORE_STACKTRACE_DEEPNESS \
 } } /*close CATCH brace */\
 _thCxt->tryObject = tryObjectPrev; \
 { { /*open to braces because END_TRY has 2 closing braces.*/
//Write on end of the whole TRY-CATCH-Block the followed macro:*/
#define END_TRY \
 } } /*close FINALLY, CATCH or TRY brace */\
 _thCxt->tryObject = tryObjectPrev; \
 if( excNrCatchTest != 0 ) /*Exception not handled*/ \
 { /* delegate exception to previous level. */ \
   throwCore_emC(_thCxt); \
 } else { /*remain exception for prev level on throwCore_emC if DEF_Exception_NO */\
   clearException(&_thCxt->exception[_thCxt->ixException]); \
 } /*remove the validy of _ixStacktrace_ entries of the deeper levels. */ \
 RESTORE_STACKTRACE_DEEPNESS \
} /*close brace from beginning TRY*/

The macros are equal for all implementation (since 2020-05). Hence the complex organization should not be written manually.

The specifics are macros in the macro, and they are special for the different implementation.

#if defined(DEF_Exception_NO)
 #define Exception_TRY
 #define Exception_CATCH if(_thCxt->exception[_thCxt->ixException].exceptionNr !=0)
#elif defined(DEF_Exception_longjmp)
 #define Exception_TRY \
 if( setjmp(tryObject.longjmpBuffer) ==0) {
 #define Exception_CATCH \
 } else  /*longjmp cames to here on THROW */
#else
 #define Exception_TRY try
 #define Exception_CATCH catch(...)
#endif

Look firstly to C++: The try and the common catch(…​) without a specified catch object is used. That is because the sophisticated C++ catch mechanism cannot made compatible with the other approaches of TRY-CATCH. The distinction between the exception type is made inside the exception Object. There the THROW stores the exception type info.

For the longjmp solution a { and } is necessary: The setjmp returns 0 if it is called straight forward, and it returns !=0 if a longjmp lands on the setjmp statement. Then the Exception_CATCH with the else is executed, which is part of the _TRY macro on end of the TRY block. The { after else is part of the _TRY similar as the opened { after catch(…​)

For the DEF_Excpetion_NO the Exception_TRY is empty, does nothing, but the Exception_CATCH tests whether an Exception is stored. Then the block beginning with { in _TRY is entered and the exception is tested as in all other cases.

8.1. Exception types, distinguish the exception reason

The CATCH is defined for C++ as well as for C’s longjmp as:

#define CATCH(EXCEPTION, EXC_OBJ) \
     RESTORE_STACKTRACE_DEEPNESS  \
   } else if((excNrCatchTest & mask_##EXCEPTION##Jc)!= 0) \
   { ExceptionJc* EXC_OBJ = &_thCxt->exception[_thCxt->ixException]; \
     excNrCatchTest = 0;  //do not check it a second time

The first statement of the macro acts as the last statement of the CATCH block above or for the first CATCH as the content of the if(false){ ` from the `_TRY. The substantial function of the CATCH is a if-chain to check exception bits and definition of a local EXC_OBJ.

#define THROW(EXCEPTION, TEXT, VAL)  throw_sJc(ident_##EXCEPTION##Jc, TEXT, VAL, __LINE__, _thCxt)

The THROW calls an operation with the current source LINE and a constant mask value which determines the exception.

The distinction of the exception reason follows the schema of Java. Java has a more simple exception concept than C++. The exception object is always derived from java.lang.Throwable respectively from the base java.lang.Exception. Some typical exception classes are defined in the core libraries, for example java.lang.IllegalArgumentException or the common java.lang.RuntimeException. The derived exception objects can hold data, but usual only a message as String, the java.lang.ArrayIndexOutOfBoundsException holds a int value, to store the faulty index.

For C usage the concept is simplified again. The ExceptionJc object stores a StringJc, the exception message, a int value and a 1-from-32-bit-value for the exception number. That’s all. It is enough to distinguish the exception type (1 of 32 bit) and hold the information to the exception. The mask characteristic of the exception ident value allows association to types of Exception. For example all Exception identificators with one of the bis masked with 0x0fff (12 exception types) is a RuntimeException. That is a simple replacement of the java approach: test instanceof RuntimeException It is a simple but sufficient system.

8.2. FINALLY and not catched exceptions

After the last CATCH block:

CATCH(SpecialException, exc) {
  ....
}
FINALLY {
  //executes it also if CATCH is not executed
}END_TRY

the content of FINALLY is executed any time. It may be important to free resources on an unexpected error. It is the same behavior like finally in Java.

If the thrown Exception is not catched, no CATCH block is executed, then on END_TRY the a throw is executed with the given exception which can be caught on a higher level.

If no CATCH is found, or a THROW is invoked without a TRY block, the routine

uncatched_ExceptionJc(&exception, stacktrcThCxt);

is invoked. This should terminate the execution of the thread or the application because nothing is catched.

9. Which moduls are necessary for exception handling and for the simple variant

The emcTest.zip package contains a TestException directory where 2 Visual Studio 15 projects are contained (with its own solution):

  • TestExcHandling

  • TestNoExc

Both projects works with the same source file emcTest/TestException/src/TestException.c. But there have different include paths and compiler options. The TestNoExc needs only 2 additional files:

'''TestNoExc''': * emc/source/emC/StringBase_emC.c necessary to copy the exception message * emc/source/appl_emC/LogException_emC.c: For logging the exception.

If the string message is not used for logging, the effort can be reduced again for a target with less ressources.

The Exception handling needs some more basic effort:

'''TestExcHandling''':

  • emc/source/appl_emC/ApplSimpleStop_emC.c: This source contains an implementation of uncatched_ExceptionJc(…​) which stops the execution of the application.

  • emc/source/appl_emC/LogException_emC.c: The routine for logging, which is used here too.

  • emc/source/emC/Exception_emC.c: The routines for the exception handling

  • emc/source/emC/ExceptionPrintStacktrace_emC.c: Special routine for stacktrace, uses os_file.c because it is possible to print a stacktrace in a file too.

  • emc/source/emC/MemC_emC.c: Usage of struct MemC which combines the pointer and the size to one struct.

  • emc/source/emC/Memfree_ThreadAndBlockHeap_emC.c: The free routine for the Threadcontext heap for the error message. Note: The BlockHeap see [[!BlockHeap_emC.html]] is denied because non-definition in the applstdef_emC.h.

  • emc/source/emC/StringBase_emC.c: necessary to copy the exception message

  • emc/source/emC/ThreadContext_emC.c: The ThreadContext for the Stacktrace and for storing messages.

  • emc/sourceSpecials/osal_Windows_Msc15/os_file: To support writing stacktrace in a file.

  • emc/sourceSpecials/osal_Windows32/os_mem.c: memory allocation with windows-API.

  • emc/sourceSpecials/osal_Windows32/os_mutex: Necessary for os_thread.c.

  • emc/sourceSpecials/osal_Windows32/os_thread.c: Organizes the os_ThreadContext.

  • emc/sourceSpecials/osal_Windows_Msc15/os_time.c: Necessary for os_thread.c.

The os_thread.c is necessary though mulittrheading isn’t use here. But the os_ThreadContext is necessary. In another OSAL-constellation especially with only hardware interrupts it is less effort.

The os_file.c depends on the possibility to write the Stacktrace in a file in the ExceptionPrintStacktrace_emC.c. For a system without file system this possibility may be deactivated.