From 886b94648edcefdbb0622f1e2c8d826f3fcfc998 Mon Sep 17 00:00:00 2001 From: Melissa Goad Date: Tue, 2 Jul 2019 08:53:55 -0500 Subject: i386: Add hardware breakpoint functionality [Melissa Goad] (#3761) --- src/devices/cpu/i386/i386.cpp | 41 ++++++++++++++++++++ src/devices/cpu/i386/i386.h | 8 ++++ src/devices/cpu/i386/i386op16.hxx | 3 ++ src/devices/cpu/i386/i386op32.hxx | 1 + src/devices/cpu/i386/i386ops.hxx | 15 +++++++- src/devices/cpu/i386/i386segs.hxx | 78 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/devices/cpu/i386/i386.cpp b/src/devices/cpu/i386/i386.cpp index 7a7529aed40..9f6eaafc83c 100644 --- a/src/devices/cpu/i386/i386.cpp +++ b/src/devices/cpu/i386/i386.cpp @@ -55,6 +55,7 @@ i386_device::i386_device(const machine_config &mconfig, device_type type, const , device_vtlb_interface(mconfig, *this, AS_PROGRAM) , m_program_config("program", ENDIANNESS_LITTLE, program_data_width, program_addr_width, 0, 32, 12) , m_io_config("io", ENDIANNESS_LITTLE, io_data_width, 16, 0) + , m_dr_breakpoints{nullptr, nullptr, nullptr, nullptr} , m_smiact(*this) , m_ferr_handler(*this) { @@ -2002,6 +2003,7 @@ void i386_device::i386_common_init() save_item(NAME(m_CPL)); + save_item(NAME(m_auto_clear_RF)); save_item(NAME(m_performed_intersegment_jump)); save_item(NAME(m_cr)); @@ -2042,6 +2044,10 @@ void i386_device::i386_common_init() m_ferr_handler(0); set_icountptr(m_cycles); + m_notifier = m_program->add_change_notifier([this](read_or_write mode) + { + dri_changed(); + }); } void i386_device::device_start() @@ -2488,6 +2494,8 @@ void i386_device::device_reset() m_CPL = 0; + m_auto_clear_RF = true; + CHANGE_PC(m_eip); } @@ -2742,6 +2750,36 @@ void i386_device::execute_run() while( m_cycles > 0 ) { i386_check_irq_line(); + + // The LE and GE bits of DR7 aren't currently implemented because they could potentially require cycle-accurate emulation. + if((m_dr[7] & 0xff) != 0) // If all of the breakpoints are disabled, skip checking for instruction breakpoint hitting entirely. + for(int i = 0; i < 4; i++) + { + bool dri_enabled = (m_dr[7] & (1 << ((i << 1) + 1))) || (m_dr[7] & (1 << (i << 1))); // Check both local AND global enable bits for this breakpoint. + if(dri_enabled && !m_RF) + { + int breakpoint_type = (m_dr[7] >> (i << 2)) & 3; + int breakpoint_length = (m_dr[7] >> ((i << 2) + 2)) & 3; + if(breakpoint_type == 0) + { + uint32_t phys_addr = 0; + uint32_t error; + phys_addr = (m_cr[0] & (1 << 31)) ? translate_address(m_CPL, TRANSLATE_FETCH, &m_dr[i], &error) : m_dr[i]; + if(breakpoint_length != 0) // Not one byte in length? logerror it, I have no idea how this works on real processors. + { + logerror("i386: Breakpoint length not 1 byte on an instruction breakpoint\n"); + } + if(m_pc == phys_addr) + { + // The processor never automatically clears bits in DR6. It only sets them. + m_dr[6] |= 1 << i; + i386_trap(1,0,0); + break; + } + } + } + } + m_operand_size = m_sreg[CS].d; m_xmm_operand_size = 0; m_address_size = m_sreg[CS].d; @@ -2772,6 +2810,7 @@ void i386_device::execute_run() { m_prev_eip = m_eip; m_ext = 1; + m_dr[6] |= (1 << 14); //Set BS bit of DR6. i386_trap(1,0,0); } if(m_lock && (m_opcode != 0xf0)) @@ -2782,6 +2821,8 @@ void i386_device::execute_run() m_ext = 1; i386_trap_with_error(e&0xffffffff,0,0,e>>32); } + if(m_RF && m_auto_clear_RF) m_RF = 0; + if(!m_auto_clear_RF) m_auto_clear_RF = true; } m_tsc += (cycles - m_cycles); } diff --git a/src/devices/cpu/i386/i386.h b/src/devices/cpu/i386/i386.h index 1de76d49339..23128aaf45c 100644 --- a/src/devices/cpu/i386/i386.h +++ b/src/devices/cpu/i386/i386.h @@ -267,6 +267,7 @@ protected: uint8_t m_CPL; // current privilege level + bool m_auto_clear_RF; uint8_t m_performed_intersegment_jump; uint8_t m_delayed_interrupt_enable; @@ -274,6 +275,13 @@ protected: uint32_t m_dr[8]; // Debug registers uint32_t m_tr[8]; // Test registers + memory_passthrough_handler* m_dr_breakpoints[4]; + int m_notifier; + + //386 Debug Register change handlers. + inline void dri_changed(); + inline void dr7_changed(uint32_t old_val, uint32_t new_val); + I386_SYS_TABLE m_gdtr; // Global Descriptor Table Register I386_SYS_TABLE m_idtr; // Interrupt Descriptor Table Register I386_SEG_DESC m_task; // Task register diff --git a/src/devices/cpu/i386/i386op16.hxx b/src/devices/cpu/i386/i386op16.hxx index 57c6c06cb63..729933204cf 100644 --- a/src/devices/cpu/i386/i386op16.hxx +++ b/src/devices/cpu/i386/i386op16.hxx @@ -799,6 +799,7 @@ void i386_device::i386_iret16() // Opcode 0xcf i386_load_segment_descriptor(CS); CHANGE_PC(m_eip); } + m_auto_clear_RF = false; CYCLES(CYCLES_IRET); } @@ -1752,6 +1753,8 @@ void i386_device::i386_popf() // Opcode 0x9d else FAULT(FAULT_SS,0) CYCLES(CYCLES_POPF); + + m_auto_clear_RF = false; } void i386_device::i386_push_ax() // Opcode 0x50 diff --git a/src/devices/cpu/i386/i386op32.hxx b/src/devices/cpu/i386/i386op32.hxx index 3b828a779a0..b81545ced81 100644 --- a/src/devices/cpu/i386/i386op32.hxx +++ b/src/devices/cpu/i386/i386op32.hxx @@ -767,6 +767,7 @@ void i386_device::i386_iret32() // Opcode 0xcf i386_load_segment_descriptor(CS); CHANGE_PC(m_eip); } + m_auto_clear_RF = false; CYCLES(CYCLES_IRET); } diff --git a/src/devices/cpu/i386/i386ops.hxx b/src/devices/cpu/i386/i386ops.hxx index 5b9d21a1c94..e77499535b1 100644 --- a/src/devices/cpu/i386/i386ops.hxx +++ b/src/devices/cpu/i386/i386ops.hxx @@ -720,19 +720,27 @@ void i386_device::i386_mov_dr_r32() // Opcode 0x0f 23 uint8_t modrm = FETCH(); uint8_t dr = (modrm >> 3) & 0x7; - m_dr[dr] = LOAD_RM32(modrm); + uint32_t rm32 = LOAD_RM32(modrm); switch(dr) { case 0: case 1: case 2: case 3: + { + m_dr[dr] = rm32; + dri_changed(dr); CYCLES(CYCLES_MOV_DR0_3_REG); break; - case 6: + } + case 6: CYCLES(CYCLES_MOV_DR6_7_REG); m_dr[dr] = LOAD_RM32(modrm); break; case 7: + { + dr7_changed(m_dr[7], rm32); CYCLES(CYCLES_MOV_DR6_7_REG); + m_dr[dr] = rm32; break; + } default: logerror("i386: mov_dr_r32 DR%d!\n", dr); return; @@ -2510,6 +2518,7 @@ void i386_device::i386_loadall() // Opcode 0x0f 0x07 (0x0f 0x05 on 80286), if(PROTECTED_MODE && (m_CPL != 0)) FAULT(FAULT_GP,0) uint32_t ea = i386_translate(ES, REG32(EDI), 0); + uint32_t old_dr7 = m_dr[7]; m_cr[0] = READ32(ea) & 0xfffeffff; // wp not supported on 386 set_flags(READ32(ea + 0x04)); m_eip = READ32(ea + 0x08); @@ -2566,6 +2575,8 @@ void i386_device::i386_loadall() // Opcode 0x0f 0x07 (0x0f 0x05 on 80286), m_sreg[i].valid = (m_sreg[i].flags & 0x80) ? true : false; m_sreg[i].d = (m_sreg[i].flags & 0x4000) ? 1 : 0; } + + dr7_changed(old_dr7, m_dr[7]); CHANGE_PC(m_eip); } diff --git a/src/devices/cpu/i386/i386segs.hxx b/src/devices/cpu/i386/i386segs.hxx index 69d55ec95f4..57b14b616e7 100644 --- a/src/devices/cpu/i386/i386segs.hxx +++ b/src/devices/cpu/i386/i386segs.hxx @@ -743,7 +743,7 @@ void i386_device::i386_trap(int irq, int irq_gate, int trap_level) } else { - PUSH32(oldflags & 0x00ffffff ); + PUSH32((oldflags & 0x00ffffff) | (1 << 16) ); //386 faults always have the RF bit set in the saved flags register. PUSH32SEG(m_sreg[CS].selector ); if(irq == 3 || irq == 4 || irq == 9 || irq_gate == 1) PUSH32(m_eip ); @@ -906,6 +906,8 @@ void i386_device::i286_task_switch(uint16_t selector, uint8_t nested) CHANGE_PC(m_eip); m_CPL = (m_sreg[SS].flags >> 5) & 3; + + m_auto_clear_RF = false; // printf("286 Task Switch from selector %04x to %04x\n",old_task,selector); } @@ -1024,6 +1026,13 @@ void i386_device::i386_task_switch(uint16_t selector, uint8_t nested) CHANGE_PC(m_eip); m_CPL = (m_sreg[SS].flags >> 5) & 3; + + int t_bit = READ32(tss+0x64) & 1; + if(t_bit) m_dr[6] |= (1 << 15); //If the T bit of the new TSS is set, set the BT bit of DR6. + + m_dr[7] &= ~(0x155); //Clear all of the local enable bits from DR7. + + m_auto_clear_RF = false; // printf("386 Task Switch from selector %04x to %04x\n",old_task,selector); } @@ -2462,3 +2471,70 @@ void i386_device::i386_protected_mode_iret(int operand32) i386_load_segment_descriptor(CS); CHANGE_PC(m_eip); } + +inline void i386_device::dri_changed() +{ + int dr; + if(!(m_dr[7] & 0xff)) return; + for(dr = 0; dr < 4; dr++) + { + if(m_dr_breakpoints[dr]) m_dr_breakpoints[dr]->remove(); + int dr_enabled = (m_dr[7] & (1 << (dr << 1))) || (m_dr[7] & (1 << ((dr << 1) + 1))); // Check both local enable AND global enable bits for this breakpoint. + if(dr_enabled) + { + int breakpoint_type = (m_dr[7] >> ((dr << 2) + 16)) & 3; + int breakpoint_length = (m_dr[7] >> ((dr << 2) + 16 + 2)) & 3; + uint32_t phys_addr = m_dr[dr]; + uint32_t error; + phys_addr = translate_address(m_CPL, TRANSLATE_READ, &phys_addr, &error); + phys_addr &= ~3; // According to CUP386, data breakpoints are only reliable on dword-aligned addresses, so align this to a dword. + uint32_t true_mask = 0; + switch(breakpoint_length) + { + case 0: true_mask = 0xff; break; + case 1: true_mask = 0xffff; break; + // Case 2 is invalid on a real 386. + case 3: true_mask = 0xffffffff; break; + } + if(true_mask == 0) + { + logerror("i386: Unknown breakpoint length value\n"); + } + else if(breakpoint_type == 1) m_dr_breakpoints[dr] = m_program->install_write_tap(phys_addr, phys_addr + 3, "i386_debug_write_breakpoint", + [&, dr, true_mask](offs_t offset, u32& data, u32 mem_mask) + { + if(true_mask & mem_mask) + { + m_dr[6] |= 1 << dr; + i386_trap(1,0,0); + } + }, m_dr_breakpoints[dr]); + else if(breakpoint_type == 3) m_dr_breakpoints[dr] = m_program->install_readwrite_tap(phys_addr, phys_addr + 3, "i386_debug_readwrite_breakpoint", + [&, dr, true_mask](offs_t offset, u32& data, u32 mem_mask) + { + if(true_mask & mem_mask) + { + m_dr[6] |= 1 << dr; + i386_trap(1,0,0); + } + }, + [&, dr, true_mask](offs_t offset, u32& data, u32 mem_mask) + { + if(true_mask & mem_mask) + { + m_dr[6] |= 1 << dr; + i386_trap(1,0,0); + } + }, m_dr_breakpoints[dr]); + } + } +} + +inline void i386_device::dr7_changed(uint32_t old_val, uint32_t new_val) +{ + //Check that none of the breakpoint types or lengths have changed. + int old_breakpoint_info = old_val >> 16; + int new_breakpoint_info = new_val >> 16; + if(old_breakpoint_info != new_breakpoint_info) dri_changed(); +} + -- cgit v1.2.3