VirtualBox

Changeset 79687 in vbox


Ignore:
Timestamp:
Jul 11, 2019 9:08:08 AM (5 years ago)
Author:
vboxsync
Message:

VMM/HMVMXR0: Nested VMX: bugref:9180 Add hmR0VmxCheckExitDueToEventDeliveryNested. Handle situations where we or the guest hypervisor injects event into the nested-guest but may not be delivered due to VM-exits caused during event delivery.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/VMM/VMMR0/HMVMXR0.cpp

    r79665 r79687  
    1117611176        if (!RTThreadPreemptIsPending(NIL_RTTHREAD))
    1117711177        {
    11178             pVCpu->hm.s.Event.fPending = false;
    11179 
    1118011178#ifdef VBOX_WITH_NESTED_HWVIRT_VMX
    1118111179            /*
    1118211180             * If we are executing a nested-guest make sure that we should intercept subsequent
    11183              * events. The one we are injecting might be part of VM-entry.
     11181             * events. The one we are injecting might be part of VM-entry. This is mainly to keep
     11182             * the VM-exit instruction emulation happy.
    1118411183             */
    1118511184            if (pVmxTransient->fIsNestedGuest)
     
    1119111190             *
    1119211191             * Note! The caller expects to continue with interrupts & longjmps disabled on successful
    11193              * returns from this function, so don't enable them here.
     11192             *       returns from this function, so do -not- enable them here.
    1119411193             */
     11194            pVCpu->hm.s.Event.fPending = false;
    1119511195            return VINF_SUCCESS;
    1119611196        }
     
    1358713587
    1358813588
    13589 /**
    13590  * Handle a condition that occurred while delivering an event through the guest
    13591  * IDT.
     13589#ifdef VBOX_WITH_NESTED_HWVIRT_VMX
     13590/**
     13591 * Handle a condition that occurred while delivering an event through the
     13592 * nested-guest IDT.
     13593 *
     13594 * @returns VBox status code.
     13595 * @param   pVCpu           The cross context virtual CPU structure.
     13596 * @param   pVmxTransient   The VMX-transient structure.
     13597 *
     13598 * @remarks No-long-jump zone!!!
     13599 */
     13600static int hmR0VmxCheckExitDueToEventDeliveryNested(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
     13601{
     13602    Assert(pVmxTransient->fIsNestedGuest);
     13603    Assert(!pVCpu->hm.s.Event.fPending);
     13604
     13605    /*
     13606     * Construct a pending event from IDT vectoring information.
     13607     *
     13608     * This event could have originated from an event that we or the guest hypervisor injected
     13609     * during nested-guest VM-entry or could arise from hardware-assisted VMX execution of the
     13610     * nested-guest (for e.g. a #GP fault causing a #PF VM-exit).
     13611     *
     13612     * If the VM-exit is caused indirectly due to delivery of:
     13613     *   - #PF: the CPU would have updated CR2.
     13614     *   - NMI: NMI/virtual-NMI blocking is in effect.
     13615     *
     13616     * The main differences between this function and its non-nested version are as follows:
     13617     *
     13618     *   - Here we record software interrupts, software exceptions and privileged software
     13619     *     exceptions as pending for re-injection when necessary along with gathering the
     13620     *     instruction length. The non-nested version would fix-up the VM-exit that occurred
     13621     *     during delivery of such an event and restart execution of the guest without
     13622     *     re-injecting the event and does not record the instruction length.
     13623     *
     13624     *   - Here we record #PF as pending for re-injection while the non-nested version would
     13625     *     handle it via the page-fault VM-exit handler which isn't required when nested paging
     13626     *     is a requirement for hardware-assisted VMX execution of nested-guests.
     13627     *
     13628     * See Intel spec. 27.1 "Architectural State Before A VM Exit".
     13629     */
     13630    int rc = hmR0VmxReadIdtVectoringInfoVmcs(pVmxTransient);
     13631    AssertRCReturn(rc, rc);
     13632
     13633    uint32_t const uIdtVectorInfo = pVmxTransient->uIdtVectoringInfo;
     13634    if (VMX_IDT_VECTORING_INFO_IS_VALID(uIdtVectorInfo))
     13635    {
     13636        uint32_t const uIdtVectorType = VMX_IDT_VECTORING_INFO_TYPE(uIdtVectorInfo);
     13637        uint8_t const  uIdtVector     = VMX_IDT_VECTORING_INFO_VECTOR(uIdtVectorInfo);
     13638
     13639        /*
     13640         * Get the nasty stuff out of the way.
     13641         */
     13642        {
     13643            int rc = hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
     13644            AssertRCReturn(rc, rc);
     13645
     13646            uint32_t const uExitIntInfo = pVmxTransient->uExitIntInfo;
     13647            if (VMX_EXIT_INT_INFO_IS_VALID(uExitIntInfo))
     13648            {
     13649                uint8_t const  uExitVector      = VMX_EXIT_INT_INFO_VECTOR(uExitIntInfo);
     13650                uint32_t const uExitVectorType  = VMX_EXIT_INT_INFO_TYPE(uExitIntInfo);
     13651                Assert(uExitVectorType == VMX_EXIT_INT_INFO_TYPE_HW_XCPT);
     13652
     13653                uint32_t const fIdtVectorFlags  = hmR0VmxGetIemXcptFlags(uIdtVector, uIdtVectorType);
     13654                uint32_t const fExitVectorFlags = hmR0VmxGetIemXcptFlags(uExitVector, uExitVectorType);
     13655
     13656                IEMXCPTRAISEINFO   fRaiseInfo;
     13657                IEMXCPTRAISE const enmRaise = IEMEvaluateRecursiveXcpt(pVCpu, fIdtVectorFlags, uIdtVector, fExitVectorFlags,
     13658                                                                       uExitVector, &fRaiseInfo);
     13659                if (enmRaise == IEMXCPTRAISE_CPU_HANG)
     13660                {
     13661                    Log4Func(("IDT: Bad guest! Entering CPU hang. fRaiseInfo=%#x\n", fRaiseInfo));
     13662                    return VERR_EM_GUEST_CPU_HANG;
     13663                }
     13664            }
     13665        }
     13666
     13667        /*
     13668         * Things look legit, continue...
     13669         */
     13670        uint32_t   u32ErrCode;
     13671        bool const fErrCodeValid = VMX_IDT_VECTORING_INFO_IS_ERROR_CODE_VALID(uIdtVectorInfo);
     13672        if (fErrCodeValid)
     13673        {
     13674            rc = hmR0VmxReadIdtVectoringErrorCodeVmcs(pVmxTransient);
     13675            AssertRCReturn(rc, rc);
     13676            u32ErrCode = pVmxTransient->uIdtVectoringErrorCode;
     13677        }
     13678        else
     13679            u32ErrCode = 0;
     13680
     13681        uint32_t cbInstr;
     13682        if (   uIdtVectorType == VMX_IDT_VECTORING_INFO_TYPE_SW_INT
     13683            || uIdtVectorType == VMX_IDT_VECTORING_INFO_TYPE_PRIV_SW_XCPT
     13684            || uIdtVectorType == VMX_IDT_VECTORING_INFO_TYPE_SW_XCPT)
     13685        {
     13686            rc = hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
     13687            AssertRCReturn(rc, rc);
     13688            cbInstr = pVmxTransient->cbInstr;
     13689        }
     13690        else
     13691            cbInstr = 0;
     13692
     13693        RTGCUINTPTR GCPtrFaultAddress;
     13694        if (VMX_IDT_VECTORING_INFO_IS_XCPT_PF(uIdtVectorInfo))
     13695            GCPtrFaultAddress = pVCpu->cpum.GstCtx.cr2;
     13696        else
     13697            GCPtrFaultAddress = 0;
     13698
     13699        if (VMX_IDT_VECTORING_INFO_IS_XCPT_NMI(uIdtVectorInfo))
     13700            CPUMSetGuestNmiBlocking(pVCpu, true);
     13701
     13702        hmR0VmxSetPendingEvent(pVCpu, uIdtVectorInfo, cbInstr, u32ErrCode, GCPtrFaultAddress);
     13703    }
     13704
     13705    return VINF_SUCCESS;
     13706}
     13707#endif
     13708
     13709
     13710/**
     13711 * Handle a condition that occurred while delivering an event through the guest or
     13712 * nested-guest IDT.
    1359213713 *
    1359313714 * @returns Strict VBox status code (i.e. informational status codes too).
     
    1360513726static VBOXSTRICTRC hmR0VmxCheckExitDueToEventDelivery(PVMCPU pVCpu, PVMXTRANSIENT pVmxTransient)
    1360613727{
     13728#ifdef VBOX_WITH_NESTED_HWVIRT_VMX
     13729    if (pVmxTransient->fIsNestedGuest)
     13730        return hmR0VmxCheckExitDueToEventDeliveryNested(pVCpu, pVmxTransient);
     13731#endif
     13732
     13733    Assert(!pVmxTransient->fIsNestedGuest);
     13734    VBOXSTRICTRC rcStrict = VINF_SUCCESS;
     13735
    1360713736    /* Read the IDT vectoring info. and VM-exit interruption info. */
    1360813737    {
     
    1361213741    }
    1361313742
    13614     VBOXSTRICTRC   rcStrict    = VINF_SUCCESS;
    13615     uint32_t const uExitVector = VMX_EXIT_INT_INFO_VECTOR(pVmxTransient->uExitIntInfo);
    13616     if (VMX_IDT_VECTORING_INFO_IS_VALID(pVmxTransient->uIdtVectoringInfo))
    13617     {
    13618         uint32_t const uIdtVectorType = VMX_IDT_VECTORING_INFO_TYPE(pVmxTransient->uIdtVectoringInfo);
    13619         uint32_t const uIdtVector     = VMX_IDT_VECTORING_INFO_VECTOR(pVmxTransient->uIdtVectoringInfo);
     13743    uint32_t const uExitIntInfo   = pVmxTransient->uExitIntInfo;
     13744    uint8_t const  uExitVector    = VMX_EXIT_INT_INFO_VECTOR(pVmxTransient->uExitIntInfo);
     13745    uint32_t const uIdtVectorInfo = pVmxTransient->uIdtVectoringInfo;
     13746    if (VMX_IDT_VECTORING_INFO_IS_VALID(uIdtVectorInfo))
     13747    {
     13748        uint32_t const uIdtVector     = VMX_IDT_VECTORING_INFO_VECTOR(uIdtVectorInfo);
     13749        uint32_t const uIdtVectorType = VMX_IDT_VECTORING_INFO_TYPE(uIdtVectorInfo);
    1362013750
    1362113751        /*
     
    1363713767            fRaiseInfo = IEMXCPTRAISEINFO_NONE;
    1363813768        }
    13639         else if (VMX_EXIT_INT_INFO_IS_VALID(pVmxTransient->uExitIntInfo))
    13640         {
    13641             uint32_t const uExitVectorType  = VMX_EXIT_INT_INFO_TYPE(pVmxTransient->uExitIntInfo);
     13769        else if (VMX_EXIT_INT_INFO_IS_VALID(uExitIntInfo))
     13770        {
     13771            uint32_t const uExitVectorType  = VMX_EXIT_INT_INFO_TYPE(uExitIntInfo);
     13772            Assert(uExitVectorType == VMX_EXIT_INT_INFO_TYPE_HW_XCPT);
     13773
    1364213774            uint32_t const fIdtVectorFlags  = hmR0VmxGetIemXcptFlags(uIdtVector, uIdtVectorType);
    1364313775            uint32_t const fExitVectorFlags = hmR0VmxGetIemXcptFlags(uExitVector, uExitVectorType);
    13644 
    13645             /** @todo Make AssertMsgReturn as just AssertMsg later. */
    13646             AssertMsgReturn(uExitVectorType == VMX_EXIT_INT_INFO_TYPE_HW_XCPT,
    13647                             ("Unexpected VM-exit interruption vector type %#x!\n", uExitVectorType), VERR_VMX_IPE_5);
    1364813776
    1364913777            enmRaise = IEMEvaluateRecursiveXcpt(pVCpu, fIdtVectorFlags, uIdtVector, fExitVectorFlags, uExitVector, &fRaiseInfo);
     
    1369113819            case IEMXCPTRAISE_CURRENT_XCPT:
    1369213820            {
    13693                 Log4Func(("IDT: Pending secondary Xcpt: uIdtVectoringInfo=%#RX64 uExitIntInfo=%#RX64\n",
    13694                           pVmxTransient->uIdtVectoringInfo, pVmxTransient->uExitIntInfo));
     13821                Log4Func(("IDT: Pending secondary Xcpt: uIdtVectoringInfo=%#RX64 uExitIntInfo=%#RX64\n", uIdtVectorInfo,
     13822                          uExitIntInfo));
    1369513823                Assert(rcStrict == VINF_SUCCESS);
    1369613824                break;
     
    1370013828            {
    1370113829                uint32_t u32ErrCode;
    13702                 if (VMX_IDT_VECTORING_INFO_IS_ERROR_CODE_VALID(pVmxTransient->uIdtVectoringInfo))
     13830                if (VMX_IDT_VECTORING_INFO_IS_ERROR_CODE_VALID(uIdtVectorInfo))
    1370313831                {
    1370413832                    int rc = hmR0VmxReadIdtVectoringErrorCodeVmcs(pVmxTransient);
     
    1371113839                /* If uExitVector is #PF, CR2 value will be updated from the VMCS if it's a guest #PF, see hmR0VmxExitXcptPF(). */
    1371213840                STAM_COUNTER_INC(&pVCpu->hm.s.StatInjectReflect);
    13713                 hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_IDT_INFO(pVmxTransient->uIdtVectoringInfo),
    13714                                        0 /* cbInstr */, u32ErrCode, pVCpu->cpum.GstCtx.cr2);
     13841                hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_IDT_INFO(uIdtVectorInfo), 0 /* cbInstr */,
     13842                                       u32ErrCode, pVCpu->cpum.GstCtx.cr2);
    1371513843
    1371613844                Log4Func(("IDT: Pending vectoring event %#RX64 Err=%#RX32\n", pVCpu->hm.s.Event.u64IntInfo,
     
    1377013898        }
    1377113899    }
    13772     else if (   VMX_EXIT_INT_INFO_IS_VALID(pVmxTransient->uExitIntInfo)
    13773              && VMX_EXIT_INT_INFO_IS_NMI_UNBLOCK_IRET(pVmxTransient->uExitIntInfo)
     13900    else if (   VMX_EXIT_INT_INFO_IS_VALID(uExitIntInfo)
     13901             && VMX_EXIT_INT_INFO_IS_NMI_UNBLOCK_IRET(uExitIntInfo)
    1377413902             && uExitVector != X86_XCPT_DF
    1377513903             && hmR0VmxIsPinCtlsSet(pVCpu, pVmxTransient, VMX_PIN_CTLS_VIRT_NMI))
    1377613904    {
    13777         Assert(!VMX_IDT_VECTORING_INFO_IS_VALID(pVmxTransient->uIdtVectoringInfo));
     13905        Assert(!VMX_IDT_VECTORING_INFO_IS_VALID(uIdtVectorInfo));
    1377813906
    1377913907        /*
     
    1629716425         * using hardware-assisted VMX would would trigger the same EPT misconfig VM-exit again.
    1629816426         */
    16299         if (RT_UNLIKELY(pVCpu->hm.s.Event.fPending))
     16427        if (!pVCpu->hm.s.Event.fPending)
     16428        { /* likely */ }
     16429        else
    1630016430        {
    1630116431            STAM_COUNTER_INC(&pVCpu->hm.s.StatInjectInterpret);
     16432#ifdef VBOX_WITH_NESTED_HWVIRT_VMX
     16433            /** @todo NSTVMX: Think about how this should be handled. */
     16434            if (pVmxTransient->fIsNestedGuest)
     16435                return VERR_VMX_IPE_3;
     16436#endif
    1630216437            return VINF_EM_RAW_INJECT_TRPM_EVENT;
    1630316438        }
     
    1641416549    TRPMAssertXcptPF(pVCpu, GCPhys, uErrorCode);
    1641516550
    16416 
    1641716551    /* Handle the pagefault trap for the nested shadow table. */
    1641816552    PVM      pVM  = pVCpu->CTX_SUFF(pVM);
     
    1681516949    HMVMX_VALIDATE_NESTED_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient);
    1681616950
    16817     int rc = hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
     16951    int rc = hmR0VmxCheckExitDueToEventDeliveryNested(pVCpu, pVmxTransient);
    1681816952    AssertRCReturn(rc, rc);
    1681916953
     16954    rc = hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
     16955    AssertRCReturn(rc, rc);
     16956
    1682016957    uint64_t const uExitIntInfo = pVmxTransient->uExitIntInfo;
    16821     uint32_t const uExtIntType = VMX_EXIT_INT_INFO_TYPE(uExitIntInfo);
     16958    uint32_t const uExitIntType = VMX_EXIT_INT_INFO_TYPE(uExitIntInfo);
    1682216959    Assert(VMX_EXIT_INT_INFO_IS_VALID(uExitIntInfo));
    1682316960
    16824     /*
    16825      * Make sure not to use stale/previous VM-exit instruction length since we read the
    16826      * instruction length from the VMCS below only for software exceptions and privileged
    16827      * software exceptions but we pass it for all exception VM-exits below.
    16828      */
    16829     pVmxTransient->cbInstr = 0;
    16830 
    16831     switch (uExtIntType)
     16961    switch (uExitIntType)
    1683216962    {
    1683316963        /*
    1683416964         * Physical NMIs:
    16835          *    We shouldn't direct host physical NMIs to the nested-guest. Dispatch it to the host.
     16965         *   We shouldn't direct host physical NMIs to the nested-guest. Dispatch it to the host.
    1683616966         */
    1683716967        case VMX_EXIT_INT_INFO_TYPE_NMI:
     
    1684216972         * Software exceptions,
    1684316973         * Privileged software exceptions:
    16844          *    Figure out if the exception must be delivered to the guest or the nested-guest.
     16974         *   Figure out if the exception must be delivered to the guest or the nested-guest.
    1684516975         *
    16846          *    For VM-exits due to software exceptions (those generated by INT3 or INTO) and privileged
    16847          *    software exceptions (those generated by INT1/ICEBP) we need to supply the VM-exit instruction
    16848          *    length.
     16976         *   For VM-exits due to software exceptions (those generated by INT3 or INTO) and privileged
     16977         *   software exceptions (those generated by INT1/ICEBP) we need to supply the VM-exit instruction
     16978         *   length. However, if delivery of a software interrupt, software exception or privileged
     16979         *   software exception causes a VM-exit, that too provides the VM-exit instruction length.
     16980         *   Hence, we read it for all exception types below to keep it simple.
    1684916981         */
    1685016982        case VMX_EXIT_INT_INFO_TYPE_SW_XCPT:
    1685116983        case VMX_EXIT_INT_INFO_TYPE_PRIV_SW_XCPT:
    16852         {
    16853             rc = hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
     16984        case VMX_EXIT_INT_INFO_TYPE_HW_XCPT:
     16985        {
     16986            rc  = hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
     16987            rc |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
    1685416988            AssertRCReturn(rc, rc);
    16855             RT_FALL_THRU();
    16856         }
    16857         case VMX_EXIT_INT_INFO_TYPE_HW_XCPT:
    16858         {
    16859             rc = hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
    16860             AssertRCReturn(rc, rc);
     16989
     16990            /* Nested paging is currently a requirement, otherwise we would need to handle shadow #PFs. */
     16991            Assert(pVCpu->CTX_SUFF(pVM)->hm.s.fNestedPaging);
    1686116992
    1686216993            uint8_t const uVector    = VMX_EXIT_INT_INFO_VECTOR(uExitIntInfo);
     
    1688217013                ExitEventInfo.uIdtVectoringInfo    = pVmxTransient->uIdtVectoringInfo;
    1688317014                ExitEventInfo.uIdtVectoringErrCode = pVmxTransient->uIdtVectoringErrorCode;
    16884 
     17015                if (pVCpu->hm.s.Event.fPending)
     17016                {
     17017                    Assert(ExitEventInfo.uIdtVectoringInfo    == pVCpu->hm.s.Event.u64IntInfo);
     17018                    Assert(ExitEventInfo.uIdtVectoringErrCode == pVCpu->hm.s.Event.u32ErrCode);
     17019                    pVCpu->hm.s.Event.fPending = false;
     17020                }
    1688517021                return IEMExecVmxVmexitXcpt(pVCpu, &ExitInfo, &ExitEventInfo);
    1688617022            }
     
    1688917025            Assert(pVCpu->CTX_SUFF(pVM)->hm.s.fNestedPaging);
    1689017026
    16891             /* If the guest hypervisor is not intercepting the exception, forward it to the guest. */
    16892             hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(uExitIntInfo), pVmxTransient->cbInstr,
    16893                                    pVmxTransient->uExitIntErrorCode, pVmxTransient->uExitQual);
     17027            /*
     17028             * If the guest hypervisor is not intercepting an exception that caused a VM-exit directly,
     17029             * forward it to the guest (for e.g, an instruction raises a #GP that causes a VM-exit but
     17030             * the guest hypervisor is not intercept #GPs, inject #GP into the guest).
     17031             */
     17032            if (!pVCpu->hm.s.Event.fPending)
     17033            {
     17034                hmR0VmxSetPendingEvent(pVCpu, VMX_ENTRY_INT_INFO_FROM_EXIT_INT_INFO(uExitIntInfo), pVmxTransient->cbInstr,
     17035                                       pVmxTransient->uExitIntErrorCode, pVmxTransient->uExitQual);
     17036            }
    1689417037            return VINF_SUCCESS;
    1689517038        }
     
    1748617629    HMVMX_VALIDATE_NESTED_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient);
    1748717630
     17631    int rc = hmR0VmxCheckExitDueToEventDeliveryNested(pVCpu, pVmxTransient);
     17632    AssertRCReturn(rc, rc);
     17633
    1748817634    Assert(CPUMIsGuestVmxProcCtls2Set(pVCpu, &pVCpu->cpum.GstCtx, VMX_PROC_CTLS2_VIRT_APIC_ACCESS));
    17489     int rc = hmR0VmxReadExitQualVmcs(pVCpu, pVmxTransient);
    17490     rc    |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
    17491     rc    |= hmR0VmxReadIdtVectoringInfoVmcs(pVmxTransient);
    17492     rc    |= hmR0VmxReadIdtVectoringErrorCodeVmcs(pVmxTransient);
     17635    rc = hmR0VmxReadExitQualVmcs(pVCpu, pVmxTransient);
     17636    rc |= hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
     17637    rc |= hmR0VmxReadIdtVectoringInfoVmcs(pVmxTransient);
     17638    rc |= hmR0VmxReadIdtVectoringErrorCodeVmcs(pVmxTransient);
    1749317639    AssertRCReturn(rc, rc);
    1749417640
     
    1750317649    ExitEventInfo.uIdtVectoringInfo    = pVmxTransient->uIdtVectoringInfo;
    1750417650    ExitEventInfo.uIdtVectoringErrCode = pVmxTransient->uIdtVectoringErrorCode;
     17651    if (pVCpu->hm.s.Event.fPending)
     17652    {
     17653        Assert(ExitEventInfo.uIdtVectoringInfo    == pVCpu->hm.s.Event.u64IntInfo);
     17654        Assert(ExitEventInfo.uIdtVectoringErrCode == pVCpu->hm.s.Event.u32ErrCode);
     17655        pVCpu->hm.s.Event.fPending = false;
     17656    }
    1750517657    return IEMExecVmxVmexitApicAccess(pVCpu, &ExitInfo, &ExitEventInfo);
    1750617658}
Note: See TracChangeset for help on using the changeset viewer.

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