1.3.12. Instrumentation Options

1.3.12.1. Stack Smashing Detection Options

The compiler provides stack protection functionality in the form of the following options:

-fstack-protector

Instruct the compiler to emit extra code to check for buffer overflows, such as stack-smashing attacks. This is done by adding a guard variable to functions with vulnerable objects. This includes functions that call alloca, and functions with buffers larger than or equal to 8 bytes. The guards are initialized when a function is entered and then checked when the function exits. If a guard check fails, an error handling function is called. The error handling function can be made to indicate the error in some way and exit the program. Only variables that are actually allocated on the stack are considered, optimized away variables or variables allocated in registers are not considered.

-fstack-protector-strong

Instruct the compiler to behave as if -fstack-protector were specified, except that a stronger heuristic is used to determine the functions for which the compiler will emit stack buffer overflow checking code.

-fstack-protector-all

Instruct the compiler to behave as if -fstack-protector were specified, except that the compiler will emit stack buffer overflow checking code for all functions instead of limiting protection as -fstack-protector does.

1.3.12.1.1. Enabling Stack Smashing Detection

To enable stack smashing detection in your application, you need to provide definitions of:

__stack_chk_fail() - This function is called from an instrumented function when a check against the stack guard value, __stack_chk_guard, fails. A simple definition of this function might look like this:

void __stack_chk_fail(void) {
  printf("__stack_chk_guard has been corrupted\n");
  exit(0);
}

__stack_chk_guard - This is a globally visible symbol whose value can be copied into a location at the boundary of a function’s allocated stack on entry into the function, and loaded just prior to function exit to perform a check that the local copy of the __stack_chk_guard value has not been overwritten. A simple definition of this symbol might look like this:

unsigned long __stack_chk_guard = 0xbadeebad;

You can then compile a file containing both of these definitions to produce an object file that can be linked into an application that is instrumented for stack smashing detection.

1.3.12.1.2. Stack Smashing Detection Example

Here is a simple example to summarize and demonstrate how the stack smashing detection capability can be used:

  • The first source file presents the definitions of __stack_chk_fail() and __stack_chk_guard (stack_check.c):

    #include <stdlib.h>
    #include <stdio.h>
    
    void  __stack_chk_fail(void);
    unsigned long __stack_chk_guard = 0xbadeebad;
    
    void __stack_chk_fail(void) {
      printf("ERROR: __stack_chk_guard has been corrupted\n");
      eixit(0);
    }
    
  • The second source file presents a use case where a function, foo, writes past the end of a local buffer (stack_smash.c):

    #include <string.h>
    
    void foo(void);
    
    int main() {
      foo();
      return 0;
    }
    
    void foo(void) {
      char buffer[3];
      strcpy(buffer, "Oi! I am smashing your stack");
    }
    

The stack_check.c source can then be compiled to generate stack_check.o:

%> tiarmclang -mcpu=cortex-m4 -c stack_check.c

and the stack_smash.c source file is compiled and linked with stack smashing detection enabled via the use of the -fstack-protector-all option:

%> tiarmclang -mcpu=cortex-m4 -fstack-protector-all stack_smash.c stack_check.o -o stack_smash.out -Wl,-llnk.cmd

When loaded and run, the error message is emitted and the program exist when the stack check fails before returning from foo:

%> load470 -q stack_smash.out
ERROR: __stack_chk_guard has been corrupted

1.3.12.2. Function Entry/Exit Hook Options

The compiler provides the capability to instrument functions with entry and exit hook function calls using the -finstrument-functions option:

-finstrument-functions

For each function being compiled, instruct the compiler to generate a call to the entry hook function, __cyg_profile_func_enter, just after entry to a given function, and a call to exit hook function, __cyg_profile_func_exit, just prior to exit from a given function.

The compiler will also call __cyg_profile_func_enter and __cyg_profile_func_exit on behalf of a function that is inlined into another function. This means that an addressable version of an inlined function must be available in the linked application to facilitate lookup of the inlined function symbol. If all uses of a function are inlined, the definition of the inlined function may incur some growth in code size for the linked application.

1.3.12.2.1. Enabling Use of Function Entry/Exit Hooks

To enable the use of function entry/exit hooks in your application, you need to provide definitions of:

  • __cyg_profile_func_entry

    The signature of the __cyg_profile_func_enter function is as follows:

void __cyg_profile_func_entry(void *this_fcn, void *call_site);
An example definition of this function might look like this:
#include "func_timer.h"

extern "C" {

// Entry Hook Function
__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void *this_fcn, void *call_site) {
  // Non-NULL function address is required
  if (!this_fcn) return;

  // Find function address in function timer map;
  // If this is the first call to the specified function, 
  // then create a timer record for it and insert record into map
  auto func_iter = func_timer_map.find((unsigned long)this_fcn);
  func_timer_record *func_timer;
  if (func_iter == func_timer_map.end()) {
    func_timer = new func_timer_record((unsigned long)this_fcn);
    func_timer_map[(unsigned long)this_fcn] = func_timer;
  }
  else {
    func_timer = func_iter->second;
  }

  // If function is not already on the call stack, start the clock
  if (func_timer->recur_level == 0) {
    func_timer->clock_start = clock();
  }
  else {
    func_timer->recur_level++;
  }
}

} /* extern "C" */
  • __cyg_profile_func_exit

    The signature of the __cyg_profile_func_exit function is as follows:

void __cyg_profile_func_exit(void *this_fcn, void *call_site);
An example definition of this function might look like this:
#include "func_timer.h"

extern "C" {

// Function Exit Hook
__attribute__((no_instrument_function))
void __cyg_profile_func_exit(void *this_fcn, void *call_site) {
  // Non-NULL function address is required
  if (!this_fcn) return;

  // Find function in function timer map; error if not found
  auto func_iter = func_timer_map.find((unsigned long)this_fcn);
  func_timer_record *func_timer;
  if (func_iter == func_timer_map.end()) {
    printf("ERROR: expected function in func_timer_map\n");
    return;
  }
  
  func_timer = func_iter->second;

  // If we're about to remove the function from the call stack,
  // add elapsed time to total accumulated time for this function
  if (func_timer->recur_level == 1) {
    func_timer->acc_func_time += (long)(clock() - func_timer->clock_start);
  }
  
  func_timer->recur_level--;
}

} /* extern "C" */

For both of the above functions, the first argument, this_fcn, is the address of the start of the current function, which can be looked up in the symbol table, and the second argument, call_site, is the return address of the current function that can be used to determine where the current function was called from.

Note

Define __cyg_profile_func_enter and __cyg_profile_func_exit as “C” Symbols

When using the -finstrument-functions option with a C++ source file, the “”tiarmclang** compiler will instrument a given function with calls to __cyg_profile_func_enter and __cyg_profile_func_exit using the “C” names of those function symbols. Consequently, when you define the __cyg_profile_func_enter and __cyg_profile_func_exit functions for use in a C++ application, you must enclose the definitions of these functions in an extern “C” construct, as indicated in the examples above.

1.3.12.2.2. Disabling Instrumentation with no_instrument_function Attribute

While applying the -finstument-functions option to an application, there may be some functions that you may want to exclude from being instrumented, such as the definitions of __cyg_profile_func_enter and __cyg_profile_func_exit described above. In such cases, the no_instrument_function function attribute can be applied to prevent calls to the entry and exit hooks from being generated for a given function.

The above definition of __cyg_profile_func_enter contains an example of how to apply the no_instrument_function attribute to a function:

__attribute__((no_instrument_function))
void __cyg_profile_func_enter(void *this_fcn, void *call_site) {
   ...
}

1.3.12.2.3. Function Entry/Exit Hooks Example

One useful application of function entry and exit hook functions is to gather profile data for the functions in an application. The above definitions of __cyg_profile_func_enter and __cyg_profile_func_exit collect the accumulated time spent in each instrumented function in an application.

The profile data is collected and recorded in a map of function_timer_record objects as detailed in func_timer.h:

#include <stdio.h>
#include <time.h>
#include <map>

class func_timer_record {
  public:
   unsigned long func_address;
   unsigned int  recur_level;
   clock_t       clock_start;
   long          acc_func_time;

   func_timer_record(unsigned long func_addr) : 
     func_address(func_addr),
     recur_level(0),
     clock_start(0),
     acc_func_time(0) { } 
   ~func_timer_record() { }
} func_timer_record;

extern std::map<unsigned long, func_timer_record *> func_timer_map;

__attribute__((no_instrument_function)) void report_function_times(void);

In this simplistic example, it is anticipated that the application being profiled will call report_function_times that will write out a comma-separated list of the function addresses and their corresponding recorded execution times:

#include "func_timer.h"
#include <list>

std::map<unsigned long, func_timer_record *> func_timer_map;

__attribute__((no_instrument_function)) void report_function_times(void) {
  // Print CSV output of function addresses and corresponding times
  std::list<function_timer_record *> curr_func_list;
  for (auto it = func_timer_map.begin(); it != func_timer_map.end(); ++it) {
    unsigned long curr_func_addr = it->first;
    unsigned long curr_func_time = (it->second)->acc_func_time;
    printf("func_address: 0x%08lx, cumulative time in function: %ld\n",
           curr_func_addr, curr_func_time);
  }
}

The application to be profiled can then be compiled with the -finstrument-functions option:

%> tiarmclang -mcpu=cortex-m4 -finstrument-functions <app source files> \
     func_timer.cpp func_enter.cpp func_exit.cpp -o app.out ...

While the functions defined in the application source files will be instrumented, the instrumentation functions themselves will not since they have been annotated with the no_instrument_function attribute.

When loaded and run, app.out will produce the function time statistics that can then be analyzed and processed by a program that has access to the app.out file’s symbol table.