diff --git a/qemu/target/i386/int_helper.c b/qemu/target/i386/int_helper.c index 5dea08ab9b..8ebdf02fe2 100644 --- a/qemu/target/i386/int_helper.c +++ b/qemu/target/i386/int_helper.c @@ -202,6 +202,14 @@ void helper_aaa(CPUX86State *env) al &= 0x0f; } env->regs[R_EAX] = (env->regs[R_EAX] & ~0xffff) | al | (ah << 8); + /* Compute PF, ZF, SF from result AL, clear OF -- matches observed + hardware behaviour (and the approach used by helper_daa/helper_das). + Intel documents these flags as undefined after AAA, but real CPUs + consistently set them based on the masked AL result. */ + eflags &= CC_C | CC_A; + eflags |= (al == 0) << 6; /* zf */ + eflags |= parity_table[al]; /* pf */ + eflags |= (al & 0x80); /* sf */ CC_SRC = eflags; } @@ -226,6 +234,11 @@ void helper_aas(CPUX86State *env) al &= 0x0f; } env->regs[R_EAX] = (env->regs[R_EAX] & ~0xffff) | al | (ah << 8); + /* Compute PF, ZF, SF from result AL, clear OF -- same fix as AAA above. */ + eflags &= CC_C | CC_A; + eflags |= (al == 0) << 6; /* zf */ + eflags |= parity_table[al]; /* pf */ + eflags |= (al & 0x80); /* sf */ CC_SRC = eflags; } diff --git a/tests/unit/test_x86.c b/tests/unit/test_x86.c index 8eedc31ee2..7e4616331c 100644 --- a/tests/unit/test_x86.c +++ b/tests/unit/test_x86.c @@ -2201,6 +2201,100 @@ static void test_x86_mem_hooks_pc_guarantee(void) OK(uc_close(uc)); } +// Test that AAA sets PF, ZF, SF based on the result AL value. +// Bug: AAA previously left these flags stale from the prior instruction. +static void test_x86_aaa_flags(void) +{ + uc_engine *uc; + + // INC ECX sets PF from ECX result (to create a conflicting PF state). + // AAA then adjusts AL and must set PF = parity(AL), ZF, SF independently. + // + // Case: EAX = 0x030A (AH=3, AL=0x0A) + // AL low nibble (0x0A & 0x0F = 0x0A) > 9, so adjustment fires: + // AL = (0x0A + 6) & 0x0F = 0x00 + // AH = 3 + 1 = 4 + // CF = 1, AF = 1 + // Result AL = 0x00: PF=1 (even parity), ZF=1, SF=0 + // + // INC ECX (0x41) ; AAA (0x37) + char code[] = "\x41\x37"; + + uint32_t r_eax = 0x030A; + uint32_t r_ecx = 0x0000; // INC 0 -> 1, PF=0 (odd parity of 1) + uint32_t r_eflags; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); + + OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + + OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); + OK(uc_reg_read(uc, UC_X86_REG_EFLAGS, &r_eflags)); + + // EAX should be 0x0400 (AH=4, AL=0) + TEST_CHECK(r_eax == 0x0400); + // PF (bit 2) = 1 (AL=0x00 has even parity) + TEST_CHECK((r_eflags & 0x04) != 0); + // ZF (bit 6) = 1 (AL == 0) + TEST_CHECK((r_eflags & 0x40) != 0); + // SF (bit 7) = 0 (AL bit 7 clear) + TEST_CHECK((r_eflags & 0x80) == 0); + // CF (bit 0) = 1 (adjustment fired) + TEST_CHECK((r_eflags & 0x01) != 0); + + OK(uc_close(uc)); +} + +// Test that AAS sets PF, ZF, SF based on the result AL value. +static void test_x86_aas_flags(void) +{ + uc_engine *uc; + + // Case: EAX = 0x030A (AH=3, AL=0x0A) + // AL low nibble > 9, so adjustment fires: + // AL = (0x0A - 6) & 0x0F = 0x04 + // AH = 3 - 1 = 2 + // CF = 1, AF = 1 + // Result AL = 0x04: PF=0 (odd parity of 0x04), ZF=0, SF=0 + // + // INC ECX (0x41) ; AAS (0x3F) + char code[] = "\x41\x3f"; + + uint32_t r_eax = 0x030A; + uint32_t r_ecx = 0x0002; // INC 2 -> 3, PF=1 (even parity of 3) + uint32_t r_eflags; + + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code, sizeof(code) - 1)); + + OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_eax)); + OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); + + OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); + + OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); + OK(uc_reg_read(uc, UC_X86_REG_EFLAGS, &r_eflags)); + + // EAX should be 0x0204 (AH=2, AL=4) + TEST_CHECK(r_eax == 0x0204); + // PF (bit 2) = 0 (AL=0x04 has odd parity: one bit set) + TEST_CHECK((r_eflags & 0x04) == 0); + // ZF (bit 6) = 0 (AL != 0) + TEST_CHECK((r_eflags & 0x40) == 0); + // SF (bit 7) = 0 (AL bit 7 clear) + TEST_CHECK((r_eflags & 0x80) == 0); + // CF (bit 0) = 1 (adjustment fired) + TEST_CHECK((r_eflags & 0x01) != 0); + + OK(uc_close(uc)); +} + TEST_LIST = { {"test_x86_in", test_x86_in}, {"test_x86_out", test_x86_out}, @@ -2265,4 +2359,6 @@ TEST_LIST = { {"test_x86_dr7", test_x86_dr7}, {"test_x86_hook_block", test_x86_hook_block}, {"test_x86_mem_hooks_pc_guarantee", test_x86_mem_hooks_pc_guarantee}, + {"test_x86_aaa_flags", test_x86_aaa_flags}, + {"test_x86_aas_flags", test_x86_aas_flags}, {NULL, NULL}};