Expand the org.qemu.gdb.i386.system feature to expose additional read-only registers to HMP/monitor:
- segment cache bases and flags (cs/ds/es/ss/fs/gs) - GDT/IDT base and limit - LDT/TR selector, base, limit, and flags - debug registers (dr0-dr3, dr6, dr7) - CPU status fields: CPL, interrupt-inhibit, A20, SMM, HLT - xcr0 - abridged x87 tag word (ftw) This will allow to implement an HMP "info registers" (cpu dump) equivalent based on QMP register query some day. Signed-off-by: Marc-André Lureau <[email protected]> --- gdbstub/gdb-xml/i386-system.xml | 41 ++++++++++++- target/i386/cpu-dump.c | 5 +- target/i386/cpu.h | 16 +++++ target/i386/gdbstub.c | 132 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 181 insertions(+), 13 deletions(-) diff --git a/gdbstub/gdb-xml/i386-system.xml b/gdbstub/gdb-xml/i386-system.xml index 9868b887eab8..7e7a5fb2e21f 100644 --- a/gdbstub/gdb-xml/i386-system.xml +++ b/gdbstub/gdb-xml/i386-system.xml @@ -3,7 +3,7 @@ SPDX-License-Identifier: GPL-2.0-or-later --> <!DOCTYPE feature SYSTEM "gdb-target.dtd"> -<!-- Segment-cache values are read-only; writes are ignored. --> +<!-- System-register values are read-only; writes are ignored. --> <feature name="org.qemu.gdb.i386.system"> <reg name="cs.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> <reg name="ds.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> @@ -11,4 +11,43 @@ <reg name="ss.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> <reg name="fs.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> <reg name="gs.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <!-- fs/gs in core regs --> + <reg name="cs.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="ds.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="es.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="ss.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="cs.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="ds.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="es.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="ss.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="fs.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="gs.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="gdt.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="gdt.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="idt.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="idt.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="ldt.selector" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="ldt.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="ldt.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="ldt.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="tr.selector" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="tr.base" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="tr.limit" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="tr.flags" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="dr0" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="dr1" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="dr2" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="dr3" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="dr6" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <reg name="dr7" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <!-- CPU status values, matching the fields printed by info registers. --> + <reg name="cpl" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <!-- ii: interrupt inhibit --> + <reg name="ii" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="a20" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="smm" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="hlt" bitsize="32" type="uint32" group="qemu-debug" save-restore="no"/> + <reg name="xcr0" bitsize="64" type="uint64" group="qemu-debug" save-restore="no"/> + <!-- Abridged x87 tag word: one bit per physical register, 1 = nonempty. --> + <reg name="ftw" bitsize="8" type="uint8" group="qemu-debug" save-restore="no"/> </feature> diff --git a/target/i386/cpu-dump.c b/target/i386/cpu-dump.c index 9e290c0b9b42..d8d7ebaf0e03 100644 --- a/target/i386/cpu-dump.c +++ b/target/i386/cpu-dump.c @@ -511,10 +511,7 @@ void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags) XSTATE_Hi16_ZMM_MASK | \ XSTATE_YMM_MASK | XSTATE_SSE_MASK, avx_mask = XSTATE_YMM_MASK | XSTATE_SSE_MASK; - fptag = 0; - for(i = 0; i < 8; i++) { - fptag |= ((!env->fptags[i]) << i); - } + fptag = x86_fptags_to_abridged(env); update_mxcsr_from_sse_status(env); qemu_fprintf(f, "FCW=%04x FSW=%04x [ST=%d] FTW=%02x MXCSR=%08x\n", env->fpuc, diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 116e28a59701..cc06f1fb7b68 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2998,6 +2998,22 @@ static inline void cpu_set_mxcsr(CPUX86State *env, uint32_t mxcsr) */ uint16_t cpu_compute_fptag(CPUX86State *env); +/** + * x86_fptags_to_abridged - Pack fptags[] into the 8-bit abridged tag word. + * + * Returns the FXSAVE/XSAVE-style tag byte: bit i is 1 when ST(i) is + * nonempty (fptags[i] == 0) and 0 when empty (fptags[i] == 1). + */ +static inline uint8_t x86_fptags_to_abridged(const CPUX86State *env) +{ + uint8_t ftw = 0; + + for (int i = 0; i < 8; i++) { + ftw |= (!env->fptags[i]) << i; + } + return ftw; +} + static inline void cpu_set_fpuc(CPUX86State *env, uint16_t fpuc) { env->fpuc = fpuc; diff --git a/target/i386/gdbstub.c b/target/i386/gdbstub.c index 2b4c28aac162..9a46c46ecff9 100644 --- a/target/i386/gdbstub.c +++ b/target/i386/gdbstub.c @@ -510,21 +510,135 @@ static int i386_cpu_gdb_set_egprs(CPUState *cs, uint8_t *mem_buf, int n) #endif #ifndef CONFIG_USER_ONLY +enum { + /* Segment limits: cs, ds, es, ss, fs, gs (6 × 32-bit) */ + X86_GDB_SEG_LIMIT_FIRST, + X86_GDB_SEG_LIMIT_LAST = X86_GDB_SEG_LIMIT_FIRST + 5, + /* Segment bases: cs, ds, es, ss (4 × 64-bit; fs/gs in core regs) */ + X86_GDB_SEG_BASE_FIRST, + X86_GDB_SEG_BASE_LAST = X86_GDB_SEG_BASE_FIRST + 3, + /* Segment flags: cs, ds, es, ss, fs, gs (6 × 32-bit) */ + X86_GDB_SEG_FLAGS_FIRST, + X86_GDB_SEG_FLAGS_LAST = X86_GDB_SEG_FLAGS_FIRST + 5, + /* Descriptor tables */ + X86_GDB_GDT_BASE, + X86_GDB_GDT_LIMIT, + X86_GDB_IDT_BASE, + X86_GDB_IDT_LIMIT, + X86_GDB_LDT_SELECTOR, + X86_GDB_LDT_BASE, + X86_GDB_LDT_LIMIT, + X86_GDB_LDT_FLAGS, + X86_GDB_TR_SELECTOR, + X86_GDB_TR_BASE, + X86_GDB_TR_LIMIT, + X86_GDB_TR_FLAGS, + /* Debug registers: dr0-dr3, dr6, dr7 (6 × 64-bit) */ + X86_GDB_DR0, + X86_GDB_DR1, + X86_GDB_DR2, + X86_GDB_DR3, + X86_GDB_DR6, + X86_GDB_DR7, + /* CPU status */ + X86_GDB_CPL, + X86_GDB_II, + X86_GDB_A20, + X86_GDB_SMM, + X86_GDB_HLT, + X86_GDB_XCR0, + X86_GDB_FTW, + X86_GDB_SYSTEM_COUNT, +}; + static int x86_gdb_read_system(CPUState *cs, GByteArray *buf, int n) { CPUX86State *env = cpu_env(cs); static const int segments[] = { R_CS, R_DS, R_ES, R_SS, R_FS, R_GS }; - if (n < 0 || n >= ARRAY_SIZE(segments)) { + switch (n) { + case X86_GDB_SEG_LIMIT_FIRST ... X86_GDB_SEG_LIMIT_LAST: + return gdb_get_reg32(buf, + env->segs[segments[n - X86_GDB_SEG_LIMIT_FIRST]].limit); + case X86_GDB_SEG_BASE_FIRST ... X86_GDB_SEG_BASE_LAST: + return gdb_get_reg64(buf, + env->segs[segments[n - X86_GDB_SEG_BASE_FIRST]].base); + case X86_GDB_SEG_FLAGS_FIRST ... X86_GDB_SEG_FLAGS_LAST: + return gdb_get_reg32(buf, + env->segs[segments[n - X86_GDB_SEG_FLAGS_FIRST]].flags); + case X86_GDB_GDT_BASE: + return gdb_get_reg64(buf, env->gdt.base); + case X86_GDB_GDT_LIMIT: + return gdb_get_reg32(buf, env->gdt.limit); + case X86_GDB_IDT_BASE: + return gdb_get_reg64(buf, env->idt.base); + case X86_GDB_IDT_LIMIT: + return gdb_get_reg32(buf, env->idt.limit); + case X86_GDB_LDT_SELECTOR: + return gdb_get_reg32(buf, env->ldt.selector); + case X86_GDB_LDT_BASE: + return gdb_get_reg64(buf, env->ldt.base); + case X86_GDB_LDT_LIMIT: + return gdb_get_reg32(buf, env->ldt.limit); + case X86_GDB_LDT_FLAGS: + return gdb_get_reg32(buf, env->ldt.flags); + case X86_GDB_TR_SELECTOR: + return gdb_get_reg32(buf, env->tr.selector); + case X86_GDB_TR_BASE: + return gdb_get_reg64(buf, env->tr.base); + case X86_GDB_TR_LIMIT: + return gdb_get_reg32(buf, env->tr.limit); + case X86_GDB_TR_FLAGS: + return gdb_get_reg32(buf, env->tr.flags); + case X86_GDB_DR0 ... X86_GDB_DR3: + return gdb_get_reg64(buf, env->dr[n - X86_GDB_DR0]); + case X86_GDB_DR6: + return gdb_get_reg64(buf, env->dr[6]); + case X86_GDB_DR7: + return gdb_get_reg64(buf, env->dr[7]); + case X86_GDB_CPL: + return gdb_get_reg32(buf, env->hflags & HF_CPL_MASK); + case X86_GDB_II: + return gdb_get_reg32(buf, !!(env->hflags & HF_INHIBIT_IRQ_MASK)); + case X86_GDB_A20: + return gdb_get_reg32(buf, (env->a20_mask >> 20) & 1); + case X86_GDB_SMM: + return gdb_get_reg32(buf, !!(env->hflags & HF_SMM_MASK)); + case X86_GDB_HLT: + return gdb_get_reg32(buf, cs->halted); + case X86_GDB_XCR0: + return gdb_get_reg64(buf, env->xcr0); + case X86_GDB_FTW: + return gdb_get_reg8(buf, x86_fptags_to_abridged(env)); + default: return 0; } - return gdb_get_reg32(buf, env->segs[segments[n]].limit); } static int x86_gdb_write_system(CPUState *cs, uint8_t *buf, int n) { - /* Segment caches are exposed for inspection only. Ignore writes. */ - return 4; + /* System registers are exposed for inspection only. Ignore writes. */ + static const uint8_t sizes[X86_GDB_SYSTEM_COUNT] = { + [X86_GDB_SEG_LIMIT_FIRST ... X86_GDB_SEG_LIMIT_LAST] = 4, + [X86_GDB_SEG_BASE_FIRST ... X86_GDB_SEG_BASE_LAST] = 8, + [X86_GDB_SEG_FLAGS_FIRST ... X86_GDB_SEG_FLAGS_LAST] = 4, + [X86_GDB_GDT_BASE] = 8, [X86_GDB_GDT_LIMIT] = 4, + [X86_GDB_IDT_BASE] = 8, [X86_GDB_IDT_LIMIT] = 4, + [X86_GDB_LDT_SELECTOR] = 4, [X86_GDB_LDT_BASE] = 8, + [X86_GDB_LDT_LIMIT] = 4, [X86_GDB_LDT_FLAGS] = 4, + [X86_GDB_TR_SELECTOR] = 4, [X86_GDB_TR_BASE] = 8, + [X86_GDB_TR_LIMIT] = 4, [X86_GDB_TR_FLAGS] = 4, + [X86_GDB_DR0 ... X86_GDB_DR7] = 8, + [X86_GDB_CPL] = 4, [X86_GDB_II] = 4, + [X86_GDB_A20] = 4, [X86_GDB_SMM] = 4, + [X86_GDB_HLT] = 4, [X86_GDB_XCR0] = 8, + [X86_GDB_FTW] = 1, + }; + + if (n < 0 || n >= X86_GDB_SYSTEM_COUNT) { + return 0; + } + return sizes[n]; } #endif @@ -559,9 +673,11 @@ void x86_cpu_gdb_init(CPUState *cs) #endif #endif #ifndef CONFIG_USER_ONLY - gdb_register_internal_coprocessor( - cs, x86_gdb_read_system, - x86_gdb_write_system, - gdb_find_static_feature("i386-system.xml")); + { + const GDBFeature *sysregs = gdb_find_static_feature("i386-system.xml"); + g_assert(sysregs->num_regs == X86_GDB_SYSTEM_COUNT); + gdb_register_internal_coprocessor(cs, x86_gdb_read_system, + x86_gdb_write_system, sysregs); + } #endif } -- 2.56.0.116.g6de20f6092dc
