Jump from IP 0xFFF0 to IP 0x0020 with a CS base that is not page aligned, and check that EIP wraps.
Signed-off-by: Paul Galbraith <[email protected]> --- tests/tcg/i386/system/meson.build | 7 ++ tests/tcg/i386/system/test-branch-wrap16.S | 118 +++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/tcg/i386/system/test-branch-wrap16.S diff --git a/tests/tcg/i386/system/meson.build b/tests/tcg/i386/system/meson.build index d3f73997c7..d831f7e9c1 100644 --- a/tests/tcg/i386/system/meson.build +++ b/tests/tcg/i386/system/meson.build @@ -34,6 +34,13 @@ foreach t: tcg_tests['multiarch-softmmu']['tests'] endforeach endforeach +tests += { + 'test-branch-wrap16.S': { + 'cflags': cflags, + 'qemu_args': qemu_def_args, + } +} + if 'qemu-system-i386' in emulators tcg_tests += { 'i386-softmmu': { diff --git a/tests/tcg/i386/system/test-branch-wrap16.S b/tests/tcg/i386/system/test-branch-wrap16.S new file mode 100644 index 0000000000..2520891ba7 --- /dev/null +++ b/tests/tcg/i386/system/test-branch-wrap16.S @@ -0,0 +1,118 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * A 16-bit near branch must truncate EIP to 16 bits. + * + * The test jumps from IP 0xFFF0 to IP 0x0020 in 16-bit code. The jump + * crosses 0xFFFF. For correct 16-bit behaviour the CPU should wrap the + * target to 0x0020, rather than branching to 0x10020. + * + * TCG used to skip the wrap when the untruncated target (CS base + + * 0x10020) was on the same guest page as the jump. That only happens + * when the CS base is not page aligned, so the code segment starts at + * a page boundary plus 0x10. + */ + +#define SEL_CODE32 0x08 +#define SEL_CODE16 0x18 + +#define RESULT_NONE 0 +#define RESULT_PASS 1 +#define RESULT_FAIL 2 + +/* + * 16-bit relative 'jmp target', hand-assembled because clang rejects a + * rel16 displacement outside [-0x8000, 0x7FFF]. But, in 16-bit code, any + * 16-bit displacement is valid. GAS correctly accepts any 16-bit offset. + */ +.macro jmp_rel16 target + .byte 0xE9 /* jmp rel16 opcode */ + .word ((\target) - (. + 2)) & 0xFFFF /* cw: 16-bit offset */ +.endm + + .data + +g_result: + .long RESULT_NONE + +/* 0x08 and 0x10 must match boot.S, which loaded the data selectors */ +gdt16: + .quad 0 + .word 0xFFFF, 0 + .byte 0, 0x9b, 0xCF, 0 + .word 0xFFFF, 0 + .byte 0, 0x93, 0xCF, 0 +gdt16_code16: + /* 16-bit code, 64 KiB limit; main fills in the base */ + .word 0xFFFF, 0 + .byte 0, 0x9b, 0x00, 0 +gdt16_end: + +gdtr16: + .word gdt16_end - gdt16 - 1 + .long gdt16 + +fail_msg: + .asciz "FAIL: branch left EIP untruncated (result=%d)\n" +pass_msg: + .asciz "PASS\n" + + /* + * Page aligned plus 0x10. A larger alignment breaks the + * multiboot load in boot.S. + */ + .balign 0x1000 + .skip 0x10 +wrap16_cs_base: + .code16 + + /* jmp lands here when EIP wraps to 0x0020 */ + .org wrap16_cs_base + 0x0020 +wrap16_target: + movl $g_result, %eax + movb $RESULT_PASS, (%eax) + ljmpl $SEL_CODE32, $wrap16_back + + .org wrap16_cs_base + 0xFFF0 + /* the problematic branch, which should wrap EIP */ + jmp_rel16 wrap16_target + + /* jmp lands here when EIP does not wrap (0x10020) */ + .org wrap16_cs_base + 0x10020 + movl $g_result, %eax + movb $RESULT_FAIL, (%eax) + ljmpl $SEL_CODE32, $wrap16_back + + .code32 + + .text + +wrap16_run: + ljmp $SEL_CODE16, $0xFFF0 +wrap16_back: + ret + + .globl main +main: + movl $wrap16_cs_base, %eax + movw %ax, gdt16_code16 + 2 + shrl $16, %eax + movb %al, gdt16_code16 + 4 + movb %ah, gdt16_code16 + 7 + lgdt gdtr16 + + call wrap16_run + cmpl $RESULT_PASS, g_result + jne test_failed + pushl $pass_msg /* test passed */ + call ml_printf + addl $4, %esp + xorl %eax, %eax + ret +test_failed: + pushl g_result + pushl $fail_msg + call ml_printf + addl $8, %esp + movl $1, %eax + ret -- 2.55.0.windows.3
