mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-17 02:30:54 +00:00
Merge pull request #1193 from TomHarte/8088Intentions
Work towards x86 access violations.
This commit is contained in:
commit
18820644b0
@ -138,7 +138,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
PartialBlock(0x00, ADD); break;
|
||||
case 0x06: Complete(PUSH, ES, None, data_size_); break;
|
||||
case 0x07: Complete(POP, ES, None, data_size_); break;
|
||||
case 0x07: Complete(POP, None, ES, data_size_); break;
|
||||
|
||||
PartialBlock(0x08, OR); break;
|
||||
case 0x0e: Complete(PUSH, CS, None, data_size_); break;
|
||||
@ -147,7 +147,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
// prefixed with $0f.
|
||||
case 0x0f:
|
||||
if constexpr (model < Model::i80286) {
|
||||
Complete(POP, CS, None, data_size_);
|
||||
Complete(POP, None, CS, data_size_);
|
||||
} else {
|
||||
phase_ = Phase::InstructionPageF;
|
||||
}
|
||||
@ -155,11 +155,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
PartialBlock(0x10, ADC); break;
|
||||
case 0x16: Complete(PUSH, SS, None, DataSize::Word); break;
|
||||
case 0x17: Complete(POP, SS, None, DataSize::Word); break;
|
||||
case 0x17: Complete(POP, None, SS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x18, SBB); break;
|
||||
case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break;
|
||||
case 0x1f: Complete(POP, DS, None, DataSize::Word); break;
|
||||
case 0x1f: Complete(POP, None, DS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x20, AND); break;
|
||||
case 0x26: segment_override_ = Source::ES; break;
|
||||
@ -523,145 +523,147 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
// MARK: - Additional F page of instructions.
|
||||
|
||||
if(phase_ == Phase::InstructionPageF && source != end) {
|
||||
// Update the instruction acquired.
|
||||
const uint8_t instr = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
if constexpr (model >= Model::i80286) {
|
||||
if(phase_ == Phase::InstructionPageF && source != end) {
|
||||
// Update the instruction acquired.
|
||||
const uint8_t instr = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
// NB: to reach here, the instruction set must be at least
|
||||
// that of an 80286.
|
||||
switch(instr) {
|
||||
default: undefined();
|
||||
// NB: to reach here, the instruction set must be at least
|
||||
// that of an 80286.
|
||||
switch(instr) {
|
||||
default: undefined();
|
||||
|
||||
case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break;
|
||||
case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break;
|
||||
case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break;
|
||||
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
||||
case 0x05:
|
||||
Requires(i80286);
|
||||
Complete(LOADALL, None, None, DataSize::Byte);
|
||||
break;
|
||||
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
||||
case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break;
|
||||
case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break;
|
||||
case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break;
|
||||
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
||||
case 0x05:
|
||||
Requires(i80286);
|
||||
Complete(LOADALL, None, None, DataSize::Byte);
|
||||
break;
|
||||
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0x20:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x21:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x22:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x23:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x24:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x26:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x20:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x21:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x22:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x23:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x24:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
case 0x26:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord);
|
||||
break;
|
||||
|
||||
case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break;
|
||||
case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break;
|
||||
case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break;
|
||||
case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break;
|
||||
case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break;
|
||||
case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break;
|
||||
case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break;
|
||||
case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break;
|
||||
case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break;
|
||||
case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break;
|
||||
case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break;
|
||||
case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break;
|
||||
case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break;
|
||||
case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break;
|
||||
case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break;
|
||||
case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break;
|
||||
case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break;
|
||||
case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break;
|
||||
case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break;
|
||||
case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break;
|
||||
case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break;
|
||||
case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break;
|
||||
case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break;
|
||||
case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break;
|
||||
case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break;
|
||||
case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break;
|
||||
case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break;
|
||||
case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break;
|
||||
case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break;
|
||||
case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break;
|
||||
case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break;
|
||||
case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break;
|
||||
|
||||
#define Set(x) \
|
||||
RequiresMin(i80386); \
|
||||
MemRegReg(SET##x, MemRegSingleOperand, DataSize::Byte);
|
||||
|
||||
case 0x90: Set(O); break;
|
||||
case 0x91: Set(NO); break;
|
||||
case 0x92: Set(B); break;
|
||||
case 0x93: Set(NB); break;
|
||||
case 0x94: Set(Z); break;
|
||||
case 0x95: Set(NZ); break;
|
||||
case 0x96: Set(BE); break;
|
||||
case 0x97: Set(NBE); break;
|
||||
case 0x98: Set(S); break;
|
||||
case 0x99: Set(NS); break;
|
||||
case 0x9a: Set(P); break;
|
||||
case 0x9b: Set(NP); break;
|
||||
case 0x9c: Set(L); break;
|
||||
case 0x9d: Set(NL); break;
|
||||
case 0x9e: Set(LE); break;
|
||||
case 0x9f: Set(NLE); break;
|
||||
case 0x90: Set(O); break;
|
||||
case 0x91: Set(NO); break;
|
||||
case 0x92: Set(B); break;
|
||||
case 0x93: Set(NB); break;
|
||||
case 0x94: Set(Z); break;
|
||||
case 0x95: Set(NZ); break;
|
||||
case 0x96: Set(BE); break;
|
||||
case 0x97: Set(NBE); break;
|
||||
case 0x98: Set(S); break;
|
||||
case 0x99: Set(NS); break;
|
||||
case 0x9a: Set(P); break;
|
||||
case 0x9b: Set(NP); break;
|
||||
case 0x9c: Set(L); break;
|
||||
case 0x9d: Set(NL); break;
|
||||
case 0x9e: Set(LE); break;
|
||||
case 0x9f: Set(NLE); break;
|
||||
|
||||
#undef Set
|
||||
|
||||
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
||||
case 0xa1: RequiresMin(i80386); Complete(POP, FS, None, data_size_); break;
|
||||
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
||||
case 0xa4:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xa5:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
||||
case 0xa9: RequiresMin(i80386); Complete(POP, GS, None, data_size_); break;
|
||||
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
||||
case 0xac:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xad:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xaf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(IMUL_2, Reg_MemReg, data_size_);
|
||||
break;
|
||||
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
||||
case 0xa1: RequiresMin(i80386); Complete(POP, None, FS, data_size_); break;
|
||||
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
||||
case 0xa4:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xa5:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
||||
case 0xa9: RequiresMin(i80386); Complete(POP, None, GS, data_size_); break;
|
||||
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
||||
case 0xac:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDimm, Reg_MemReg, data_size_);
|
||||
operand_size_ = DataSize::Byte;
|
||||
break;
|
||||
case 0xad:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(SHRDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xaf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(IMUL_2, Reg_MemReg, data_size_);
|
||||
break;
|
||||
|
||||
case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break;
|
||||
case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break;
|
||||
case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break;
|
||||
case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break;
|
||||
case 0xb6:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xb7:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break;
|
||||
case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break;
|
||||
case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break;
|
||||
case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break;
|
||||
case 0xbe:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xbf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break;
|
||||
case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break;
|
||||
case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break;
|
||||
case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break;
|
||||
case 0xb6:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xb7:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break;
|
||||
case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break;
|
||||
case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break;
|
||||
case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break;
|
||||
case 0xbe:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte);
|
||||
break;
|
||||
case 0xbf:
|
||||
RequiresMin(i80386);
|
||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Word);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -979,18 +981,20 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
// MARK: - ScaleIndexBase
|
||||
|
||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||
sib_ = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
if constexpr (is_32bit(model)) {
|
||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||
sib_ = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
// Potentially record the lack of a base.
|
||||
if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) {
|
||||
source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_;
|
||||
destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_;
|
||||
// Potentially record the lack of a base.
|
||||
if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) {
|
||||
source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_;
|
||||
destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_;
|
||||
}
|
||||
|
||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
|
||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
|
||||
// MARK: - Displacement and operand.
|
||||
@ -1041,6 +1045,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
// MARK: - Check for completion.
|
||||
|
||||
if(phase_ == Phase::ReadyToPost) {
|
||||
// TODO: map to #UD where applicable; build LOCK into the Operation type, buying an extra bit for the operation?
|
||||
//
|
||||
// As of the P6 Intel stipulates that:
|
||||
//
|
||||
// "The LOCK prefix can be prepended only to the following instructions and to those forms of the instructions
|
||||
// that use a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR,
|
||||
// XADD, and XCHG."
|
||||
//
|
||||
// ... and the #UD exception will be raised if LOCK is encountered elsewhere. So adding 17 additional
|
||||
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
||||
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
||||
|
||||
const auto result = std::make_pair(
|
||||
consumed_,
|
||||
InstructionT(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -163,7 +163,7 @@ enum class Operation: uint8_t {
|
||||
XOR,
|
||||
/// NOP; no further fields.
|
||||
NOP,
|
||||
/// POP from the stack to source.
|
||||
/// POP from the stack to destination.
|
||||
POP,
|
||||
/// POP from the stack to the flags register.
|
||||
POPF,
|
||||
|
@ -15,24 +15,59 @@
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
|
||||
/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor
|
||||
/// intends to reference.
|
||||
///
|
||||
/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate
|
||||
/// any exceptions that might be applicable.
|
||||
enum class AccessType {
|
||||
/// The requested value will be read from.
|
||||
Read,
|
||||
/// The requested value will be written to.
|
||||
Write,
|
||||
/// The requested value will be read from and then written to.
|
||||
ReadModifyWrite,
|
||||
/// The requested value has already been authorised for whatever form of access is now intended, so there's no
|
||||
/// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that
|
||||
/// all necessary stack space is available ahead of pushing anything, though each individual push will then result in
|
||||
/// a further `Preauthorised` access.
|
||||
PreauthorisedRead,
|
||||
PreauthorisedWrite,
|
||||
};
|
||||
|
||||
template <
|
||||
Model model_,
|
||||
typename FlowControllerT,
|
||||
typename RegistersT,
|
||||
typename MemoryT,
|
||||
typename IOT
|
||||
> struct ExecutionContext {
|
||||
FlowControllerT flow_controller;
|
||||
Status status;
|
||||
RegistersT registers;
|
||||
MemoryT memory;
|
||||
IOT io;
|
||||
static constexpr Model model = model_;
|
||||
};
|
||||
|
||||
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
|
||||
/// and providing any flow control effects to @c flow_controller.
|
||||
///
|
||||
/// Any change in processor status will be applied to @c status.
|
||||
template <
|
||||
Model model,
|
||||
typename InstructionT,
|
||||
typename FlowControllerT,
|
||||
typename RegistersT,
|
||||
typename MemoryT,
|
||||
typename IOT
|
||||
typename ContextT
|
||||
> void perform(
|
||||
const InstructionT &instruction,
|
||||
Status &status,
|
||||
FlowControllerT &flow_controller,
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
IOT &io
|
||||
ContextT &context
|
||||
);
|
||||
|
||||
template <
|
||||
typename ContextT
|
||||
> void interrupt(
|
||||
int index,
|
||||
ContextT &context
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "NSData+dataWithContentsOfGZippedFile.h"
|
||||
|
||||
@ -91,110 +93,193 @@ struct Registers {
|
||||
}
|
||||
};
|
||||
struct Memory {
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
FlagsL,
|
||||
FlagsH
|
||||
};
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
const Registers ®isters_;
|
||||
template <typename IntT, AccessType type> struct ReturnType;
|
||||
|
||||
Memory(Registers ®isters) : registers_(registers) {
|
||||
memory.resize(1024*1024);
|
||||
}
|
||||
// Reads: return a value directly.
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::Read> { using type = IntT; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
|
||||
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
// Writes: return a reference.
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::Write> { using type = IntT &; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedWrite> { using type = IntT &; };
|
||||
|
||||
void seed(uint32_t address, uint8_t value) {
|
||||
memory[address] = value;
|
||||
tags[address] = Tag::Seeded;
|
||||
}
|
||||
|
||||
void touch(uint32_t address) {
|
||||
tags[address] = Tag::AccessExpected;
|
||||
}
|
||||
|
||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||
uint32_t physical_address;
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: physical_address = registers_.ds_; break;
|
||||
case Source::ES: physical_address = registers_.es_; break;
|
||||
case Source::CS: physical_address = registers_.cs_; break;
|
||||
case Source::SS: physical_address = registers_.ss_; break;
|
||||
// Constructor.
|
||||
Memory(Registers ®isters) : registers_(registers) {
|
||||
memory.resize(1024*1024);
|
||||
}
|
||||
return physical_address << 4;
|
||||
}
|
||||
|
||||
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||
template <typename IntT> IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
|
||||
return access<IntT>(physical_address, tag);
|
||||
}
|
||||
// Initialisation.
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
|
||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||
// to a selector, they're just at an absolute location.
|
||||
template <typename IntT> IntT &access(uint32_t address, Tag tag) {
|
||||
// Check for address wraparound
|
||||
if(address >= 0x10'0001 - sizeof(IntT)) {
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
address &= 0xf'ffff;
|
||||
} else {
|
||||
if(address == 0xf'ffff) {
|
||||
// This is a 16-bit access comprising the final byte in memory and the first.
|
||||
write_back_address_[0] = address;
|
||||
write_back_address_[1] = 0;
|
||||
void seed(uint32_t address, uint8_t value) {
|
||||
memory[address] = value;
|
||||
tags[address] = Tag::Seeded;
|
||||
}
|
||||
|
||||
void touch(uint32_t address) {
|
||||
tags[address] = Tag::AccessExpected;
|
||||
}
|
||||
|
||||
// Preauthorisation call-ins.
|
||||
void preauthorise_stack_write(uint32_t length) {
|
||||
uint16_t sp = registers_.sp_;
|
||||
while(length--) {
|
||||
--sp;
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
}
|
||||
}
|
||||
void preauthorise_stack_read(uint32_t length) {
|
||||
uint16_t sp = registers_.sp_;
|
||||
while(length--) {
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
++sp;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(segment, start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(uint32_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
// Access call-ins.
|
||||
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint32_t address) {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(address == 0xffff) {
|
||||
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
||||
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
} else {
|
||||
address &= 0xf'ffff;
|
||||
}
|
||||
}
|
||||
auto &value = access<IntT, type>(segment, address, Tag::Accessed);
|
||||
|
||||
// If the CPU has indicated a write, it should be safe to fuzz the value now.
|
||||
if(type == AccessType::Write) {
|
||||
value = IntT(~0);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(uint32_t address) {
|
||||
return access<IntT, type>(address, Tag::Accessed);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tags.find(address) == tags.end()) {
|
||||
printf("Access to unexpected RAM address");
|
||||
}
|
||||
tags[address] = tag;
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
}
|
||||
private:
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
};
|
||||
|
||||
// Entry point for the 8086; simply notes that memory was accessed.
|
||||
template <typename IntT> IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(address == 0xffff) {
|
||||
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
||||
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
std::unordered_set<uint32_t> preauthorisations;
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
const Registers ®isters_;
|
||||
|
||||
void preauthorise(uint32_t address) {
|
||||
preauthorisations.insert(address);
|
||||
}
|
||||
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
|
||||
preauthorise((segment_base(segment) + address) & 0xf'ffff);
|
||||
}
|
||||
bool test_preauthorisation(uint32_t address) {
|
||||
auto authorisation = preauthorisations.find(address);
|
||||
if(authorisation == preauthorisations.end()) {
|
||||
return false;
|
||||
}
|
||||
preauthorisations.erase(authorisation);
|
||||
return true;
|
||||
}
|
||||
return access<IntT>(segment, address, Tag::Accessed);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||
uint32_t physical_address;
|
||||
using Source = InstructionSet::x86::Source;
|
||||
switch(segment) {
|
||||
default: physical_address = registers_.ds_; break;
|
||||
case Source::ES: physical_address = registers_.es_; break;
|
||||
case Source::CS: physical_address = registers_.cs_; break;
|
||||
case Source::SS: physical_address = registers_.ss_; break;
|
||||
}
|
||||
return physical_address << 4;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
|
||||
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
|
||||
return access<IntT, type>(physical_address, tag);
|
||||
}
|
||||
|
||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||
// to a selector, they're just at an absolute location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(uint32_t address, Tag tag) {
|
||||
if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) {
|
||||
if(!test_preauthorisation(address)) {
|
||||
printf("Non preauthorised access\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for address wraparound
|
||||
if(address > 0x10'0000 - sizeof(IntT)) {
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
address &= 0xf'ffff;
|
||||
} else {
|
||||
address &= 0xf'ffff;
|
||||
if(address == 0xf'ffff) {
|
||||
// This is a 16-bit access comprising the final byte in memory and the first.
|
||||
write_back_address_[0] = address;
|
||||
write_back_address_[1] = 0;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tags.find(address) == tags.end()) {
|
||||
printf("Access to unexpected RAM address\n");
|
||||
}
|
||||
tags[address] = tag;
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
};
|
||||
struct IO {
|
||||
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
|
||||
@ -205,39 +290,7 @@ class FlowController {
|
||||
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
||||
memory_(memory), registers_(registers), status_(status) {}
|
||||
|
||||
void did_iret() {}
|
||||
void did_near_ret() {}
|
||||
void did_far_ret() {}
|
||||
|
||||
void interrupt(int index) {
|
||||
const uint16_t address = static_cast<uint16_t>(index) << 2;
|
||||
const uint16_t new_ip = memory_.access<uint16_t>(address, Memory::Tag::Accessed);
|
||||
const uint16_t new_cs = memory_.access<uint16_t>(address + 2, Memory::Tag::Accessed);
|
||||
|
||||
push(status_.get(), true);
|
||||
|
||||
using Flag = InstructionSet::x86::Flag;
|
||||
status_.set_from<Flag::Interrupt, Flag::Trap>(0);
|
||||
|
||||
// Push CS and IP.
|
||||
push(registers_.cs_);
|
||||
push(registers_.ip_);
|
||||
|
||||
registers_.cs_ = new_cs;
|
||||
registers_.ip_ = new_ip;
|
||||
}
|
||||
|
||||
void call(uint16_t address) {
|
||||
push(registers_.ip_);
|
||||
jump(address);
|
||||
}
|
||||
|
||||
void call(uint16_t segment, uint16_t offset) {
|
||||
push(registers_.cs_);
|
||||
push(registers_.ip_);
|
||||
jump(segment, offset);
|
||||
}
|
||||
|
||||
// Requirements for perform.
|
||||
void jump(uint16_t address) {
|
||||
registers_.ip_ = address;
|
||||
}
|
||||
@ -250,12 +303,14 @@ class FlowController {
|
||||
void halt() {}
|
||||
void wait() {}
|
||||
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
@ -265,23 +320,6 @@ class FlowController {
|
||||
Registers ®isters_;
|
||||
Status &status_;
|
||||
bool should_repeat_ = false;
|
||||
|
||||
void push(uint16_t value, bool is_flags = false) {
|
||||
// Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and
|
||||
// FlagsH can be set separately.
|
||||
--registers_.sp_;
|
||||
memory_.access<uint8_t>(
|
||||
InstructionSet::x86::Source::SS,
|
||||
registers_.sp_,
|
||||
is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed
|
||||
) = value >> 8;
|
||||
--registers_.sp_;
|
||||
memory_.access<uint8_t>(
|
||||
InstructionSet::x86::Source::SS,
|
||||
registers_.sp_,
|
||||
is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed
|
||||
) = value & 0xff;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExecutionSupport {
|
||||
@ -290,8 +328,9 @@ struct ExecutionSupport {
|
||||
Memory memory;
|
||||
FlowController flow_controller;
|
||||
IO io;
|
||||
static constexpr auto model = InstructionSet::x86::Model::i8086;
|
||||
|
||||
ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {}
|
||||
ExecutionSupport(): memory(registers), flow_controller(memory, registers, status) {}
|
||||
|
||||
void clear() {
|
||||
memory.clear();
|
||||
@ -310,8 +349,8 @@ struct FailedExecution {
|
||||
@end
|
||||
|
||||
@implementation i8088Tests {
|
||||
ExecutionSupport execution_support;
|
||||
std::vector<FailedExecution> execution_failures;
|
||||
ExecutionSupport execution_support;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)testFiles {
|
||||
@ -383,6 +422,8 @@ struct FailedExecution {
|
||||
return hexInstruction;
|
||||
};
|
||||
|
||||
EACCES;
|
||||
|
||||
const auto decoded = decoder.decode(data.data(), data.size());
|
||||
const bool sizeMatched = decoded.first == data.size();
|
||||
if(assert) {
|
||||
@ -522,13 +563,9 @@ struct FailedExecution {
|
||||
execution_support.registers.ip_ += decoded.first;
|
||||
do {
|
||||
execution_support.flow_controller.begin_instruction();
|
||||
InstructionSet::x86::perform<InstructionSet::x86::Model::i8086>(
|
||||
InstructionSet::x86::perform(
|
||||
decoded.second,
|
||||
execution_support.status,
|
||||
execution_support.flow_controller,
|
||||
execution_support.registers,
|
||||
execution_support.memory,
|
||||
execution_support.io
|
||||
execution_support
|
||||
);
|
||||
} while (execution_support.flow_controller.should_repeat());
|
||||
|
||||
@ -537,21 +574,32 @@ struct FailedExecution {
|
||||
InstructionSet::x86::Status intended_status;
|
||||
|
||||
bool ramEqual = true;
|
||||
int mask_position = 0;
|
||||
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
||||
const uint32_t address = [ram[0] intValue];
|
||||
const auto value = execution_support.memory.access<uint8_t, Memory::AccessType::Read>(address);
|
||||
|
||||
uint8_t mask = 0xff;
|
||||
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
|
||||
switch(tag->second) {
|
||||
default: break;
|
||||
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
|
||||
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
|
||||
if((mask_position != 1) && value == [ram[1] intValue]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Consider whether this apparent mismatch might be because flags have been written to memory;
|
||||
// allow only one use of the [16-bit] mask per test.
|
||||
bool matched_with_mask = false;
|
||||
while(mask_position < 2) {
|
||||
const uint8_t mask = mask_position ? (flags_mask >> 8) : (flags_mask & 0xff);
|
||||
++mask_position;
|
||||
if((value & mask) == ([ram[1] intValue] & mask)) {
|
||||
matched_with_mask = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) {
|
||||
ramEqual = false;
|
||||
if(matched_with_mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ramEqual = false;
|
||||
break;
|
||||
}
|
||||
|
||||
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
||||
@ -663,7 +711,15 @@ struct FailedExecution {
|
||||
}
|
||||
|
||||
// Lock in current failure rate.
|
||||
XCTAssertLessThanOrEqual(execution_failures.size(), 1654);
|
||||
XCTAssertLessThanOrEqual(execution_failures.size(), 4138);
|
||||
|
||||
// Current accepted failures:
|
||||
// * 65 instances of DAA with invalid BCD input, and 64 of DAS;
|
||||
// * 2484 instances of LEA from a register, which officially has undefined results;
|
||||
// * 42 instances of AAM 00h for which I can't figure out what to do with flags; and
|
||||
// * 1486 instances of IDIV, most either with a rep or repne that on the 8086 specifically negatives the result,
|
||||
// but some admittedly still unexplained (primarily setting overflow even though the result doesn't overflow;
|
||||
// a couple of online 8086 emulators also didn't throw so maybe this is an 8086 quirk?)
|
||||
|
||||
for(const auto &failure: execution_failures) {
|
||||
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
||||
|
Loading…
x
Reference in New Issue
Block a user