1. Principle of virtual call
It is an interface, only define operations, without implementation.
class IfcCvirt { public: virtual void doFirst ( float val )=0; public: virtual void doOther ( float val )=0; public: virtual void doAny ( int val1, int val2 )=0; };
It is one from more implementation classes:
class ImplAvirt : public IfcCvirt { private: int i1, i2; float f1; //any data
public: virtual void doFirst ( float val ); public: void doOther ( float val ); public: void doAny ( int val1, int val2 ); public: virtual char const* doAnywhat(); };
The doFirst
can be overridden again, a new doAnywhat has an implementation,
but can be overridden in an derived class.
A further inheritance overrides both operations and stops ability to override doFirst
:
class ImplBvirt : public ImpBvirt { public: void doFirst ( float val ); public: virtual char const* doAnywhat(); };
The implementations of the operations are done in the cpp file.
The invocation of a virtual call is written in C:
void testVtbl_virtual ( IfcCvirt* ifc ) { ifc->doFirst(2.25f); }
The machine code for this routine for a Texas Instruments processor TMS320C28000 compiled with the "TI composer studio" looks like:
0000002b _testVtbl_virtual__FP8IfcCvirt: 0000002b 0204 MOVB ACC,#4 0000002c 07C4 ADDL ACC,*+XAR4[0] 0000002d 83A9 MOVL XAR5,ACC 0000002e E802 MOVIZ R0H,#16400 0000002f 0080 00000030 3B01 SETC SXM 00000031 A8A9 MOVL ACC,XAR4 00000032 C5D5 MOVL XAR7,*+XAR5[2] 00000033 81C5 ADD ACC,*+XAR5[0] 00000034 8AA9 MOVL XAR4,ACC 00000035 3E67 LCR *XAR7 ; call occurs [XAR7] ; [] |20|
The last machine instruction is LCR
, a Long Call which uses register XAR7
containing the calling address. The variable ifc
comes into with the register XAR4
.
It refers anywhere in the RAM area. In line 002c
the RAM is accessed, XAR5
contains
the reference to the vtbl after that. In line 0032
the start address of the
called routine is read from the vtbl.
2. Possible errors
The vtbl is located in the ROM area or inside
any protected area in Memory, because it is program space. Hence the start addresss
in the vtbl is safe. But the reference to the vtbl in XAR5
, read from a RAM location,
can be faulty. If there are no software errors, it is safe. But any not found error
in any software module can overwrite any location in the RAM. Then the reference to the
vtbl is faulty, any other location is addressed, and any desired start address is read
and called. The probability of such an error is less. But the impact may be hard.
If such an error occurs on a PC application, usual an unexpected memory access exception may occur, and an exception handling rescues the situation. The functionality may be disturbed, the PC application may be restart.
But such an error on an embedded platform maybe in safety critical environments may be non acceptable.
The problem is: The pointer to the vtbl is placed inside data, and the access is not checked.
3. Solution for gcc: -vtable-verify
In 2012 in a conference https://gcc.gnu.org/wiki/cauldron2012 the problem of unsafe vtable was discussed. A paper is:
In conclusion the option
--enable-vtable-verify
was proposed for gcc.
Another document https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html names:
-fvtable-verify
as option. It depends on the compiler implementation of some embedded platforms whether this options are available and whether they works.
For the Texas Instruments a request was triggered:
with the entry EXT_EP-10096
4. Solution for clang compiler: -fsanitize=cfi-vcall
For ARM compiler using LLVM the options
-flto -fvisibility=hidden -fsanitize=cfi-vcall
are proposed, see https://clang.llvm.org/docs/ControlFlowIntegrity.html.
Nevertheless they don’t work on my compiler ArmClang.exe V6.14
. It comes back with an error message:
error: use of LTO is disallowed in this variant of ARM Compiler
It may be a version or license problem.
Obviously supporting safety for the vtable access (executing virtual operations) is not in focus of most C++ developer, but it is in focus by the compiler builder organizations.
5. Solution: An explicit virtual table with check
A solution of the non-safety problem is: The same mechanism which is used internally in C++ is implemented as explicit mechanism. There are two advantages:
-
The sensitive reference to the vtbl can be checked.
-
If more as one operations are called with the same vtbl, the refence should be gotten from data only one time, it is faster.
But of course, it is important that this reference should not be disturbed in the same way. If the reference is hold in stack variables while an operation runs, the stack area may be seen as more safe than any data area. Because: The stack contains return addresses which are sensitive in the same kind. If the stack area is unsafe against software errors, the whole application may be unsafe. But the data area is large, can be accessed by other threads or processes, because in Embedded Target systems a memory protection is often not present.
The disadvantage of the solution is:
-
Additional program effort.
An effort can be a source of errors, it may be erroneous programmed. Hence an automatic code generation for that parts can be aimed.
5.1. Definition of an explicit vtbl for an interface
The basics for the vtbl are function pointer. There are available in C syntax. C++ function pointer can present only uniform operations in the same class, it cannot be used here.
/**Firstly we need C-FunctionType-Prototypes. * Note: the typedef is syntactically more clear than (*Oper_doit...) */ typedef void Oper_doitFloat_Base_ifc(class IfcExpl* thiz, float val); typedef void Oper_doitInt2_Base_ifc(class IfcExpl* thiz, int val1, int val2);
The next one is a significance string to check:
#define sign_Vtbl_IfcExpl "sign_Vtbl_IfcExpl_MyAppl"
Now a vtbl struct for this interface can be defined:
/**This is the explicitely virtual table of the interface. * Its content is adequate the vtbl internally in C++ * if virtual operations are defined.*/ typedef struct Vtbl_IfcExpl_T { char const* sign; Oper_doitFloat_IfcExpl* doFirst; Oper_doitFloat_IfcExpl* doOther; Oper_doitInt2_IfcExpl* doAny; } Vtbl_IfcExpl_s;
It is similar as the automatic created vtbl from the C++ compiler. Additionally
it has the sign
member.
5.2. An interface class with this vtbl struct type
The interface C++ class does not define any operations, because it does not implement one, it contains only the explicit const reference to the vtbl and a constuctor:
/**This class is used as interface reference, without implememtation. * It has only a protected ctor, cannot instantiate. */ class IfcExpl { /**The IfcExpl has not virtual operations, * but the explicit reference to the vtbl. */ public: Vtbl_IfcExpl_s const* const vtbl_IfcExpl; /**An explicit ctor is necessary: */ protected: IfcExpl ( struct Vtbl_IfcExpl_T const* const); };
The interface class is not able to instantiate, it should not be instantiate.
The ctor is written as:
IfcExpl::IfcExpl ( Vtbl_IfcExpl_s const* const vtbl) : vtbl_IfcExpl(vtbl) {}
5.3. An implementing class for the interface
The following class should implement all operations of the interface. It is defined as:
class ImplA : public IfcExpl { protected: Vtbl_ImplA_s const* const vtbl_ImplA; protected: int i1, i2; float f; public: ImplA ( ); protected: ImplA ( Vtbl_ImplA_s const* vtblA); /**Defines the implementation of the interface-operations. * The identifier do not need identically, but it is strongly recommended. */ public: void doFirst ( float val); public: void doOther ( float val); public: void doAny ( int val1, int val2); public: float getVal ( ){ return this->f; } };
In this example the implementing class supports further override of its operations.
Hence it has a own vtbl too. This is a complex example. For a simple interface
implementation this vtbl_ImplA
is not necessary.
The implementing operations are defined in the class as normal operations, without virtual``
.
5.4. How to define the vtbl instance for ImplA
The vtbl should be defined for the implementation in ImplA
. Because the referenced
operations are C-functions, they should be defined in the cpp file:
/**********************************************************************/ /*Implementation for the explicit vtbl wraps the C++ operations in C */
static void doFirst_ImplA ( IfcExpl* thizi, float val) { ImplA* thiz = static_cast<ImplA*>(thizi); thiz->doFirst(val); //calls routine from ImplementorA }
static void doOther_ImplA ( IfcExpl* thizi, float val) { (static_cast<ImplA*>(thizi))->doOther(val); //calls routine from ImplementorA }
The principle is shown only for two operations. The second one is more simple,
the first one defines explicitely the casted reference from the base ifcExpl
reference type to the implementation type.
This casting is done on C++ implicit virtual call implicit in an adequate way.
The static_cast<..>(..)
tunes the address value of the reference. It is done in the
TI listing in chapter above with the ADD ACC,*+XAR5[0]
- statement on 0033
.
The offset for the thiz
address is stored in [0]
of the vtbl.
With the C-wrapper the vtbl is defined:
//The definition of the vtbl for ImplA, written to const memory area (!) static Vtbl_ImplA_s const vtbl_ImplA_Def = { sign_Vtbl_ImplA , doFirst_ImplA , doAnywhat , { sign_Vtbl_IfcExpl , doFirst_ImplA , doOther_ImplA , doAny_ImplA } };
Because of the class ImplA
has its own vtbl definition, the vtbl for the ifcExpl
is part of them, it is a nested struct.
The vtbl definition is located in the const memory area which can be mapped to the ROM
space, because it is a pure const
definition. It is adequate as the implicit vtbl
generated from the C++ compiler. The vtbl contains the start addresses of the
C wrapper functions, and the pointer to the sign string.
5.5. ctor, const vtbl initialization in the class instances
The public ctor of ImplA looks like:
ImplA::ImplA ( ) : IfcExpl(&vtbl_ImplA_Def.IfcExpl) , vtbl_ImplA(&vtbl_ImplA_Def) { }
It initializes the IfcExpl base class (ctor in chapter above) with the proper part
of the vtbl_ImplA_Def
( it is a const reference). The own const reference to the vtbl
is initialized in the intializer list of the ctor adequate. It produces a similar
memory layout as in C++ implicit generated code for virtual operations.
5.6. Using the explicit vtbl
This is the important part. All stuff above do the adequate things like a C++ compiler for virtual operations.
void test_TestVtblExplicit ( ) { TEST_START("TestVtblExplicit"); ImplA* implA = new ImplA(); IfcExpl* ifcA = implA; //automatic static cast
//vtbl as stack-local variable, //secured because stack area should be secure anycase Vtbl_IfcExpl_s const* const vtblA = ifcA->vtbl_IfcExpl; if(ASSERT_emC(strcmp(vtblA->sign, sign_Vtbl_IfcExpl)==0, "check implA", 0,0)) { vtblA->doFirst(ifcA, 2.25f); //... some more usage of vtblA in this thread }
This is a part of the test routine in sources
Test_emC/src/test/cpp/emC_Test_C_Cpp/TestVtblExplicit.cpp
This code should be part of the user. The ifcA
is a given interface-type-reference
to any implementing instance. The implementing instance is unknown from this focus.
Accessing ifcA→vtbl_IfcExmpl
gets the vtbl-refernce in an explicit pointer variable.
This variable should never be stored in any common data location. It is a register variable
or it is in stack area.
The strcmp
operation evaluates the string comparison of the sign
inside the vtbl with the given string literal. If the comparison is true, the vtbl
references exactly this string. The same string in any other meaning should not be
present in memory. All references to this string identifies the vtbl as correct.
Note that instead a string compare only the comparison of the reference to the string
may be satisfy, if the string literal is stored only one in memory. This strcmp
is
necessary if different independent compilation units are used (for dll, for example
in S-functions in Simulink (Mathworks ®), that code should be able to use in such
environments too.
The ASSERT_emC
causes a throw if Exception handling is present. Without exception handling
only the defective usage of the vtblA is prevented, but the occurrence of that error
can be written in a log area inside the Embedded Device. Hence a functionality
does not work because of a malign and unexpected data error, but the processing
of machine instructions are not disturbed. It is not a crash.
The follwing line does the same as a virtual call in C++, without check, with possible crash:
//This is just as well unsafe as C++ virtual call: ifcB->vtbl_IfcExpl->doFirst(ifcB, 2.25f);
The example, inclusively the more complex further derivation and implementation of explicit virtual operations, can be download and tested via
You can clone or copy Test_emC, inside the build the src_emC is cloned. This is a more complex collection of sources able to use for Embedded Control and especially test of sources for embedded PC-based. The vtbl topic is only a minor fact for that. The sources and tests of emC are improved in this time (april, may 2020).
See also vishia.org/emc.
6. Solution: Vtbl referenced from reflection
This solution was created with the decision using C language for Java to C translation (and not C++ language). This decision was done in 2007 because the vulnerability of virtual tables in C++ was obvious, C++ seemed over engineered and not a really proper implementation language. C is closer to machine code than non obviously C++ used as only implementation language (not used as primary high level language). But the overridden operations in Java should be mapped to the C destination language. This solution has proven also for manual C programming (maybe mixed with C++, maybe compiled with C++).
The ClassJc
is referenced from any ObjectJc
. It is located in a const data segment, it is ROM in cheap processors, and it is non changeable. If there are errors in the ClassJc reference, it is obviously for the application. Additionally the consistence of the ClassJc
data can be checked by its own reflection of ClassJc const refl_ClassJc
.
Based on this ClassJc
a pointer to a
#ifdef DEF_ClassJc_Vtbl /**Pointer to jump table for dynamic calls (virtual methods). * This is a typed struct, starting with Vtbl_ObjectJc. */ struct VtblHeadJc_T const* mtbl; #endif
is the last element of the definition of ClassJc
in emC/Base/ClassJc_FullReflection_emC.h
.
The struct VtblHeadJc_T
is defined in emC/Base/ObjectVtbl_emC.h
as
typedef struct VtblHeadJc_T { char const* sign; struct Size_Vtbl_t* sizeTable; }VtblHeadJc;
It is only a head. But it has a reference to a sign
for significance check, and a number of entries. The number of entries is stored as pointer type, because any element in this table should be a pointer type, to have identically sizes of the elements.
The Vtbl type for ObjectJc is defined as:
typedef struct Vtbl_ObjectJc_t { VtblHeadJc head; /**Methods of ObjectJc. */ MT_clone_ObjectJc* clone; MT_equals_ObjectJc* equals; MT_finalize_ObjectJc* finalize; MT_hashCode_ObjectJc* hashCode; MT_toString_ObjectJc* toString; } Vtbl_ObjectJc;
It has the size of 7 pointers and refers 5 operations with the well defined function pointer types. The instantiation of this table for a simple ObjectJc bases class with non overridden ObjectJc operations is based on the type:
typedef struct VtblDef_ObjectJc_t { Vtbl_ObjectJc tbl; VtblHeadJc end; } VtblDef_ObjectJc;
It has only an additinal end designation. The instance is defined as:
const VtblDef_ObjectJc vtbl_ObjectJc = { { { sign_Vtbl_ObjectJc //J2C: Head of methodtable of ObjectJc , (struct Size_Vtbl_t*)((5 + 2) * sizeof(void*)) } //J2C: Dynamic methods of the class :ObjectJc: , clone_ObjectJc_F //clone , equals_ObjectJc_F //equals , finalize_ObjectJc_F //finalize , hashCode_ObjectJc_F //hashCode , toString_ObjectJc_F //toString } , { signEnd_Vtbl_ObjectJc, null } };
The 5 operations are existing as C functions for the first implementation (_F
) for a Simple ObjectJc
with the correct argument pattern (signature).
If a C struct offers overridden methods, with some more virtual (C-) operations it is defined for example in emC/Base/LogMessage_emC
as:
typedef struct Vtbl_LogMessageFW_t { VtblHeadJc head; MT_sendMsgVaList_LogMessageFW* sendMsgVaList; MT_flush_LogMessageFW* flush; MT_close_LogMessageFW* close; MT_isOnline_LogMessageFW* isOnline; MT_sendMsg_LogMessageFW* sendMsg; MT_sendMsg_time_LogMessageFW* sendMsgTime; Vtbl_ObjectJc ObjectJc; }Vtbl_LogMessageFW;
This contains the Vtbl_ObjectJc
on end.
…TODO