1/* 2 * Copyright 2009-2012, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2011-2012, Rene Gollent, rene@gollent.com. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8#include "ArchitectureX86.h" 9 10#include <new> 11 12#include <String.h> 13 14#include <AutoDeleter.h> 15 16#include "CfaContext.h" 17#include "CpuStateX86.h" 18#include "DisassembledCode.h" 19#include "FunctionDebugInfo.h" 20#include "InstructionInfo.h" 21#include "NoOpStackFrameDebugInfo.h" 22#include "RegisterMap.h" 23#include "StackFrame.h" 24#include "Statement.h" 25#include "TeamMemory.h" 26#include "ValueLocation.h" 27#include "X86AssemblyLanguage.h" 28 29#include "disasm/DisassemblerX86.h" 30 31 32static const int32 kFromDwarfRegisters[] = { 33 X86_REGISTER_EAX, 34 X86_REGISTER_ECX, 35 X86_REGISTER_EDX, 36 X86_REGISTER_EBX, 37 X86_REGISTER_ESP, 38 X86_REGISTER_EBP, 39 X86_REGISTER_ESI, 40 X86_REGISTER_EDI, 41 X86_REGISTER_EIP, 42 -1, // eflags 43 -1, // trap number 44 -1, // st(0) 45 -1, // st(1) 46 -1, // st(2) 47 -1, // st(3) 48 -1, // st(4) 49 -1, // st(5) 50 -1, // st(6) 51 -1, // st(7) 52 -1, // ? 53 -1, // ? 54 -1, -1, -1, -1, -1, -1, -1, -1, // SSE 55 -1, -1, -1, -1, -1, -1, -1, -1 // MMX 56}; 57 58static const int32 kFromDwarfRegisterCount = sizeof(kFromDwarfRegisters) / 4; 59 60 61// #pragma mark - ToDwarfRegisterMap 62 63 64struct ArchitectureX86::ToDwarfRegisterMap : RegisterMap { 65 ToDwarfRegisterMap() 66 { 67 // init the index array from the reverse map 68 memset(fIndices, -1, sizeof(fIndices)); 69 for (int32 i = 0; i < kFromDwarfRegisterCount; i++) { 70 if (kFromDwarfRegisters[i] >= 0) 71 fIndices[kFromDwarfRegisters[i]] = i; 72 } 73 } 74 75 virtual int32 CountRegisters() const 76 { 77 return X86_REGISTER_COUNT; 78 } 79 80 virtual int32 MapRegisterIndex(int32 index) const 81 { 82 return index >= 0 && index < X86_REGISTER_COUNT ? fIndices[index] : -1; 83 } 84 85private: 86 int32 fIndices[X86_REGISTER_COUNT]; 87}; 88 89 90// #pragma mark - FromDwarfRegisterMap 91 92 93struct ArchitectureX86::FromDwarfRegisterMap : RegisterMap { 94 virtual int32 CountRegisters() const 95 { 96 return kFromDwarfRegisterCount; 97 } 98 99 virtual int32 MapRegisterIndex(int32 index) const 100 { 101 return index >= 0 && index < kFromDwarfRegisterCount 102 ? kFromDwarfRegisters[index] : -1; 103 } 104}; 105 106 107// #pragma mark - ArchitectureX86 108 109 110ArchitectureX86::ArchitectureX86(TeamMemory* teamMemory) 111 : 112 Architecture(teamMemory, 4, false), 113 fAssemblyLanguage(NULL), 114 fToDwarfRegisterMap(NULL), 115 fFromDwarfRegisterMap(NULL) 116{ 117} 118 119 120ArchitectureX86::~ArchitectureX86() 121{ 122 if (fToDwarfRegisterMap != NULL) 123 fToDwarfRegisterMap->ReleaseReference(); 124 if (fFromDwarfRegisterMap != NULL) 125 fFromDwarfRegisterMap->ReleaseReference(); 126 if (fAssemblyLanguage != NULL) 127 fAssemblyLanguage->ReleaseReference(); 128} 129 130 131status_t 132ArchitectureX86::Init() 133{ 134 fAssemblyLanguage = new(std::nothrow) X86AssemblyLanguage; 135 if (fAssemblyLanguage == NULL) 136 return B_NO_MEMORY; 137 138 try { 139 _AddIntegerRegister(X86_REGISTER_EIP, "eip", B_UINT32_TYPE, 140 REGISTER_TYPE_INSTRUCTION_POINTER, false); 141 _AddIntegerRegister(X86_REGISTER_ESP, "esp", B_UINT32_TYPE, 142 REGISTER_TYPE_STACK_POINTER, true); 143 _AddIntegerRegister(X86_REGISTER_EBP, "ebp", B_UINT32_TYPE, 144 REGISTER_TYPE_GENERAL_PURPOSE, true); 145 146 _AddIntegerRegister(X86_REGISTER_EAX, "eax", B_UINT32_TYPE, 147 REGISTER_TYPE_GENERAL_PURPOSE, false); 148 _AddIntegerRegister(X86_REGISTER_EBX, "ebx", B_UINT32_TYPE, 149 REGISTER_TYPE_GENERAL_PURPOSE, true); 150 _AddIntegerRegister(X86_REGISTER_ECX, "ecx", B_UINT32_TYPE, 151 REGISTER_TYPE_GENERAL_PURPOSE, false); 152 _AddIntegerRegister(X86_REGISTER_EDX, "edx", B_UINT32_TYPE, 153 REGISTER_TYPE_GENERAL_PURPOSE, false); 154 155 _AddIntegerRegister(X86_REGISTER_ESI, "esi", B_UINT32_TYPE, 156 REGISTER_TYPE_GENERAL_PURPOSE, true); 157 _AddIntegerRegister(X86_REGISTER_EDI, "edi", B_UINT32_TYPE, 158 REGISTER_TYPE_GENERAL_PURPOSE, true); 159 160 _AddIntegerRegister(X86_REGISTER_CS, "cs", B_UINT16_TYPE, 161 REGISTER_TYPE_SPECIAL_PURPOSE, true); 162 _AddIntegerRegister(X86_REGISTER_DS, "ds", B_UINT16_TYPE, 163 REGISTER_TYPE_SPECIAL_PURPOSE, true); 164 _AddIntegerRegister(X86_REGISTER_ES, "es", B_UINT16_TYPE, 165 REGISTER_TYPE_SPECIAL_PURPOSE, true); 166 _AddIntegerRegister(X86_REGISTER_FS, "fs", B_UINT16_TYPE, 167 REGISTER_TYPE_SPECIAL_PURPOSE, true); 168 _AddIntegerRegister(X86_REGISTER_GS, "gs", B_UINT16_TYPE, 169 REGISTER_TYPE_SPECIAL_PURPOSE, true); 170 _AddIntegerRegister(X86_REGISTER_SS, "ss", B_UINT16_TYPE, 171 REGISTER_TYPE_SPECIAL_PURPOSE, true); 172 } catch (std::bad_alloc) { 173 return B_NO_MEMORY; 174 } 175 176 fToDwarfRegisterMap = new(std::nothrow) ToDwarfRegisterMap; 177 fFromDwarfRegisterMap = new(std::nothrow) FromDwarfRegisterMap; 178 179 if (fToDwarfRegisterMap == NULL || fFromDwarfRegisterMap == NULL) 180 return B_NO_MEMORY; 181 182 return B_OK; 183} 184 185 186int32 187ArchitectureX86::StackGrowthDirection() const 188{ 189 return STACK_GROWTH_DIRECTION_NEGATIVE; 190} 191 192 193int32 194ArchitectureX86::CountRegisters() const 195{ 196 return fRegisters.Count(); 197} 198 199 200const Register* 201ArchitectureX86::Registers() const 202{ 203 return fRegisters.Elements(); 204} 205 206 207status_t 208ArchitectureX86::InitRegisterRules(CfaContext& context) const 209{ 210 status_t error = Architecture::InitRegisterRules(context); 211 if (error != B_OK) 212 return error; 213 214 // set up rule for EIP register 215 context.RegisterRule(fToDwarfRegisterMap->MapRegisterIndex( 216 X86_REGISTER_EIP))->SetToLocationOffset(-4); 217 218 return B_OK; 219} 220 221 222status_t 223ArchitectureX86::GetDwarfRegisterMaps(RegisterMap** _toDwarf, 224 RegisterMap** _fromDwarf) const 225{ 226 if (_toDwarf != NULL) { 227 *_toDwarf = fToDwarfRegisterMap; 228 fToDwarfRegisterMap->AcquireReference(); 229 } 230 231 if (_fromDwarf != NULL) { 232 *_fromDwarf = fFromDwarfRegisterMap; 233 fFromDwarfRegisterMap->AcquireReference(); 234 } 235 236 return B_OK; 237} 238 239 240status_t 241ArchitectureX86::CreateCpuState(CpuState*& _state) 242{ 243 CpuStateX86* state = new(std::nothrow) CpuStateX86; 244 if (state == NULL) 245 return B_NO_MEMORY; 246 247 _state = state; 248 return B_OK; 249} 250 251 252status_t 253ArchitectureX86::CreateCpuState(const void* cpuStateData, size_t size, 254 CpuState*& _state) 255{ 256 if (size != sizeof(x86_debug_cpu_state)) 257 return B_BAD_VALUE; 258 259 CpuStateX86* state = new(std::nothrow) CpuStateX86( 260 *(const x86_debug_cpu_state*)cpuStateData); 261 if (state == NULL) 262 return B_NO_MEMORY; 263 264 _state = state; 265 return B_OK; 266} 267 268 269status_t 270ArchitectureX86::CreateStackFrame(Image* image, FunctionDebugInfo* function, 271 CpuState* _cpuState, bool isTopFrame, StackFrame*& _frame, 272 CpuState*& _previousCpuState) 273{ 274 CpuStateX86* cpuState = dynamic_cast<CpuStateX86*>(_cpuState); 275 276 uint32 framePointer = cpuState->IntRegisterValue(X86_REGISTER_EBP); 277 uint32 eip = cpuState->IntRegisterValue(X86_REGISTER_EIP); 278 279 bool readStandardFrame = true; 280 uint32 previousFramePointer = 0; 281 uint32 returnAddress = 0; 282 283 // check for syscall frames 284 stack_frame_type frameType; 285 bool hasPrologue = false; 286 if (isTopFrame && cpuState->InterruptVector() == 99) { 287 // The thread is performing a syscall. So this frame is not really the 288 // top-most frame and we need to adjust the eip. 289 frameType = STACK_FRAME_TYPE_SYSCALL; 290 eip -= 2; 291 // int 99, sysenter, and syscall all are 2 byte instructions 292 293 // The syscall stubs are frameless, the return address is on top of the 294 // stack. 295 uint32 esp = cpuState->IntRegisterValue(X86_REGISTER_ESP); 296 uint32 address; 297 if (fTeamMemory->ReadMemory(esp, &address, 4) == 4) { 298 returnAddress = address; 299 previousFramePointer = framePointer; 300 framePointer = 0; 301 readStandardFrame = false; 302 } 303 } else { 304 hasPrologue = _HasFunctionPrologue(function); 305 if (hasPrologue) 306 frameType = STACK_FRAME_TYPE_STANDARD; 307 else 308 frameType = STACK_FRAME_TYPE_FRAMELESS; 309 // TODO: Handling for frameless functions. It's not trivial to find the 310 // return address on the stack, though. 311 312 // If the function is not frameless and we're at the top frame we need 313 // to check whether the prologue has not been executed (completely) or 314 // we're already after the epilogue. 315 if (isTopFrame) { 316 uint32 stack = 0; 317 if (hasPrologue) { 318 if (eip < function->Address() + 3) { 319 // The prologue has not been executed yet, i.e. there's no 320 // stack frame yet. Get the return address from the stack. 321 stack = cpuState->IntRegisterValue(X86_REGISTER_ESP); 322 if (eip > function->Address()) { 323 // The "push %ebp" has already been executed. 324 stack += 4; 325 } 326 } else { 327 // Not in the function prologue, but maybe after the 328 // epilogue. The epilogue is a single "pop %ebp", so we 329 // check whether the current instruction is already a 330 // "ret". 331 uint8 code[1]; 332 if (fTeamMemory->ReadMemory(eip, &code, 1) == 1 333 && code[0] == 0xc3) { 334 stack = cpuState->IntRegisterValue(X86_REGISTER_ESP); 335 } 336 } 337 } else { 338 // Check if the instruction pointer is at a readable location. 339 // If it isn't, then chances are we got here via a bogus 340 // function pointer, and the prologue hasn't actually been 341 // executed. In such a case, what we need is right at the top 342 // of the stack. 343 uint8 data[1]; 344 if (fTeamMemory->ReadMemory(eip, &data, 1) != 1) 345 stack = cpuState->IntRegisterValue(X86_REGISTER_ESP); 346 } 347 348 if (stack != 0) { 349 uint32 address; 350 if (fTeamMemory->ReadMemory(stack, &address, 4) == 4) { 351 returnAddress = address; 352 previousFramePointer = framePointer; 353 framePointer = 0; 354 readStandardFrame = false; 355 frameType = STACK_FRAME_TYPE_FRAMELESS; 356 } 357 } 358 } 359 } 360 361 // create the stack frame 362 StackFrameDebugInfo* stackFrameDebugInfo 363 = new(std::nothrow) NoOpStackFrameDebugInfo; 364 if (stackFrameDebugInfo == NULL) 365 return B_NO_MEMORY; 366 BReference<StackFrameDebugInfo> stackFrameDebugInfoReference( 367 stackFrameDebugInfo, true); 368 369 StackFrame* frame = new(std::nothrow) StackFrame(frameType, cpuState, 370 framePointer, eip, stackFrameDebugInfo); 371 if (frame == NULL) 372 return B_NO_MEMORY; 373 BReference<StackFrame> frameReference(frame, true); 374 375 status_t error = frame->Init(); 376 if (error != B_OK) 377 return error; 378 379 // read the previous frame and return address, if this is a standard frame 380 if (readStandardFrame) { 381 uint32 frameData[2]; 382 if (framePointer != 0 383 && fTeamMemory->ReadMemory(framePointer, frameData, 8) == 8) { 384 previousFramePointer = frameData[0]; 385 returnAddress = frameData[1]; 386 } 387 } 388 389 // create the CPU state, if we have any info 390 CpuStateX86* previousCpuState = NULL; 391 if (returnAddress != 0) { 392 // prepare the previous CPU state 393 previousCpuState = new(std::nothrow) CpuStateX86; 394 if (previousCpuState == NULL) 395 return B_NO_MEMORY; 396 397 previousCpuState->SetIntRegister(X86_REGISTER_EBP, 398 previousFramePointer); 399 previousCpuState->SetIntRegister(X86_REGISTER_EIP, returnAddress); 400 frame->SetPreviousCpuState(previousCpuState); 401 } 402 403 frame->SetReturnAddress(returnAddress); 404 405 _frame = frameReference.Detach(); 406 _previousCpuState = previousCpuState; 407 return B_OK; 408} 409 410 411void 412ArchitectureX86::UpdateStackFrameCpuState(const StackFrame* frame, 413 Image* previousImage, FunctionDebugInfo* previousFunction, 414 CpuState* previousCpuState) 415{ 416 // This is not a top frame, so we want to offset eip to the previous 417 // (calling) instruction. 418 CpuStateX86* cpuState = dynamic_cast<CpuStateX86*>(previousCpuState); 419 420 // get eip 421 uint32 eip = cpuState->IntRegisterValue(X86_REGISTER_EIP); 422 if (previousFunction == NULL || eip <= previousFunction->Address()) 423 return; 424 target_addr_t functionAddress = previousFunction->Address(); 425 426 // allocate a buffer for the function code to disassemble 427 size_t bufferSize = eip - functionAddress; 428 void* buffer = malloc(bufferSize); 429 if (buffer == NULL) 430 return; 431 MemoryDeleter bufferDeleter(buffer); 432 433 // read the code 434 ssize_t bytesRead = fTeamMemory->ReadMemory(functionAddress, buffer, 435 bufferSize); 436 if (bytesRead != (ssize_t)bufferSize) 437 return; 438 439 // disassemble to get the previous instruction 440 DisassemblerX86 disassembler; 441 target_addr_t instructionAddress; 442 target_size_t instructionSize; 443 if (disassembler.Init(functionAddress, buffer, bufferSize) == B_OK 444 && disassembler.GetPreviousInstruction(eip, instructionAddress, 445 instructionSize) == B_OK) { 446 eip -= instructionSize; 447 cpuState->SetIntRegister(X86_REGISTER_EIP, eip); 448 } 449} 450 451 452status_t 453ArchitectureX86::ReadValueFromMemory(target_addr_t address, uint32 valueType, 454 BVariant& _value) const 455{ 456 uint8 buffer[64]; 457 size_t size = BVariant::SizeOfType(valueType); 458 if (size == 0 || size > sizeof(buffer)) 459 return B_BAD_VALUE; 460 461 ssize_t bytesRead = fTeamMemory->ReadMemory(address, buffer, size); 462 if (bytesRead < 0) 463 return bytesRead; 464 if ((size_t)bytesRead != size) 465 return B_ERROR; 466 467 // TODO: We need to swap endianess, if the host is big endian! 468 469 switch (valueType) { 470 case B_INT8_TYPE: 471 _value.SetTo(*(int8*)buffer); 472 return B_OK; 473 case B_UINT8_TYPE: 474 _value.SetTo(*(uint8*)buffer); 475 return B_OK; 476 case B_INT16_TYPE: 477 _value.SetTo(*(int16*)buffer); 478 return B_OK; 479 case B_UINT16_TYPE: 480 _value.SetTo(*(uint16*)buffer); 481 return B_OK; 482 case B_INT32_TYPE: 483 _value.SetTo(*(int32*)buffer); 484 return B_OK; 485 case B_UINT32_TYPE: 486 _value.SetTo(*(uint32*)buffer); 487 return B_OK; 488 case B_INT64_TYPE: 489 _value.SetTo(*(int64*)buffer); 490 return B_OK; 491 case B_UINT64_TYPE: 492 _value.SetTo(*(uint64*)buffer); 493 return B_OK; 494 case B_FLOAT_TYPE: 495 _value.SetTo(*(float*)buffer); 496 // TODO: float on the host might work differently! 497 return B_OK; 498 case B_DOUBLE_TYPE: 499 _value.SetTo(*(double*)buffer); 500 // TODO: double on the host might work differently! 501 return B_OK; 502 default: 503 return B_BAD_VALUE; 504 } 505} 506 507 508status_t 509ArchitectureX86::ReadValueFromMemory(target_addr_t addressSpace, 510 target_addr_t address, uint32 valueType, BVariant& _value) const 511{ 512 // n/a on this architecture 513 return B_BAD_VALUE; 514} 515 516 517status_t 518ArchitectureX86::DisassembleCode(FunctionDebugInfo* function, 519 const void* buffer, size_t bufferSize, DisassembledCode*& _sourceCode) 520{ 521 DisassembledCode* source = new(std::nothrow) DisassembledCode( 522 fAssemblyLanguage); 523 if (source == NULL) 524 return B_NO_MEMORY; 525 BReference<DisassembledCode> sourceReference(source, true); 526 527 // init disassembler 528 DisassemblerX86 disassembler; 529 status_t error = disassembler.Init(function->Address(), buffer, bufferSize); 530 if (error != B_OK) 531 return error; 532 533 // add a function name line 534 BString functionName(function->PrettyName()); 535 if (!source->AddCommentLine((functionName << ':').String())) 536 return B_NO_MEMORY; 537 538 // disassemble the instructions 539 BString line; 540 target_addr_t instructionAddress; 541 target_size_t instructionSize; 542 bool breakpointAllowed; 543 while (disassembler.GetNextInstruction(line, instructionAddress, 544 instructionSize, breakpointAllowed) == B_OK) { 545// TODO: Respect breakpointAllowed! 546 if (!source->AddInstructionLine(line, instructionAddress, 547 instructionSize)) { 548 return B_NO_MEMORY; 549 } 550 } 551 552 _sourceCode = sourceReference.Detach(); 553 return B_OK; 554} 555 556 557status_t 558ArchitectureX86::GetStatement(FunctionDebugInfo* function, 559 target_addr_t address, Statement*& _statement) 560{ 561// TODO: This is not architecture dependent anymore! 562 // get the instruction info 563 InstructionInfo info; 564 status_t error = GetInstructionInfo(address, info, NULL); 565 if (error != B_OK) 566 return error; 567 568 // create a statement 569 ContiguousStatement* statement = new(std::nothrow) ContiguousStatement( 570 SourceLocation(-1), TargetAddressRange(info.Address(), info.Size())); 571 if (statement == NULL) 572 return B_NO_MEMORY; 573 574 _statement = statement; 575 return B_OK; 576} 577 578 579status_t 580ArchitectureX86::GetInstructionInfo(target_addr_t address, 581 InstructionInfo& _info, CpuState* state) 582{ 583 // read the code - maximum x86{-64} instruction size = 15 bytes 584 uint8 buffer[16]; 585 ssize_t bytesRead = fTeamMemory->ReadMemory(address, buffer, 586 sizeof(buffer)); 587 if (bytesRead < 0) 588 return bytesRead; 589 590 // init disassembler 591 DisassemblerX86 disassembler; 592 status_t error = disassembler.Init(address, buffer, bytesRead); 593 if (error != B_OK) 594 return error; 595 596 return disassembler.GetNextInstructionInfo(_info, state); 597} 598 599 600status_t 601ArchitectureX86::GetWatchpointDebugCapabilities(int32& _maxRegisterCount, 602 int32& _maxBytesPerRegister, uint8& _watchpointCapabilityFlags) 603{ 604 // while x86 technically has 4 hardware debug registers, one is reserved by 605 // the kernel, and one is required for breakpoint support, which leaves 606 // two available for watchpoints. 607 _maxRegisterCount = 2; 608 _maxBytesPerRegister = 4; 609 610 // x86 only supports write and read/write watchpoints. 611 _watchpointCapabilityFlags = WATCHPOINT_CAPABILITY_FLAG_WRITE 612 | WATCHPOINT_CAPABILITY_FLAG_READ_WRITE; 613 614 return B_OK; 615} 616 617 618status_t 619ArchitectureX86::GetReturnAddressLocation(StackFrame* frame, 620 target_size_t valueSize, ValueLocation*& _location) 621{ 622 // for the calling conventions currently in use on Haiku, 623 // the x86 rules for how values are returned are as follows: 624 // 625 // - 32 bits or smaller values are returned directly in EAX. 626 // - 32-64 bit values are returned across EAX:EDX. 627 // - > 64 bit values are returned on the stack. 628 ValueLocation* location = new(std::nothrow) ValueLocation( 629 IsBigEndian()); 630 if (location == NULL) 631 return B_NO_MEMORY; 632 BReference<ValueLocation> locationReference(location, 633 true); 634 635 if (valueSize <= 4) { 636 ValuePieceLocation piece; 637 piece.SetSize(valueSize); 638 piece.SetToRegister(X86_REGISTER_EAX); 639 if (!location->AddPiece(piece)) 640 return B_NO_MEMORY; 641 } else if (valueSize <= 8) { 642 ValuePieceLocation piece; 643 piece.SetSize(4); 644 piece.SetToRegister(X86_REGISTER_EAX); 645 if (!location->AddPiece(piece)) 646 return B_NO_MEMORY; 647 piece.SetToRegister(X86_REGISTER_EDX); 648 piece.SetSize(valueSize - 4); 649 if (!location->AddPiece(piece)) 650 return B_NO_MEMORY; 651 } else { 652 ValuePieceLocation piece; 653 piece.SetToMemory(frame->GetCpuState()->StackPointer()); 654 piece.SetSize(valueSize); 655 if (!location->AddPiece(piece)) 656 return B_NO_MEMORY; 657 } 658 659 _location = locationReference.Detach(); 660 return B_OK; 661} 662 663 664void 665ArchitectureX86::_AddRegister(int32 index, const char* name, 666 uint32 bitSize, uint32 valueType, register_type type, bool calleePreserved) 667{ 668 if (!fRegisters.Add(Register(index, name, bitSize, valueType, type, 669 calleePreserved))) { 670 throw std::bad_alloc(); 671 } 672} 673 674 675void 676ArchitectureX86::_AddIntegerRegister(int32 index, const char* name, 677 uint32 valueType, register_type type, bool calleePreserved) 678{ 679 _AddRegister(index, name, 8 * BVariant::SizeOfType(valueType), valueType, 680 type, calleePreserved); 681} 682 683 684bool 685ArchitectureX86::_HasFunctionPrologue(FunctionDebugInfo* function) const 686{ 687 if (function == NULL) 688 return false; 689 690 // check whether the function has the typical prologue 691 if (function->Size() < 3) 692 return false; 693 694 uint8 buffer[3]; 695 if (fTeamMemory->ReadMemory(function->Address(), buffer, 3) != 3) 696 return false; 697 698 return buffer[0] == 0x55 && buffer[1] == 0x89 && buffer[2] == 0xe5; 699} 700