VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/MachineDebuggerImpl.cpp@ 35502

Last change on this file since 35502 was 35502, checked in by vboxsync, 14 years ago

MachineDebugger::GetRegister: Initial implementation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.1 KB
Line 
1/* $Id: MachineDebuggerImpl.cpp 35502 2011-01-12 11:19:30Z vboxsync $ */
2/** @file
3 * VBox IMachineDebugger COM class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include "MachineDebuggerImpl.h"
19
20#include "Global.h"
21#include "ConsoleImpl.h"
22
23#include "AutoCaller.h"
24#include "Logging.h"
25
26#include <VBox/vmm/em.h>
27#include <VBox/vmm/patm.h>
28#include <VBox/vmm/csam.h>
29#include <VBox/vmm/vm.h>
30#include <VBox/vmm/tm.h>
31#include <VBox/vmm/hwaccm.h>
32#include <VBox/err.h>
33#include <iprt/cpp/utils.h>
34
35// defines
36/////////////////////////////////////////////////////////////////////////////
37
38
39// globals
40/////////////////////////////////////////////////////////////////////////////
41
42
43// constructor / destructor
44/////////////////////////////////////////////////////////////////////////////
45
46MachineDebugger::MachineDebugger()
47 : mParent(NULL)
48{
49}
50
51MachineDebugger::~MachineDebugger()
52{
53}
54
55HRESULT MachineDebugger::FinalConstruct()
56{
57 unconst(mParent) = NULL;
58 return S_OK;
59}
60
61void MachineDebugger::FinalRelease()
62{
63 uninit();
64}
65
66// public initializer/uninitializer for internal purposes only
67/////////////////////////////////////////////////////////////////////////////
68
69/**
70 * Initializes the machine debugger object.
71 *
72 * @returns COM result indicator
73 * @param aParent handle of our parent object
74 */
75HRESULT MachineDebugger::init (Console *aParent)
76{
77 LogFlowThisFunc(("aParent=%p\n", aParent));
78
79 ComAssertRet(aParent, E_INVALIDARG);
80
81 /* Enclose the state transition NotReady->InInit->Ready */
82 AutoInitSpan autoInitSpan(this);
83 AssertReturn(autoInitSpan.isOk(), E_FAIL);
84
85 unconst(mParent) = aParent;
86
87 mSinglestepQueued = ~0;
88 mRecompileUserQueued = ~0;
89 mRecompileSupervisorQueued = ~0;
90 mPatmEnabledQueued = ~0;
91 mCsamEnabledQueued = ~0;
92 mLogEnabledQueued = ~0;
93 mVirtualTimeRateQueued = ~0;
94 mFlushMode = false;
95
96 /* Confirm a successful initialization */
97 autoInitSpan.setSucceeded();
98
99 return S_OK;
100}
101
102/**
103 * Uninitializes the instance and sets the ready flag to FALSE.
104 * Called either from FinalRelease() or by the parent when it gets destroyed.
105 */
106void MachineDebugger::uninit()
107{
108 LogFlowThisFunc(("\n"));
109
110 /* Enclose the state transition Ready->InUninit->NotReady */
111 AutoUninitSpan autoUninitSpan(this);
112 if (autoUninitSpan.uninitDone())
113 return;
114
115 unconst(mParent) = NULL;
116 mFlushMode = false;
117}
118
119// IMachineDebugger properties
120/////////////////////////////////////////////////////////////////////////////
121
122/**
123 * Returns the current singlestepping flag.
124 *
125 * @returns COM status code
126 * @param aEnabled address of result variable
127 */
128STDMETHODIMP MachineDebugger::COMGETTER(Singlestep) (BOOL *aEnabled)
129{
130 CheckComArgOutPointerValid(aEnabled);
131
132 AutoCaller autoCaller(this);
133 if (FAILED(autoCaller.rc())) return autoCaller.rc();
134
135 /** @todo */
136 ReturnComNotImplemented();
137}
138
139/**
140 * Sets the singlestepping flag.
141 *
142 * @returns COM status code
143 * @param aEnable new singlestepping flag
144 */
145STDMETHODIMP MachineDebugger::COMSETTER(Singlestep) (BOOL aEnable)
146{
147 AutoCaller autoCaller(this);
148 if (FAILED(autoCaller.rc())) return autoCaller.rc();
149
150 /** @todo */
151 ReturnComNotImplemented();
152}
153
154/**
155 * Returns the current recompile user mode code flag.
156 *
157 * @returns COM status code
158 * @param aEnabled address of result variable
159 */
160STDMETHODIMP MachineDebugger::COMGETTER(RecompileUser) (BOOL *aEnabled)
161{
162 CheckComArgOutPointerValid(aEnabled);
163
164 AutoCaller autoCaller(this);
165 if (FAILED(autoCaller.rc())) return autoCaller.rc();
166
167 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
168
169 Console::SafeVMPtrQuiet pVM (mParent);
170
171 if (pVM.isOk())
172 *aEnabled = !EMIsRawRing3Enabled (pVM.raw());
173 else
174 *aEnabled = false;
175
176 return S_OK;
177}
178
179/**
180 * Sets the recompile user mode code flag.
181 *
182 * @returns COM status
183 * @param aEnable new user mode code recompile flag.
184 */
185STDMETHODIMP MachineDebugger::COMSETTER(RecompileUser) (BOOL aEnable)
186{
187 LogFlowThisFunc(("enable=%d\n", aEnable));
188
189 AutoCaller autoCaller(this);
190 if (FAILED(autoCaller.rc())) return autoCaller.rc();
191
192 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
193
194 if (queueSettings())
195 {
196 // queue the request
197 mRecompileUserQueued = aEnable;
198 return S_OK;
199 }
200
201 Console::SafeVMPtr pVM (mParent);
202 if (FAILED(pVM.rc())) return pVM.rc();
203
204 EMRAWMODE rawModeFlag = aEnable ? EMRAW_RING3_DISABLE : EMRAW_RING3_ENABLE;
205 int rcVBox = VMR3ReqCallWait(pVM, VMCPUID_ANY, (PFNRT)EMR3RawSetMode, 2, pVM.raw(), rawModeFlag);
206 if (RT_SUCCESS(rcVBox))
207 return S_OK;
208
209 AssertMsgFailed (("Could not set raw mode flags to %d, rcVBox = %Rrc\n",
210 rawModeFlag, rcVBox));
211 return E_FAIL;
212}
213
214/**
215 * Returns the current recompile supervisor code flag.
216 *
217 * @returns COM status code
218 * @param aEnabled address of result variable
219 */
220STDMETHODIMP MachineDebugger::COMGETTER(RecompileSupervisor) (BOOL *aEnabled)
221{
222 CheckComArgOutPointerValid(aEnabled);
223
224 AutoCaller autoCaller(this);
225 if (FAILED(autoCaller.rc())) return autoCaller.rc();
226
227 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
228
229 Console::SafeVMPtrQuiet pVM (mParent);
230
231 if (pVM.isOk())
232 *aEnabled = !EMIsRawRing0Enabled (pVM.raw());
233 else
234 *aEnabled = false;
235
236 return S_OK;
237}
238
239/**
240 * Sets the new recompile supervisor code flag.
241 *
242 * @returns COM status code
243 * @param aEnable new recompile supervisor code flag
244 */
245STDMETHODIMP MachineDebugger::COMSETTER(RecompileSupervisor) (BOOL aEnable)
246{
247 LogFlowThisFunc(("enable=%d\n", aEnable));
248
249 AutoCaller autoCaller(this);
250 if (FAILED(autoCaller.rc())) return autoCaller.rc();
251
252 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
253
254 if (queueSettings())
255 {
256 // queue the request
257 mRecompileSupervisorQueued = aEnable;
258 return S_OK;
259 }
260
261 Console::SafeVMPtr pVM (mParent);
262 if (FAILED(pVM.rc())) return pVM.rc();
263
264 EMRAWMODE rawModeFlag = aEnable ? EMRAW_RING0_DISABLE : EMRAW_RING0_ENABLE;
265 int rcVBox = VMR3ReqCallWait(pVM, VMCPUID_ANY, (PFNRT)EMR3RawSetMode, 2, pVM.raw(), rawModeFlag);
266 if (RT_SUCCESS(rcVBox))
267 return S_OK;
268
269 AssertMsgFailed (("Could not set raw mode flags to %d, rcVBox = %Rrc\n",
270 rawModeFlag, rcVBox));
271 return E_FAIL;
272}
273
274/**
275 * Returns the current patch manager enabled flag.
276 *
277 * @returns COM status code
278 * @param aEnabled address of result variable
279 */
280STDMETHODIMP MachineDebugger::COMGETTER(PATMEnabled) (BOOL *aEnabled)
281{
282 CheckComArgOutPointerValid(aEnabled);
283
284 AutoCaller autoCaller(this);
285 if (FAILED(autoCaller.rc())) return autoCaller.rc();
286
287 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
288
289 Console::SafeVMPtrQuiet pVM (mParent);
290
291 if (pVM.isOk())
292 *aEnabled = PATMIsEnabled (pVM.raw());
293 else
294 *aEnabled = false;
295
296 return S_OK;
297}
298
299/**
300 * Set the new patch manager enabled flag.
301 *
302 * @returns COM status code
303 * @param aEnable new patch manager enabled flag
304 */
305STDMETHODIMP MachineDebugger::COMSETTER(PATMEnabled) (BOOL aEnable)
306{
307 LogFlowThisFunc(("enable=%d\n", aEnable));
308
309 AutoCaller autoCaller(this);
310 if (FAILED(autoCaller.rc())) return autoCaller.rc();
311
312 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
313
314 if (queueSettings())
315 {
316 // queue the request
317 mPatmEnabledQueued = aEnable;
318 return S_OK;
319 }
320
321 Console::SafeVMPtr pVM(mParent);
322 if (FAILED(pVM.rc())) return pVM.rc();
323
324 PATMR3AllowPatching (pVM, aEnable);
325
326 return S_OK;
327}
328
329/**
330 * Returns the current code scanner enabled flag.
331 *
332 * @returns COM status code
333 * @param aEnabled address of result variable
334 */
335STDMETHODIMP MachineDebugger::COMGETTER(CSAMEnabled) (BOOL *aEnabled)
336{
337 CheckComArgOutPointerValid(aEnabled);
338
339 AutoCaller autoCaller(this);
340 if (FAILED(autoCaller.rc())) return autoCaller.rc();
341
342 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
343
344 Console::SafeVMPtrQuiet pVM (mParent);
345
346 if (pVM.isOk())
347 *aEnabled = CSAMIsEnabled (pVM.raw());
348 else
349 *aEnabled = false;
350
351 return S_OK;
352}
353
354/**
355 * Sets the new code scanner enabled flag.
356 *
357 * @returns COM status code
358 * @param aEnable new code scanner enabled flag
359 */
360STDMETHODIMP MachineDebugger::COMSETTER(CSAMEnabled) (BOOL aEnable)
361{
362 LogFlowThisFunc(("enable=%d\n", aEnable));
363
364 AutoCaller autoCaller(this);
365 if (FAILED(autoCaller.rc())) return autoCaller.rc();
366
367 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
368
369 if (queueSettings())
370 {
371 // queue the request
372 mCsamEnabledQueued = aEnable;
373 return S_OK;
374 }
375
376 Console::SafeVMPtr pVM(mParent);
377 if (FAILED(pVM.rc())) return pVM.rc();
378
379 int vrc;
380 if (aEnable)
381 vrc = CSAMEnableScanning (pVM);
382 else
383 vrc = CSAMDisableScanning (pVM);
384
385 if (RT_FAILURE(vrc))
386 {
387 /** @todo handle error case */
388 }
389
390 return S_OK;
391}
392
393/**
394 * Returns the log enabled / disabled status.
395 *
396 * @returns COM status code
397 * @param aEnabled address of result variable
398 */
399STDMETHODIMP MachineDebugger::COMGETTER(LogEnabled) (BOOL *aEnabled)
400{
401 CheckComArgOutPointerValid(aEnabled);
402
403 AutoCaller autoCaller(this);
404 if (FAILED(autoCaller.rc())) return autoCaller.rc();
405
406#ifdef LOG_ENABLED
407 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
408
409 const PRTLOGGER pLogInstance = RTLogDefaultInstance();
410 *aEnabled = pLogInstance && !(pLogInstance->fFlags & RTLOGFLAGS_DISABLED);
411#else
412 *aEnabled = false;
413#endif
414
415 return S_OK;
416}
417
418/**
419 * Enables or disables logging.
420 *
421 * @returns COM status code
422 * @param aEnabled The new code log state.
423 */
424STDMETHODIMP MachineDebugger::COMSETTER(LogEnabled) (BOOL aEnabled)
425{
426 LogFlowThisFunc(("aEnabled=%d\n", aEnabled));
427
428 AutoCaller autoCaller(this);
429 if (FAILED(autoCaller.rc())) return autoCaller.rc();
430
431 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
432
433 if (queueSettings())
434 {
435 // queue the request
436 mLogEnabledQueued = aEnabled;
437 return S_OK;
438 }
439
440 Console::SafeVMPtr pVM(mParent);
441 if (FAILED(pVM.rc())) return pVM.rc();
442
443#ifdef LOG_ENABLED
444 int vrc = DBGFR3LogModifyFlags (pVM, aEnabled ? "enabled" : "disabled");
445 if (RT_FAILURE(vrc))
446 {
447 /** @todo handle error code. */
448 }
449#endif
450
451 return S_OK;
452}
453
454STDMETHODIMP MachineDebugger::COMGETTER(LogFlags)(BSTR *a_pbstrSettings)
455{
456 ReturnComNotImplemented();
457}
458
459STDMETHODIMP MachineDebugger::COMGETTER(LogGroups)(BSTR *a_pbstrSettings)
460{
461 ReturnComNotImplemented();
462}
463
464STDMETHODIMP MachineDebugger::COMGETTER(LogDestinations)(BSTR *a_pbstrSettings)
465{
466 ReturnComNotImplemented();
467}
468
469/**
470 * Returns the current hardware virtualization flag.
471 *
472 * @returns COM status code
473 * @param aEnabled address of result variable
474 */
475STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExEnabled) (BOOL *aEnabled)
476{
477 CheckComArgOutPointerValid(aEnabled);
478
479 AutoCaller autoCaller(this);
480 if (FAILED(autoCaller.rc())) return autoCaller.rc();
481
482 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
483
484 Console::SafeVMPtrQuiet pVM (mParent);
485
486 if (pVM.isOk())
487 *aEnabled = HWACCMIsEnabled (pVM.raw());
488 else
489 *aEnabled = false;
490
491 return S_OK;
492}
493
494/**
495 * Returns the current nested paging flag.
496 *
497 * @returns COM status code
498 * @param aEnabled address of result variable
499 */
500STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExNestedPagingEnabled) (BOOL *aEnabled)
501{
502 CheckComArgOutPointerValid(aEnabled);
503
504 AutoCaller autoCaller(this);
505 if (FAILED(autoCaller.rc())) return autoCaller.rc();
506
507 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
508
509 Console::SafeVMPtrQuiet pVM (mParent);
510
511 if (pVM.isOk())
512 *aEnabled = HWACCMR3IsNestedPagingActive (pVM.raw());
513 else
514 *aEnabled = false;
515
516 return S_OK;
517}
518
519/**
520 * Returns the current VPID flag.
521 *
522 * @returns COM status code
523 * @param aEnabled address of result variable
524 */
525STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExVPIDEnabled) (BOOL *aEnabled)
526{
527 CheckComArgOutPointerValid(aEnabled);
528
529 AutoCaller autoCaller(this);
530 if (FAILED(autoCaller.rc())) return autoCaller.rc();
531
532 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
533
534 Console::SafeVMPtrQuiet pVM (mParent);
535
536 if (pVM.isOk())
537 *aEnabled = HWACCMR3IsVPIDActive (pVM.raw());
538 else
539 *aEnabled = false;
540
541 return S_OK;
542}
543
544STDMETHODIMP MachineDebugger::COMGETTER(OSName)(BSTR *a_pbstrName)
545{
546 LogFlowThisFunc(("\n"));
547 CheckComArgNotNull(a_pbstrName);
548 AutoCaller autoCaller(this);
549 HRESULT hrc = autoCaller.rc();
550 if (SUCCEEDED(hrc))
551 {
552 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
553 Console::SafeVMPtr ptrVM(mParent);
554 hrc = ptrVM.rc();
555 if (SUCCEEDED(hrc))
556 {
557 /*
558 * Do the job and try convert the name.
559 */
560 char szName[64];
561 int vrc = DBGFR3OSQueryNameAndVersion(ptrVM.raw(), szName, sizeof(szName), NULL, 0);
562 if (RT_SUCCESS(vrc))
563 {
564 try
565 {
566 Bstr bstrName(szName);
567 bstrName.detachTo(a_pbstrName);
568 }
569 catch (std::bad_alloc)
570 {
571 hrc = E_OUTOFMEMORY;
572 }
573 }
574 else
575 hrc = setError(VBOX_E_VM_ERROR, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
576 }
577 }
578 return hrc;
579}
580
581STDMETHODIMP MachineDebugger::COMGETTER(OSVersion)(BSTR *a_pbstrVersion)
582{
583 LogFlowThisFunc(("\n"));
584 CheckComArgNotNull(a_pbstrVersion);
585 AutoCaller autoCaller(this);
586 HRESULT hrc = autoCaller.rc();
587 if (SUCCEEDED(hrc))
588 {
589 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
590 Console::SafeVMPtr ptrVM(mParent);
591 hrc = ptrVM.rc();
592 if (SUCCEEDED(hrc))
593 {
594 /*
595 * Do the job and try convert the name.
596 */
597 char szVersion[256];
598 int vrc = DBGFR3OSQueryNameAndVersion(ptrVM.raw(), NULL, 0, szVersion, sizeof(szVersion));
599 if (RT_SUCCESS(vrc))
600 {
601 try
602 {
603 Bstr bstrVersion(szVersion);
604 bstrVersion.detachTo(a_pbstrVersion);
605 }
606 catch (std::bad_alloc)
607 {
608 hrc = E_OUTOFMEMORY;
609 }
610 }
611 else
612 hrc = setError(VBOX_E_VM_ERROR, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
613 }
614 }
615 return hrc;
616}
617
618/**
619 * Returns the current PAE flag.
620 *
621 * @returns COM status code
622 * @param aEnabled address of result variable
623 */
624STDMETHODIMP MachineDebugger::COMGETTER(PAEEnabled) (BOOL *aEnabled)
625{
626 CheckComArgOutPointerValid(aEnabled);
627
628 AutoCaller autoCaller(this);
629 if (FAILED(autoCaller.rc())) return autoCaller.rc();
630
631 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
632
633 Console::SafeVMPtrQuiet pVM (mParent);
634
635 if (pVM.isOk())
636 {
637 uint64_t cr4 = CPUMGetGuestCR4 (VMMGetCpu0(pVM.raw()));
638 *aEnabled = !!(cr4 & X86_CR4_PAE);
639 }
640 else
641 *aEnabled = false;
642
643 return S_OK;
644}
645
646/**
647 * Returns the current virtual time rate.
648 *
649 * @returns COM status code.
650 * @param aPct Where to store the rate.
651 */
652STDMETHODIMP MachineDebugger::COMGETTER(VirtualTimeRate) (ULONG *aPct)
653{
654 CheckComArgOutPointerValid(aPct);
655
656 AutoCaller autoCaller(this);
657 if (FAILED(autoCaller.rc())) return autoCaller.rc();
658
659 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
660
661 Console::SafeVMPtrQuiet pVM (mParent);
662
663 if (pVM.isOk())
664 *aPct = TMGetWarpDrive (pVM);
665 else
666 *aPct = 100;
667
668 return S_OK;
669}
670
671/**
672 * Returns the current virtual time rate.
673 *
674 * @returns COM status code.
675 * @param aPct Where to store the rate.
676 */
677STDMETHODIMP MachineDebugger::COMSETTER(VirtualTimeRate) (ULONG aPct)
678{
679 if (aPct < 2 || aPct > 20000)
680 return E_INVALIDARG;
681
682 AutoCaller autoCaller(this);
683 if (FAILED(autoCaller.rc())) return autoCaller.rc();
684
685 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
686
687 if (queueSettings())
688 {
689 // queue the request
690 mVirtualTimeRateQueued = aPct;
691 return S_OK;
692 }
693
694 Console::SafeVMPtr pVM(mParent);
695 if (FAILED(pVM.rc())) return pVM.rc();
696
697 int vrc = TMR3SetWarpDrive (pVM, aPct);
698 if (RT_FAILURE(vrc))
699 {
700 /** @todo handle error code. */
701 }
702
703 return S_OK;
704}
705
706/**
707 * Hack for getting the VM handle.
708 * This is only temporary (promise) while prototyping the debugger.
709 *
710 * @returns COM status code
711 * @param aVm Where to store the vm handle.
712 * Since there is no uintptr_t in COM, we're using the max integer.
713 * (No, ULONG is not pointer sized!)
714 */
715STDMETHODIMP MachineDebugger::COMGETTER(VM) (LONG64 *aVm)
716{
717 CheckComArgOutPointerValid(aVm);
718
719 AutoCaller autoCaller(this);
720 if (FAILED(autoCaller.rc())) return autoCaller.rc();
721
722 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
723
724 Console::SafeVMPtr pVM(mParent);
725 if (FAILED(pVM.rc())) return pVM.rc();
726
727 *aVm = (intptr_t)pVM.raw();
728
729 /*
730 * Note: pVM protection provided by SafeVMPtr is no more effective
731 * after we return from this method.
732 */
733
734 return S_OK;
735}
736
737// IMachineDebugger methods
738/////////////////////////////////////////////////////////////////////////////
739
740STDMETHODIMP MachineDebugger::DumpGuestCore(IN_BSTR a_bstrFilename, IN_BSTR a_bstrCompression)
741{
742 CheckComArgStrNotEmptyOrNull(a_bstrFilename);
743 Utf8Str strFilename(a_bstrFilename);
744 if (a_bstrCompression && *a_bstrCompression)
745 return setError(E_INVALIDARG, tr("The compression parameter must be empty"));
746
747 AutoCaller autoCaller(this);
748 HRESULT hrc = autoCaller.rc();
749 if (SUCCEEDED(hrc))
750 {
751 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
752 Console::SafeVMPtr ptrVM(mParent);
753 hrc = ptrVM.rc();
754 if (SUCCEEDED(hrc))
755 {
756 int vrc = DBGFR3CoreWrite(ptrVM, strFilename.c_str(), false /*fReplaceFile*/);
757 if (RT_SUCCESS(vrc))
758 hrc = S_OK;
759 else
760 hrc = setError(E_FAIL, tr("DBGFR3CoreWrite failed with %Rrc"), vrc);
761 }
762 }
763
764 return hrc;
765}
766
767STDMETHODIMP MachineDebugger::DumpHostProcessCore(IN_BSTR a_bstrFilename, IN_BSTR a_bstrCompression)
768{
769 ReturnComNotImplemented();
770}
771
772/**
773 * Debug info string buffer formatter.
774 */
775typedef struct MACHINEDEBUGGERINOFHLP
776{
777 /** The core info helper structure. */
778 DBGFINFOHLP Core;
779 /** Pointer to the buffer. */
780 char *pszBuf;
781 /** The size of the buffer. */
782 size_t cbBuf;
783 /** The offset into the buffer */
784 size_t offBuf;
785 /** Indicates an out-of-memory condition. */
786 bool fOutOfMemory;
787} MACHINEDEBUGGERINOFHLP;
788/** Pointer to a Debug info string buffer formatter. */
789typedef MACHINEDEBUGGERINOFHLP *PMACHINEDEBUGGERINOFHLP;
790
791
792/**
793 * @callback_method_impl{FNRTSTROUTPUT}
794 */
795static DECLCALLBACK(size_t) MachineDebuggerInfoOutput(void *pvArg, const char *pachChars, size_t cbChars)
796{
797 PMACHINEDEBUGGERINOFHLP pHlp = (PMACHINEDEBUGGERINOFHLP)pvArg;
798
799 /*
800 * Grow the buffer if required.
801 */
802 size_t const cbRequired = cbChars + pHlp->offBuf + 1;
803 if (cbRequired > pHlp->cbBuf)
804 {
805 if (RT_UNLIKELY(pHlp->fOutOfMemory))
806 return 0;
807
808 size_t cbBufNew = pHlp->cbBuf * 2;
809 if (cbRequired > cbBufNew)
810 cbBufNew = RT_ALIGN_Z(cbRequired, 256);
811 void *pvBufNew = RTMemRealloc(pHlp->pszBuf, cbBufNew);
812 if (RT_UNLIKELY(!pvBufNew))
813 {
814 pHlp->fOutOfMemory = true;
815 RTMemFree(pHlp->pszBuf);
816 pHlp->pszBuf = NULL;
817 pHlp->cbBuf = 0;
818 pHlp->offBuf = 0;
819 return 0;
820 }
821
822 pHlp->pszBuf = (char *)pvBufNew;
823 pHlp->cbBuf = cbBufNew;
824 }
825
826 /*
827 * Copy the bytes into the buffer and terminate it.
828 */
829 memcpy(&pHlp->pszBuf[pHlp->offBuf], pachChars, cbChars);
830 pHlp->offBuf += cbChars;
831 pHlp->pszBuf[pHlp->offBuf] = '\0';
832 Assert(pHlp->offBuf < pHlp->cbBuf);
833 return cbChars;
834}
835
836/**
837 * @interface_method_impl{DBGFINFOHLP, pfnPrintfV}
838 */
839static DECLCALLBACK(void) MachineDebuggerInfoPrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list va)
840{
841 RTStrFormatV(MachineDebuggerInfoOutput, (void *)pHlp, NULL, NULL, pszFormat, va);
842}
843
844/**
845 * @interface_method_impl{DBGFINFOHLP, pfnPrintf}
846 */
847static DECLCALLBACK(void) MachineDebuggerInfoPrintf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...)
848{
849 va_list va;
850 va_start(va, pszFormat);
851 MachineDebuggerInfoPrintfV(pHlp, pszFormat, va);
852 va_end(va);
853}
854
855/**
856 * Initializes the debug info string buffer formatter
857 *
858 * @param pHlp The help structure to init.
859 */
860static void MachineDebuggerInfoInit(PMACHINEDEBUGGERINOFHLP pHlp)
861{
862 pHlp->Core.pfnPrintf = MachineDebuggerInfoPrintf;
863 pHlp->Core.pfnPrintfV = MachineDebuggerInfoPrintfV;
864 pHlp->pszBuf = NULL;
865 pHlp->cbBuf = 0;
866 pHlp->offBuf = 0;
867 pHlp->fOutOfMemory = false;
868}
869
870/**
871 * Deletes the debug info string buffer formatter.
872 * @param pHlp The helper structure to delete.
873 */
874static void MachineDebuggerInfoDelete(PMACHINEDEBUGGERINOFHLP pHlp)
875{
876 RTMemFree(pHlp->pszBuf);
877 pHlp->pszBuf = NULL;
878}
879
880STDMETHODIMP MachineDebugger::Info(IN_BSTR a_bstrName, IN_BSTR a_bstrArgs, BSTR *a_pbstrInfo)
881{
882 LogFlowThisFunc(("\n"));
883
884 /*
885 * Validate and convert input.
886 */
887 CheckComArgStrNotEmptyOrNull(a_bstrName);
888 Utf8Str strName, strArgs;
889 try
890 {
891 strName = a_bstrName;
892 strArgs = a_bstrArgs;
893 }
894 catch (std::bad_alloc)
895 {
896 return E_OUTOFMEMORY;
897 }
898
899 /*
900 * Do the autocaller and lock bits.
901 */
902 AutoCaller autoCaller(this);
903 HRESULT hrc = autoCaller.rc();
904 if (SUCCEEDED(hrc))
905 {
906 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
907 Console::SafeVMPtr ptrVM(mParent);
908 hrc = ptrVM.rc();
909 if (SUCCEEDED(hrc))
910 {
911 /*
912 * Create a helper and call DBGFR3Info.
913 */
914 MACHINEDEBUGGERINOFHLP Hlp;
915 MachineDebuggerInfoInit(&Hlp);
916 int vrc = DBGFR3Info(ptrVM.raw(), strName.c_str(), strArgs.c_str(), &Hlp.Core);
917 if (RT_SUCCESS(vrc))
918 {
919 if (!Hlp.fOutOfMemory)
920 {
921 /*
922 * Convert the info string, watching out for allocation errors.
923 */
924 try
925 {
926 Bstr bstrInfo(Hlp.pszBuf);
927 bstrInfo.detachTo(a_pbstrInfo);
928 }
929 catch (std::bad_alloc)
930 {
931 hrc = E_OUTOFMEMORY;
932 }
933 }
934 else
935 hrc = E_OUTOFMEMORY;
936 }
937 else
938 hrc = setError(VBOX_E_VM_ERROR, tr("DBGFR3Info failed with %Rrc"), vrc);
939 MachineDebuggerInfoDelete(&Hlp);
940 }
941 }
942 return hrc;
943}
944
945STDMETHODIMP MachineDebugger::InjectNMI()
946{
947 LogFlowThisFunc(("\n"));
948
949 AutoCaller autoCaller(this);
950 HRESULT hrc = autoCaller.rc();
951 if (SUCCEEDED(hrc))
952 {
953 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
954 Console::SafeVMPtr ptrVM(mParent);
955 hrc = ptrVM.rc();
956 if (SUCCEEDED(hrc))
957 {
958 int vrc = HWACCMR3InjectNMI(ptrVM);
959 if (RT_SUCCESS(vrc))
960 hrc = S_OK;
961 else
962 hrc = setError(E_FAIL, tr("HWACCMR3InjectNMI failed with %Rrc"), vrc);
963 }
964 }
965 return hrc;
966}
967
968STDMETHODIMP MachineDebugger::ModifyLogFlags(IN_BSTR a_bstrSettings)
969{
970 ReturnComNotImplemented();
971}
972
973STDMETHODIMP MachineDebugger::ModifyLogGroups(IN_BSTR a_bstrSettings)
974{
975 ReturnComNotImplemented();
976}
977
978STDMETHODIMP MachineDebugger::ModifyLogDestinations(IN_BSTR a_bstrSettings)
979{
980 ReturnComNotImplemented();
981}
982
983STDMETHODIMP MachineDebugger::ReadPhysicalMemory(LONG64 a_Address, ULONG a_cbRead, ComSafeArrayOut(BYTE, a_abData))
984{
985 ReturnComNotImplemented();
986}
987
988STDMETHODIMP MachineDebugger::WritePhysicalMemory(LONG64 a_Address, ULONG a_cbRead, ComSafeArrayIn(BYTE, a_abData))
989{
990 ReturnComNotImplemented();
991}
992
993STDMETHODIMP MachineDebugger::ReadVirtualMemory(ULONG a_idCpu, LONG64 a_Address, ULONG a_cbRead, ComSafeArrayOut(BYTE, a_abData))
994{
995 ReturnComNotImplemented();
996}
997
998STDMETHODIMP MachineDebugger::WriteVirtualMemory(ULONG a_idCpu, LONG64 a_Address, ULONG a_cbRead, ComSafeArrayIn(BYTE, a_abData))
999{
1000 ReturnComNotImplemented();
1001}
1002
1003STDMETHODIMP MachineDebugger::DetectOS(BSTR *a_pbstrName)
1004{
1005 LogFlowThisFunc(("\n"));
1006 CheckComArgNotNull(a_pbstrName);
1007
1008 /*
1009 * Do the autocaller and lock bits.
1010 */
1011 AutoCaller autoCaller(this);
1012 HRESULT hrc = autoCaller.rc();
1013 if (SUCCEEDED(hrc))
1014 {
1015 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1016 Console::SafeVMPtr ptrVM(mParent);
1017 hrc = ptrVM.rc();
1018 if (SUCCEEDED(hrc))
1019 {
1020 /*
1021 * Do the job and try convert the name.
1022 */
1023/** @todo automatically load the DBGC plugins or this is a waste of time. */
1024 char szName[64];
1025 int vrc = DBGFR3OSDetect(ptrVM.raw(), szName, sizeof(szName));
1026 if (RT_SUCCESS(vrc) && vrc != VINF_DBGF_OS_NOT_DETCTED)
1027 {
1028 try
1029 {
1030 Bstr bstrName(szName);
1031 bstrName.detachTo(a_pbstrName);
1032 }
1033 catch (std::bad_alloc)
1034 {
1035 hrc = E_OUTOFMEMORY;
1036 }
1037 }
1038 else
1039 hrc = setError(VBOX_E_VM_ERROR, tr("DBGFR3OSDetect failed with %Rrc"), vrc);
1040 }
1041 }
1042 return hrc;
1043}
1044
1045/**
1046 * Formats a register value.
1047 *
1048 * This is used by both register getter methods.
1049 *
1050 * @returns
1051 * @param a_pbstr The output Bstr variable.
1052 * @param a_pValue The value to format.
1053 * @param a_enmType The type of the value.
1054 */
1055static HRESULT formatRegisterValue(Bstr *a_pbstr, PCDBGFREGVAL a_pValue, DBGFREGVALTYPE a_enmType)
1056{
1057 char szHex[128]; /* Must be big because RTStrFormatNumber is unsafe. */
1058
1059 switch (a_enmType)
1060 {
1061 case DBGFREGVALTYPE_U8:
1062 RTStrFormatNumber(szHex, a_pValue->u8, 16, 2+2, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_8BIT);
1063 *a_pbstr = szHex;
1064 return S_OK;
1065
1066 case DBGFREGVALTYPE_U16:
1067 RTStrFormatNumber(szHex, a_pValue->u16, 16, 2+4, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_16BIT);
1068 *a_pbstr = szHex;
1069 return S_OK;
1070
1071 case DBGFREGVALTYPE_U32:
1072 RTStrFormatNumber(szHex, a_pValue->u32, 16, 2+8, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_32BIT);
1073 *a_pbstr = szHex;
1074 return S_OK;
1075
1076 case DBGFREGVALTYPE_U64:
1077 RTStrFormatNumber(szHex, a_pValue->u64, 16, 2+16, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
1078 *a_pbstr = szHex;
1079 return S_OK;
1080
1081 case DBGFREGVALTYPE_U128:
1082 RTStrFormatNumber(szHex, a_pValue->au64[1], 16, 2+16, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
1083 RTStrFormatNumber(&szHex[2+16], a_pValue->au64[0], 16, 16, 0, RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
1084 *a_pbstr = szHex;
1085 return S_OK;
1086
1087 case DBGFREGVALTYPE_LRD:
1088 /** @todo long double -> string conversion. */
1089 /** @todo long double == double on MSC. Stupid, stupid,
1090 * microsoft! */
1091 RTStrFormatNumber(szHex, a_pValue->au16[5], 16, 2+4, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_16BIT);
1092 RTStrFormatNumber(&szHex[2+4], a_pValue->au64[0], 16, 16, 0, RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
1093 *a_pbstr = szHex;
1094 return S_OK;
1095
1096 case DBGFREGVALTYPE_DTR:
1097 RTStrFormatNumber(szHex, a_pValue->dtr.u64Base, 16, 2+16, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
1098 szHex[2+16] = ':';
1099 RTStrFormatNumber(&szHex[2+16+1], a_pValue->dtr.u32Limit, 16, 4, 0, RTSTR_F_ZEROPAD | RTSTR_F_32BIT);
1100 *a_pbstr = szHex;
1101 return S_OK;
1102
1103 case DBGFREGVALTYPE_INVALID:
1104 case DBGFREGVALTYPE_END:
1105 case DBGFREGVALTYPE_32BIT_HACK:
1106 break;
1107 /* no default */
1108 }
1109
1110 return E_UNEXPECTED;
1111}
1112
1113STDMETHODIMP MachineDebugger::GetRegister(ULONG a_idCpu, IN_BSTR a_bstrName, BSTR *a_pbstrValue)
1114{
1115 /*
1116 * Validate and convert input.
1117 */
1118 CheckComArgStrNotEmptyOrNull(a_bstrName);
1119 CheckComArgNotNull(a_pbstrValue);
1120 Utf8Str strName;
1121 try
1122 {
1123 strName = a_bstrName;
1124 }
1125 catch (std::bad_alloc)
1126 {
1127 return E_OUTOFMEMORY;
1128 }
1129
1130 /*
1131 * The prologue.
1132 */
1133 LogFlowThisFunc(("\n"));
1134 AutoCaller autoCaller(this);
1135 HRESULT hrc = autoCaller.rc();
1136 if (SUCCEEDED(hrc))
1137 {
1138 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1139 Console::SafeVMPtr ptrVM(mParent);
1140 hrc = ptrVM.rc();
1141 if (SUCCEEDED(hrc))
1142 {
1143 /*
1144 * Real work.
1145 */
1146 DBGFREGVAL Value;
1147 DBGFREGVALTYPE enmType;
1148 int vrc = DBGFR3RegNmQuery(ptrVM.raw(), a_idCpu, strName.c_str(), &Value, &enmType);
1149 if (RT_SUCCESS(vrc))
1150 {
1151 try
1152 {
1153 Bstr bstrValue;
1154 hrc = formatRegisterValue(&bstrValue, &Value, enmType);
1155 if (SUCCEEDED(hrc))
1156 bstrValue.detachTo(a_pbstrValue);
1157 }
1158 catch (std::bad_alloc)
1159 {
1160 hrc = E_OUTOFMEMORY;
1161 }
1162 }
1163 }
1164 }
1165
1166 return hrc;
1167}
1168
1169STDMETHODIMP MachineDebugger::GetRegisters(ULONG a_idCpu, ComSafeArrayOut(BSTR, a_bstrNames), ComSafeArrayOut(BSTR, a_bstrValues))
1170{
1171 /*
1172 * The prologue.
1173 */
1174 LogFlowThisFunc(("\n"));
1175 AutoCaller autoCaller(this);
1176 HRESULT hrc = autoCaller.rc();
1177 if (SUCCEEDED(hrc))
1178 {
1179 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1180 Console::SafeVMPtr ptrVM(mParent);
1181 hrc = ptrVM.rc();
1182 if (SUCCEEDED(hrc))
1183 {
1184 /*
1185 * Real work.
1186 */
1187 /** @todo query all registers. */
1188 DBGFREGENTRY aRegs[DBGFREG_ALL_COUNT];
1189 int vrc = DBGFR3RegCpuQueryAll(ptrVM.raw(), a_idCpu, aRegs, RT_ELEMENTS(aRegs));
1190 if (RT_SUCCESS(vrc))
1191 {
1192 try
1193 {
1194 com::SafeArray<BSTR> abstrNames(RT_ELEMENTS(aRegs));
1195 com::SafeArray<BSTR> abstrValues(RT_ELEMENTS(aRegs));
1196
1197 for (uint32_t iReg = 0; iReg < RT_ELEMENTS(aRegs); iReg++)
1198 {
1199 char szHex[128];
1200 Bstr bstrValue;
1201
1202 hrc = formatRegisterValue(&bstrValue, &aRegs[iReg].Val, aRegs[iReg].enmType);
1203 AssertComRC(hrc);
1204 bstrValue.detachTo(&abstrValues[iReg]);
1205
1206 Bstr bstrName(DBGFR3RegCpuName(ptrVM.raw(), aRegs[iReg].enmReg, DBGFREGVALTYPE_INVALID));
1207 bstrName.detachTo(&abstrNames[iReg]);
1208 }
1209
1210 abstrValues.detachTo(ComSafeArrayOutArg(a_bstrNames));
1211 abstrValues.detachTo(ComSafeArrayOutArg(a_bstrValues));
1212 }
1213 catch (std::bad_alloc)
1214 {
1215 hrc = E_OUTOFMEMORY;
1216 }
1217 }
1218 else
1219 hrc = setError(E_FAIL, tr("DBGFR3RegQueryAll failed with %Rrc"), vrc);
1220 }
1221 }
1222 return hrc;
1223}
1224
1225STDMETHODIMP MachineDebugger::SetRegister(ULONG a_idCpu, IN_BSTR a_bstrName, IN_BSTR a_bstrValue)
1226{
1227 ReturnComNotImplemented();
1228}
1229
1230STDMETHODIMP MachineDebugger::SetRegisters(ULONG a_idCpu, ComSafeArrayIn(IN_BSTR, a_bstrNames), ComSafeArrayIn(IN_BSTR, a_bstrValues))
1231{
1232 ReturnComNotImplemented();
1233}
1234
1235STDMETHODIMP MachineDebugger::DumpGuestStack(ULONG a_idCpu, BSTR *a_pbstrStack)
1236{
1237 ReturnComNotImplemented();
1238}
1239
1240/**
1241 * Resets VM statistics.
1242 *
1243 * @returns COM status code.
1244 * @param aPattern The selection pattern. A bit similar to filename globbing.
1245 */
1246STDMETHODIMP MachineDebugger::ResetStats(IN_BSTR aPattern)
1247{
1248 Console::SafeVMPtrQuiet pVM (mParent);
1249
1250 if (!pVM.isOk())
1251 return setError(VBOX_E_INVALID_VM_STATE, "Machine is not running");
1252
1253 STAMR3Reset(pVM, Utf8Str(aPattern).c_str());
1254
1255 return S_OK;
1256}
1257
1258/**
1259 * Dumps VM statistics to the log.
1260 *
1261 * @returns COM status code.
1262 * @param aPattern The selection pattern. A bit similar to filename globbing.
1263 */
1264STDMETHODIMP MachineDebugger::DumpStats (IN_BSTR aPattern)
1265{
1266 Console::SafeVMPtrQuiet pVM (mParent);
1267
1268 if (!pVM.isOk())
1269 return setError(VBOX_E_INVALID_VM_STATE, "Machine is not running");
1270
1271 STAMR3Dump(pVM, Utf8Str(aPattern).c_str());
1272
1273 return S_OK;
1274}
1275
1276/**
1277 * Get the VM statistics in an XML format.
1278 *
1279 * @returns COM status code.
1280 * @param aPattern The selection pattern. A bit similar to filename globbing.
1281 * @param aWithDescriptions Whether to include the descriptions.
1282 * @param aStats The XML document containing the statistics.
1283 */
1284STDMETHODIMP MachineDebugger::GetStats (IN_BSTR aPattern, BOOL aWithDescriptions, BSTR *aStats)
1285{
1286 Console::SafeVMPtrQuiet pVM (mParent);
1287
1288 if (!pVM.isOk())
1289 return setError(VBOX_E_INVALID_VM_STATE, "Machine is not running");
1290
1291 char *pszSnapshot;
1292 int vrc = STAMR3Snapshot(pVM, Utf8Str(aPattern).c_str(), &pszSnapshot, NULL,
1293 !!aWithDescriptions);
1294 if (RT_FAILURE(vrc))
1295 return vrc == VERR_NO_MEMORY ? E_OUTOFMEMORY : E_FAIL;
1296
1297 /** @todo this is horribly inefficient! And it's kinda difficult to tell whether it failed...
1298 * Must use UTF-8 or ASCII here and completely avoid these two extra copy operations.
1299 * Until that's done, this method is kind of useless for debugger statistics GUI because
1300 * of the amount statistics in a debug build. */
1301 Bstr(pszSnapshot).detachTo(aStats);
1302
1303 return S_OK;
1304}
1305
1306
1307// public methods only for internal purposes
1308/////////////////////////////////////////////////////////////////////////////
1309
1310void MachineDebugger::flushQueuedSettings()
1311{
1312 mFlushMode = true;
1313 if (mSinglestepQueued != ~0)
1314 {
1315 COMSETTER(Singlestep) (mSinglestepQueued);
1316 mSinglestepQueued = ~0;
1317 }
1318 if (mRecompileUserQueued != ~0)
1319 {
1320 COMSETTER(RecompileUser) (mRecompileUserQueued);
1321 mRecompileUserQueued = ~0;
1322 }
1323 if (mRecompileSupervisorQueued != ~0)
1324 {
1325 COMSETTER(RecompileSupervisor) (mRecompileSupervisorQueued);
1326 mRecompileSupervisorQueued = ~0;
1327 }
1328 if (mPatmEnabledQueued != ~0)
1329 {
1330 COMSETTER(PATMEnabled) (mPatmEnabledQueued);
1331 mPatmEnabledQueued = ~0;
1332 }
1333 if (mCsamEnabledQueued != ~0)
1334 {
1335 COMSETTER(CSAMEnabled) (mCsamEnabledQueued);
1336 mCsamEnabledQueued = ~0;
1337 }
1338 if (mLogEnabledQueued != ~0)
1339 {
1340 COMSETTER(LogEnabled) (mLogEnabledQueued);
1341 mLogEnabledQueued = ~0;
1342 }
1343 if (mVirtualTimeRateQueued != ~(uint32_t)0)
1344 {
1345 COMSETTER(VirtualTimeRate) (mVirtualTimeRateQueued);
1346 mVirtualTimeRateQueued = ~0;
1347 }
1348 mFlushMode = false;
1349}
1350
1351// private methods
1352/////////////////////////////////////////////////////////////////////////////
1353
1354bool MachineDebugger::queueSettings() const
1355{
1356 if (!mFlushMode)
1357 {
1358 // check if the machine is running
1359 MachineState_T machineState;
1360 mParent->COMGETTER(State) (&machineState);
1361 switch (machineState)
1362 {
1363 // queue the request
1364 default:
1365 return true;
1366
1367 case MachineState_Running:
1368 case MachineState_Paused:
1369 case MachineState_Stuck:
1370 case MachineState_LiveSnapshotting:
1371 case MachineState_Teleporting:
1372 break;
1373 }
1374 }
1375 return false;
1376}
1377/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette