/////////////////////////////////////////////////////////////////////////////
//
//  Module: disas.cpp (disas.exe - Detours Test Program)
//
//  Microsoft Research Detours Package
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//

#define DETOURS_INTERNAL
#include <detours.h>
#include <stdio.h>
#include <stdlib.h>

///////////////////////////////////////////////////////////////////////// ARM.
//
#ifdef DETOURS_ARM

extern "C" BYTE TestCodes[];

void DumpMemoryFragment(PBYTE pbData, ULONG cbData, ULONG cbSpace)
{
    ULONG n = 0;
    if (cbData >= 4) {
        printf("%04x%04x ", ((PUSHORT)pbData)[0], ((PUSHORT)pbData)[1]);
        n += 4;
    }
    else if (cbData >= 2) {
        printf("%04x ", *((PUSHORT)pbData));
        n += 2;
    }

    for (; n < cbSpace; n++) {
        if (n < cbData) {
            printf("%02x", pbData[n]);
        }
        else {
            printf("  ");
        }
    }
    if (n < cbData) {
        printf(".");
    }
    else {
        printf(" ");
    }
}

inline ULONG fetch_thumb_opcode(PBYTE pbCode)
{
    ULONG Opcode = *(UINT16 *)&pbCode[0];
    if (Opcode >= 0xe800) {
        Opcode = (Opcode << 16) | *(UINT16 *)&pbCode[2];
    }
    return Opcode;
}

BOOL IsTerminate(PBYTE pbSrc)
{
    ULONG opcode = fetch_thumb_opcode(pbSrc);

    if ((opcode & 0xff87) == 0x4700) {
        // bx r
        return TRUE;
    }

#if 0
    if ((opcode & 0xfbf08f00) == 0xf2400c00) {          // movw r12,#xxxx
        return TRUE;
    }

    if ((opcode == 0xf8dcf000) {                 // ldr  pc,[r12]
                ULONG Immediate = ((opcode2 << 12) & 0xf7000000) |
                                  ((opcode2 <<  1) & 0x08000000) |
                                  ((opcode2 << 16) & 0x00ff0000) |
                                  ((opcode  >>  4) & 0x0000f700) |
                                  ((opcode  >> 15) & 0x00000800) |
                                  ((opcode  >>  0) & 0x000000ff);
                PBYTE pbTarget = *(PBYTE *)Immediate;
                if (detour_is_imported(pbCode, pbTarget)) {
                    PBYTE pbNew = *(PBYTE *)pbTarget;
                    DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew));
                    return pbNew;
                }
            }
        }
    }
#endif

    return FALSE;
}

#endif // DETOURS_ARM

///////////////////////////////////////////////////////////////// X86 and X64.
//
#if defined(DETOURS_X86) || defined(DETOURS_X64)

extern "C" BYTE TestCodes[];

void DumpMemoryFragment(PBYTE pbData, ULONG cbData, ULONG cbSpace)
{
    ULONG n = 0;
    for (; n < cbSpace; n++) {
        if (n < cbData) {
            printf("%02x", pbData[n]);
        }
        else {
            printf("  ");
        }
    }
    if (n < cbData) {
        printf(".");
    }
    else {
        printf(" ");
    }
}

BOOL IsTerminate(PBYTE pbSrc)
{
    if ((0xC3 == pbSrc[0] && 0x00 == pbSrc[1]) ||       // bx lr
        0xCB == pbSrc[0] ||                             // RETF
        0xC2 == pbSrc[0] ||                             // RET dw
        0xCA == pbSrc[0] ||                             // RETF dw
        0xEB == pbSrc[0] ||                             // JMP ob
        0xE9 == pbSrc[0] ||                             // JMP ol
        0xEA == pbSrc[0]) {                             // JMP ol

        return TRUE;
    }
    if (0xff == pbSrc[0] && 0x25 == pbSrc[1])           // JMP [addr]
        return TRUE;
    return FALSE;
}

#endif // DETOURS_X86 || DETOURS_X64

/////////////////////////////////////////////////////////// X86, X64, and ARM.
//
#if defined(DETOURS_X86) || defined(DETOURS_X64) || defined(DETOURS_ARM)
struct BasicBlockLink
{
  public:
    BasicBlockLink *    m_pNext;
    PBYTE               m_pbEntry;
    PCHAR               m_pszName;

  public:
    BasicBlockLink(PBYTE pbEntry, PCHAR pszName = NULL)
    {
        m_pNext = NULL;
        m_pbEntry = pbEntry;
        m_pszName = pszName;

        *s_ppTail = this;
        s_ppTail = &m_pNext;
    }

    BasicBlockLink * Next()
    {
        return m_pNext;
    }

    static BasicBlockLink * GetListHead()
    {
        return s_pHead;
    }

  protected:
    static BasicBlockLink *     s_pHead;
    static BasicBlockLink **    s_ppTail;
};

BasicBlockLink *    BasicBlockLink::s_pHead = NULL;
BasicBlockLink **   BasicBlockLink::s_ppTail = &BasicBlockLink::s_pHead;

static PBYTE s_pbBegin = NULL;
static PBYTE s_pbLimit = NULL;

int TestDetourCopyInstruction(PBYTE pbSrcInstruction, PCHAR pszFunction)
{
    PBYTE pbSrc = pbSrcInstruction;
    ULONG nIns = 0;

    if (pszFunction) {
        printf("%s:\n", pszFunction);
    }
    for (; nIns < 4096; nIns++) {
        BYTE rbDst[128];
        PVOID pbDstPool = (PVOID)(rbDst + sizeof(rbDst));
        LONG lExtra = 0;
        PVOID pbTarget = NULL;
        ULONG cbStep = (ULONG)((PBYTE)DetourCopyInstruction(rbDst, &pbDstPool, pbSrc,
                                                            &pbTarget, &lExtra) - pbSrc);

        printf("    %p:", pbSrc);
        DumpMemoryFragment(rbDst, cbStep, 10);
        printf(" ");
        DumpMemoryFragment(rbDst, cbStep, 10);
        if (pbTarget) {
            if (pbTarget == DETOUR_INSTRUCTION_TARGET_DYNAMIC) {
                printf("  Dynamic\n");
            }
            else {
                printf(" %p%c\n", pbTarget,
                       (pbTarget >= s_pbBegin && pbTarget < s_pbLimit) ? ' ' : '!');
            }
        }
        else {
            printf("\n");
        }

        if (pbTarget && pbTarget != DETOUR_INSTRUCTION_TARGET_DYNAMIC) {
            if (pbTarget > pbSrc &&
                pbTarget >= s_pbBegin &&
                pbTarget < s_pbLimit
               ) {
                (void) new BasicBlockLink((PBYTE)pbTarget, NULL);
            }
        }

        if (IsTerminate(pbSrc)) {
            break;
        }

        pbSrc += cbStep;
    }
    return nIns;
}

BOOL CALLBACK ExportCallback(_In_opt_ PVOID pContext,
                             _In_ ULONG nOrdinal,
                             _In_opt_ LPCSTR pszName,
                             _In_opt_ PVOID pCode)
{
    (void)pContext;
    (void)nOrdinal;
    (void)pCode;

    (VOID) new BasicBlockLink((PBYTE)pCode, pszName ? pszName : "[NO NAME]");
    return TRUE;
}
#endif // DETOURS_X86 || DETOURS_X64

//////////////////////////////////////////////////////////////////////// IA64.
//
#ifdef DETOURS_IA64
#pragma warning(disable: 4201)  // ignore warning about unnamed sturcture in union.

void DumpHi(PBYTE pbData, ULONG cbData, ULONG cbSpace)
{
    ULONG n = 0;
    for (; n < cbSpace; n++) {
        if (n < cbData) {
            printf("%02x", pbData[(cbData - 1) - n]);
        }
        else {
            printf("  ");
        }
    }
    printf("\n");
}

struct DETOUR_IA64_BUNDLE_DISASSEMBLE : public DETOUR_IA64_BUNDLE
{
  public:
    void SetBrx(UINT64 raw)
    {
        SetBrl();
        SetBrlImm(raw);
    }

    void Dis()
    {
        const char szUnitNames[17] = "?aimbflx?AIMBFLX";

        printf("%p: ", data);
        BYTE nTemplate = GetTemplate();
        BYTE nInst0 = GetInst0();
        BYTE nInst1 = GetInst1();
        BYTE nInst2 = GetInst2();
        BYTE nUnit0 = GetUnit0();
        BYTE nUnit1 = GetUnit1();
        BYTE nUnit2 = GetUnit2();
        if (nUnit1 == L_UNIT) { // MLX instruction
            UINT64 d2 = (
                         //          0x0000000000fffff0
                         ((wide[1] & 0x00fffff000000000) >> 32) |
                         //          0x000000ffff000000
                         ((wide[0] & 0xffff000000000000) >> 24) |
                         //          0x7fffff0000000000
                         ((wide[1] & 0x00000000007fffff) << 40) |
                         //          0x8000000000000000
                         ((wide[1] & 0x0800000000000000) <<  4)
                        );
            printf("%02x %c%01x %010I64lx %c%01x %016I64lx",
                   nTemplate,
                   szUnitNames[nUnit0], nInst0, GetData0(),
                   szUnitNames[nUnit2], nInst2, d2);
        }
        else {
            printf("%02x %c%01x %010I64lx %c%01x %010I64lx %c%01x %010I64lx",
                   nTemplate,
                   szUnitNames[nUnit0], nInst0, GetData0(),
                   szUnitNames[nUnit1], nInst1, GetData1(),
                   szUnitNames[nUnit2], nInst2, GetData2());
        }

        if (IsBrl()) {
            printf(" brl  %p", GetBrlTarget());
        }
        else if (IsMovlGp()) {
            printf(" movl gp=%p", GetMovlGp());
        }
        if ((wide[0] & 0xfffffc000603ffff) == 0x002024000200100b &&
            wide[1] == 0x0004000000203008) {

            ULONG64 offset =
                ((wide[0] & 0x0000000001fc0000) >> 18) |  // imm7b
                ((wide[0] & 0x000001ff00000000) >> 25) |  // imm9d
                ((wide[0] & 0x00000000f8000000) >> 11);   // imm5c
            if (wide[0] & 0x0000020000000000) {
                offset |= 0xffffffffffe00000;
            }
            printf(" imm=%016I64lx", offset);
        }
        printf("\n");
    }
};

//////////////////////////////////////////////////////////////////////////////
//
BOOL CALLBACK ExportCallbackIA64(_In_opt_ PVOID pContext,
                                 _In_ ULONG nOrdinal,
                                 _In_opt_ LPCSTR pszName,
                                 _In_opt_ PVOID pCode)
{
    (void)pContext;
    (void)nOrdinal;

    DETOUR_IA64_BUNDLE_DISASSEMBLE *pb = *(DETOUR_IA64_BUNDLE_DISASSEMBLE **)pCode;
    DETOUR_IA64_BUNDLE temp;

    if (!pb[0].Copy(&temp)) {
        printf("%s:\n  ", pszName ? pszName : "[NO NAME]");
        pb[0].Dis();
    }
    return TRUE;
}

#if 0
void TestBoth()
{
    LPVOID pvBase = VirtualAlloc((PBYTE)0x800000000, 0x10000,
                                 MEM_RESERVE | MEM_COMMIT,
                                 PAGE_EXECUTE_READWRITE);

    DETOUR_IA64_BUNDLE *pbBase = (DETOUR_IA64_BUNDLE *)pvBase;
    DETOUR_IA64_BUNDLE *pb = pbBase;

    printf("TestBoth:\n");
    for (UINT64 i = 0x10; i < 0x8000000000000000; i <<= 1) {
        pb->SetMovlGp(i);
        if (pb->GetMovlGp() != i) {
            printf("Error in MovlGp!\n");
            return;
        }
        pb++;

        pb->SetBrl(i);
        if (pb->GetBrlEip() != i) {
            printf("Error in Brl!\n");
            return;
        }
        pb++;
    }

    for (UINT64 i = (UINT64)(INT64)-0x10; i > 0; i <<= 1) {
        pb->SetMovlGp(i);
        if (pb->GetMovlGp() != i) {
            printf("Error in MovlGp!\n");
            return;
        }
        pb++;

        pb->SetBrl(i);
        if (pb->GetBrlEip() != i) {
            printf("Error in Brl!\n");
            return;
        }
        pb++;
    }

    printf("u %p %p\n", pbBase, pb);
}
#endif
#endif // DETOURS_IA64

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR lpszCmdLine, int nCmdShow)
{
    (void)hprev;
    (void)hinst;
    (void)lpszCmdLine;
    (void)nCmdShow;

    // Bug report, but it works here.
    // 07ff8`4b783054 49ba 70b3d93a d40fb998 mov r10,98B90FD43AD9B370h
    //
    {
        static const UCHAR mov_r10_imm64[] = {0x49, 0xba, 1, 2, 3, 4, 5, 6, 7, 8 };

        PVOID const after = DetourCopyInstructionX64(0, 0, const_cast<PUCHAR>(mov_r10_imm64), 0, 0);

        if (after != &mov_r10_imm64 + 1)
        {
            printf("mov_r10_imm64 failed, expected:%p vs. got:%p\n", &mov_r10_imm64 + 1, after);
            if (IsDebuggerPresent())
            {
                __debugbreak();
                DetourCopyInstructionX64(0, 0, const_cast<PUCHAR>(mov_r10_imm64), 0, 0);
            }
            return 1;
        }
    }

#ifdef DETOURS_IA64
    // First we check the pre-canned TestCodes from disasm.asm
    //
    PBYTE pbTest = *(PBYTE*)WinMain;
    for (;; pbTest += 16) {
        DETOUR_IA64_BUNDLE_DISASSEMBLE *pb = (DETOUR_IA64_BUNDLE_DISASSEMBLE *)pbTest;

        pb->Dis();
        if (pbTest[0] == 0xff) {
            break;
        }
        DumpHi(pbTest, 16, 16);
    }

#if 0
    printf("\n\n");

    DETOUR_IA64_BUNDLE_DISASSEMBLE *pb = (DETOUR_IA64_BUNDLE_DISASSEMBLE *)pbTest;
    DETOUR_IA64_BUNDLE_DISASSEMBLE *pbBeg = pb;
    DWORD dwOld;
    VirtualProtect(pb, 0x2000, PAGE_EXECUTE_READWRITE, &dwOld);
    printf("%p: (%d)\n", pb, sizeof(pb));
    pb++;
    printf("%p: (%d)\n", pb, sizeof(pb));
    pb++; pb->SetBrx(0);
    pb++; pb->SetBrx(0);
    pb++; pb->SetBrx(0);
    pb++; pb->SetBrx(0xffffffffffffffff);
    pb++; pb->SetBrx(0x0fffffffffffffff);
    pb++; pb->SetBrx(0x00ffffffffffffff);
    pb++; pb->SetBrx(0x000fffffffffffff);
    pb++; pb->SetBrx(0x0000ffffffffffff);
    pb++; pb->SetBrx(0x00000fffffffffff);
    pb++; pb->SetBrx(0x000000ffffffffff);
    pb++; pb->SetBrx(0x0000000fffffffff);
    pb++; pb->SetBrx(0x00000000ffffffff);
    pb++; pb->SetBrx(0x000000000fffffff);
    pb++; pb->SetBrx(0x0000000000ffffff);
    pb++; pb->SetBrx(0x00000000000fffff);
    pb++; pb->SetBrx(0x000000000000ffff);
    pb++; pb->SetBrx(0x0000000000000fff);
    pb++; pb->SetBrx(0x00000000000000ff);
    pb++; pb->SetBrx(0x000000000000000f);
    pb++; pb->SetBrx(0x0000000000000000);
    pb++; pb->SetBrx(0xffffffffffffffff);
    pb++; pb->SetBrx(0xffffffffffffffff);
    pb->SetInst0(0xff);
    pb->SetData0(0xffffffffffffffff);
    printf("%p:\n", pb);
    DETOUR_IA64_BUNDLE_DISASSEMBLE *pbEnd = pb;
    for (pb = pbBeg; pb < pbEnd; pb++) {
        printf("  %p: ", pb);
        DumpHi((BYTE*)pb, 16, 16);
    }
#endif

#if 1
    {
        // Then we check all of the code we can find in user32.dll
        //
        printf("\n");
        HINSTANCE hInst = LoadLibraryA("user32.dll");
        printf("Loaded: user32.dll: %p\n", hInst);

        PBYTE pbEntry = (PBYTE)DetourGetEntryPoint(hInst);
        printf("Entry: %p\n", pbEntry);
        ExportCallbackIA64(NULL, 0, "[Entry]", pbEntry);
        DetourEnumerateExports(hInst, NULL, ExportCallbackIA64);
    }

    {
        // Then we check all of the code we can find in opengl32.dll
        //
        printf("\n");
        HINSTANCE hInst = LoadLibraryA("opengl32.dll");
        printf("Loaded: opengl32.dll: %p\n", hInst);

        PBYTE pbEntry = (PBYTE)DetourGetEntryPoint(hInst);
        printf("Entry: %p\n", pbEntry);
        ExportCallbackIA64(NULL, 0, "[Entry]", pbEntry);
        DetourEnumerateExports(hInst, NULL, ExportCallbackIA64);
    }

    printf("\n");
    for (HINSTANCE hInst = NULL; (hInst = DetourEnumerateModules(hInst)) != NULL;) {
        CHAR szModuleName[512];
        GetModuleFileNameA(hInst, szModuleName,
                           sizeof(szModuleName)/sizeof(szModuleName[0]));
        printf("%p : %s\n", hInst, szModuleName);
        DetourEnumerateExports(hInst, NULL, ExportCallbackIA64);
    }

    printf("\n");
#endif
#if 0
    TestBoth();
#endif
#endif // DETOURS_IA64

#if defined(DETOURS_X64) || defined(DETOURS_X86)
    // First we check the pre-canned TestCodes from disasm.asm
    //
    PBYTE pbBegin = (PBYTE)DetourCodeFromPointer(TestCodes, NULL);
    printf("%p:\n", pbBegin);
    for (PBYTE pbTest = pbBegin;;) {
        if (pbTest[0] != 0xcc) {    // int 3
            printf("%08lx  ", (ULONG)(pbTest - pbBegin));
            DumpMemoryFragment(pbTest, 8, 8);
            printf("\n");
            printf("failed on last.\n");
            return 1;
        }
        pbTest++;

        if (pbTest[0] == 0x70 || pbTest[0] == 0x71) {
            printf("[%p]:\n", pbTest);
        }
        BYTE rbDst[128];
        PVOID pbDstPool = (PVOID)(rbDst + sizeof(rbDst));
        LONG lExtra = 0;
        PVOID pbTarget = NULL;
        PBYTE pbNext = (PBYTE)DetourCopyInstruction(rbDst, &pbDstPool, pbTest,
                                                    &pbTarget, &lExtra);

        LONG cbTest = (LONG)(pbNext - pbTest);

        printf("%08lx  ", (ULONG)(pbTest - pbBegin));
        DumpMemoryFragment(pbTest, cbTest, 12);
        printf("[%16p] ", pbTarget);
        DumpMemoryFragment(rbDst, cbTest + lExtra, 11);
        printf("\n");

        if (pbTest[cbTest] != 0xcc) {
            printf("failed!\n");
            return 1;
        }

        pbTest += cbTest;

        if (pbTest[0] == 0xcc && pbTest[1] == 0xcc) {
            break;
        }
    }

#if 0
    // Then we check all of the code we can find in user32.dll
    //
    HINSTANCE hInst = LoadLibraryA("user32.dll");
    printf("Loaded: user32.dll: %p\n", hInst);

    s_pbBegin = (PBYTE)hInst;
    s_pbLimit = s_pbBegin + DetourGetModuleSize(hInst);

    PBYTE pbEntry = DetourGetEntryPoint(hInst);
    (VOID) new BasicBlockLink(pbEntry, "user32.dll");

    DetourEnumerateExports(hInst, NULL, ExportCallback);

    ULONG nIns = 0;
    for (BasicBlockLink *pLink = BasicBlockLink::GetListHead();
         pLink; pLink = pLink->Next()) {

        nIns += TestDetourCopyInstruction(pLink->m_pbEntry, pLink->m_pszName);
        if (nIns > 100000) {
            break;
        }
    }
    printf("Disassembled %d instructions.\n", nIns);
#endif
#endif // DETOURS_X86 || DETOURS_X64

#ifdef DETOURS_ARM
    // Create an output buffer and fill it with debugbreaks.
    //
    PBYTE pbBuffer
        = (PBYTE)VirtualAlloc(NULL, 0x400, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    for (PBYTE pbOut = pbBuffer; pbOut < pbBuffer + 0x400;) {
        *pbOut++ = 0xfe;
        *pbOut++ = 0xde;
    }
    PBYTE pbDst = pbBuffer;
    PVOID pvDstPool = (PVOID)(pbBuffer + 0x400);

    // First we check the pre-canned TestCodes from disasm.asm
    //
    PBYTE pbBegin = (PBYTE)DetourCodeFromPointer(TestCodes, NULL);
    printf("%p: (TestCodes %p) => %p\n", pbBegin, TestCodes, pbBuffer);
    for (PBYTE pbSrc = pbBegin;;) {
        if (pbSrc[0] != 0xfe && pbSrc[1] != 0xde) {    // BREAK
            printf("%08x  ", pbSrc - pbBegin);
            DumpMemoryFragment(pbSrc, 8, 8);
            printf("\n");
            printf("failed on last.\n");
            return 1;
        }
        pbSrc += 2;
        *pbDst++ = 0xfe;
        *pbDst++ = 0xde;

        if ((pbSrc[0] == 0x00 && pbSrc[1] == 0xbf) &&  // NOP
            (pbSrc[2] != 0xfe && pbSrc[3] != 0xde)) {    // BREAK
            // Skip over a single NOP so we can test alignment.
            pbSrc += 2;
        }

        if ((pbSrc[0] == 0x00 && pbSrc[1] == 0xbf) &&  // NOP
            (pbSrc[2] != 0xfe && pbSrc[3] != 0xde)) {    // BREAK
            // If there is a second NOP, then we insert alignment.
            pbSrc += 2;
            *pbDst++ = 0x00;
            *pbDst++ = 0xbf;
        }


        LONG lExtra = 0;
        PVOID pbTarget = NULL;
        PBYTE pbNext = (PBYTE)DetourCopyInstruction(pbDst, &pvDstPool, pbSrc, &pbTarget, &lExtra);

        LONG cbTest = (LONG)(pbNext - pbSrc);

        printf("%08x  ", pbSrc - pbBegin);
        DumpMemoryFragment(pbSrc, cbTest, 4);
        printf("[%8p] ", pbTarget);
        DumpMemoryFragment(pbDst, cbTest + lExtra, 16);
        printf("\n");

        if (pbSrc[cbTest] != 0xfe || pbSrc[cbTest+1] != 0xde) {
            printf("%p: failed! (pbSrc[n]=%02x, pbSrc[n+1]=%02x\n",
                   pbSrc,
                   pbSrc[cbTest], pbSrc[cbTest+1]);
            __debugbreak();
            pbNext = (PBYTE)DetourCopyInstruction(pbDst, &pvDstPool, pbSrc, &pbTarget, &lExtra);
            cbTest = (LONG)(pbNext - pbSrc);
            return 1;
        }

        pbDst += cbTest + lExtra;
        pbSrc += cbTest;

        if (pbSrc[0] == 0xfe && pbSrc[1] == 0xde &&
            pbSrc[2] == 0xfe && pbSrc[3] == 0xde) {
            break;
        }
    }

#if 0
    // Then we check all of the code we can find in user32.dll
    //
    HINSTANCE hInst = LoadLibraryA("user32.dll");
    printf("Loaded: user32.dll: %p\n", hInst);

    s_pbBegin = (PBYTE)hInst;
    s_pbLimit = s_pbBegin + DetourGetModuleSize(hInst);

    PBYTE pbEntry = DetourGetEntryPoint(hInst);
    (VOID) new BasicBlockLink(pbEntry, "user32.dll");

    DetourEnumerateExports(hInst, NULL, ExportCallback);

    ULONG nIns = 0;
    for (BasicBlockLink *pLink = BasicBlockLink::GetListHead();
         pLink; pLink = pLink->Next()) {

        nIns += TestDetourCopyInstruction(pLink->m_pbEntry, pLink->m_pszName);
        if (nIns > 100000) {
            break;
        }
    }
    printf("Disassembled %d instructions.\n", nIns);
#endif
#endif // DETOURS_ARM

    return 0;
}
//
///////////////////////////////////////////////////////////////// End of File.
