The environment for standard C/C++ applications is supported by the libraries in another way: the Microsoft Visual Studio compilers supports some of the language features by calling out to libraries:
64-bit Math (32-bit C/C++) Floating-point Debug Support Security Support Optimization new/delete __purecall The 32-bit versions of the Microsoft Visual C++ compiler generate calls to library routines to handle 64-bit math. These functions use non-standard calling conventions.
_alldiv Divide a 64-bit signed value by a 64-bit integer value and returns a 64-bit integer quotient. _alldvrm Divide a 64-bit signed value by a 64-bit signed value and returns a 64-bit signed quotient and remainder. _allmul Multiplies a 64-bit signed or unsigned value by a 64-bit signed or unsigned value and returns a 64-bit result. _allrem Divides a 64-bit signed value by another 64-bit signed value and return the 64-bit signed remainder. _allshl Shifts a 64-bit signed value left by a certain number of bits. _allshr Shifts a 64-bit signed value right by a certain number of bits. _aulldiv Divides a 64-bit unsigned value with a 64-bit unsigned value and returns a 64-bit unsigned result. _aulldvrm Divides a 64-bit signed value with a 64-bit signed value and returns a 64-bit signed quotient and remainder. _aullrem Divides a 64-bit unsigned value by another 64-bit unsigned value and returns the 64-bit unsigned remainder. _aullshr Shifts a 64-bit unsigned value right by a certain number of bits.
64-bit Math
Floating-Point Support
The following library functions are used when using floating-point inside your application.
|
__ftol2 |
Converts a scalar double-precision floating point value to a 32-bit integer. Only present in 32-bit versions of the compiler. |
|
__ftol2_sse |
Converts a scalar double-precision floating point value to a 32-bit integer. Only present in 32-bit versions of the compiler. |
|
__ftol2_pentium4 |
Converts a scalar double-precision floating point value to a 32-bit integer using either a CPU instruction or manually. |
|
__ftol2_sse_excpt |
Convert Scalar Double-precision floating-point value to 32-bit integer. If the CPU doesn’t supports this instruction then do it by itself, or enable this instruction in CPU and do it by CPU instruction. |
Debug Support
Using the /RTC command-line option, the Microsoft Visual Studio compilers insert additional checking for errors which can only be detected at run-time. These include:
1. Detecting Unbalanced Stack
2. Detecting Local Variable Corruption
3. Detecting Usage Of Uninitialized Variables
4. Detecting Loss Of Data During Type Conversion
Detecting An Unbalanced Stack
In debug versions of executables, the Microsoft Visual Studio compilers insert a call to RTC_CheckEsp() after each function call. Prior to the function call, the contents of the stack pointer register (ESP/RSP) are saved into a general-purpose register. This function compares the contents of that general-purpose register with the stack pointer to verify that the stack pointer was restored correctly. This error usually occurs when a function is declared using one calling-convention and actually implemented using another. If there is a mismatch, the library generates an error message and a breakpoint.
Detecting Local Variable Corruption
In debug versions of executables, the Microsoft Visual Studio compilers allocates additional stack space before and after each of the function’s local variables and fills this extra stack space with the value 0xCC. Just before returning from the function, the compiler inserts a call to RTC_CheckStackVars(). This function checks to see if the 0xCC values are still intact, and, if not, displays an error message and issues a break point.
Detecting Use Of Uninitialized Variables
In debug versions of executables, the Microsoft Visual Studio compilers allocate additional storage to track whether a variable has been initialized. If a variable is used without first being initialized, the compiler inserts a call to RTC_UninitUse() to display an error message and issue a breakpoint.
Detecting Data Loss During Type Conversion
Finally, the Microsoft Visual C++ compilers can detect when a conversion from a larger integer to a smaller integer would cause a loss of significant data. This happens when a programmer uses a type-cast to coerce one type to another. For example:
unsigned long x = 0xFFFFFFFF;
unsigned char y = (unsigned char) x;
While perfectly legal in C, this is not good programming practice. In debug versions, the compiler inserts a call to RTC_Check_2_to_1(), RTC_Check_4_to_2(), RTC_Check_4_to_1(), RTC_Check_8_to_4(), RTC_Check_8_to_2() or RTC_Check_8_to_1() and will generate an error and break point if such a loss of significant data would occur.
By the way, the correct way to handle this if you really wanted to get the lower 8-bits of ‘x’, would be:
unsigned char y = (unsigned char) (x & 0xff);
The Microsoft Visual Studio compilers can generate extra code which guards against certain types of buffer overflow attacks. This support is enabled when the /GS command-line option is used. The following diagram shows a typical stack frame for both 32-bit and 64-bit C/C++ code: The calling function pushes the “Function Parameters” on to the stack. The exact order and whether some are passed in registers and others on the stack depends on the calling convention. The “Return Address” is placed on the stack by the CALL x86 instruction. The “Saved Frame Pointer” is the saved contents of the EBP/RBP CPU register. It is not always present. The “Local Variables and Buffers” are the function’s local variables. Finally, the function may save CPU registers which need to be preserved across the function call. A buffer overrun attack relies on the fact that many functions do not completely validate the data they process, especially the length. These attacks create malformed data. When the function processes this data and copies it into “Local Variables and Buffers” , it is too large and overwrites not only the local variable, but also the “Return Address”. When the function returns, instead of returning to the caller, it returns to the address which was part of the malformed data. With the /GS option, the compiler inserts a “Cookie” between the local variables and the return address on the stack and initializes it with a guaranteed unique value. The compiler also inserts a check prior to returning from the function to insure it has not been corrupted. If it has, it calls the library function __security_check_cookie(). This function then can generate a breakpoint or error message. An excellent article describing this behavior can be found on Microsoft’s web site here. In C++, the new and delete operators call out to four separate library functions: § new – Allocate memory for a new object. Ex: UINT32 *x1 = new UINT32; § new[] – Allocate memory for an array of objects. Ex: UINT32 *x2 = new UINT32[15]; § delete – Free memory for an object. Ex: delete x1; § delete[] – Free memory for an array of objects. Ex: delete x2; Typically, these call the same memory allocation functions as malloc() and free(). In C++, when a member function is declared as a “pure” virtual functions, it is never intended to be called. For example:
class class2; class class1 { public: class1(class2 *derived): m_Derived(derived) {}; ~class1(); virtual void myfunction(void) = 0; class2 *m_Derived; }; class class2:public class1 { public: class2() : class1(this) {}; virtual void myfunction(void) {}; }; class1::~class1() { m_Derived->myfunction(); } int main() { class2 c; }Security Support
new/delete (C++)
__purecall (C++)
In this example, when the destructor for “c” is called, it calls ends up calling myfunction() from the class1, which is declared as a pure virtual function. The compiler has a dummy function for handling erroneous calls like this, which can generate a breakpoint to alert the developer of the problem.
Next Week
Next week, we'll put it all together to create a UEFI application which links with and uses the C/C++ library.



Comments