You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

726 lines
23 KiB

/*
* Copyright 2002-2019 Intel Corporation.
*
* This software is provided to you as Sample Source Code as defined in the accompanying
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
* section 1.L.
*
* This software and the related documents are provided as is, with no express or implied
* warranties, other than those that are expressly stated in the License.
*/
#include "alarms.H"
#include "alarm_manager.H"
#include "call-stack.H"
#include <iostream>
#include <string>
using namespace std;
using namespace CONTROLLER;
using namespace CALLSTACK;
/// @cond INTERNAL_DOXYGEN
//*****************************************************************************
VOID ALARM_ICOUNT::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_ICOUNT::Trace(TRACE trace, VOID* v)
{
ALARM_ICOUNT* icount_alarm = static_cast<ALARM_ICOUNT*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
INS ins = BBL_InsHead(bbl);
UINT32 ninst = BBL_NumIns(bbl);
InsertIfCall_Count(icount_alarm, ins, ninst);
InsertThenCall_Fire(icount_alarm, ins);
}
}
//*****************************************************************************
VOID ALARM_SSC::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_SSC::Trace(TRACE trace, VOID* v)
{
ALARM_SSC* ssc_alarm = static_cast<ALARM_SSC*>(v);
UINT32 h = Uint32FromString("0x"+ssc_alarm->_ssc);
const UINT32 pattern_len = 8;
const unsigned int movebx_size = 5;
const unsigned int special_nop_size = 3;
unsigned int ins_size = 0 ,next_ins_size = 0;
EXCEPTION_INFO excep = EXCEPTION_INFO();
INS ins,next_ins=INS_Invalid();
//the template of ssc marker
unsigned char ssc_marker[] = { 0xbb, 0x00, 0x00, 0x00, 0x00,
0x64, 0x67, 0x90};
for(int j=0;j<4;j++){
//fill in the ssc value
ssc_marker[1+j]= (h>>(j*8))&0xff;
}
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
ins = BBL_InsHead(bbl);
if (INS_Valid(ins))
{
ins_size = INS_Size(ins);
next_ins = INS_Next(ins);
}
while (INS_Valid(next_ins))
{
next_ins_size = INS_Size(next_ins);
if (ins_size + next_ins_size == pattern_len)
{
unsigned char* pc;
pc = reinterpret_cast<unsigned char*>(INS_Address(ins));
unsigned char dst_buf[pattern_len];
size_t copy_size = PIN_FetchCode(dst_buf, pc, pattern_len, &excep);
if (copy_size == pattern_len &&
memcmp(ssc_marker,dst_buf,pattern_len) == 0){
InsertIfCall_Count(ssc_alarm, ins, 1);
InsertThenCall_Fire(ssc_alarm, ins);
// Add late handler instrumentation if needed
Insert_LateInstrumentation(ssc_alarm,ins);
}
}
ins = next_ins;
ins_size = next_ins_size;
next_ins = INS_Next(next_ins);
}
//For the last instruction in the BBL, we want to check the next
//instruction, the head of the next BBL.
if (ins_size == movebx_size)
{
BBL next_bbl = BBL_Next(bbl);
if (BBL_Valid(next_bbl))
{
next_ins = BBL_InsHead(next_bbl);
}
//If the head of the next BBL can potentailly be joint with the
//current ins to an ssc_mark, or if this is the last instruction in
//the trace.
if (!INS_Valid(next_ins) || INS_Size(next_ins) == special_nop_size)
{
unsigned char* pc;
pc = reinterpret_cast<unsigned char*>(INS_Address(ins));
unsigned char dst_buf[pattern_len];
size_t copy_size = PIN_FetchCode(dst_buf, pc, pattern_len, &excep);
if (copy_size == pattern_len &&
memcmp(ssc_marker,dst_buf,pattern_len) == 0){
InsertIfCall_Count(ssc_alarm, ins, 1);
InsertThenCall_Fire(ssc_alarm, ins);
// Add late handler instrumentation if needed
Insert_LateInstrumentation(ssc_alarm,ins);
}
}
}
}
}
//*****************************************************************************
VOID ALARM_ITEXT::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_ITEXT::Trace(TRACE trace, VOID* v)
{
ALARM_ITEXT* itext_alarm = static_cast<ALARM_ITEXT*>(v);
UINT32 pattern_len = itext_alarm->_itext.length();
UINT32 pattern_bytes = pattern_len / 2; //nibbels -> bytes
EXCEPTION_INFO excep = EXCEPTION_INFO();
const size_t max_inst = 15;
unsigned char pattern_duf[max_inst];
PARSER::str2hex(itext_alarm->_itext.c_str(),pattern_duf,pattern_len);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
if (INS_Size(ins) == pattern_bytes)
{
unsigned char* pc;
pc = reinterpret_cast<unsigned char*>(INS_Address(ins));
unsigned char dst_buf[max_inst];
size_t copy_size = PIN_FetchCode(dst_buf, pc, pattern_bytes, &excep);
if (copy_size == pattern_bytes &&
memcmp(pattern_duf,dst_buf,pattern_bytes) == 0)
{
InsertIfCall_Count(itext_alarm, ins, 1);
InsertThenCall_Fire(itext_alarm, ins);
}
}
}
}
}
//*****************************************************************************
VOID ALARM_INT3::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_INT3::Trace(TRACE trace, VOID* v)
{
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
xed_iclass_enum_t iclass;
iclass = static_cast<xed_iclass_enum_t>(INS_Opcode(ins));
if (iclass == XED_ICLASS_INT3){
ALARM_INT3* int3_alarm = static_cast<ALARM_INT3*>(v);
InsertIfCall_Count(int3_alarm, ins, 1);
InsertThenCall_Fire(int3_alarm, ins);
INS_Delete(ins); // so no "int3" will be actually executed
}
}
}
}
//*****************************************************************************
VOID ALARM_ISA_CATEGORY::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_ISA_CATEGORY::Trace(TRACE trace, VOID* v)
{
ALARM_ISA_CATEGORY* isa_ctg_alarm = static_cast<ALARM_ISA_CATEGORY*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
xed_category_enum_t category;
category = static_cast<xed_category_enum_t>(INS_Category(ins));
if (category == isa_ctg_alarm->_required_ctg){
InsertIfCall_Count(isa_ctg_alarm, ins, 1);
InsertThenCall_Fire(isa_ctg_alarm, ins);
// Add late handler instrumentation if needed
Insert_LateInstrumentation(isa_ctg_alarm,ins);
}
}
}
}
//*****************************************************************************
VOID ALARM_ISA_EXTENSION::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_ISA_EXTENSION::Trace(TRACE trace, VOID* v)
{
ALARM_ISA_EXTENSION* isa_ext_alarm = static_cast<ALARM_ISA_EXTENSION*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
xed_extension_enum_t extension;
extension = static_cast<xed_extension_enum_t>(INS_Extension(ins));
if (extension == isa_ext_alarm->_required_ext){
InsertIfCall_Count(isa_ext_alarm, ins, 1);
InsertThenCall_Fire(isa_ext_alarm, ins);
// Add late handler instrumentation if needed
Insert_LateInstrumentation(isa_ext_alarm,ins);
}
}
}
}
//*****************************************************************************
VOID ALARM_ADDRESS::Activate(){
TRACE_AddInstrumentFunction(TraceAddress, this);
}
//*****************************************************************************
VOID ALARM_SYMBOL::Activate(){
PIN_InitSymbols();
//this is for finding the address of the required symbol
IMG_AddInstrumentFunction(Img, this);
TRACE_AddInstrumentFunction(TraceAddress, this);
}
VOID ALARM_SYMBOL::Img(IMG img, VOID* v)
{
ALARM_SYMBOL* symbol_alarm = static_cast<ALARM_SYMBOL*>(v);
for(SYM sym = IMG_RegsymHead(img); SYM_Valid(sym); sym = SYM_Next(sym) )
{
string symbol = SYM_Name(sym);
if (symbol == symbol_alarm->_symbol){
symbol_alarm->_address = SYM_Value(sym) + IMG_LoadOffset(img);
return;
}
}
}
//*****************************************************************************
VOID ALARM_IMAGE::Activate(){
PIN_InitSymbols();
//this is for finding the address of the required symbol
IMG_AddInstrumentFunction(Img, this);
TRACE_AddInstrumentFunction(TraceAddress, this);
}
VOID ALARM_IMAGE::Img(IMG img, VOID* v)
{
ALARM_IMAGE* image_alarm = static_cast<ALARM_IMAGE*>(v);
string img_name = IMG_Name(img);
bool found = false;
if (img_name == image_alarm->_image)
found = true;
else {
// check against the base name of the image
string::size_type pos = img_name.rfind('/');
#if defined(TARGET_WINDOWS)
string::size_type wpos = img_name.rfind('\\');
if (pos == string::npos)
pos = wpos;
else if (wpos != string::npos && wpos > pos)
pos = wpos;
#endif
if (pos != string::npos) {
string basename = img_name.substr(pos+1);
if (basename == image_alarm->_image)
found = true;
}
}
if (found) {
image_alarm->_address = IMG_LowAddress(img) + image_alarm->_offset;
return;
}
}
//*****************************************************************************
VOID ALARM_INTERACTIVE::Activate()
{
_listener = _alarm_manager->GetListener();
if (_listener == NULL){
PIN_ERROR("interactive controller must be used "
"with the knob -interactive_file <file name>\n\n");
}
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_INTERACTIVE::Trace(TRACE trace, VOID* v)
{
ALARM_INTERACTIVE* alarm = static_cast<ALARM_INTERACTIVE*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
INS ins = BBL_InsHead(bbl);
InsertInteractiveIf(alarm, ins);
InsertThenCall_Fire(alarm, ins);
}
}
VOID ALARM_INTERACTIVE::InsertInteractiveIf(ALARM_INTERACTIVE* alarm,
INS ins)
{
INS_InsertIfCall(ins, IPOINT_BEFORE,
AFUNPTR(InteractiveShouldFire),
IARG_FAST_ANALYSIS_CALL,
IARG_CALL_ORDER, alarm->GetInstrumentOrder(),
IARG_ADDRINT, alarm,
IARG_THREAD_ID,
IARG_END);
}
ADDRINT PIN_FAST_ANALYSIS_CALL
ALARM_INTERACTIVE::InteractiveShouldFire(ALARM_INTERACTIVE* alarm,
UINT32 tid)
{
UINT32 armed = alarm->_armed[tid];
UINT32 correct_tid = (alarm->_tid == tid) | (alarm->_tid == ALL_THREADS);
// In order to make sure that this routine is inline we call
// GetClearSignal that checks if we can clear signal
// The actual modification by cmpxchgl is done in Fire routine
return (armed & correct_tid & alarm->_listener->GetClearSignal());
}
//*****************************************************************************
VOID ALARM_ENTER_FUNC::Activate(){
CallStackManager* mngr = CallStackManager::get_instance();
mngr->activate();
//register the callback
mngr->on_function_enter(OnFunctionStart, _func_name, this, _need_context);
}
VOID ALARM_ENTER_FUNC::OnFunctionStart(CONTEXT* ctxt, ADDRINT ip, THREADID tid, VOID *v){
ALARM_ENTER_FUNC* alarm = static_cast<ALARM_ENTER_FUNC*>(v);
if ((!alarm->HasGlobalCounter() && alarm->Count(alarm, tid, 1)) ||
(alarm->HasGlobalCounter() && alarm->GlobalCount(alarm, tid, 1))) {
alarm->_alarm_manager->Fire(ctxt,reinterpret_cast<VOID*>(ip),tid);
}
}
//*****************************************************************************
VOID ALARM_EXIT_FUNC::Activate(){
CallStackManager* mngr = CallStackManager::get_instance();
mngr->activate();
//register the callback
mngr->on_function_exit(OnFunctionEnd, _func_name, this, _need_context);
}
VOID ALARM_EXIT_FUNC::OnFunctionEnd(CONTEXT* ctxt, ADDRINT ip, THREADID tid, VOID *v){
ALARM_EXIT_FUNC* alarm = static_cast<ALARM_EXIT_FUNC*>(v);
if ((!alarm->HasGlobalCounter() && alarm->Count(alarm, tid, 1)) ||
(alarm->HasGlobalCounter() && alarm->GlobalCount(alarm, tid, 1))) {
alarm->_alarm_manager->Fire(ctxt,reinterpret_cast<VOID*>(ip),tid);
}
}
//*****************************************************************************
VOID ALARM_CPUID::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_CPUID::Trace(TRACE trace, VOID* v)
{
ALARM_CPUID* alarm_cpuid = static_cast<ALARM_CPUID*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
xed_iclass_enum_t iclass;
iclass = static_cast<xed_iclass_enum_t>(INS_Opcode(ins));
if (iclass == XED_ICLASS_CPUID) {
INS_InsertIfCall(ins, IPOINT_BEFORE,
AFUNPTR(ShouldFire),
IARG_FAST_ANALYSIS_CALL,
IARG_CALL_ORDER, alarm_cpuid->GetInstrumentOrder(),
IARG_ADDRINT, alarm_cpuid,
IARG_REG_VALUE, REG_GAX,
IARG_UINT32, alarm_cpuid->_val,
IARG_THREAD_ID,
IARG_END);
InsertThenCall_Fire(alarm_cpuid, ins);
}
}
}
}
ADDRINT PIN_FAST_ANALYSIS_CALL
ALARM_CPUID::ShouldFire(IALARM* alarm,
ADDRINT eax,
UINT32 val,
UINT32 tid)
{
// if eax match val then return apply the count and tid checks
if (eax == val) {
if (!alarm->HasGlobalCounter())
return alarm->Count(alarm, tid, 1);
else
return alarm->GlobalCount(alarm, tid, 1);
}
return 0;
}
//*****************************************************************************
ALARM_MAGIC::ALARM_MAGIC(const string& val_str, UINT32 tid, UINT32 count,
BOOL need_ctxt, ALARM_MANAGER* manager)
: IALARM(tid, count, need_ctxt, manager)
{
vector<string> tokens;
PARSER::SplitArgs(".", val_str, tokens);
ASSERT(tokens.size() == 2,
"MAGIC alarm needs two parameters separated by \".\"");
_a = PARSER::StringToUint32(tokens[0]);
_b = PARSER::StringToUint32(tokens[1]);
Activate();
}
VOID ALARM_MAGIC::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
}
VOID ALARM_MAGIC::Trace(TRACE trace, VOID* v)
{
ALARM_MAGIC* alarm_magic = static_cast<ALARM_MAGIC*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
{
// Simics-style magic instruction: xchg bx, bx
if (INS_IsXchg(ins) && INS_OperandReg(ins, 0) == REG_BX &&
INS_OperandReg(ins, 1) == REG_BX)
{
INS_InsertIfCall(ins, IPOINT_BEFORE,
AFUNPTR(ShouldFire),
IARG_FAST_ANALYSIS_CALL,
IARG_CALL_ORDER, alarm_magic->GetInstrumentOrder(),
IARG_ADDRINT, alarm_magic,
IARG_REG_VALUE, REG_GAX,
#if defined(__i386)
IARG_REG_VALUE, REG_GDX,
#else
IARG_REG_VALUE, REG_GBX,
#endif
IARG_REG_VALUE, REG_GCX,
IARG_UINT32, alarm_magic->_a,
IARG_UINT32, alarm_magic->_b,
IARG_THREAD_ID,
IARG_END);
InsertThenCall_Fire(alarm_magic, ins);
}
}
}
}
ADDRINT PIN_FAST_ANALYSIS_CALL
ALARM_MAGIC::ShouldFire(IALARM* alarm,
ADDRINT eax,
ADDRINT ebx,
ADDRINT ecx,
UINT32 a,
UINT32 b,
UINT32 tid)
{
// if eax/ebx/ecx match SIM_CMD_MARKER/a/b then return apply the count and tid checks
if (eax == SIM_CMD_MARKER && ebx == a && ecx == b) {
if (!alarm->HasGlobalCounter())
return alarm->Count(alarm, tid, 1);
else
return alarm->GlobalCount(alarm, tid, 1);
}
return 0;
}
//*****************************************************************************
VOID ALARM_PCONTROL::Activate(){
RTN_AddInstrumentFunction(Rtn, this);
}
VOID ALARM_PCONTROL::Rtn(RTN rtn, VOID *v)
{
ALARM_PCONTROL* alarm_pcontrol = static_cast<ALARM_PCONTROL*>(v);
std::string rtn_name = RTN_Name(rtn);
if (rtn_name.find("MPI_Pcontrol") != std::string::npos)
{
RTN_Open(rtn);
RTN_InsertCall(rtn, IPOINT_BEFORE,
AFUNPTR(OnMpiPcontrol),
IARG_CALL_ORDER, alarm_pcontrol->GetInstrumentOrder(),
IARG_ADDRINT, alarm_pcontrol,
IARG_CONTEXT,
IARG_INST_PTR,
IARG_FUNCARG_ENTRYPOINT_VALUE, 1,
IARG_THREAD_ID,
IARG_END);
RTN_Close(rtn);
}
}
VOID
ALARM_PCONTROL::OnMpiPcontrol(ALARM_PCONTROL* alarm,
CONTEXT* ctxt,
ADDRINT ip,
ADDRINT region_app,
UINT32 tid)
{
UINT32 length = alarm->_region.size() + 1;
ASSERT(length < 128, "MPI_Pcontrol region name should be <128 characters");
char region_app_local[128];
PIN_SafeCopy(region_app_local, (const VOID*)region_app, length);
if (strcmp(region_app_local, alarm->_region.c_str()) == 0)
{
if ((!alarm->HasGlobalCounter() && alarm->Count(alarm, tid, 1)) ||
(alarm->HasGlobalCounter() && alarm->GlobalCount(alarm, tid, 1))) {
alarm->_alarm_manager->Fire(ctxt,reinterpret_cast<VOID*>(ip),tid);
}
}
}
//*****************************************************************************
VOID ALARM_TIMEOUT::Activate(){
TRACE_AddInstrumentFunction(Trace, this);
PIN_SpawnInternalThread(WaitForTimeout, this, 0 ,NULL);
}
VOID ALARM_TIMEOUT::WaitForTimeout(VOID* v)
{
ALARM_TIMEOUT* timeout_alarm = static_cast<ALARM_TIMEOUT*>(v);
while (1)
{
// Wait 100 millisecond till the alarm is armed
// Timeout alarm is not relevant to any specific thread so just pick 0.
while (!timeout_alarm->_armed[0])
{
PIN_Sleep(100);
}
PIN_Sleep(timeout_alarm->_seconds_timeout * 1000);
timeout_alarm->_timeout_passed = TRUE;
break;
}
}
VOID ALARM_TIMEOUT::Trace(TRACE trace, VOID* v)
{
ALARM_TIMEOUT* timeout_alarm = static_cast<ALARM_TIMEOUT*>(v);
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
INS ins = BBL_InsHead(bbl);
InsertIfCall_CheckTime(timeout_alarm, ins);
InsertThenCall_Fire(timeout_alarm, ins);
}
}
// Instrumentation of time checking
VOID ALARM_TIMEOUT::InsertIfCall_CheckTime(ALARM_TIMEOUT* alarm, INS ins){
INS_InsertIfCall(ins, IPOINT_BEFORE,
AFUNPTR(CheckTime),
IARG_FAST_ANALYSIS_CALL,
IARG_CALL_ORDER, alarm->GetInstrumentOrder(),
IARG_ADDRINT, alarm,
IARG_THREAD_ID,
IARG_END);
}
// Check if we have reached the timeout we need
ADDRINT PIN_FAST_ANALYSIS_CALL ALARM_TIMEOUT::CheckTime(ALARM_TIMEOUT* ialarm,
UINT32 tid) {
BOOL armed = ialarm->_armed[tid];
BOOL correct_tid = (ialarm->_tid == tid) | (ialarm->_tid == ALL_THREADS);
return armed & correct_tid & ialarm->_timeout_passed;
}
//*****************************************************************************
VOID ALARM_SIGNAL::Activate(){
#if (!defined(TARGET_WINDOWS))
PIN_AddContextChangeFunction(ContextChangeCallback, this);
#endif
}
#if (!defined(TARGET_WINDOWS))
VOID ALARM_SIGNAL::ContextChangeCallback(
THREADID tid,
CONTEXT_CHANGE_REASON reason,
const CONTEXT *ctxtFrom,
CONTEXT *ctxtTo,
INT32 sig,
VOID *v)
{
// Check if we need to file the signal alarm now for the specific thread
ALARM_SIGNAL *ialarm = static_cast<ALARM_SIGNAL *>(v);
BOOL armed = ialarm->_armed[tid];
BOOL correct_tid = (ialarm->_tid == tid) | (ialarm->_tid == ALL_THREADS);
// Fire alarm if we got the correct signal
if (armed & correct_tid && reason == CONTEXT_CHANGE_REASON_SIGNAL && sig == ialarm->_signal_id)
{
ADDRINT ip = PIN_GetContextReg(ctxtFrom, REG_INST_PTR);
Fire(ialarm,(CONTEXT *)ctxtFrom,(VOID*)ip,tid);
}
}
#endif
//*****************************************************************************
VOID ALARM_IMAGE_LOAD::Activate()
{
IMG_AddInstrumentFunction(ImageLoad, this);
}
VOID ALARM_IMAGE_LOAD::ImageLoad(IMG img, VOID* v)
{
// Check if we need to file the signal alarm now for the specific thread
ALARM_IMAGE_LOAD *image_alarm = static_cast<ALARM_IMAGE_LOAD *>(v);
const string image_name = IMG_Name(img);
// Fire alarm if we got the correct image
string image_file_name = ExtractFileName(image_name);
if (image_file_name.compare(image_alarm->_image_name) == 0)
{
Fire(image_alarm, NULL, 0, PIN_ThreadId());
}
}
// Find the last directory separator and return everything after that, or the
// whole string if there is no separator.
// On Windows we allow either '/' or '\\', since both are accepted by
// the kernel.
string ALARM_IMAGE_LOAD::ExtractFileName(const string & fullpath)
{
size_t max_slash = 0;
BOOL found_slash = FALSE;
// Handle Unix slash
size_t unix_slash = fullpath.rfind('/');
if (unix_slash != string::npos)
{
max_slash = unix_slash;
found_slash = TRUE;
}
// Handle Windows slash
#if defined (TARGET_WINDOWS)
size_t windows_slash = fullpath.rfind('\\');
if (windows_slash != string::npos && windows_slash > max_slash)
{
max_slash = windows_slash;
found_slash = TRUE;
}
#endif
if (found_slash)
max_slash++;
return fullpath.substr(max_slash);
}
/// @endcond