xref: /qemu/target/i386/tcg/system/bpt_helper.c (revision 3e57baa22ea6892d1b8c212253b2859be30f45ee)
16d8d1a03SClaudio Fontana /*
232cad1ffSPhilippe Mathieu-Daudé  *  i386 breakpoint helpers - system code
36d8d1a03SClaudio Fontana  *
46d8d1a03SClaudio Fontana  *  Copyright (c) 2003 Fabrice Bellard
56d8d1a03SClaudio Fontana  *
66d8d1a03SClaudio Fontana  * This library is free software; you can redistribute it and/or
76d8d1a03SClaudio Fontana  * modify it under the terms of the GNU Lesser General Public
86d8d1a03SClaudio Fontana  * License as published by the Free Software Foundation; either
96d8d1a03SClaudio Fontana  * version 2.1 of the License, or (at your option) any later version.
106d8d1a03SClaudio Fontana  *
116d8d1a03SClaudio Fontana  * This library is distributed in the hope that it will be useful,
126d8d1a03SClaudio Fontana  * but WITHOUT ANY WARRANTY; without even the implied warranty of
136d8d1a03SClaudio Fontana  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
146d8d1a03SClaudio Fontana  * Lesser General Public License for more details.
156d8d1a03SClaudio Fontana  *
166d8d1a03SClaudio Fontana  * You should have received a copy of the GNU Lesser General Public
176d8d1a03SClaudio Fontana  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
186d8d1a03SClaudio Fontana  */
196d8d1a03SClaudio Fontana 
206d8d1a03SClaudio Fontana #include "qemu/osdep.h"
216d8d1a03SClaudio Fontana #include "cpu.h"
226d8d1a03SClaudio Fontana #include "exec/exec-all.h"
236d8d1a03SClaudio Fontana #include "exec/helper-proto.h"
24*3e57baa2SRichard Henderson #include "exec/watchpoint.h"
256d8d1a03SClaudio Fontana #include "tcg/helper-tcg.h"
266d8d1a03SClaudio Fontana 
276d8d1a03SClaudio Fontana 
286d8d1a03SClaudio Fontana static inline bool hw_local_breakpoint_enabled(unsigned long dr7, int index)
296d8d1a03SClaudio Fontana {
306d8d1a03SClaudio Fontana     return (dr7 >> (index * 2)) & 1;
316d8d1a03SClaudio Fontana }
326d8d1a03SClaudio Fontana 
336d8d1a03SClaudio Fontana static inline bool hw_global_breakpoint_enabled(unsigned long dr7, int index)
346d8d1a03SClaudio Fontana {
356d8d1a03SClaudio Fontana     return (dr7 >> (index * 2)) & 2;
366d8d1a03SClaudio Fontana 
376d8d1a03SClaudio Fontana }
386d8d1a03SClaudio Fontana static inline bool hw_breakpoint_enabled(unsigned long dr7, int index)
396d8d1a03SClaudio Fontana {
406d8d1a03SClaudio Fontana     return hw_global_breakpoint_enabled(dr7, index) ||
416d8d1a03SClaudio Fontana            hw_local_breakpoint_enabled(dr7, index);
426d8d1a03SClaudio Fontana }
436d8d1a03SClaudio Fontana 
446d8d1a03SClaudio Fontana static inline int hw_breakpoint_type(unsigned long dr7, int index)
456d8d1a03SClaudio Fontana {
466d8d1a03SClaudio Fontana     return (dr7 >> (DR7_TYPE_SHIFT + (index * 4))) & 3;
476d8d1a03SClaudio Fontana }
486d8d1a03SClaudio Fontana 
496d8d1a03SClaudio Fontana static inline int hw_breakpoint_len(unsigned long dr7, int index)
506d8d1a03SClaudio Fontana {
516d8d1a03SClaudio Fontana     int len = ((dr7 >> (DR7_LEN_SHIFT + (index * 4))) & 3);
526d8d1a03SClaudio Fontana     return (len == 2) ? 8 : len + 1;
536d8d1a03SClaudio Fontana }
546d8d1a03SClaudio Fontana 
556d8d1a03SClaudio Fontana static int hw_breakpoint_insert(CPUX86State *env, int index)
566d8d1a03SClaudio Fontana {
576d8d1a03SClaudio Fontana     CPUState *cs = env_cpu(env);
586d8d1a03SClaudio Fontana     target_ulong dr7 = env->dr[7];
596d8d1a03SClaudio Fontana     target_ulong drN = env->dr[index];
606d8d1a03SClaudio Fontana     int err = 0;
616d8d1a03SClaudio Fontana 
626d8d1a03SClaudio Fontana     switch (hw_breakpoint_type(dr7, index)) {
636d8d1a03SClaudio Fontana     case DR7_TYPE_BP_INST:
646d8d1a03SClaudio Fontana         if (hw_breakpoint_enabled(dr7, index)) {
656d8d1a03SClaudio Fontana             err = cpu_breakpoint_insert(cs, drN, BP_CPU,
666d8d1a03SClaudio Fontana                                         &env->cpu_breakpoint[index]);
676d8d1a03SClaudio Fontana         }
686d8d1a03SClaudio Fontana         break;
696d8d1a03SClaudio Fontana 
706d8d1a03SClaudio Fontana     case DR7_TYPE_IO_RW:
716d8d1a03SClaudio Fontana         /* Notice when we should enable calls to bpt_io.  */
726d8d1a03SClaudio Fontana         return hw_breakpoint_enabled(env->dr[7], index)
736d8d1a03SClaudio Fontana                ? HF_IOBPT_MASK : 0;
746d8d1a03SClaudio Fontana 
756d8d1a03SClaudio Fontana     case DR7_TYPE_DATA_WR:
766d8d1a03SClaudio Fontana         if (hw_breakpoint_enabled(dr7, index)) {
776d8d1a03SClaudio Fontana             err = cpu_watchpoint_insert(cs, drN,
786d8d1a03SClaudio Fontana                                         hw_breakpoint_len(dr7, index),
796d8d1a03SClaudio Fontana                                         BP_CPU | BP_MEM_WRITE,
806d8d1a03SClaudio Fontana                                         &env->cpu_watchpoint[index]);
816d8d1a03SClaudio Fontana         }
826d8d1a03SClaudio Fontana         break;
836d8d1a03SClaudio Fontana 
846d8d1a03SClaudio Fontana     case DR7_TYPE_DATA_RW:
856d8d1a03SClaudio Fontana         if (hw_breakpoint_enabled(dr7, index)) {
866d8d1a03SClaudio Fontana             err = cpu_watchpoint_insert(cs, drN,
876d8d1a03SClaudio Fontana                                         hw_breakpoint_len(dr7, index),
886d8d1a03SClaudio Fontana                                         BP_CPU | BP_MEM_ACCESS,
896d8d1a03SClaudio Fontana                                         &env->cpu_watchpoint[index]);
906d8d1a03SClaudio Fontana         }
916d8d1a03SClaudio Fontana         break;
926d8d1a03SClaudio Fontana     }
936d8d1a03SClaudio Fontana     if (err) {
946d8d1a03SClaudio Fontana         env->cpu_breakpoint[index] = NULL;
956d8d1a03SClaudio Fontana     }
966d8d1a03SClaudio Fontana     return 0;
976d8d1a03SClaudio Fontana }
986d8d1a03SClaudio Fontana 
996d8d1a03SClaudio Fontana static void hw_breakpoint_remove(CPUX86State *env, int index)
1006d8d1a03SClaudio Fontana {
1016d8d1a03SClaudio Fontana     CPUState *cs = env_cpu(env);
1026d8d1a03SClaudio Fontana 
1036d8d1a03SClaudio Fontana     switch (hw_breakpoint_type(env->dr[7], index)) {
1046d8d1a03SClaudio Fontana     case DR7_TYPE_BP_INST:
1056d8d1a03SClaudio Fontana         if (env->cpu_breakpoint[index]) {
1066d8d1a03SClaudio Fontana             cpu_breakpoint_remove_by_ref(cs, env->cpu_breakpoint[index]);
1076d8d1a03SClaudio Fontana             env->cpu_breakpoint[index] = NULL;
1086d8d1a03SClaudio Fontana         }
1096d8d1a03SClaudio Fontana         break;
1106d8d1a03SClaudio Fontana 
1116d8d1a03SClaudio Fontana     case DR7_TYPE_DATA_WR:
1126d8d1a03SClaudio Fontana     case DR7_TYPE_DATA_RW:
113080ac335SDmitry Voronetskiy         if (env->cpu_watchpoint[index]) {
1146d8d1a03SClaudio Fontana             cpu_watchpoint_remove_by_ref(cs, env->cpu_watchpoint[index]);
115080ac335SDmitry Voronetskiy             env->cpu_watchpoint[index] = NULL;
1166d8d1a03SClaudio Fontana         }
1176d8d1a03SClaudio Fontana         break;
1186d8d1a03SClaudio Fontana 
1196d8d1a03SClaudio Fontana     case DR7_TYPE_IO_RW:
1206d8d1a03SClaudio Fontana         /* HF_IOBPT_MASK cleared elsewhere.  */
1216d8d1a03SClaudio Fontana         break;
1226d8d1a03SClaudio Fontana     }
1236d8d1a03SClaudio Fontana }
1246d8d1a03SClaudio Fontana 
1256d8d1a03SClaudio Fontana void cpu_x86_update_dr7(CPUX86State *env, uint32_t new_dr7)
1266d8d1a03SClaudio Fontana {
1276d8d1a03SClaudio Fontana     target_ulong old_dr7 = env->dr[7];
1286d8d1a03SClaudio Fontana     int iobpt = 0;
1296d8d1a03SClaudio Fontana     int i;
1306d8d1a03SClaudio Fontana 
1316d8d1a03SClaudio Fontana     new_dr7 |= DR7_FIXED_1;
1326d8d1a03SClaudio Fontana 
1336d8d1a03SClaudio Fontana     /* If nothing is changing except the global/local enable bits,
1346d8d1a03SClaudio Fontana        then we can make the change more efficient.  */
1356d8d1a03SClaudio Fontana     if (((old_dr7 ^ new_dr7) & ~0xff) == 0) {
1366d8d1a03SClaudio Fontana         /* Fold the global and local enable bits together into the
1376d8d1a03SClaudio Fontana            global fields, then xor to show which registers have
1386d8d1a03SClaudio Fontana            changed collective enable state.  */
1396d8d1a03SClaudio Fontana         int mod = ((old_dr7 | old_dr7 * 2) ^ (new_dr7 | new_dr7 * 2)) & 0xff;
1406d8d1a03SClaudio Fontana 
1416d8d1a03SClaudio Fontana         for (i = 0; i < DR7_MAX_BP; i++) {
1426d8d1a03SClaudio Fontana             if ((mod & (2 << i * 2)) && !hw_breakpoint_enabled(new_dr7, i)) {
1436d8d1a03SClaudio Fontana                 hw_breakpoint_remove(env, i);
1446d8d1a03SClaudio Fontana             }
1456d8d1a03SClaudio Fontana         }
1466d8d1a03SClaudio Fontana         env->dr[7] = new_dr7;
1476d8d1a03SClaudio Fontana         for (i = 0; i < DR7_MAX_BP; i++) {
1486d8d1a03SClaudio Fontana             if (mod & (2 << i * 2) && hw_breakpoint_enabled(new_dr7, i)) {
1496d8d1a03SClaudio Fontana                 iobpt |= hw_breakpoint_insert(env, i);
1506d8d1a03SClaudio Fontana             } else if (hw_breakpoint_type(new_dr7, i) == DR7_TYPE_IO_RW
1516d8d1a03SClaudio Fontana                        && hw_breakpoint_enabled(new_dr7, i)) {
1526d8d1a03SClaudio Fontana                 iobpt |= HF_IOBPT_MASK;
1536d8d1a03SClaudio Fontana             }
1546d8d1a03SClaudio Fontana         }
1556d8d1a03SClaudio Fontana     } else {
1566d8d1a03SClaudio Fontana         for (i = 0; i < DR7_MAX_BP; i++) {
1576d8d1a03SClaudio Fontana             hw_breakpoint_remove(env, i);
1586d8d1a03SClaudio Fontana         }
1596d8d1a03SClaudio Fontana         env->dr[7] = new_dr7;
1606d8d1a03SClaudio Fontana         for (i = 0; i < DR7_MAX_BP; i++) {
1616d8d1a03SClaudio Fontana             iobpt |= hw_breakpoint_insert(env, i);
1626d8d1a03SClaudio Fontana         }
1636d8d1a03SClaudio Fontana     }
1646d8d1a03SClaudio Fontana 
1656d8d1a03SClaudio Fontana     env->hflags = (env->hflags & ~HF_IOBPT_MASK) | iobpt;
1666d8d1a03SClaudio Fontana }
1676d8d1a03SClaudio Fontana 
1686d8d1a03SClaudio Fontana bool check_hw_breakpoints(CPUX86State *env, bool force_dr6_update)
1696d8d1a03SClaudio Fontana {
1706d8d1a03SClaudio Fontana     target_ulong dr6;
1716d8d1a03SClaudio Fontana     int reg;
1726d8d1a03SClaudio Fontana     bool hit_enabled = false;
1736d8d1a03SClaudio Fontana 
1746d8d1a03SClaudio Fontana     dr6 = env->dr[6] & ~0xf;
1756d8d1a03SClaudio Fontana     for (reg = 0; reg < DR7_MAX_BP; reg++) {
1766d8d1a03SClaudio Fontana         bool bp_match = false;
1776d8d1a03SClaudio Fontana         bool wp_match = false;
1786d8d1a03SClaudio Fontana 
1796d8d1a03SClaudio Fontana         switch (hw_breakpoint_type(env->dr[7], reg)) {
1806d8d1a03SClaudio Fontana         case DR7_TYPE_BP_INST:
1816d8d1a03SClaudio Fontana             if (env->dr[reg] == env->eip) {
1826d8d1a03SClaudio Fontana                 bp_match = true;
1836d8d1a03SClaudio Fontana             }
1846d8d1a03SClaudio Fontana             break;
1856d8d1a03SClaudio Fontana         case DR7_TYPE_DATA_WR:
1866d8d1a03SClaudio Fontana         case DR7_TYPE_DATA_RW:
1876d8d1a03SClaudio Fontana             if (env->cpu_watchpoint[reg] &&
1886d8d1a03SClaudio Fontana                 env->cpu_watchpoint[reg]->flags & BP_WATCHPOINT_HIT) {
1896d8d1a03SClaudio Fontana                 wp_match = true;
1906d8d1a03SClaudio Fontana             }
1916d8d1a03SClaudio Fontana             break;
1926d8d1a03SClaudio Fontana         case DR7_TYPE_IO_RW:
1936d8d1a03SClaudio Fontana             break;
1946d8d1a03SClaudio Fontana         }
1956d8d1a03SClaudio Fontana         if (bp_match || wp_match) {
1966d8d1a03SClaudio Fontana             dr6 |= 1 << reg;
1976d8d1a03SClaudio Fontana             if (hw_breakpoint_enabled(env->dr[7], reg)) {
1986d8d1a03SClaudio Fontana                 hit_enabled = true;
1996d8d1a03SClaudio Fontana             }
2006d8d1a03SClaudio Fontana         }
2016d8d1a03SClaudio Fontana     }
2026d8d1a03SClaudio Fontana 
2036d8d1a03SClaudio Fontana     if (hit_enabled || force_dr6_update) {
2046d8d1a03SClaudio Fontana         env->dr[6] = dr6;
2056d8d1a03SClaudio Fontana     }
2066d8d1a03SClaudio Fontana 
2076d8d1a03SClaudio Fontana     return hit_enabled;
2086d8d1a03SClaudio Fontana }
2096d8d1a03SClaudio Fontana 
2106d8d1a03SClaudio Fontana void breakpoint_handler(CPUState *cs)
2116d8d1a03SClaudio Fontana {
2126d8d1a03SClaudio Fontana     X86CPU *cpu = X86_CPU(cs);
2136d8d1a03SClaudio Fontana     CPUX86State *env = &cpu->env;
2146d8d1a03SClaudio Fontana 
2156d8d1a03SClaudio Fontana     if (cs->watchpoint_hit) {
2166d8d1a03SClaudio Fontana         if (cs->watchpoint_hit->flags & BP_CPU) {
2176d8d1a03SClaudio Fontana             cs->watchpoint_hit = NULL;
2186d8d1a03SClaudio Fontana             if (check_hw_breakpoints(env, false)) {
219cdc829b3SPaolo Bonzini                 /*
220cdc829b3SPaolo Bonzini                  * FIXME: #DB should be delayed by one instruction if
221cdc829b3SPaolo Bonzini                  * INHIBIT_IRQ is set (STI cannot trigger a watchpoint).
222cdc829b3SPaolo Bonzini                  * The delayed #DB should also fuse with one generated
223cdc829b3SPaolo Bonzini                  * by ICEBP (aka INT1).
224cdc829b3SPaolo Bonzini                  */
2256d8d1a03SClaudio Fontana                 raise_exception(env, EXCP01_DB);
2266d8d1a03SClaudio Fontana             } else {
2276d8d1a03SClaudio Fontana                 cpu_loop_exit_noexc(cs);
2286d8d1a03SClaudio Fontana             }
2296d8d1a03SClaudio Fontana         }
2306d8d1a03SClaudio Fontana     } else {
23150b208b8SRichard Henderson         if (cpu_breakpoint_test(cs, env->eip, BP_CPU)) {
2326d8d1a03SClaudio Fontana             check_hw_breakpoints(env, true);
2336d8d1a03SClaudio Fontana             raise_exception(env, EXCP01_DB);
2346d8d1a03SClaudio Fontana         }
2356d8d1a03SClaudio Fontana     }
2366d8d1a03SClaudio Fontana }
2376d8d1a03SClaudio Fontana 
238533883fdSPaolo Bonzini target_ulong helper_get_dr(CPUX86State *env, int reg)
239533883fdSPaolo Bonzini {
240533883fdSPaolo Bonzini     if (reg >= 4 && reg < 6) {
241533883fdSPaolo Bonzini         if (env->cr[4] & CR4_DE_MASK) {
242533883fdSPaolo Bonzini             raise_exception_ra(env, EXCP06_ILLOP, GETPC());
243533883fdSPaolo Bonzini         } else {
244533883fdSPaolo Bonzini             reg += 2;
245533883fdSPaolo Bonzini         }
246533883fdSPaolo Bonzini     }
247533883fdSPaolo Bonzini 
24857f8dbdbSPaolo Bonzini     if (env->dr[7] & DR7_GD) {
24957f8dbdbSPaolo Bonzini         env->dr[7] &= ~DR7_GD;
25057f8dbdbSPaolo Bonzini         env->dr[6] |= DR6_BD;
25157f8dbdbSPaolo Bonzini         raise_exception_ra(env, EXCP01_DB, GETPC());
25257f8dbdbSPaolo Bonzini     }
25357f8dbdbSPaolo Bonzini 
254533883fdSPaolo Bonzini     return env->dr[reg];
255533883fdSPaolo Bonzini }
256533883fdSPaolo Bonzini 
2576d8d1a03SClaudio Fontana void helper_set_dr(CPUX86State *env, int reg, target_ulong t0)
2586d8d1a03SClaudio Fontana {
259533883fdSPaolo Bonzini     if (reg >= 4 && reg < 6) {
260533883fdSPaolo Bonzini         if (env->cr[4] & CR4_DE_MASK) {
261533883fdSPaolo Bonzini             raise_exception_ra(env, EXCP06_ILLOP, GETPC());
262533883fdSPaolo Bonzini         } else {
263533883fdSPaolo Bonzini             reg += 2;
264533883fdSPaolo Bonzini         }
265533883fdSPaolo Bonzini     }
266533883fdSPaolo Bonzini 
26757f8dbdbSPaolo Bonzini     if (env->dr[7] & DR7_GD) {
26857f8dbdbSPaolo Bonzini         env->dr[7] &= ~DR7_GD;
26957f8dbdbSPaolo Bonzini         env->dr[6] |= DR6_BD;
27057f8dbdbSPaolo Bonzini         raise_exception_ra(env, EXCP01_DB, GETPC());
27157f8dbdbSPaolo Bonzini     }
27257f8dbdbSPaolo Bonzini 
273533883fdSPaolo Bonzini     if (reg < 4) {
2746d8d1a03SClaudio Fontana         if (hw_breakpoint_enabled(env->dr[7], reg)
2756d8d1a03SClaudio Fontana             && hw_breakpoint_type(env->dr[7], reg) != DR7_TYPE_IO_RW) {
2766d8d1a03SClaudio Fontana             hw_breakpoint_remove(env, reg);
2776d8d1a03SClaudio Fontana             env->dr[reg] = t0;
2786d8d1a03SClaudio Fontana             hw_breakpoint_insert(env, reg);
2796d8d1a03SClaudio Fontana         } else {
2806d8d1a03SClaudio Fontana             env->dr[reg] = t0;
2816d8d1a03SClaudio Fontana         }
282533883fdSPaolo Bonzini     } else {
283533883fdSPaolo Bonzini         if (t0 & DR_RESERVED_MASK) {
284533883fdSPaolo Bonzini             raise_exception_err_ra(env, EXCP0D_GPF, 0, GETPC());
2856d8d1a03SClaudio Fontana         }
286533883fdSPaolo Bonzini         if (reg == 6) {
2876d8d1a03SClaudio Fontana             env->dr[6] = t0 | DR6_FIXED_1;
288533883fdSPaolo Bonzini         } else {
2896d8d1a03SClaudio Fontana             cpu_x86_update_dr7(env, t0);
2906d8d1a03SClaudio Fontana         }
291533883fdSPaolo Bonzini     }
2926d8d1a03SClaudio Fontana }
2936d8d1a03SClaudio Fontana 
2946d8d1a03SClaudio Fontana /* Check if Port I/O is trapped by a breakpoint.  */
2956d8d1a03SClaudio Fontana void helper_bpt_io(CPUX86State *env, uint32_t port,
2966d8d1a03SClaudio Fontana                    uint32_t size, target_ulong next_eip)
2976d8d1a03SClaudio Fontana {
2986d8d1a03SClaudio Fontana     target_ulong dr7 = env->dr[7];
2996d8d1a03SClaudio Fontana     int i, hit = 0;
3006d8d1a03SClaudio Fontana 
3016d8d1a03SClaudio Fontana     for (i = 0; i < DR7_MAX_BP; ++i) {
3026d8d1a03SClaudio Fontana         if (hw_breakpoint_type(dr7, i) == DR7_TYPE_IO_RW
3036d8d1a03SClaudio Fontana             && hw_breakpoint_enabled(dr7, i)) {
3046d8d1a03SClaudio Fontana             int bpt_len = hw_breakpoint_len(dr7, i);
3056d8d1a03SClaudio Fontana             if (port + size - 1 >= env->dr[i]
3066d8d1a03SClaudio Fontana                 && port <= env->dr[i] + bpt_len - 1) {
3076d8d1a03SClaudio Fontana                 hit |= 1 << i;
3086d8d1a03SClaudio Fontana             }
3096d8d1a03SClaudio Fontana         }
3106d8d1a03SClaudio Fontana     }
3116d8d1a03SClaudio Fontana 
3126d8d1a03SClaudio Fontana     if (hit) {
3136d8d1a03SClaudio Fontana         env->dr[6] = (env->dr[6] & ~0xf) | hit;
3146d8d1a03SClaudio Fontana         env->eip = next_eip;
3156d8d1a03SClaudio Fontana         raise_exception(env, EXCP01_DB);
3166d8d1a03SClaudio Fontana     }
3176d8d1a03SClaudio Fontana }
318