i386: Add hardware breakpoint functionality [Melissa Goad] (#3761)

This commit is contained in:
Melissa Goad 2019-07-02 08:53:55 -05:00 committed by R. Belmont
parent 562de4d2f3
commit 886b94648e
6 changed files with 143 additions and 3 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();
}