mirror of
https://github.com/holub/mame
synced 2025-06-05 20:33:45 +03:00
i386: Add hardware breakpoint functionality [Melissa Goad] (#3761)
This commit is contained in:
parent
562de4d2f3
commit
886b94648e
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user