Studio:Integrating the Intercept Module (IM)
Intercept Module Files and their purpose
The Intercept Module is generated by STRIDE Studio for the specific set of function interfaces you select. Three files are created, each prepended with the name of the Intercept Module that you gave it when it was created:
- The Intercept Module source file (imnameIM.c or imnameIM.cpp)
This file contains the code that allows the remoting of functions so that they can be accessed by the Target and Host. - The Delegate Mangling header file (imnameIM.h)
This file must be included in all of your C files that are using Delegates (Dynamic or Tracing). It contains macros that will mangle the appropriate function names so that they can be intercepted and routed through the IM. When used, it must be the last file #include'd in the source. Also, it is only required if the C file uses Delegates. Stubs and Proxy functions do not require this file - The IM Entry point header file (imnameIMEntry.h)
This file contains the prototypes for the external entry points into the IM. It is needed only by the function that starts the IM Stub Read Thread.
These filenames are all prepended by the name you give the IM when it is generated. For example, if you call the IM "MyProject", then the resulting filenames will be "MyProjectIM.c", "MyProjectIM.h", and "MyProjectIMEntry.h".
Configuring and generating an Intercept Module
You can either generate the IM via STRIDE Studio's Intercept Module Wizard, or you can utilize scripts to automatically configure and generate the IM files. These files will contain Studio-based configuration definitions. In addition, the script used for configuration will denote in which context the IM runtime functions are to be executed.
The script can execute automatically so that the Intercept Module is built as part of the test automation framework, as shown in the following example. The IM generation script should open a Studio workspace, optionally compile the workspace and save the database (if needed), then configure and create the IM as shown in the following Perl example:
# Build the workspace database
$main::studio->Workspace->Build();
# Intercept Module (IM) name and path constants
my $IM_FILE_NAME = "MyProject";
my $IM_LOCATION_PATH = $main::studio->Workspace->Path;
# Create a reference to the workspace IM object
my $IM = $main::studio->Workspace->Intercept;
# Local variables
my ($i, $fx);
# Reset all of the IM configuration settings to off and
# reset the delegate group Id's to their default name.
$IM->Reset();
# Setup the IM name and path
$IM->{Name} = $IM_FILE_NAME;
$IM->{SourcePath} = $IM_LOCATION_PATH;
$IM->{HeaderPath} = $IM_LOCATION_PATH;
# For each interface in the IM collection
for ($i=0; $i < $IM->Count(); $i++)
{
# reference the ith interface;
$fx = $IM->Item($i);
$fx->{Stub} = 1; # configure as stub
# and delegate
$fx->Delegate->{Owner} = 1; # mangle from owner's perspective
$fx->Delegate->{Explicit} = 1; # implicit name mangling
$fx->Delegate->{Dynamic} = 1; # enable dynamic interception
}
# Create IM files.
$IM->Create();
Note in the above script how the IM name and output directory were defined. In this case, all three files are created in the same directory as the STRIDE workspace. The resulting Intercept Module file must then be compiled and built with the rest of your target code.
Activating the Intercept Module
Compiling the IM
To compile the IM, you must define the macro srIMON when you compile the IM.c file or any file that includes one of the headers. This "turns on" the intercept feature. For example:
cc -c -IC:\STRIDE\inc -DsrIMON MyProjectIM.c
Notice that the IM.c and IMEntry.h files reference header files in the STRIDE installation directory. The build must include this directory when compiling either of these files. The Delegate (IM.h) header file does not have any dependencies on this directory.
IM Resource Requirements
The Intercept Module is usually run as a separate task, and therefore has some resource requirements that are in addition to those of the Runtime and the PAL. One of these is the task resource itself. Another is space for the task stack.
The required task stack size if difficult to predict since it is dependent on the underlying system and on the data that is passed through function arguments. A stack size between 2-4K would be a good starting point.
Starting the srThread and IMStubRead threads
The IM must be started after the Runtime and Transport have been initialized, but before any intercepted calls can be made. Failure to do this before making an intercepted call can crash or hang the target.
This code snippet for Linux, based on using the POSIX call pthread_create() to start a task (your OS may use a different call), demonstrates how to initialize the PAL and the Runtime, start the IM, then becomes the Runtime message processing loop. Note that this code assumes that it will become the Runtime thread's message processing loop (because srThread() will never return unless thread is stopped or killed). The comments note that this could instead create a second task for the Runtime and return.
Linux Implementation
This code snippet for Linux also handles termination signals based on POSIX APIs in signal.h in order to cleanly shut down the target and clean up resources. This is required if multi-process target is enabled in the Runtime, especially for Linux OS.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
/* STRIDE runtime includes */
#include <sr.h>
/* The OS PAL configuration file */
#include <palcfg.h>
/* Intercept Module includes */
#include "MyProjectIMEntry.h"
extern palBOOL palInit();
extern palBOOL palUninit();
/* The IM message handler ("MyProject" is the IM name) */
extern void MyProjectIMStubThread();
/* Thread handling */
typedef void (*_ThreadFunc_t)( void );
void* _threadFunc(void* param)
{
if (param)
{
_ThreadFunc_t proc = (_ThreadFunc_t)param;
proc();
}
return NULL;
}
static pthread_t _threads[15] = {0};
/* Termination signals handling */
void _cleanup(void)
{
int i = 0;
fprintf(stdout, "TestAgent exiting.\n");
for (i = sizeof(_threads)/sizeof(_threads[0]); i > 0; i--)
{
if (_threads[i-1] != 0)
{
palNotify(_threads[i-1], palSTOP_EVENT);
pthread_join(_threads[i-1], NULL);
pthread_detach(_threads[i-1]);
}
}
srUninit();
palUninit();
}
volatile sig_atomic_t termination_in_progress = 0;
void _terminate(int sig)
{
/* Since this handler is established for more than one kind of signal,
it might still get invoked recursively by delivery of some other kind
of signal. Use a static variable to keep track of that. */
if (termination_in_progress)
raise (sig);
termination_in_progress = 1;
/* cleanup */
_cleanup();
/* Now reraise the signal. We reactivate the signal's
default handling, which is to terminate the process.
We could just call exit or abort,
but reraising the signal sets the return status
from the process correctly. */
signal (sig, SIG_DFL);
raise (sig);
}
/* A function to start the STRIDE threads */
void STRIDEstartup()
{
/* Set termination signals handling */
(void) signal(SIGABRT, _terminate);
(void) signal(SIGTERM, _terminate);
(void) signal(SIGINT, _terminate);
(void) signal(SIGQUIT, _terminate);
(void) signal(SIGHUP, _terminate);
/* Initialize PAL. It will return palFALSE if it fails
(This will open the transport to the host) */
if (palInit() == palFALSE) {
palLog(palLOG_LEVEL_ERROR, "PAL Initialization failed.");
palLog(palLOG_LEVEL_ERROR, "Quitting.");
exit(1);
}
/* Initialize Runtime */
if (srInit() != srOK)
{
palLog(palLOG_LEVEL_ERROR, "srInit failed.");
exit(1);
}
/* Start the Runtime thread first */
pthread_create(&_threads[0], NULL, _threadFunc, (void*)srThread);
/* Start the IM stub read thread */
pthread_create(&_threads[1], NULL, _threadFunc, (void*)MyProjectIMStubThread);
/* Wait for Runtime thread */
pthread_join(_threads[0], NULL);
_cleanup();
return 0;
}
Windows Implementation
This code snippet for Windows is based on using the Win32 API calls.
#include "stdafx.h"
#include <windows.h>
#include <commctrl.h>
#include <sr.h>
#include <palcfg.h>
#include "MyProjectIMEntry.h"
static bool bUseSerial = false;
struct ThreadInfo
{
HANDLE handle;
DWORD id;
};
typedef void (*_ThreadFunc_t)( void );
static DWORD WINAPI _threadFunc(LPVOID param)
{
if (param)
{
_ThreadFunc_t proc = (_ThreadFunc_t)param;
proc();
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
ThreadInfo threads[15] = {{NULL, 0}};
int i = 0;
// Initialize the PAL
if (palOSInit() != palTRUE) {
palLog(palLOG_LEVEL_ERROR, "palOSInit FAILED.");
return -1;
}
pal_io_config_t cfg;
pal_prot_t protocol;
if (bUseSerial) {
protocol = PAL_PROT_SERIAL;
cfg.u.serial.BaudRate = PAL_SERIAL_BAUDRATE;
cfg.u.serial.ByteSize = PAL_SERIAL_BYTESIZE;
cfg.u.serial.ComPort = PAL_SERIAL_PORT;
cfg.u.serial.Parity = PAL_SERIAL_PARITY;
cfg.u.serial.StopBits = PAL_SERIAL_STOPBITS;
}
else {
protocol = PAL_PROT_TCP;
cfg.u.tcp.Port = PAL_DEFAULT_TCP_PORT;
}
// Initialize tcp transport
if (palIOInit(protocol, &cfg) != palTRUE) {
palLog(palLOG_LEVEL_ERROR, "palIOInit FAILED.");
return -1;
}
// Initialize Runtime
if (srInit() != srOK) {
palLog(palLOG_LEVEL_ERROR, "srInit FAILED.");
return -1;
}
// Start the Runtime thread first
threads[i].handle = CreateThread(NULL, 0, _threadFunc, srThread, 0, &threads[i].id);
if (!threads[i++].handle) {
palLog(palLOG_LEVEL_ERROR, "CreateThread FAILED for Runtime Thread.");
return -1;
}
// Start the IM stub read thread
threads[i].handle = CreateThread(NULL, 0, _threadFunc, MyProjectIMStubThread, 0, &threads[i].id);
if (!threads[i++].handle) {
palLog(palLOG_LEVEL_ERROR, "CreateThread FAILED for IM Thread.");
return -1;
}
// Wait for Runtime thread
WaitForSingleObject(threads[0].handle, INFINITE);
fprintf (stdout, "TestAgent exiting.\n");
for (i = sizeof(threads)/sizeof(threads[0]); i > 0; i--)
{
if (threads[i-1].handle != NULL)
{
palNotify(threads[i-1].id, palSTOP_EVENT);
WaitForSingleObject(threads[i-1].handle, INFINITE);
CloseHandle(threads[i-1].handle);
}
}
srUninit();
palOSUninit();
return 0;
}
Development of off-target applications is discussed in more detail in the Windows Off-Target Apps page.