(file) Return to injector.cpp.bkp CVS log (file) (dir) Up to [OMI] / omi / nits / injector

File: [OMI] / omi / nits / injector / injector.cpp.bkp (download)
Revision: 1.1, Mon Apr 20 17:19:53 2015 UTC (9 years, 2 months ago) by krisbash
Branch: MAIN
CVS Tags: OMI_1_0_8_2, OMI_1_0_8_1, HEAD
OMI 1.0.8-1

#include <nits/base/Globals.h>

#ifdef _MSC_VER
    #include <psapi.h>
#else
    #include <stdio.h>
#endif
#include <pal/palcommon.h>
#include <pal/process.h>
#include <pal/strings.h>
#include <pal/lock.h>
#include <iostream>
#include <fstream>


using namespace std;
#if defined(_MSC_VER)
# pragma warning(disable : 4702)
#endif

struct SharedSegmentHeader
{
    union
    {
        ptrdiff_t status;
        double doubleAlignment;
    };
};

PAL_Char *Copy(_In_opt_z_ const PAL_Char * str)
{
    if (str == NULL)
    {
        return NULL;
    }

    int length = (int)Tcslen(str);
    PAL_Char *copy = new PAL_Char[length + 1];

    if(!copy)
    {
        return NULL;
    }
    
    Tcslcpy(copy, str, length + 1);

    return copy;
}

PSTR ConvertStringToA(_In_opt_z_ const wchar_t *buf)
{
    if(buf == NULL)
        return NULL;

#ifdef _MSC_VER
    int size = WideCharToMultiByte(CP_THREAD_ACP, 0, buf, -1, NULL, NULL, NULL, NULL);
#else
    int size = wcstombs(NULL, buf, 0);
#endif
    if (size == 0)
    {
        return NULL;
    }

#ifdef _MSC_VER
    PSTR ansibuf = new char[size];
    size = WideCharToMultiByte(CP_THREAD_ACP, 0, buf, -1, ansibuf, size, NULL, NULL);
#else
    PSTR ansibuf = new char[size + 1];
    size = wcstombs(ansibuf, buf, size + 1);
#endif
    if (size == 0)
    {
        delete [] ansibuf;
        return NULL;
    }
#ifndef _MSC_VER
    else
    {
        ansibuf[size] = '\0';
    }
#endif

    return ansibuf;
}

PWSTR ConvertStringToW(_In_opt_z_ const char *buf)
{
    if(buf == NULL)
        return NULL;

#ifdef _MSC_VER
    int size = MultiByteToWideChar(CP_THREAD_ACP, 0, buf, -1, NULL, NULL);
#else
    int size = mbstowcs(0, buf, 0);
#endif
    if (size == 0)
    {
        return NULL;
    }



#ifdef _MSC_VER
    PWSTR widebuf = new wchar_t[size];
    size = MultiByteToWideChar(CP_THREAD_ACP, 0,
            buf, -1, widebuf, size);
#else
    PWSTR widebuf = new wchar_t[size + 1];
    size = mbstowcs(widebuf, buf, size);
#endif
    if (size == 0)
    {
        delete [] widebuf;
        return NULL;
    }
#ifndef _MSC_VER
    else
    {
        widebuf[size] = L'\0';
    }
#endif

    return widebuf;
}

char *ConvertPalCharToStringA(_In_opt_z_ const PAL_Char *buf)
{
#ifdef CONFIG_ENABLE_WCHAR
    return ConvertStringToA(buf);
#else
    return Copy(buf);
#endif

}

PAL_Char *ConvertStringAToPalChar(_In_opt_z_ const char *buf)
{
    if(buf == NULL)
        return NULL;

#ifdef CONFIG_ENABLE_WCHAR
    return ConvertStringToW(buf);
#else
    return Copy(buf);
#endif
}

namespace TestSystem {

Fault::Fault() : m_lock(0)
{
    Reset(CallSite_NONE, 0, false);

    m_mainThread = Thread_ID();;
    m_minimumAttemptDifferentThread = 0;
}

void Fault::Reset(int site, int iteration, bool breakOnFault)
{
    m_site = site;
    m_iteration = iteration;
    m_attempt = 0;

    m_break = breakOnFault;
    m_faulted = false;
    m_filtered = false;
    m_hit = CallSite_NONE;
    m_line = 0;
    m_file[0] = '\0';

    m_faultedAttempt = 0;
    m_firstAttemptDifferentThread = 0;
}

Globals::Globals()
    : m_version(SharedMemoryVersion),
      m_runLock(0),
      m_attachLock(0),
      m_pipeLock(FALSE),
      m_pipeChars(0)
{
    m_pipe[0] = '\0';
    m_debugger[0] = '\0';
    m_binaryFilter[0] = '\0';
    m_binaryTarget[0] = '\0';

    for (int i = 0; i < InjectorListSize; i++)
        m_injectors[i].process = 0;

    Reset();

    for (int i = 0; i < ResultCount; i++)
    {
        m_statistics[i] = 0;
    }
}

Globals::~Globals()
{
}

void Globals::Reset()
{
    m_result = Skipped;

    m_stopReportingIgnoredErrors = false;

    m_config.traces = NitsTraceAllTests;
    m_config.mode = NitsTestCompoundFault;
    m_config.breakFault = false;
    m_config.breakAssert = false;
    m_config.skipFlakyTests = false;

    m_simAuto.Reset(CallSite_NONE, 0, false);
    m_simManual.Reset(CallSite_NONE, 0, false);

    m_faultError = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
    m_faultEvent[0] = L'\0';
    m_debugger[0] = L'\0';
    m_binaryFilter[0] = '\0';
    m_binaryTarget[0] = '\0';
}

} //namespace TestSystem

// I have ported this as much as I could; main remaining things are EnumProcessModules/GetModuleBaseName
// which I did not find equivalents; so I would prefer to leave this code in windows only
// also we have not finalized on the method we will be using to have product call into framework apis
// so I will defer this work to that point when we decide how linux product will call into framework
static void PatchBinary(
    _In_z_ PAL_Char *binary)
{
    Shlib *library = NULL;
    Shlib *framework;
    NitsFT *target;
    NitsFT *source;
    ptrdiff_t *targetNitsPresence = NULL;

    //printf("\nPatching binary %s\n", binary);
    
    library = Shlib_Open_Injected(binary, NitsReservedCallSite());

    if (library == NULL)
    {
        return;
    }

    target = (NitsFT *)Shlib_Sym(library, "NITS_STUB");
    if (target == NULL)
    {
        Shlib_Close(library);
        return;
    }

    targetNitsPresence = (ptrdiff_t *)Shlib_Sym(library, "NITS_PRESENCE_STUB");
    if (targetNitsPresence == NULL)
    {
        Shlib_Close(library);
        return;
    }

#ifdef _MSC_VER
    framework = Shlib_Open_Injected(PAL_T("nitsdll.dll"), NitsReservedCallSite());
#else
    framework = Shlib_Open_Injected(PAL_T("libnits.so"), NitsReservedCallSite());
#endif
    if (framework == NULL)
    {
        return;
    }

    source = (NitsFT *)Shlib_Sym(framework, "NITS_IMPL");
    if (source == NULL)
    {
        Shlib_Close(library);
        return;
    }

#ifdef _MSC_VER
    // changing the protection on the page to execute read write so that 
    // injector can patch the NITS stub table with the NITS API table values
    DWORD oldProtection = 0;
    if(VirtualProtect(target, sizeof(NitsFT), PAGE_EXECUTE_READWRITE, &oldProtection) == 0)
    {
        Shlib_Close(library);
        return;
    }
#endif

    memcpy(target, source, sizeof(NitsFT));

    Atomic_Swap(targetNitsPresence, NitsActive);

    Shlib_Close(library);
}

static unsigned DoesBinaryMatch(
    _In_ PAL_Char *list,
    _In_z_ PAL_Char *name)
{
    for (;;)
    {
#ifdef _MSC_VER
#pragma prefast(push)
#pragma prefast (disable: 26006)
#endif
        if (*list == '\0')
            return FALSE;
        
        // This is to enable specifying a substring of the 
        // target string; it is useful when all your target dll have some common substring
        if(*list == PAL_T('*'))
        {
            list++;
            if(Tcsstr(name, list))
                return TRUE;
        }
        else if (Tcscasecmp(list, name) == 0)
            return TRUE;

#ifdef _MSC_VER
#pragma prefast(pop)
#endif
        list += Tcslen(list) + 1;
    }
}

#ifdef _MSC_VER
typedef HMODULE LoadedModule;
#else
typedef struct _LoadedModule
{
    PAL_Char *modulePath;
    PAL_Char *moduleBaseName;
} LoadedModule;
#endif

BOOL EnumProcessModulesHelper(
        _Out_writes_bytes_(cb) LoadedModule *lphModule,
        _In_ DWORD cb,
        _Out_ LPDWORD lpcbNeeded)
{
#ifdef _MSC_VER
    return EnumProcessModules(GetCurrentProcess(), lphModule, cb, lpcbNeeded);
#else
    char buf[MAX_PATH] = "/proc/";
    DWORD count = 0;
    DWORD maxCount = cb / sizeof(LoadedModule);

    ostringstream s;
    string convertedString;
    s << Process_ID();
    convertedString = s.str();
    Strlcpy(buf + 6, convertedString.c_str(), MAX_PATH - 6);
    Strlcpy(buf + Strlen(buf), "/maps", 6);
    
    ifstream file(buf);

    if (!file.good())
    {
        NitsErr << PAL_T("ERROR: maps file could not be loaded for current process");
        return FALSE;
    }
    while (file.good() && (count < maxCount))
    {
        char *startOfPath = NULL;
        PAL_Char *nextFwdSlash = NULL;
        PAL_Char *currentBaseName = NULL;
        lphModule[count].modulePath = NULL;
        lphModule[count].moduleBaseName = NULL;
        file >> buf;
        startOfPath = strchr(buf, '/');
        if(startOfPath)
        {
            lphModule[count].modulePath = ConvertStringAToPalChar(startOfPath);
            if(lphModule[count].modulePath == NULL)
            {
                NitsErr << PAL_T("ERROR: could not allocate memory while enumerating modules");
                return FALSE;
            }

            currentBaseName = lphModule[count].modulePath;
            nextFwdSlash = Tcschr(currentBaseName, PAL_T('/'));
            while(nextFwdSlash && nextFwdSlash[1] != PAL_T('\0'))
            {
                currentBaseName = nextFwdSlash + 1;
                nextFwdSlash = Tcschr(currentBaseName, PAL_T('/'));
            }
            lphModule[count].moduleBaseName = currentBaseName;

            count++;
        }
    }

    *lpcbNeeded = count * sizeof(LoadedModule);
    return TRUE;
#endif
}

DWORD GetModuleBaseNameHelper(
        _In_opt_ LoadedModule hModule,
        _Out_writes_z_(nSize) PAL_Char *lpBaseName,
        _In_ DWORD nSize)
{
#ifdef _MSC_VER
    return GetModuleBaseName(GetCurrentProcess(), hModule, lpBaseName, nSize);
#else
    if(hModule.moduleBaseName == NULL)
        return 0;

    return Tcslcpy(lpBaseName, hModule.moduleBaseName, nSize);
#endif
}

void ProcessPatches(TestSystem::Globals *globals)
{
    if (globals->m_runLock == 0)
        return;

    unsigned isMatch = 0;
    /* Sweep through the list of loaded modules. */
    LoadedModule modules[200];
    DWORD size = 0;
    unsigned count = 0;
    unsigned i;
    PAL_Char name[MAX_PATH];

    if (EnumProcessModulesHelper(modules, sizeof(modules), &size) == FALSE)
    {
        return;
    }

    count = size / sizeof(LoadedModule);

    if (globals->m_binaryFilter[0] == '\0')
    {
        /* There is no filter. Match target binaries in any process. */
        isMatch = 1;
    }
    else for (i = 0; i < count; i++)
    {
        if(GetModuleBaseNameHelper(modules[i], name, MAX_PATH) == 0)
            continue;
        
        if (DoesBinaryMatch(globals->m_binaryFilter, name))
        {
            /* Found one of the filter modules. */
            isMatch = 1;
            break;
        }
    }

    if (isMatch == 0)
        goto Cleanup;

    for (i = 0; i < count; i++)
    {
        if(GetModuleBaseNameHelper(modules[i], name, MAX_PATH) == 0)
            continue;
        
        if (DoesBinaryMatch(globals->m_binaryTarget, name))
        {
            /* Found a target module. Trap this! */
            PatchBinary(name);
        }
    }
    
Cleanup:
#ifndef _MSC_VER
    if(count != 0)
    {
        for(i = 0; i < count; i++)
        {
            if(modules[i].modulePath != NULL)
            {
                delete [] (modules[i].modulePath);
            }
        }
    }
#endif
    return;
}

static TestSystem::Globals g_tempGlobals;

static Lock g_signalSemaphoreCleanupLock = LOCK_INITIALIZER;
static NamedSem g_signalSemaphore;
static PAL_Boolean g_signalSemaphoreInitialized = PAL_FALSE;
static NamedSem g_waitSemaphore;
static PAL_Boolean g_waitSemaphoreInitialized = PAL_FALSE;
static NamedSem g_lockSemaphore;
static PAL_Boolean g_lockSemaphoreInitialized = PAL_FALSE;
static TestSystem::Globals *g_globals = NULL;
static TestSystem::InjectorTarget *g_injectorTarget = NULL;
static Shmem g_mapping;
volatile ptrdiff_t *g_status = NULL;
static volatile ptrdiff_t s_injectorStopping;

unsigned long InjectorSetup()
{
    void *start;
    size_t bytes = sizeof(TestSystem::Globals) + sizeof(SharedSegmentHeader);
    PAL_Char nameSignal[128] = PAL_T(CONFIG_SEMNAMELOCALPREFIX) PAL_T("NitsInjectorIn_");
    PAL_Char nameWait[128] = PAL_T(CONFIG_SEMNAMELOCALPREFIX) PAL_T("NitsInjectorOut_");
    PAL_Char conversionBuf[64] = PAL_T("");    
    const PAL_Char *convertedStr = NULL;
    size_t convertedSize = 0;
    // the lock semaphore is kept non-windows only since the issue with the injector is not observed on windows
    PAL_Char nameLock[128] = PAL_T(CONFIG_SEMNAMELOCALPREFIX) PAL_T("NitsInjectorLock_");    

    const PAL_Char globalMappingName[] = PAL_T(CONFIG_SHMNAMEGLOBALPREFIX) PAL_T("NitsGlobalData");

    s_injectorStopping = 0;    
    
    /* TODO: portable shared memory mapping. */
    if(Shmem_Open(&g_mapping, globalMappingName, SHMEM_ACCESS_READWRITE, SHMEM_USER_ACCESS_ALLOW_ALL, bytes) != NO_ERROR)
    {
#ifdef _MSC_VER
        return GetLastError();
#else
        return ERROR_OUTOFMEMORY;
#endif
    }
    
    start = Shmem_Map(&g_mapping, SHMEM_ACCESS_READWRITE, 0, bytes);
    if (start == NULL)
    {
#ifdef _MSC_VER
        return GetLastError();
#else
        return ERROR_OUTOFMEMORY;
#endif
    }

    /* The shared memory is mapped. Make sure the contents are initialized. */
    g_status = (ptrdiff_t *)start;
    g_globals = (TestSystem::Globals *)((char*)g_status + sizeof(SharedSegmentHeader));
    if (Atomic_CompareAndSwap(g_status, TestSystem::Unloaded, TestSystem::Loading) == TestSystem::Unloaded)
    {
        memcpy(g_globals, &g_tempGlobals, sizeof(TestSystem::Globals));
        *g_status = TestSystem::Loaded;
    }

    while (*g_status != TestSystem::Loaded)
    {
        Sleep_Milliseconds(10);
    }

    /* The shared memory space is now initialized. */
    /* Set up the semaphores used to trigger patching. */

    if (g_globals->m_version != TestSystem::SharedMemoryVersion)
    {
        g_globals = NULL;
        return 0;
    }

    TcsFromUInt64(conversionBuf, Process_ID(), &convertedStr, &convertedSize);
    Tcscat(nameSignal, 128, convertedStr);
    Tcscat(nameWait, 128, convertedStr);
    
    /* The semaphre signalled by the product to us. */
    g_signalSemaphoreInitialized = (0 == NamedSem_Open_Injected(&g_signalSemaphore, SEM_USER_ACCESS_ALLOW_ALL, 0, nameSignal, NAMEDSEM_FLAG_CREATE, NitsReservedCallSite()));

    /* The product waits on this semaphre to continue execution. */
    g_waitSemaphoreInitialized = (0 == NamedSem_Open_Injected(&g_waitSemaphore, SEM_USER_ACCESS_ALLOW_ALL, 0, nameWait, NAMEDSEM_FLAG_CREATE, NitsReservedCallSite()));

    Tcscat(nameLock, 128, convertedStr);

    /* The product or unittest framework use this as a locking mechanism to access the wait and signal semaphore */
    g_lockSemaphoreInitialized = (0 == NamedSem_Open_Injected(&g_lockSemaphore, SEM_USER_ACCESS_ALLOW_ALL, 1, nameLock, NAMEDSEM_FLAG_CREATE, NitsReservedCallSite()));

    /* Register the handles with the harness. */
    for (int i = 0; i < TestSystem::InjectorListSize; i++)
    {
        TestSystem::InjectorTarget *target = g_globals->m_injectors + i;

        if (target->process != 0)
            continue;

        if (Atomic_CompareAndSwap(&target->process, 0, Process_ID()) != 0)
            continue;
#ifdef _MSC_VER
        target->signalSemaphore = g_signalSemaphore.handle;
        target->waitSemaphore = g_waitSemaphore.handle;
        target->lockSemaphore = g_lockSemaphore.handle;
#endif
        g_injectorTarget = target;
        break;
    }

    return 0;
}

void CloseSemaphoreIfRequired(PAL_Boolean *initialized, NamedSem *semaphore)
{
    if(*initialized)
    {
        NamedSem_Close(semaphore);
        NamedSem_Destroy(semaphore);
        *initialized = PAL_FALSE;
    }
}

#ifdef _MSC_VER
HMODULE s_injectorModule = NULL;
#endif

static volatile ptrdiff_t s_injectorRefs = 0;

#define INJECTOR_STOPPED  0
#define INJECTOR_STARTING 1
#define INJECTOR_STOPPING 2
#define INJECTOR_RUNNING  3

static Thread s_injectorThread;
static volatile ptrdiff_t s_injectorThreadState = 0;

#define INJECTOR_THREAD_NOT_RUNNING 0
#define INJECTOR_THREAD_RUNNING 1

void Unload()
{
    if(g_injectorTarget != NULL)
    {
        /* Unregister with the harness. */
        Atomic_CompareAndSwap(&(g_injectorTarget->process), Process_ID(), 0);
    }

    if(g_status)
    {
        g_injectorTarget = NULL;
        Shmem_Unmap(&g_mapping, const_cast<ptrdiff_t *>(g_status), sizeof(TestSystem::Globals) + sizeof(SharedSegmentHeader));
        Shmem_Close(&g_mapping);
    }

    CloseSemaphoreIfRequired(&g_waitSemaphoreInitialized, &g_waitSemaphore);
    CloseSemaphoreIfRequired(&g_lockSemaphoreInitialized, &g_lockSemaphore);
    // if other thread calling NitsStopInjector is posting on this semaphore
    // it will grab the same lock and till it is done, we should not close it
    Lock_Acquire(&g_signalSemaphoreCleanupLock);    
    CloseSemaphoreIfRequired(&g_signalSemaphoreInitialized, &g_signalSemaphore);        
    Lock_Release(&g_signalSemaphoreCleanupLock);    

    Atomic_Swap(&s_injectorThreadState, INJECTOR_THREAD_NOT_RUNNING);

#ifdef _MSC_VER
    FreeLibrary(s_injectorModule);
#endif
    //printf("\nUnloading injector\n");
}


NITS_EXTERN_C PAL_Uint32 THREAD_API InjectorProc(_In_ void * param)
{
    PAL_UNUSED(param);

    Atomic_Swap(&s_injectorThreadState, INJECTOR_THREAD_RUNNING);

    if (g_globals == NULL || g_injectorTarget == NULL)
    {
        //printf("\nUnloading injector since globals is null\n");
        Unload();
        return 0;
    }

    for(;;)
    {
        int waitResult = NamedSem_TimedWait(&g_signalSemaphore, 10000);

        if (g_globals->m_unload ||
            g_globals->m_version != TestSystem::SharedMemoryVersion ||
            g_injectorTarget->process != (long)Process_ID() ||
            s_injectorStopping)
        {
            NamedSem_Post(&g_waitSemaphore, 1);
            Unload();
            //printf("\nUnloading injector since globals told it to\n");
            return 0;
        }

        // if the wait timed out, no need to patch; just go back to wait
        if(waitResult != 0)
        {
            continue;
        }

        ProcessPatches(g_globals);

        NamedSem_Post(&g_waitSemaphore, 1);
    }
}

PAL_BEGIN_EXTERNC


NITS_DLLEXPORT int NITS_CALL NitsStartInjector(void)
{
    int result;
    ptrdiff_t oldRefs;
    ptrdiff_t newRefs;
    ptrdiff_t swapRefs;

    for (;;)
    {
        oldRefs = PAL_PREFETCH(&s_injectorRefs);

        if (oldRefs >= INJECTOR_RUNNING)
            newRefs = oldRefs + 1;
        else if (oldRefs == INJECTOR_STOPPED)
            newRefs = INJECTOR_STARTING;
        else
            continue;

        swapRefs = Atomic_CompareAndSwap(&s_injectorRefs, oldRefs, newRefs);
        if (swapRefs == oldRefs)
            break;
    }

    if (newRefs != INJECTOR_STARTING)
        return 0;

    // this is a quick transient state which will end as soon as the previous injector proc finishes
    for(;;)
    {
        ptrdiff_t injectorThreadState = PAL_PREFETCH(&s_injectorThreadState);
        if(injectorThreadState == INJECTOR_THREAD_NOT_RUNNING)
            break;
        Sleep_Milliseconds(10); 
    }

    result = InjectorSetup();
    if (result)
        goto Fail;

#ifdef _MSC_VER
    HMODULE hModule;
    void *address = Unload;
    if (!GetModuleHandleEx(
            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
            (LPCTSTR)address,
            &hModule))
        goto Fail;

    s_injectorModule = hModule;
#endif

    result = Thread_CreateJoinable_Injected(&s_injectorThread, InjectorProc, NULL, NULL, NitsReservedCallSite());
    if (result)
        goto Fail;

    s_injectorRefs = INJECTOR_RUNNING;
    return 0;

Fail:
    s_injectorRefs = INJECTOR_STOPPED;
    return result;
}

NITS_DLLEXPORT void NITS_CALL NitsStopInjector(void)
{
    ptrdiff_t oldRefs;
    ptrdiff_t newRefs;
    ptrdiff_t swapRefs;

    for (;;)
    {
        oldRefs = PAL_PREFETCH(&s_injectorRefs);

        if (oldRefs > INJECTOR_RUNNING)
            newRefs = oldRefs - 1;
        else if (oldRefs == INJECTOR_RUNNING)
            newRefs = INJECTOR_STOPPING;
        else if (oldRefs == INJECTOR_STOPPED)
            abort();
        else
            continue;

        swapRefs = Atomic_CompareAndSwap(&s_injectorRefs, oldRefs, newRefs);
        if (swapRefs == oldRefs)
            break;
    }

    if (newRefs != INJECTOR_STOPPING)
        return;

    s_injectorStopping = 1;
    
    // if injector has already called Unload and ended InjectorProc
    // in that case we dont need to post signalsemaphore
    // in other case we will post it since that will make the thread end quicker
    Lock_Acquire(&g_signalSemaphoreCleanupLock);    
    if(g_signalSemaphoreInitialized)
    {
        NamedSem_Post(&g_signalSemaphore, 1);
    }
    Lock_Release(&g_signalSemaphoreCleanupLock);

    PAL_Uint32 result = 0;
    // on windows this can deadlock due to loader lock 
    // preventing freelibrary call from other thread
    // not doing this is fine since other thread(InjectorProc) already has a ref of itself
    // and it is not touching the signalSemaphore if it was marked for deletion
    
#ifndef _MSC_VER
    Thread_Join(&s_injectorThread, &result);
#endif

    Thread_Destroy(&s_injectorThread);
        
    s_injectorRefs = INJECTOR_STOPPED;
    
}

PAL_END_EXTERNC


ViewCVS 0.9.2