diff --git a/GleeBug/Debugger.Loop.cpp b/GleeBug/Debugger.Loop.cpp index 037d32e..f558016 100644 --- a/GleeBug/Debugger.Loop.cpp +++ b/GleeBug/Debugger.Loop.cpp @@ -25,25 +25,32 @@ namespace GleeBug return; } + bool allowEmulation = true; + bool inEmulation = false; + while (!mBreakDebugger) { //wait for a debug event mIsRunning = true; - bool emulatedEvent = false; - // We go over all active processes and see if any emulator has an active event // if thats the case process the emulated event first. - for (auto&& process : mProcesses) + if (inEmulation) { - if (process.second->emulator.WaitForEvent(mDebugEvent)) + inEmulation = false; + + for (auto&& process : mProcesses) { - emulatedEvent = true; + if (process.second->emulator.WaitForEvent(mDebugEvent)) + { + inEmulation = true; + break; + } } } // Emulated events have priority over normal debug events. - if (!emulatedEvent) + if (!inEmulation) { if (!MyWaitForDebugEvent(&mDebugEvent, INFINITE)) break; @@ -137,14 +144,13 @@ namespace GleeBug //continue the debug event - bool continueEmulated = false; - if (mThread->isInternalStepping) + if (allowEmulation && mThread != nullptr && mThread->isSingleStepping) { auto& emulator = mProcess->emulator; - continueEmulated = emulator.Emulate(mThread); + inEmulation = emulator.Emulate(mThread); } - if (continueEmulated == false) + if (inEmulation == false) { if (!ContinueDebugEvent(mDebugEvent.dwProcessId, mDebugEvent.dwThreadId, mContinueStatus)) break; diff --git a/GleeBug/Debugger.Process.Breakpoint.cpp b/GleeBug/Debugger.Process.Breakpoint.cpp index 3551dc6..8c48b84 100644 --- a/GleeBug/Debugger.Process.Breakpoint.cpp +++ b/GleeBug/Debugger.Process.Breakpoint.cpp @@ -225,7 +225,6 @@ namespace GleeBug bool Process::SetNewPageProtection(ptr page, MemoryBreakpointData & data, MemoryType type) { - DPRINTF(); //TODO: handle PAGE_NOACCESS and such correctly (since it cannot be combined with PAGE_GUARD) auto found = memoryBreakpointPages.find(page); @@ -268,7 +267,6 @@ namespace GleeBug bool Process::SetMemoryBreakpoint(ptr address, ptr size, MemoryType type, bool singleshoot) { - DPRINTF(); dprintf("SetMemoryBreakpoint(%p, %p, %d, %d)\n", address, size, type, singleshoot); //TODO: error reporting @@ -421,4 +419,4 @@ namespace GleeBug return false; } } -}; \ No newline at end of file +}; diff --git a/GleeBug/Debugger.Process.cpp b/GleeBug/Debugger.Process.cpp index 4523260..5e61f8d 100644 --- a/GleeBug/Debugger.Process.cpp +++ b/GleeBug/Debugger.Process.cpp @@ -15,7 +15,8 @@ namespace GleeBug createProcessInfo(createProcessInfo), thread(nullptr), systemBreakpoint(false), - permanentDep(false) + permanentDep(false), + emulator(this) { for (int i = 0; i < HWBP_COUNT; i++) hardwareBreakpoints[i].internal.hardware.enabled = false; diff --git a/GleeBug/Debugger.Process.h b/GleeBug/Debugger.Process.h index d86da64..86b8860 100644 --- a/GleeBug/Debugger.Process.h +++ b/GleeBug/Debugger.Process.h @@ -32,7 +32,7 @@ namespace GleeBug BreakpointInfo hardwareBreakpoints[4]; MemoryBreakpointSet memoryBreakpointRanges; MemoryBreakpointMap memoryBreakpointPages; - Emulator emulator; + X86Emulator emulator; /** \brief Constructor. diff --git a/GleeBug/Emulation.Helpers.h b/GleeBug/Emulation.Helpers.h index 2eae764..edda6db 100644 --- a/GleeBug/Emulation.Helpers.h +++ b/GleeBug/Emulation.Helpers.h @@ -1,6 +1,8 @@ #ifndef EMULATIONHELPERS_H #define EMULATIONHELPERS_H +#include + namespace GleeBug { @@ -9,50 +11,70 @@ inline T getRegisterValue(const ZydisRegister reg, Registers& regs) { switch (reg) { + // 64 Bit. case ZYDIS_REGISTER_RAX: return (T)regs.Rax.Get(); - case ZYDIS_REGISTER_RBX: return (T)regs.Rbx.Get(); - case ZYDIS_REGISTER_RCX: return (T)regs.Rcx.Get(); - case ZYDIS_REGISTER_RDX: return (T)regs.Rdx.Get(); - case ZYDIS_REGISTER_RBP: return (T)regs.Rbp.Get(); - case ZYDIS_REGISTER_RSP: return (T)regs.Rsp.Get(); - case ZYDIS_REGISTER_RSI: return (T)regs.Rsi.Get(); - case ZYDIS_REGISTER_RDI: return (T)regs.Rdi.Get(); - case ZYDIS_REGISTER_R8: return (T)regs.R8.Get(); - case ZYDIS_REGISTER_R9: return (T)regs.R9.Get(); - case ZYDIS_REGISTER_R10: return (T)regs.R10.Get(); - case ZYDIS_REGISTER_R11: return (T)regs.R11.Get(); - case ZYDIS_REGISTER_R12: return (T)regs.R12.Get(); - case ZYDIS_REGISTER_R13: return (T)regs.R13.Get(); - case ZYDIS_REGISTER_R14: return (T)regs.R14.Get(); + // 32 bit + case ZYDIS_REGISTER_EAX: + return (T)regs.Eax.Get(); + case ZYDIS_REGISTER_EBX: + return (T)regs.Ebx.Get(); + case ZYDIS_REGISTER_ECX: + return (T)regs.Ecx.Get(); + case ZYDIS_REGISTER_EDX: + return (T)regs.Edx.Get(); + case ZYDIS_REGISTER_EBP: + return (T)regs.Ebp.Get(); + case ZYDIS_REGISTER_ESP: + return (T)regs.Esp.Get(); + case ZYDIS_REGISTER_ESI: + return (T)regs.Esi.Get(); + case ZYDIS_REGISTER_EDI: + return (T)regs.Edi.Get(); + case ZYDIS_REGISTER_R8D: + return (T)regs.R8d.Get(); + case ZYDIS_REGISTER_R9D: + return (T)regs.R9d.Get(); + case ZYDIS_REGISTER_R10D: + return (T)regs.R10d.Get(); + case ZYDIS_REGISTER_R11D: + return (T)regs.R11d.Get(); + case ZYDIS_REGISTER_R12D: + return (T)regs.R12d.Get(); + case ZYDIS_REGISTER_R13D: + return (T)regs.R13d.Get(); + case ZYDIS_REGISTER_R14D: + return (T)regs.R14d.Get(); + default: + assert(false); } } @@ -61,50 +83,70 @@ inline void setRegisterValue(const ZydisRegister reg, Registers& regs, T val) { switch (reg) { + // 64 bit. case ZYDIS_REGISTER_RAX: return regs.Rax.Set(val); - case ZYDIS_REGISTER_RBX: return regs.Rbx.Set(val); - case ZYDIS_REGISTER_RCX: return regs.Rcx.Set(val); - case ZYDIS_REGISTER_RDX: return regs.Rdx.Set(val); - case ZYDIS_REGISTER_RBP: return regs.Rbp.Set(val); - case ZYDIS_REGISTER_RSP: return regs.Rsp.Set(val); - case ZYDIS_REGISTER_RSI: return regs.Rsi.Set(val); - case ZYDIS_REGISTER_RDI: return regs.Rdi.Set(val); - case ZYDIS_REGISTER_R8: return regs.R8.Set(val); - case ZYDIS_REGISTER_R9: return regs.R9.Set(val); - case ZYDIS_REGISTER_R10: return regs.R10.Set(val); - case ZYDIS_REGISTER_R11: return regs.R11.Set(val); - case ZYDIS_REGISTER_R12: return regs.R12.Set(val); - case ZYDIS_REGISTER_R13: return regs.R13.Set(val); - case ZYDIS_REGISTER_R14: return regs.R14.Set(val); + // 32 bit + case ZYDIS_REGISTER_EAX: + return regs.Eax.Set(val); + case ZYDIS_REGISTER_EBX: + return regs.Ebx.Set(val); + case ZYDIS_REGISTER_ECX: + return regs.Ecx.Set(val); + case ZYDIS_REGISTER_EDX: + return regs.Edx.Set(val); + case ZYDIS_REGISTER_EBP: + return regs.Ebp.Set(val); + case ZYDIS_REGISTER_ESP: + return regs.Esp.Set(val); + case ZYDIS_REGISTER_ESI: + return regs.Esi.Set(val); + case ZYDIS_REGISTER_EDI: + return regs.Edi.Set(val); + case ZYDIS_REGISTER_R8D: + return regs.R8d.Set(val); + case ZYDIS_REGISTER_R9D: + return regs.R9d.Set(val); + case ZYDIS_REGISTER_R10D: + return regs.R10d.Set(val); + case ZYDIS_REGISTER_R11D: + return regs.R11d.Set(val); + case ZYDIS_REGISTER_R12D: + return regs.R12d.Set(val); + case ZYDIS_REGISTER_R13D: + return regs.R13d.Set(val); + case ZYDIS_REGISTER_R14D: + return regs.R14d.Set(val); + default: + assert(false); } } diff --git a/GleeBug/Emulation.Instructions.cpp b/GleeBug/Emulation.Instructions.cpp index 74cd19e..eadf953 100644 --- a/GleeBug/Emulation.Instructions.cpp +++ b/GleeBug/Emulation.Instructions.cpp @@ -69,13 +69,18 @@ template -struct InstrSig +struct InstrSig : X86Instruction { static constexpr size_t Hash() noexcept { return InstructionDataHash(InstructionDataHash(N)); } + virtual uint16_t GetMnemonic() const + { + return MNEMONIC; + } + virtual bool Matches(const ZydisInstructionInfo& info) const { if(info.mnemonic != MNEMONIC) @@ -95,34 +100,29 @@ struct InstrSig } }; -static std::vector _registeredInstructions; +static std::vector> _registeredInstructions; template struct X86InstructionRegistrator { + T inst; X86InstructionRegistrator() { - static T inst; - _registeredInstructions.push_back(&inst); + uint16_t groupId = inst.GetMnemonic(); + if (groupId >= _registeredInstructions.size()) + { + _registeredInstructions.resize(groupId + 1); + } + _registeredInstructions[groupId].push_back(&inst); } }; -const X86Instruction* LookupInstruction(const ZydisInstructionInfo& info) -{ - for (X86Instruction *instr : _registeredInstructions) - { - if(instr->Matches(info)) - return instr; - } - return nullptr; -} - -#define REGISTER_X86_INSTRUCTION(instrCls) static X86InstructionRegistrator<##instrCls##> __##instrCls##__() +#define REGISTER_X86_INSTRUCTION(instrCls) static X86InstructionRegistrator<##instrCls##> __##instrCls##__ template struct SubImpl { - static T Execute(int32_t val1, int32_t val2, Registers& regs, uint32_t flagsOut) + static T Execute(T val1, T val2, Registers& regs, uint32_t flagsOut) { constexpr int NumBits = sizeof(T) * 8; constexpr T SignMask = T(1) << (NumBits - 1); @@ -144,38 +144,123 @@ struct SubImpl } }; -struct Instr_Sub32 : X86Instruction, InstrSig< ZYDIS_MNEMONIC_SUB, OpSig<32, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_IMMEDIATE> > +struct Instr_MovR32R32 : InstrSig< ZYDIS_MNEMONIC_MOV, OpSig<32, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_REGISTER> > { - virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) override + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override + { + int32_t val1 = getRegisterValue(info.operands[1].reg, ctx.regs); + + setRegisterValue(info.operands[0].reg, ctx.regs, val1); + return true; + } +}; + +struct Instr_MovR32I32 : InstrSig< ZYDIS_MNEMONIC_MOV, OpSig<32, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_IMMEDIATE> > +{ + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override + { + int32_t val1 = info.operands[1].imm.value.sdword; + + setRegisterValue(info.operands[0].reg, ctx.regs, val1); + return true; + } +}; + +struct Instr_MovR64R64 : InstrSig< ZYDIS_MNEMONIC_MOV, OpSig<64, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<64, ZYDIS_OPERAND_TYPE_REGISTER> > +{ + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override + { + int64_t val2 = getRegisterValue(info.operands[1].reg, ctx.regs); + + setRegisterValue(info.operands[0].reg, ctx.regs, val2); + return true; + } +}; + +struct Instr_Sub32 : InstrSig< ZYDIS_MNEMONIC_SUB, OpSig<32, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_IMMEDIATE> > +{ + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override { // Value is sign extended. - int32_t val1 = getRegisterValue(info.operands[1].reg, ctx.regs); + int32_t val1 = getRegisterValue(info.operands[0].reg, ctx.regs); int32_t val2 = info.operands[1].imm.value.sdword; uint32_t flagsOut = 0; int32_t res = SubImpl::Execute(val1, val2, ctx.regs, flagsOut); - setRegisterValue(info.operands[1].reg, ctx.regs, res); + setRegisterValue(info.operands[0].reg, ctx.regs, res); + ctx.regs.Eflags.Set(ctx.regs.Eflags.Get() | flagsOut); + + return true; } }; -REGISTER_X86_INSTRUCTION(Instr_Sub32); -struct Instr_Sub64 : X86Instruction, InstrSig< ZYDIS_MNEMONIC_SUB, OpSig<64, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_IMMEDIATE> > +struct Instr_SubR64I64 : InstrSig< ZYDIS_MNEMONIC_SUB, OpSig<64, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<64, ZYDIS_OPERAND_TYPE_IMMEDIATE> > { - virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) override + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override { // Value is sign extended. - int64_t val1 = getRegisterValue(info.operands[1].reg, ctx.regs); + int64_t val1 = getRegisterValue(info.operands[0].reg, ctx.regs); + int64_t val2 = info.operands[1].imm.value.sqword; + + uint32_t flagsOut = 0; + int64_t res = SubImpl::Execute(val1, val2, ctx.regs, flagsOut); + + setRegisterValue(info.operands[0].reg, ctx.regs, res); + ctx.regs.Eflags.Set(ctx.regs.Eflags.Get() | flagsOut); + + return true; + } +}; + +struct Instr_SubR64I32 : InstrSig< ZYDIS_MNEMONIC_SUB, OpSig<64, ZYDIS_OPERAND_TYPE_REGISTER>, OpSig<32, ZYDIS_OPERAND_TYPE_IMMEDIATE> > +{ + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const override + { + // Value is sign extended. + int64_t val1 = getRegisterValue(info.operands[0].reg, ctx.regs); int64_t val2 = info.operands[1].imm.value.sdword; uint32_t flagsOut = 0; int64_t res = SubImpl::Execute(val1, val2, ctx.regs, flagsOut); - setRegisterValue(info.operands[1].reg, ctx.regs, res); + setRegisterValue(info.operands[0].reg, ctx.regs, res); + ctx.regs.Eflags.Set(ctx.regs.Eflags.Get() | flagsOut); + + return true; } }; -REGISTER_X86_INSTRUCTION(Instr_Sub64); +static bool _initialzed = false; + +const X86Instruction* LookupInstruction(const ZydisInstructionInfo& info) +{ + if (_initialzed == false) + { + REGISTER_X86_INSTRUCTION(Instr_MovR32R32); + REGISTER_X86_INSTRUCTION(Instr_MovR32I32); + REGISTER_X86_INSTRUCTION(Instr_MovR64R64); + REGISTER_X86_INSTRUCTION(Instr_Sub32); + REGISTER_X86_INSTRUCTION(Instr_SubR64I64); + REGISTER_X86_INSTRUCTION(Instr_SubR64I32); + _initialzed = true; + } + + if(info.mnemonic >= _registeredInstructions.size()) + return nullptr; + + auto& group = _registeredInstructions[info.mnemonic]; + if(group.empty()) + return nullptr; + + for (X86Instruction *instr : group) + { + if (instr->Matches(info)) + return instr; + } + + return nullptr; +} } } diff --git a/GleeBug/Emulation.Instructions.h b/GleeBug/Emulation.Instructions.h index 91a7321..1f24d6b 100644 --- a/GleeBug/Emulation.Instructions.h +++ b/GleeBug/Emulation.Instructions.h @@ -28,8 +28,9 @@ struct X86Context struct X86Instruction { - virtual bool Matches(const ZydisInstructionInfo& info) = 0; - virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) = 0; + virtual uint16_t GetMnemonic() const = 0; + virtual bool Matches(const ZydisInstructionInfo& info) const = 0; + virtual bool Execute(const ZydisInstructionInfo& info, X86Context& ctx) const = 0; }; const X86Instruction* LookupInstruction(const ZydisInstructionInfo& info); diff --git a/GleeBug/Emulation.cpp b/GleeBug/Emulation.cpp index 92888c4..a0eb7c1 100644 --- a/GleeBug/Emulation.cpp +++ b/GleeBug/Emulation.cpp @@ -1,26 +1,129 @@ +#include "GleeBug.h" #include "Emulation.h" #include "Emulation.Instructions.h" +#include "Debugger.Thread.Registers.h" +#include "Debugger.Thread.Registers.Flag.h" + +#define ZYDIS_EXPORTS +#define ZYDIS_ENABLE_FEATURE_IMPLICITLY_USED_REGISTERS +#define ZYDIS_ENABLE_FEATURE_AFFECTED_FLAGS +#include +#include +#include namespace GleeBug { -bool Emulator::IsActive() const +#ifdef _WIN64 +static constexpr ZydisOperatingMode ZYDIS_OPERATING_MODE = ZYDIS_OPERATING_MODE_64BIT; +#else +static constexpr ZydisOperatingMode ZYDIS_OPERATING_MODE = ZYDIS_OPERATING_MODE_32BIT; +#endif + +X86Emulator::X86Emulator(Process* process) + : _process(process) +{ +} + +bool X86Emulator::IsActive() const { return _active; } -bool Emulator::WaitForEvent(DEBUG_EVENT& debugEvent) const +bool X86Emulator::WaitForEvent(DEBUG_EVENT& debugEvent) { - return false; -} + if(_hasEvent == false) + return false; -bool Emulator::Emulate(Thread* thread) -{ - SetActiveThread(thread); + debugEvent = _currentEvent; + _hasEvent = false; return true; } -void Emulator::Flush() +bool GenerateDebugEvent(const ZydisInstructionInfo& instrInfo, Emulation::X86Context& ctx, DEBUG_EVENT& debugEvent) +{ + if (ctx.regs.TrapFlag) + { + debugEvent.dwDebugEventCode = EXCEPTION_DEBUG_EVENT; + + auto& exData = debugEvent.u.Exception; + exData.ExceptionRecord.ExceptionCode = STATUS_SINGLE_STEP; + exData.ExceptionRecord.ExceptionAddress = (PVOID)ctx.regs.Gip.Get(); + exData.ExceptionRecord.NumberParameters = 0; + exData.dwFirstChance = 1; + + return true; + } + // TODO: Deal with breakpoint emulation. + + return false; +} + +bool X86Emulator::Emulate(Thread* thread) +{ + SetActiveThread(thread); + + uint8_t instrBytes[16]; + ptr bytesRead = 0; + if (!_process->MemRead(_registers->Gip.Get(), instrBytes, sizeof(instrBytes), &bytesRead)) + { + Flush(); + return false; + } + + const ptr curIP = _registers->Gip.Get(); + + ZydisInstructionInfo instrInfo; + if (ZYDIS_SUCCESS(ZydisDecode(ZYDIS_OPERATING_MODE, instrBytes, 16, curIP, &instrInfo))) + { +#ifdef _DEBUG + ZydisInstructionFormatter formatter; + ZydisFormatterInitInstructionFormatter(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); + + char decodedInstr[128]; + ZydisFormatterFormatInstruction(&formatter, &instrInfo, decodedInstr, sizeof(decodedInstr)); +#endif + + const Emulation::X86Instruction *instr = Emulation::LookupInstruction(instrInfo); + if (instr == nullptr) + { + Flush(); + return false; + } + + bool isSingleStepping = _activeThread->isInternalStepping || _activeThread->isSingleStepping; + + Emulation::X86Context ctx(*_registers, *_activeThread); + if (instr->Execute(instrInfo, ctx)) + { + _registers->Gip.Set(curIP + instrInfo.length); + } + + //dprintf("Stepped with emulation at %p\n", curIP); + + _currentEvent.dwProcessId = _process->dwProcessId; + _currentEvent.dwThreadId = _activeThread->dwThreadId; + + if (GenerateDebugEvent(instrInfo, ctx, _currentEvent)) + { + if (isSingleStepping) + { + ctx.regs.TrapFlag = false; + } + _hasEvent = true; + Flush(); + } + } + else + { + Flush(); + return false; + } + + return true; +} + +void X86Emulator::Flush() { if (_registers != nullptr) { @@ -30,14 +133,14 @@ void Emulator::Flush() _activeThread = nullptr; } -void Emulator::SetActiveThread(Thread *thread) +void X86Emulator::SetActiveThread(Thread *thread) { if (_activeThread != thread) { // Commit current state if we change threads between. Flush(); - _registers = new Registers(thread->hThread, CONTEXT_CONTROL); + _registers = new Registers(thread->hThread, CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS | CONTEXT_INTEGER); _activeThread = thread; } } diff --git a/GleeBug/Emulation.h b/GleeBug/Emulation.h index 40edcaf..2458578 100644 --- a/GleeBug/Emulation.h +++ b/GleeBug/Emulation.h @@ -6,20 +6,27 @@ #include "Debugger.Thread.h" #include "Debugger.Thread.Registers.h" +#include + namespace GleeBug { class Process; -class Emulator +class X86Emulator { bool _active = false; Thread *_activeThread = nullptr; Registers* _registers = nullptr; + Process* _process = nullptr; + bool _hasEvent = false; + DEBUG_EVENT _currentEvent{}; public: + X86Emulator(Process* process); + bool IsActive() const; - bool WaitForEvent(DEBUG_EVENT& debugEvent) const; + bool WaitForEvent(DEBUG_EVENT& debugEvent); bool Emulate(Thread* thread); void Flush(); diff --git a/GleeBug/GleeBug.h b/GleeBug/GleeBug.h index b4ed193..fc5dde0 100644 --- a/GleeBug/GleeBug.h +++ b/GleeBug/GleeBug.h @@ -25,10 +25,14 @@ #define X64DBG_MOD L"x32dbg.dll" #endif //_WIN64 -#define DPRINTF() \ - static auto dprintf = (int(*)(const char* format, ...))GetProcAddress(GetModuleHandleW(X64DBG_MOD), "_plugin_logprintf"); \ - if(!dprintf) \ - dprintf = printf +template +inline void dprintf(const char *fmt, Args... args) +{ + static auto fn = (int(*)(const char* format, ...))GetProcAddress(GetModuleHandleW(X64DBG_MOD), "_plugin_logprintf"); + if (!fn) + fn = printf; + fn(fmt, args...); +} #ifdef _WIN64 #define GleeArchValue(x32value, x64value) x64value @@ -72,4 +76,4 @@ namespace GleeBug }; } -#endif //GLEEBUG_H \ No newline at end of file +#endif //GLEEBUG_H