Changeset 96867 in vbox for trunk/src/VBox/Debugger
- Timestamp:
- Sep 26, 2022 2:51:41 PM (2 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Debugger/DBGPlugInWinNt.cpp
r96407 r96867 35 35 #include <VBox/vmm/cpumctx.h> 36 36 #include <VBox/vmm/mm.h> 37 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 38 # include <VBox/vmm/vmapi.h> 39 # include <VBox/dis.h> 40 #endif 37 41 #include <VBox/vmm/vmmr3vtable.h> 38 42 #include <VBox/err.h> … … 270 274 /** The Windows NT specifics interface. */ 271 275 DBGFOSIWINNT IWinNt; 276 277 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 278 /** Breakpoint owner handle for the DbgPrint/vDbgPrint{,Ex}... interception. */ 279 DBGFBPOWNER hBpOwnerDbgPrint; 280 /** Breakpoint handle for the DbgPrint/vDbgPrint{,Ex}... interception. */ 281 DBGFBP hBpDbgPrint; 282 #endif 272 283 } DBGDIGGERWINNT; 273 284 /** Pointer to the linux guest OS digger instance data. */ … … 344 355 }; 345 356 357 358 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 359 /** 360 * Queries the string from guest memory with the pointer in the given register, sanitizing it. 361 * 362 * @returns VBox status code. 363 * @param pUVM The user mode VM handle. 364 * @param idCpu The CPU ID. 365 * @param enmReg The register to query the string pointer from. 366 * @param pszBuf Where to store the sanitized string. 367 * @param cbBuf Size of the buffer in number of bytes. 368 */ 369 static int dbgDiggerWinNtDbgPrintQueryStringFromReg(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, char *pszBuf, size_t cbBuf) 370 { 371 uint64_t u64RegPtr = 0; 372 int rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, enmReg, &u64RegPtr); 373 if ( rc == VINF_SUCCESS 374 || rc == VINF_DBGF_ZERO_EXTENDED_REGISTER) /* Being strict about what we expect here. */ 375 { 376 DBGFADDRESS AddrStr; 377 DBGFR3AddrFromFlat(pUVM, &AddrStr, u64RegPtr); 378 rc = DBGFR3MemRead(pUVM, idCpu, &AddrStr, pszBuf, cbBuf); 379 if (RT_SUCCESS(rc)) 380 { 381 /* Check that there is a zero terminator and purge invalid encoding (expecting UTF-8 here). */ 382 size_t idx = 0; 383 for (idx = 0; idx < cbBuf; idx++) 384 if (pszBuf[idx] == '\0') 385 break; 386 387 if (idx == cbBuf) 388 pszBuf[cbBuf - 1] = '\0'; /* Force terminator, truncating the string. */ 389 else 390 memset(&pszBuf[idx], 0, cbBuf - idx); /* Clear everything afterwards. */ 391 392 /* Purge the string encoding. */ 393 RTStrPurgeEncoding(pszBuf); 394 } 395 } 396 else if (RT_SUCCESS(rc)) 397 rc = VERR_INVALID_STATE; 398 399 return rc; 400 } 401 402 403 /** 404 * @copydoc{FNDBGFBPHIT, Breakpoint callback for the DbgPrint interception.} 405 */ 406 static DECLCALLBACK(VBOXSTRICTRC) dbgDiggerWinNtDbgPrintHit(PVM pVM, VMCPUID idCpu, void *pvUserBp, DBGFBP hBp, PCDBGFBPPUB pBpPub, uint16_t fFlags) 407 { 408 RT_NOREF(hBp, pBpPub, fFlags); 409 PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvUserBp; 410 PUVM pUVM = VMR3GetUVM(pVM); 411 412 /* 413 * The worker prototype looks like the following: 414 * vDbgPrintExWorker(PCCH Prefix, ULONG ComponentId, ULONG Level, PCCH Format, va_list arglist, BOOL fUnknown) 415 * 416 * Depending on the bitness the parameters are grabbed from the appropriate registers and stack locations. 417 * For amd64 reading the following is recommended: 418 * https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019 419 * https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=vs-2019 420 * https://docs.microsoft.com/en-us/cpp/build/stack-usage?view=vs-2019 421 * 422 * @todo 32bit 423 */ 424 int rc = VINF_SUCCESS; 425 uint32_t idComponent = 0; 426 uint32_t iLevel = 0; 427 char aszPrefixStr[128]; /* Restricted size. */ 428 char aszFmtStr[_1K]; /* Restricted size. */ 429 DBGFADDRESS AddrVaList; 430 if (!pThis->f32Bit) 431 { 432 /* 433 * Grab the prefix, component, level, format string pointer from the registers and the argument list from the 434 * stack (mind the home area for the register arguments). 435 */ 436 rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_RCX, &aszPrefixStr[0], sizeof(aszPrefixStr)); 437 if (RT_SUCCESS(rc)) 438 rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_RDX, &idComponent); 439 if (RT_SUCCESS(rc)) 440 rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_R8, &iLevel); 441 if (RT_SUCCESS(rc)) 442 rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_R9, &aszFmtStr[0], sizeof(aszFmtStr)); 443 if (RT_SUCCESS(rc)) 444 { 445 /* Grabbing the pointer to the va list. The stack layout when we are here looks like (each entry is 64bit): 446 * +-------------+ 447 * | ... | 448 * | VA list ptr | 449 * | (arg3/r9) | 450 * | (arg2/r8) | 451 * | (arg1/rdx) | 452 * | (arg0/rcx) | 453 * | return RIP | 454 * +-------------+ <- RSP 455 */ 456 uint64_t uRegRsp = 0; 457 rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_RSP, &uRegRsp); 458 if (rc == VINF_SUCCESS) 459 { 460 DBGFADDRESS AddrVaListPtr; 461 RTGCUINTPTR GCPtrVaList = 0; 462 463 DBGFR3AddrFromFlat(pUVM, &AddrVaListPtr, uRegRsp + 5 * sizeof(RTGCUINTPTR)); 464 rc = DBGFR3MemRead(pUVM, idCpu, &AddrVaListPtr, &GCPtrVaList, sizeof(GCPtrVaList)); 465 if (RT_SUCCESS(rc)) 466 DBGFR3AddrFromFlat(pUVM, &AddrVaList, GCPtrVaList); 467 } 468 else 469 rc = VERR_INVALID_STATE; 470 } 471 } 472 else 473 rc = VERR_NOT_IMPLEMENTED; /** @todo */ 474 475 if (RT_SUCCESS(rc)) 476 { 477 LogRel(("DigWinNt/DbgPrint: Queried arguments %s %#x %u %s %RGv\n", &aszPrefixStr[0], idComponent, iLevel, &aszFmtStr[0], AddrVaList.FlatPtr)); 478 /** @todo Continue here. */ 479 } 480 else 481 LogRel(("DigWinNt/DbgPrint: Failed to query all arguments with rc=%Rrc\n", rc)); 482 483 return VINF_SUCCESS; 484 } 485 486 487 /** 488 * Disassembles the given instruction and checks whether it is a call with a fixed address. 489 * 490 * @returns Flag whether the insturction at the given address is a call. 491 * @param pThis The instance data. 492 * @param pUVM The user mode VM handle. 493 * @param pAddrInsn Guest address of the instruction. 494 * @param pAddrCall Where to store the destination if the instruction is a call. 495 */ 496 static bool dbgDiggerWinNtDbgPrintWrapperInsnIsCall(PDBGDIGGERWINNT pThis, PUVM pUVM, PCDBGFADDRESS pAddrInsn, PDBGFADDRESS pAddrCall) 497 { 498 DISSTATE DisState; 499 RT_ZERO(DisState); 500 501 /* Prefetch the instruction. */ 502 uint8_t abInstr[32]; 503 int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrInsn, &abInstr[0], sizeof(abInstr)); 504 if (RT_SUCCESS(rc)) 505 { 506 uint32_t cbInsn = 0; 507 rc = DISInstr(&abInstr[0], pThis->f32Bit ? DISCPUMODE_32BIT : DISCPUMODE_64BIT, &DisState, &cbInsn); 508 if ( RT_SUCCESS(rc) 509 && DisState.pCurInstr->uOpcode == OP_CALL 510 && DisState.Param1.fUse & DISUSE_IMMEDIATE) 511 { 512 if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32 | DISUSE_IMMEDIATE64)) 513 DBGFR3AddrFromFlat(pUVM, pAddrCall, DisState.Param1.uValue); 514 else if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32_REL | DISUSE_IMMEDIATE64_REL)) 515 { 516 *pAddrCall = *pAddrInsn; 517 DBGFR3AddrAdd(pAddrCall, DisState.Param1.uValue + cbInsn); 518 } 519 520 return true; 521 } 522 } 523 524 return false; 525 } 526 527 528 /** 529 * Tries to find the single call instruction of the DbgPrint/etc. worker in the given control flow graph 530 * (single basic block assumed). 531 * 532 * @returns VBox status code. 533 * @param pThis The instance data. 534 * @param pUVM The user mode VM handle. 535 * @param hFlow The control flow graph handle. 536 * @param pAddr Where to store the worker address on success. 537 */ 538 static int dbgDiggerWinNtDbgPrintResolveWorker(PDBGDIGGERWINNT pThis, PUVM pUVM, DBGFFLOW hFlow, PDBGFADDRESS pAddr) 539 { 540 DBGFFLOWBB hBb; 541 int rc = DBGFR3FlowQueryStartBb(hFlow, &hBb); 542 if (RT_SUCCESS(rc)) 543 { 544 bool fCallFound = false; 545 546 for (uint32_t i = 0; i < DBGFR3FlowBbGetInstrCount(hBb) && RT_SUCCESS(rc); i++) 547 { 548 DBGFADDRESS AddrInsn; 549 uint32_t cbInsn; 550 rc = DBGFR3FlowBbQueryInstr(hBb, i, &AddrInsn, &cbInsn, NULL); 551 if (RT_SUCCESS(rc)) 552 { 553 DBGFADDRESS AddrCall; 554 if (dbgDiggerWinNtDbgPrintWrapperInsnIsCall(pThis, pUVM, &AddrInsn, &AddrCall)) 555 { 556 if (!fCallFound) 557 { 558 *pAddr = AddrCall; 559 fCallFound = true; 560 } 561 else 562 { 563 LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx contains multiple call instructions!\n")); 564 rc = VERR_ALREADY_EXISTS; 565 } 566 } 567 } 568 } 569 570 DBGFR3FlowBbRelease(hBb); 571 } 572 573 return rc; 574 } 575 576 577 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 578 /** 579 * Tries to resolve and hook into the worker for all the DbgPrint like wrappers to be able 580 * to gather debug information from the system. 581 * 582 * @returns nothing. 583 * @param pThis The instance data. 584 * @param pUVM The user mode VM handle. 585 */ 586 static void dbgDiggerWinNtDbgPrintHook(PDBGDIGGERWINNT pThis, PUVM pUVM) 587 { 588 /* 589 * This is a multi step process: 590 * 1. Try to resolve the address of vDbgPrint() (available since XP). 591 * 2. Create a control flow graph from the code and verify the following assumptions: 592 * 1. Only a single basic block. 593 * 2. Just one call instruction. 594 * @todo More? 595 * 3. Get the address from the called worker 596 * 4. Set a hardware breakpoint with our callback. 597 */ 598 RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL); 599 if (hAs != NIL_RTDBGAS) 600 { 601 RTDBGSYMBOL SymInfo; 602 int rc = RTDbgAsSymbolByName(hAs, "nt!vDbgPrintEx", &SymInfo, NULL /*phMod*/); 603 if (RT_SUCCESS(rc)) 604 { 605 DBGFADDRESS Addr; 606 DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value); 607 608 LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx resolved to %RGv\n", SymInfo.Value)); 609 610 DBGFFLOW hCfg; 611 rc = DBGFR3FlowCreate(pUVM, 0 /*idCpu*/, &Addr, 512 /*cbDisasmMax*/, 612 0 /*fFlagsFlow*/, DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED | DBGF_DISAS_FLAGS_DEFAULT_MODE, 613 &hCfg); 614 if (RT_SUCCESS(rc)) 615 { 616 /* Verify assumptions. */ 617 if (DBGFR3FlowGetBbCount(hCfg) == 1) 618 { 619 rc = dbgDiggerWinNtDbgPrintResolveWorker(pThis, pUVM, hCfg, &Addr); 620 if (RT_SUCCESS(rc)) 621 { 622 /* Try to hook the worker. */ 623 LogRel(("DigWinNt/DbgPrint: Worker for nt!vDbgPrintEx resolved to %RGv\n", Addr.FlatPtr)); 624 rc = DBGFR3BpOwnerCreate(pUVM, dbgDiggerWinNtDbgPrintHit, NULL /*pfnBpIoHit*/, &pThis->hBpOwnerDbgPrint); 625 if (RT_SUCCESS(rc)) 626 { 627 rc = DBGFR3BpSetInt3Ex(pUVM, pThis->hBpOwnerDbgPrint, pThis, 0 /*idCpu*/, &Addr, DBGF_BP_F_DEFAULT, 628 0 /*iHitTrigger*/, 0 /*iHitDisable*/, &pThis->hBpDbgPrint); 629 if (RT_SUCCESS(rc)) 630 LogRel(("DigWinNt/DbgPrint: Hooked nt!vDbgPrintEx worker hBp=%#x\n", pThis->hBpDbgPrint)); 631 else 632 { 633 LogRel(("DigWinNt/DbgPrint: Setting hardware breakpoint for nt!vDbgPrintEx worker failed with rc=%Rrc\n", rc)); 634 int rc2 = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint); 635 pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER; 636 AssertRC(rc2); 637 } 638 } 639 } 640 /* else LogRel() already done */ 641 } 642 else 643 LogRel(("DigWinNt/DbgPrint: Control flow graph for nt!vDbgPrintEx has more than one basic block (%u)\n", 644 DBGFR3FlowGetBbCount(hCfg))); 645 646 DBGFR3FlowRelease(hCfg); 647 } 648 else 649 LogRel(("DigWinNt/DbgPrint: Failed to create control flow graph from nt!vDbgPrintEx rc=%Rrc\n", rc)); 650 } 651 else 652 LogRel(("DigWinNt/DbgPrint: Failed to resolve nt!vDbgPrintEx -> rc=%Rrc\n", rc)); 653 RTDbgAsRelease(hAs); 654 } 655 else 656 LogRel(("DigWinNt/DbgPrint: Failed to resolve kernel address space handle\n")); 657 } 346 658 347 659 … … 905 1217 Assert(pThis->fValid); 906 1218 1219 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 1220 if (pThis->hBpDbgPrint != NIL_DBGFBP) 1221 { 1222 int rc = DBGFR3BpClear(pUVM, pThis->hBpDbgPrint); 1223 AssertRC(rc); 1224 pThis->hBpDbgPrint = NIL_DBGFBP; 1225 } 1226 1227 if (pThis->hBpOwnerDbgPrint != NIL_DBGFBPOWNER) 1228 { 1229 int rc = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint); 1230 AssertRC(rc); 1231 pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER; 1232 } 1233 #endif 1234 907 1235 /* 908 1236 * As long as we're using our private LDR reader implementation, … … 1094 1422 /* Try resolving the KPCR and KPCRB addresses for each vCPU. */ 1095 1423 dbgDiggerWinNtResolveKpcr(pThis, pUVM, pVMM); 1424 1425 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 1426 /* Try to hook into the DbgPrint/vDbgPrint... code so we can gather information from the drivers. */ 1427 dbgDiggerWinNtDbgPrintHook(pThis, pUVM); 1428 #endif 1096 1429 1097 1430 pThis->fValid = true; … … 1418 1751 pThis->IWinNt.u32EndMagic = DBGFOSIWINNT_MAGIC; 1419 1752 1753 #ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING 1754 pThis->hBpDbgPrint = NIL_DBGFBP; 1755 pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER; 1756 #endif 1757 1420 1758 return VINF_SUCCESS; 1421 1759 }
Note:
See TracChangeset
for help on using the changeset viewer.