VirtualBox

source: vbox/trunk/src/VBox/Main/MachineDebuggerImpl.cpp@ 23012

Last change on this file since 23012 was 23012, checked in by vboxsync, 15 years ago

VMM,Devices,Main: VMR3ReqCall w/ RT_INDEFINITE_WAIT -> VMR3ReqCallWait.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.3 KB
Line 
1/* $Id: MachineDebuggerImpl.cpp 23012 2009-09-14 16:38:13Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2008 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "MachineDebuggerImpl.h"
25
26#include "Global.h"
27#include "ConsoleImpl.h"
28#include "Logging.h"
29
30#include <VBox/em.h>
31#include <VBox/patm.h>
32#include <VBox/csam.h>
33#include <VBox/vm.h>
34#include <VBox/tm.h>
35#include <VBox/err.h>
36#include <VBox/hwaccm.h>
37
38// defines
39/////////////////////////////////////////////////////////////////////////////
40
41
42// globals
43/////////////////////////////////////////////////////////////////////////////
44
45
46// constructor / destructor
47/////////////////////////////////////////////////////////////////////////////
48
49DEFINE_EMPTY_CTOR_DTOR (MachineDebugger)
50
51HRESULT MachineDebugger::FinalConstruct()
52{
53 unconst(mParent) = NULL;
54 return S_OK;
55}
56
57void MachineDebugger::FinalRelease()
58{
59 uninit();
60}
61
62// public initializer/uninitializer for internal purposes only
63/////////////////////////////////////////////////////////////////////////////
64
65/**
66 * Initializes the machine debugger object.
67 *
68 * @returns COM result indicator
69 * @param aParent handle of our parent object
70 */
71HRESULT MachineDebugger::init (Console *aParent)
72{
73 LogFlowThisFunc(("aParent=%p\n", aParent));
74
75 ComAssertRet (aParent, E_INVALIDARG);
76
77 /* Enclose the state transition NotReady->InInit->Ready */
78 AutoInitSpan autoInitSpan(this);
79 AssertReturn(autoInitSpan.isOk(), E_FAIL);
80
81 unconst(mParent) = aParent;
82
83 mSinglestepQueued = ~0;
84 mRecompileUserQueued = ~0;
85 mRecompileSupervisorQueued = ~0;
86 mPatmEnabledQueued = ~0;
87 mCsamEnabledQueued = ~0;
88 mLogEnabledQueued = ~0;
89 mVirtualTimeRateQueued = ~0;
90 mFlushMode = false;
91
92 /* Confirm a successful initialization */
93 autoInitSpan.setSucceeded();
94
95 return S_OK;
96}
97
98/**
99 * Uninitializes the instance and sets the ready flag to FALSE.
100 * Called either from FinalRelease() or by the parent when it gets destroyed.
101 */
102void MachineDebugger::uninit()
103{
104 LogFlowThisFunc(("\n"));
105
106 /* Enclose the state transition Ready->InUninit->NotReady */
107 AutoUninitSpan autoUninitSpan(this);
108 if (autoUninitSpan.uninitDone())
109 return;
110
111 unconst(mParent).setNull();
112 mFlushMode = false;
113}
114
115// IMachineDebugger properties
116/////////////////////////////////////////////////////////////////////////////
117
118/**
119 * Returns the current singlestepping flag.
120 *
121 * @returns COM status code
122 * @param aEnabled address of result variable
123 */
124STDMETHODIMP MachineDebugger::COMGETTER(Singlestep) (BOOL *aEnabled)
125{
126 CheckComArgOutPointerValid(aEnabled);
127
128 AutoCaller autoCaller(this);
129 CheckComRCReturnRC(autoCaller.rc());
130
131 /** @todo */
132 ReturnComNotImplemented();
133}
134
135/**
136 * Sets the singlestepping flag.
137 *
138 * @returns COM status code
139 * @param aEnable new singlestepping flag
140 */
141STDMETHODIMP MachineDebugger::COMSETTER(Singlestep) (BOOL aEnable)
142{
143 AutoCaller autoCaller(this);
144 CheckComRCReturnRC(autoCaller.rc());
145
146 /** @todo */
147 ReturnComNotImplemented();
148}
149
150/**
151 * Returns the current recompile user mode code flag.
152 *
153 * @returns COM status code
154 * @param aEnabled address of result variable
155 */
156STDMETHODIMP MachineDebugger::COMGETTER(RecompileUser) (BOOL *aEnabled)
157{
158 CheckComArgOutPointerValid(aEnabled);
159
160 AutoCaller autoCaller(this);
161 CheckComRCReturnRC(autoCaller.rc());
162
163 AutoReadLock alock(this);
164
165 Console::SafeVMPtrQuiet pVM (mParent);
166
167 if (pVM.isOk())
168 *aEnabled = !EMIsRawRing3Enabled (pVM.raw());
169 else
170 *aEnabled = false;
171
172 return S_OK;
173}
174
175/**
176 * Sets the recompile user mode code flag.
177 *
178 * @returns COM status
179 * @param aEnable new user mode code recompile flag.
180 */
181STDMETHODIMP MachineDebugger::COMSETTER(RecompileUser) (BOOL aEnable)
182{
183 LogFlowThisFunc(("enable=%d\n", aEnable));
184
185 AutoCaller autoCaller(this);
186 CheckComRCReturnRC(autoCaller.rc());
187
188 AutoWriteLock alock(this);
189
190 if (queueSettings())
191 {
192 // queue the request
193 mRecompileUserQueued = aEnable;
194 return S_OK;
195 }
196
197 Console::SafeVMPtr pVM (mParent);
198 CheckComRCReturnRC(pVM.rc());
199
200 EMRAWMODE rawModeFlag = aEnable ? EMRAW_RING3_DISABLE : EMRAW_RING3_ENABLE;
201 int rcVBox = VMR3ReqCallWait(pVM, VMCPUID_ANY, (PFNRT)EMR3RawSetMode, 2, pVM.raw(), rawModeFlag);
202 if (RT_SUCCESS(rcVBox))
203 return S_OK;
204
205 AssertMsgFailed (("Could not set raw mode flags to %d, rcVBox = %Rrc\n",
206 rawModeFlag, rcVBox));
207 return E_FAIL;
208}
209
210/**
211 * Returns the current recompile supervisor code flag.
212 *
213 * @returns COM status code
214 * @param aEnabled address of result variable
215 */
216STDMETHODIMP MachineDebugger::COMGETTER(RecompileSupervisor) (BOOL *aEnabled)
217{
218 CheckComArgOutPointerValid(aEnabled);
219
220 AutoCaller autoCaller(this);
221 CheckComRCReturnRC(autoCaller.rc());
222
223 AutoReadLock alock(this);
224
225 Console::SafeVMPtrQuiet pVM (mParent);
226
227 if (pVM.isOk())
228 *aEnabled = !EMIsRawRing0Enabled (pVM.raw());
229 else
230 *aEnabled = false;
231
232 return S_OK;
233}
234
235/**
236 * Sets the new recompile supervisor code flag.
237 *
238 * @returns COM status code
239 * @param aEnable new recompile supervisor code flag
240 */
241STDMETHODIMP MachineDebugger::COMSETTER(RecompileSupervisor) (BOOL aEnable)
242{
243 LogFlowThisFunc(("enable=%d\n", aEnable));
244
245 AutoCaller autoCaller(this);
246 CheckComRCReturnRC(autoCaller.rc());
247
248 AutoWriteLock alock(this);
249
250 if (queueSettings())
251 {
252 // queue the request
253 mRecompileSupervisorQueued = aEnable;
254 return S_OK;
255 }
256
257 Console::SafeVMPtr pVM (mParent);
258 CheckComRCReturnRC(pVM.rc());
259
260 EMRAWMODE rawModeFlag = aEnable ? EMRAW_RING0_DISABLE : EMRAW_RING0_ENABLE;
261 int rcVBox = VMR3ReqCallWait(pVM, VMCPUID_ANY, (PFNRT)EMR3RawSetMode, 2, pVM.raw(), rawModeFlag);
262 if (RT_SUCCESS(rcVBox))
263 return S_OK;
264
265 AssertMsgFailed (("Could not set raw mode flags to %d, rcVBox = %Rrc\n",
266 rawModeFlag, rcVBox));
267 return E_FAIL;
268}
269
270/**
271 * Returns the current patch manager enabled flag.
272 *
273 * @returns COM status code
274 * @param aEnabled address of result variable
275 */
276STDMETHODIMP MachineDebugger::COMGETTER(PATMEnabled) (BOOL *aEnabled)
277{
278 CheckComArgOutPointerValid(aEnabled);
279
280 AutoCaller autoCaller(this);
281 CheckComRCReturnRC(autoCaller.rc());
282
283 AutoReadLock alock(this);
284
285 Console::SafeVMPtrQuiet pVM (mParent);
286
287 if (pVM.isOk())
288 *aEnabled = PATMIsEnabled (pVM.raw());
289 else
290 *aEnabled = false;
291
292 return S_OK;
293}
294
295/**
296 * Set the new patch manager enabled flag.
297 *
298 * @returns COM status code
299 * @param aEnable new patch manager enabled flag
300 */
301STDMETHODIMP MachineDebugger::COMSETTER(PATMEnabled) (BOOL aEnable)
302{
303 LogFlowThisFunc(("enable=%d\n", aEnable));
304
305 AutoCaller autoCaller(this);
306 CheckComRCReturnRC(autoCaller.rc());
307
308 AutoWriteLock alock(this);
309
310 if (queueSettings())
311 {
312 // queue the request
313 mPatmEnabledQueued = aEnable;
314 return S_OK;
315 }
316
317 Console::SafeVMPtr pVM (mParent);
318 CheckComRCReturnRC(pVM.rc());
319
320 PATMR3AllowPatching (pVM, aEnable);
321
322 return S_OK;
323}
324
325/**
326 * Returns the current code scanner enabled flag.
327 *
328 * @returns COM status code
329 * @param aEnabled address of result variable
330 */
331STDMETHODIMP MachineDebugger::COMGETTER(CSAMEnabled) (BOOL *aEnabled)
332{
333 CheckComArgOutPointerValid(aEnabled);
334
335 AutoCaller autoCaller(this);
336 CheckComRCReturnRC(autoCaller.rc());
337
338 AutoReadLock alock(this);
339
340 Console::SafeVMPtrQuiet pVM (mParent);
341
342 if (pVM.isOk())
343 *aEnabled = CSAMIsEnabled (pVM.raw());
344 else
345 *aEnabled = false;
346
347 return S_OK;
348}
349
350/**
351 * Sets the new code scanner enabled flag.
352 *
353 * @returns COM status code
354 * @param aEnable new code scanner enabled flag
355 */
356STDMETHODIMP MachineDebugger::COMSETTER(CSAMEnabled) (BOOL aEnable)
357{
358 LogFlowThisFunc(("enable=%d\n", aEnable));
359
360 AutoCaller autoCaller(this);
361 CheckComRCReturnRC(autoCaller.rc());
362
363 AutoWriteLock alock(this);
364
365 if (queueSettings())
366 {
367 // queue the request
368 mCsamEnabledQueued = aEnable;
369 return S_OK;
370 }
371
372 Console::SafeVMPtr pVM (mParent);
373 CheckComRCReturnRC(pVM.rc());
374
375 int vrc;
376 if (aEnable)
377 vrc = CSAMEnableScanning (pVM);
378 else
379 vrc = CSAMDisableScanning (pVM);
380
381 if (RT_FAILURE(vrc))
382 {
383 /** @todo handle error case */
384 }
385
386 return S_OK;
387}
388
389/**
390 * Returns the log enabled / disabled status.
391 *
392 * @returns COM status code
393 * @param aEnabled address of result variable
394 */
395STDMETHODIMP MachineDebugger::COMGETTER(LogEnabled) (BOOL *aEnabled)
396{
397 CheckComArgOutPointerValid(aEnabled);
398
399 AutoCaller autoCaller(this);
400 CheckComRCReturnRC(autoCaller.rc());
401
402#ifdef LOG_ENABLED
403 AutoReadLock alock(this);
404
405 const PRTLOGGER pLogInstance = RTLogDefaultInstance();
406 *aEnabled = pLogInstance && !(pLogInstance->fFlags & RTLOGFLAGS_DISABLED);
407#else
408 *aEnabled = false;
409#endif
410
411 return S_OK;
412}
413
414/**
415 * Enables or disables logging.
416 *
417 * @returns COM status code
418 * @param aEnabled The new code log state.
419 */
420STDMETHODIMP MachineDebugger::COMSETTER(LogEnabled) (BOOL aEnabled)
421{
422 LogFlowThisFunc(("aEnabled=%d\n", aEnabled));
423
424 AutoCaller autoCaller(this);
425 CheckComRCReturnRC(autoCaller.rc());
426
427 AutoWriteLock alock(this);
428
429 if (queueSettings())
430 {
431 // queue the request
432 mLogEnabledQueued = aEnabled;
433 return S_OK;
434 }
435
436 Console::SafeVMPtr pVM (mParent);
437 CheckComRCReturnRC(pVM.rc());
438
439#ifdef LOG_ENABLED
440 int vrc = DBGFR3LogModifyFlags (pVM, aEnabled ? "enabled" : "disabled");
441 if (RT_FAILURE(vrc))
442 {
443 /** @todo handle error code. */
444 }
445#endif
446
447 return S_OK;
448}
449
450/**
451 * Returns the current hardware virtualization flag.
452 *
453 * @returns COM status code
454 * @param aEnabled address of result variable
455 */
456STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExEnabled) (BOOL *aEnabled)
457{
458 CheckComArgOutPointerValid(aEnabled);
459
460 AutoCaller autoCaller(this);
461 CheckComRCReturnRC(autoCaller.rc());
462
463 AutoReadLock alock(this);
464
465 Console::SafeVMPtrQuiet pVM (mParent);
466
467 if (pVM.isOk())
468 *aEnabled = HWACCMIsEnabled (pVM.raw());
469 else
470 *aEnabled = false;
471
472 return S_OK;
473}
474
475/**
476 * Returns the current nested paging flag.
477 *
478 * @returns COM status code
479 * @param aEnabled address of result variable
480 */
481STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExNestedPagingEnabled) (BOOL *aEnabled)
482{
483 CheckComArgOutPointerValid(aEnabled);
484
485 AutoCaller autoCaller(this);
486 CheckComRCReturnRC(autoCaller.rc());
487
488 AutoReadLock alock(this);
489
490 Console::SafeVMPtrQuiet pVM (mParent);
491
492 if (pVM.isOk())
493 *aEnabled = HWACCMR3IsNestedPagingActive (pVM.raw());
494 else
495 *aEnabled = false;
496
497 return S_OK;
498}
499
500/**
501 * Returns the current VPID flag.
502 *
503 * @returns COM status code
504 * @param aEnabled address of result variable
505 */
506STDMETHODIMP MachineDebugger::COMGETTER(HWVirtExVPIDEnabled) (BOOL *aEnabled)
507{
508 CheckComArgOutPointerValid(aEnabled);
509
510 AutoCaller autoCaller(this);
511 CheckComRCReturnRC(autoCaller.rc());
512
513 AutoReadLock alock(this);
514
515 Console::SafeVMPtrQuiet pVM (mParent);
516
517 if (pVM.isOk())
518 *aEnabled = HWACCMR3IsVPIDActive (pVM.raw());
519 else
520 *aEnabled = false;
521
522 return S_OK;
523}
524
525/**
526 * Returns the current PAE flag.
527 *
528 * @returns COM status code
529 * @param aEnabled address of result variable
530 */
531STDMETHODIMP MachineDebugger::COMGETTER(PAEEnabled) (BOOL *aEnabled)
532{
533 CheckComArgOutPointerValid(aEnabled);
534
535 AutoCaller autoCaller(this);
536 CheckComRCReturnRC(autoCaller.rc());
537
538 AutoReadLock alock(this);
539
540 Console::SafeVMPtrQuiet pVM (mParent);
541
542 if (pVM.isOk())
543 {
544 uint64_t cr4 = CPUMGetGuestCR4 (VMMGetCpu0(pVM.raw()));
545 *aEnabled = !!(cr4 & X86_CR4_PAE);
546 }
547 else
548 *aEnabled = false;
549
550 return S_OK;
551}
552
553/**
554 * Returns the current virtual time rate.
555 *
556 * @returns COM status code.
557 * @param aPct Where to store the rate.
558 */
559STDMETHODIMP MachineDebugger::COMGETTER(VirtualTimeRate) (ULONG *aPct)
560{
561 CheckComArgOutPointerValid(aPct);
562
563 AutoCaller autoCaller(this);
564 CheckComRCReturnRC(autoCaller.rc());
565
566 AutoReadLock alock(this);
567
568 Console::SafeVMPtrQuiet pVM (mParent);
569
570 if (pVM.isOk())
571 *aPct = TMGetWarpDrive (pVM);
572 else
573 *aPct = 100;
574
575 return S_OK;
576}
577
578/**
579 * Returns the current virtual time rate.
580 *
581 * @returns COM status code.
582 * @param aPct Where to store the rate.
583 */
584STDMETHODIMP MachineDebugger::COMSETTER(VirtualTimeRate) (ULONG aPct)
585{
586 if (aPct < 2 || aPct > 20000)
587 return E_INVALIDARG;
588
589 AutoCaller autoCaller(this);
590 CheckComRCReturnRC(autoCaller.rc());
591
592 AutoWriteLock alock(this);
593
594 if (queueSettings())
595 {
596 // queue the request
597 mVirtualTimeRateQueued = aPct;
598 return S_OK;
599 }
600
601 Console::SafeVMPtr pVM (mParent);
602 CheckComRCReturnRC(pVM.rc());
603
604 int vrc = TMR3SetWarpDrive (pVM, aPct);
605 if (RT_FAILURE(vrc))
606 {
607 /** @todo handle error code. */
608 }
609
610 return S_OK;
611}
612
613/**
614 * Hack for getting the VM handle.
615 * This is only temporary (promise) while prototyping the debugger.
616 *
617 * @returns COM status code
618 * @param aVm Where to store the vm handle.
619 * Since there is no uintptr_t in COM, we're using the max integer.
620 * (No, ULONG is not pointer sized!)
621 */
622STDMETHODIMP MachineDebugger::COMGETTER(VM) (ULONG64 *aVm)
623{
624 CheckComArgOutPointerValid(aVm);
625
626 AutoCaller autoCaller(this);
627 CheckComRCReturnRC(autoCaller.rc());
628
629 AutoReadLock alock(this);
630
631 Console::SafeVMPtr pVM (mParent);
632 CheckComRCReturnRC(pVM.rc());
633
634 *aVm = (uintptr_t)pVM.raw();
635
636 /*
637 * Note: pVM protection provided by SafeVMPtr is no more effective
638 * after we return from this method.
639 */
640
641 return S_OK;
642}
643
644// IMachineDebugger methods
645/////////////////////////////////////////////////////////////////////////////
646
647/**
648 * Resets VM statistics.
649 *
650 * @returns COM status code.
651 * @param aPattern The selection pattern. A bit similar to filename globbing.
652 */
653STDMETHODIMP MachineDebugger::ResetStats (IN_BSTR aPattern)
654{
655 Console::SafeVMPtrQuiet pVM (mParent);
656
657 if (!pVM.isOk())
658 return E_FAIL;
659
660 STAMR3Reset (pVM, Utf8Str (aPattern).raw());
661
662 return S_OK;
663}
664
665/**
666 * Dumps VM statistics to the log.
667 *
668 * @returns COM status code.
669 * @param aPattern The selection pattern. A bit similar to filename globbing.
670 */
671STDMETHODIMP MachineDebugger::DumpStats (IN_BSTR aPattern)
672{
673 Console::SafeVMPtrQuiet pVM (mParent);
674
675 if (!pVM.isOk())
676 return E_FAIL;
677
678 STAMR3Dump (pVM, Utf8Str (aPattern).raw());
679
680 return S_OK;
681}
682
683/**
684 * Get the VM statistics in an XML format.
685 *
686 * @returns COM status code.
687 * @param aPattern The selection pattern. A bit similar to filename globbing.
688 * @param aWithDescriptions Whether to include the descriptions.
689 * @param aStats The XML document containing the statistics.
690 */
691STDMETHODIMP MachineDebugger::GetStats (IN_BSTR aPattern, BOOL aWithDescriptions, BSTR *aStats)
692{
693 Console::SafeVMPtrQuiet pVM (mParent);
694
695 if (!pVM.isOk())
696 return E_FAIL;
697
698 char *pszSnapshot;
699 int vrc = STAMR3Snapshot (pVM, Utf8Str (aPattern).raw(), &pszSnapshot, NULL,
700 !!aWithDescriptions);
701 if (RT_FAILURE(vrc))
702 return vrc == VERR_NO_MEMORY ? E_OUTOFMEMORY : E_FAIL;
703
704 /** @todo this is horribly inefficient! And it's kinda difficult to tell whether it failed...
705 * Must use UTF-8 or ASCII here and completely avoid these two extra copy operations.
706 * Until that's done, this method is kind of useless for debugger statistics GUI because
707 * of the amount statistics in a debug build. */
708 Bstr (pszSnapshot).cloneTo(aStats);
709
710 return S_OK;
711}
712
713/**
714 * Set the new patch manager enabled flag.
715 *
716 * @returns COM status code
717 * @param new patch manager enabled flag
718 */
719STDMETHODIMP MachineDebugger::InjectNMI()
720{
721 LogFlowThisFunc((""));
722
723 AutoCaller autoCaller(this);
724 CheckComRCReturnRC(autoCaller.rc());
725
726 AutoWriteLock alock(this);
727
728 Console::SafeVMPtr pVM (mParent);
729 CheckComRCReturnRC(pVM.rc());
730
731 HWACCMR3InjectNMI(pVM);
732
733 return S_OK;
734}
735
736
737// public methods only for internal purposes
738/////////////////////////////////////////////////////////////////////////////
739
740void MachineDebugger::flushQueuedSettings()
741{
742 mFlushMode = true;
743 if (mSinglestepQueued != ~0)
744 {
745 COMSETTER(Singlestep) (mSinglestepQueued);
746 mSinglestepQueued = ~0;
747 }
748 if (mRecompileUserQueued != ~0)
749 {
750 COMSETTER(RecompileUser) (mRecompileUserQueued);
751 mRecompileUserQueued = ~0;
752 }
753 if (mRecompileSupervisorQueued != ~0)
754 {
755 COMSETTER(RecompileSupervisor) (mRecompileSupervisorQueued);
756 mRecompileSupervisorQueued = ~0;
757 }
758 if (mPatmEnabledQueued != ~0)
759 {
760 COMSETTER(PATMEnabled) (mPatmEnabledQueued);
761 mPatmEnabledQueued = ~0;
762 }
763 if (mCsamEnabledQueued != ~0)
764 {
765 COMSETTER(CSAMEnabled) (mCsamEnabledQueued);
766 mCsamEnabledQueued = ~0;
767 }
768 if (mLogEnabledQueued != ~0)
769 {
770 COMSETTER(LogEnabled) (mLogEnabledQueued);
771 mLogEnabledQueued = ~0;
772 }
773 if (mVirtualTimeRateQueued != ~(uint32_t)0)
774 {
775 COMSETTER(VirtualTimeRate) (mVirtualTimeRateQueued);
776 mVirtualTimeRateQueued = ~0;
777 }
778 mFlushMode = false;
779}
780
781// private methods
782/////////////////////////////////////////////////////////////////////////////
783
784bool MachineDebugger::queueSettings() const
785{
786 if (!mFlushMode)
787 {
788 // check if the machine is running
789 MachineState_T machineState;
790 mParent->COMGETTER(State) (&machineState);
791 if (!Global::IsActive (machineState))
792 // queue the request
793 return true;
794 }
795 return false;
796}
797/* 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