/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Copyright (C) 2012-2013 Ian Preston This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ End of licence header */ package org.jpc.emulator.processor; public class Tasking { public static enum Source {CALL, INT, IRET, JUMP} public static void task_switch(Processor cpu, Source source, ProtectedModeSegment tss, boolean hasErrorCode, int errorCode) { // STEP 1: The following checks are made before calling task_switch(), // for JUMP & CALL only. These checks are NOT made for exceptions, // interrupts & IRET. // // 1) TSS DPL must be >= CPL // 2) TSS DPL must be >= TSS selector RPL // 3) TSS descriptor is not busy. // STEP 2: The processor performs limit-checking on the target TSS // to verify that the TSS limit is greater than or equal // to 67h (2Bh for 16-bit TSS). int newTssMax; if (tss.getType() <= 3) // 1, 3 newTssMax = 0x2B; else // 9, 11 newTssMax = 0x67; int newBase32 = tss.getBase(); int newTSSLimit = tss.getLimit(); if (newTSSLimit < newTssMax) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, tss.getSelector() & 0xfffc, true); // TODO support SVM // TODO support VMX int oldTssMax; if (cpu.tss.getType() <= 3) oldTssMax = 0x29; else oldTssMax = 0x5f; int oldBase32 = cpu.tss.getBase(); int oldTssLimit = cpu.tss.getLimit(); if (oldTssLimit < oldTssMax) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, cpu.tss.getSelector() & 0xfffc, true); if (oldBase32 == newBase32) System.out.println("TSS: Switching to the same Task!"); // ensure old and new TSS are in the TLB if (cpu.pagingEnabled()) { } // Step 3: If JMP or IRET, clear busy bit in old task TSS descriptor, otherwise leave set. if (source == Source.JUMP || source == Source.IRET) { int addr = cpu.gdtr.getBase() + 8*(cpu.tss.getSelector() & 0xfffc) + 4; int tmp = cpu.linearMemory.getDoubleWord(addr); tmp &= ~0x200; cpu.linearMemory.setDoubleWord(addr, tmp); } // STEP 4: If the task switch was initiated with an IRET instruction, // clears the NT flag in a temporarily saved EFLAGS image; // if initiated with a CALL or JMP instruction, an exception, or // an interrupt, the NT flag is left unchanged. int oldEFlags = cpu.getEFlags(); if ((tss instanceof ProtectedModeSegment.Busy16BitTSS) || (tss instanceof ProtectedModeSegment.Busy32BitTSS)) oldEFlags &= ~Processor.EFLAGS_NT_MASK; // STEP 5: Save the current task state in the TSS. Up to this point, // any exception that occurs aborts the task switch without // changing the processor state. // save current machine state in old task's TSS if (cpu.tss.getType() <= 3) { // check that we won't page fault while writing if (cpu.pagingEnabled()) { int start = oldBase32 + 14; int end = oldBase32 + 41; cpu.linearMemory.setByte(start, cpu.linearMemory.getByte(start)); cpu.linearMemory.setByte(end, cpu.linearMemory.getByte(end)); } boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); cpu.linearMemory.setWord(oldBase32 + 14, (short) cpu.eip); cpu.linearMemory.setWord(oldBase32 + 16, (short) oldEFlags); cpu.linearMemory.setWord(oldBase32 + 18, cpu.r_ax.get16()); cpu.linearMemory.setWord(oldBase32 + 20, cpu.r_cx.get16()); cpu.linearMemory.setWord(oldBase32 + 22, cpu.r_dx.get16()); cpu.linearMemory.setWord(oldBase32 + 24, cpu.r_bx.get16()); cpu.linearMemory.setWord(oldBase32 + 26, cpu.r_sp.get16()); cpu.linearMemory.setWord(oldBase32 + 28, cpu.r_bp.get16()); cpu.linearMemory.setWord(oldBase32 + 30, cpu.r_si.get16()); cpu.linearMemory.setWord(oldBase32 + 32, cpu.r_di.get16()); cpu.linearMemory.setWord(oldBase32 + 34, (short) cpu.es()); cpu.linearMemory.setWord(oldBase32 + 36, (short) cpu.cs()); cpu.linearMemory.setWord(oldBase32 + 38, (short) cpu.ss()); cpu.linearMemory.setWord(oldBase32 + 40, (short) cpu.ds()); } finally { cpu.linearMemory.setSupervisor(isSup); } } else { // check that we won't page fault while writing if (cpu.pagingEnabled()) { int start = oldBase32 + 0x20; int end = oldBase32 + 0x5d; cpu.linearMemory.setByte(start, cpu.linearMemory.getByte(start)); cpu.linearMemory.setByte(end, cpu.linearMemory.getByte(end)); } boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); cpu.linearMemory.setDoubleWord(oldBase32 + 0x20, cpu.eip); cpu.linearMemory.setDoubleWord(oldBase32 + 0x24, oldEFlags); cpu.linearMemory.setDoubleWord(oldBase32 + 0x28, cpu.r_eax.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x2c, cpu.r_ecx.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x30, cpu.r_edx.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x34, cpu.r_ebx.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x38, cpu.r_esp.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x3c, cpu.r_ebp.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x40, cpu.r_esi.get32()); cpu.linearMemory.setDoubleWord(oldBase32 + 0x44, cpu.r_edi.get32()); cpu.linearMemory.setWord(oldBase32 + 0x48, (short) cpu.es()); cpu.linearMemory.setWord(oldBase32 + 0x4c, (short) cpu.cs()); cpu.linearMemory.setWord(oldBase32 + 0x50, (short) cpu.ss()); cpu.linearMemory.setWord(oldBase32 + 0x54, (short) cpu.ds()); cpu.linearMemory.setWord(oldBase32 + 0x58, (short) cpu.fs()); cpu.linearMemory.setWord(oldBase32 + 0x5c, (short) cpu.gs()); } finally { cpu.linearMemory.setSupervisor(isSup); } } // effect on link field of new task if ((source == Source.CALL) || (source == Source.INT)) { boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); cpu.linearMemory.setWord(newBase32, (short) cpu.tss.getSelector()); } finally { cpu.linearMemory.setSupervisor(isSup); } } // STEP 6: The new-task state is loaded from the TSS int newEip, newEflags, newEax, newEcx, newEdx, newEbx, newEsp, newEbp, newEsi, newEdi; int newEs, newCs, newSs, newDs, newFs, newGs, newLdt, newCR3, trap_word; if (cpu.tss.getType() <= 3) { boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); newEip = 0xffff & cpu.linearMemory.getWord(newBase32 + 14); newEflags = 0xffff & cpu.linearMemory.getWord(newBase32 + 16); newEax = cpu.linearMemory.getWord(newBase32 + 18) | 0xffff0000; newEcx = cpu.linearMemory.getWord(newBase32 + 20) | 0xffff0000; newEdx = cpu.linearMemory.getWord(newBase32 + 22) | 0xffff0000; newEbx = cpu.linearMemory.getWord(newBase32 + 24) | 0xffff0000; newEsp = cpu.linearMemory.getWord(newBase32 + 26) | 0xffff0000; newEbp = cpu.linearMemory.getWord(newBase32 + 28) | 0xffff0000; newEsi = cpu.linearMemory.getWord(newBase32 + 30) | 0xffff0000; newEdi = cpu.linearMemory.getWord(newBase32 + 32) | 0xffff0000; newEs = cpu.linearMemory.getWord(newBase32 + 34) & 0xffff; newCs = cpu.linearMemory.getWord(newBase32 + 36) & 0xffff; newSs = cpu.linearMemory.getWord(newBase32 + 38) & 0xffff; newDs = cpu.linearMemory.getWord(newBase32 + 40) & 0xffff; newLdt = cpu.linearMemory.getWord(newBase32 + 42) & 0xffff; newFs = newGs = 0; // No CR3 change for 286 task switch newCR3 = trap_word = 0; } finally { cpu.linearMemory.setSupervisor(isSup); } } else { boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); if (cpu.pagingEnabled()) newCR3 = cpu.linearMemory.getDoubleWord(newBase32 + 0x1c); else newCR3 = 0; newEip = cpu.linearMemory.getDoubleWord(newBase32 + 0x20); newEflags = cpu.linearMemory.getDoubleWord(newBase32 + 0x24); newEax = cpu.linearMemory.getDoubleWord(newBase32 + 0x28); newEcx = cpu.linearMemory.getDoubleWord(newBase32 + 0x2c); newEdx = cpu.linearMemory.getDoubleWord(newBase32 + 0x30); newEbx = cpu.linearMemory.getDoubleWord(newBase32 + 0x34); newEsp = cpu.linearMemory.getDoubleWord(newBase32 + 0x38); newEbp = cpu.linearMemory.getDoubleWord(newBase32 + 0x3c); newEsi = cpu.linearMemory.getDoubleWord(newBase32 + 0x40); newEdi = cpu.linearMemory.getDoubleWord(newBase32 + 0x44); newEs = cpu.linearMemory.getWord(newBase32 + 0x48) & 0xffff; newCs = cpu.linearMemory.getWord(newBase32 + 0x4c) & 0xffff; newSs = cpu.linearMemory.getWord(newBase32 + 0x50) & 0xffff; newDs = cpu.linearMemory.getWord(newBase32 + 0x54) & 0xffff; newFs = cpu.linearMemory.getWord(newBase32 + 0x58) & 0xffff; newGs = cpu.linearMemory.getWord(newBase32 + 0x5c) & 0xffff; newLdt = cpu.linearMemory.getWord(newBase32 + 0x60) & 0xffff; trap_word = cpu.linearMemory.getWord(newBase32 + 0x64) & 0xffff; } finally { cpu.linearMemory.setSupervisor(isSup); } } // Step 7: If CALL, interrupt, or JMP, set busy flag in new task's TSS descriptor. If IRET, leave set. if (source != Source.IRET) { // set the new task's busy bit int addr = cpu.gdtr.getBase() + 8*(cpu.tss.getSelector() & 0xfffc) + 4; int tmp = cpu.linearMemory.getDoubleWord(addr); tmp |= 0x200; cpu.linearMemory.setDoubleWord(addr, tmp); tss = (ProtectedModeSegment) cpu.getSegment(tss.getSelector()); } // Commit point. At this point, we commit to the new // context. If an unrecoverable error occurs in further // processing, we complete the task switch without performing // additional access and segment availablility checks and // generate the appropriate exception prior to beginning // execution of the new task. // Step 8: Load the task register with the new TSS cpu.tss = tss; // Step 9: Set TS flag in the CR0 image stored in the new task TSS. cpu.setCR0(cpu.getCR0() | cpu.CR0_TASK_SWITCHED); // Task switch clears LE/L3/L2/L1/L0 in DR7 cpu.dr7 &= ~0x00000155; // Step 10: If call or interrupt, set the NT flag in the eflags // image stored in new task's TSS. If IRET or JMP, // NT is restored from new TSS eflags image. (no change) if ((source == Source.CALL) || (source == Source.INT)) newEflags |= Processor.EFLAGS_NT_MASK; // Step 11: Load the new task (dynamic) state from new TSS. // Any errors associated with loading and qualification of // segment descriptors in this step occur in the new task's // context. State loaded here includes LDTR, CR3, // EFLAGS, EIP, general purpose registers, and segment // descriptor parts of the segment registers. cpu.eip = newEip; cpu.r_eax.set32(newEax); cpu.r_ecx.set32(newEcx); cpu.r_edx.set32(newEdx); cpu.r_ebx.set32(newEbx); cpu.r_esp.set32(newEsp); cpu.r_ebp.set32(newEbp); cpu.r_esi.set32(newEsi); cpu.r_edi.set32(newEdi); cpu.setEFlags(newEflags, Processor.EFLAGS_VALID_MASK); // Set selectors TODO // cpu.cs(newCs); // cpu.ss(newSs); // cpu.ds(newDs); // cpu.es(newEs); // cpu.fs(newFs); // cpu.gs(newGs); if (newLdt == 0) { cpu.ldtr = SegmentFactory.NULL_SEGMENT; } else { Segment newSegment = cpu.getSegment(newLdt & ~0x4); cpu.ldtr = newSegment; } if ((tss.getType() >= 9) && (cpu.pagingEnabled())) { // change CR3 only if it actually modified if (newCR3 != cpu.getCR3()) { cpu.setCR3(newCR3); if (Processor.cpuLevel >= 6) { if (cpu.pagingEnabled() && cpu.physicalAddressExtension()) { // TODO // clear PDPTRs before raising task switch exception } } } } int saveCPL = cpu.getCPL(); // set CPL to 3 to force a privilege level change and stack switch if SS is not properly loaded cpu.setCPL(3); if ((newLdt & 4) != 0) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newLdt & 0xfffc, true); if ((newLdt & 0xfffc) != 0) { if (!cpu.ldtr.isPresent() || !cpu.ldtr.isSystem() || !(cpu.ldtr instanceof ProtectedModeSegment.LDT)) throw new ProcessorException(ProcessorException.Type.NOT_PRESENT, newLdt & 0xfffc, true); } else { // OK } if (cpu.isVirtual8086Mode()) { cpu.ss(newSs); cpu.ds(newDs); cpu.es(newEs); cpu.fs(newFs); cpu.gs(newGs); cpu.cs(newCs); } else { // SS if ((newSs & 0xfffc) == 0) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newSs & 0xfffc, true); Segment ss = cpu.loadSegment(newSs, true); if (!((ProtectedModeSegment)ss).isDataWritable() || ((ProtectedModeSegment)ss).isCode()) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newSs & 0xfffc, true); if (ss.getDPL() != (newCs & 3)) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newSs & 0xfffc, true); if (ss.getDPL() != ss.getRPL()) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newSs & 0xfffc, true); if (touch_segment((ProtectedModeSegment)ss, cpu)) ss = cpu.loadSegment(newSs, true); cpu.ss(ss); cpu.setCPL(saveCPL); Segment ds = cpu.loadSegment(newDs); checkSegment(ds, newCs & 3, cpu); cpu.ds(ds); Segment es = cpu.loadSegment(newEs); checkSegment(es, newCs & 3, cpu); cpu.es(es); Segment fs = cpu.loadSegment(newFs); checkSegment(fs, newCs & 3, cpu); cpu.fs(fs); Segment gs = cpu.loadSegment(newGs); checkSegment(gs, newCs & 3, cpu); cpu.gs(gs); // CS if ((newCs & 0xfffc) == 0) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newCs & 0xfffc, true); Segment cs = cpu.loadSegment(newCs); if (((ProtectedModeSegment)cs).isDataWritable()) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newCs & 0xfffc, true); if (!((ProtectedModeSegment) cs).isConforming() && cs.getDPL() != cs.getRPL()) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newCs & 0xfffc, true); if (((ProtectedModeSegment) cs).isConforming() && cs.getDPL() > cs.getRPL()) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, newCs & 0xfffc, true); if (touch_segment((ProtectedModeSegment)cs, cpu)) cs = cpu.loadSegment(newCs); // TODO support cs limit demotion cpu.cs(cs); if (Processor.cpuLevel >= 4) { cpu.checkAlignmentChecking(); } if ((tss.getType() >= 9) && ((trap_word & 1) != 0)) // TODO debug trap if (Processor.cpuLevel >= 6) { // TODO handle SSE mode change // TODO if support AVX, handleAVXmodeChange() } if (hasErrorCode) { boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); if (tss.getType() >= 9) cpu.push32(errorCode); else cpu.push16((short) errorCode); } finally { cpu.linearMemory.setSupervisor(isSup); } } if (cpu.eip > cpu.cs.getLimit()) throw new ProcessorException(ProcessorException.Type.GENERAL_PROTECTION, 0, true); } } private static boolean checkSegment(Segment seg, int cs_rpl, Processor cpu) { if (seg instanceof SegmentFactory.NullSegment) return false; ProtectedModeSegment s = (ProtectedModeSegment) seg; if (s.isSystem() || (s.isCode() && !s.isCodeReadable())) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, s.getSelector() & 0xfffc, true); if (s.isDataWritable() || !s.isConforming()) if ((s.getRPL() > s.getDPL()) || (cs_rpl > s.getDPL())) throw new ProcessorException(ProcessorException.Type.TASK_SWITCH, s.getSelector() & 0xfffc, true); return touch_segment(s, cpu); } public static boolean touch_segment(ProtectedModeSegment s, Processor cpu) { // set accessed bit if (!s.isAccessed()) { boolean isSup = cpu.linearMemory.isSupervisor(); try { cpu.linearMemory.setSupervisor(true); if ((s.getSelector() & 0x4) != 0) cpu.ldtr.VMsetByte((s.getSelector() & 0xfff8) + 5, (byte) (cpu.ldtr.getByte((s.getSelector() & 0xfff8) + 5) | 1)); else cpu.gdtr.VMsetByte((s.getSelector() & 0xfff8) + 5, (byte) (cpu.gdtr.getByte((s.getSelector() & 0xfff8) + 5) | 1)); } finally { cpu.linearMemory.setSupervisor(isSup); } return true; } return false; } }