blob: 755b5d18c814b612972d499275a1822b0bc69bff [file] [log] [blame]
asm(".code16gcc");
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned u32;
typedef unsigned long long u64;
void test_function(void);
asm(
"test_function: \n\t"
"mov $0x1234, %eax \n\t"
"ret"
);
static int strlen(const char *str)
{
int n;
for (n = 0; *str; ++str)
++n;
return n;
}
static void print_serial(const char *buf)
{
unsigned long len = strlen(buf);
asm volatile ("addr32/rep/outsb" : "+S"(buf), "+c"(len) : "d"(0xf1));
}
static void exit(int code)
{
asm volatile("out %0, %1" : : "a"(code), "d"((short)0xf4));
}
struct regs {
u32 eax, ebx, ecx, edx;
u32 esi, edi, esp, ebp;
u32 eip, eflags;
};
static u64 gdt[] = {
0,
0x00cf9b000000ffffull, // flat 32-bit code segment
0x00cf93000000ffffull, // flat 32-bit data segment
};
static struct {
u16 limit;
void *base;
} __attribute__((packed)) gdt_descr = {
sizeof(gdt) - 1,
gdt,
};
static void exec_in_big_real_mode(const struct regs *inregs,
struct regs *outregs,
const u8 *insn, int insn_len)
{
unsigned long tmp;
static struct regs save;
int i;
extern u8 test_insn[], test_insn_end[];
for (i = 0; i < insn_len; ++i)
test_insn[i] = insn[i];
for (; i < test_insn_end - test_insn; ++i)
test_insn[i] = 0x90; // nop
save = *inregs;
asm volatile(
"lgdtl %[gdt_descr] \n\t"
"mov %%cr0, %[tmp] \n\t"
"or $1, %[tmp] \n\t"
"mov %[tmp], %%cr0 \n\t"
"mov %[bigseg], %%gs \n\t"
"and $-2, %[tmp] \n\t"
"mov %[tmp], %%cr0 \n\t"
"xchg %%eax, %[save]+0 \n\t"
"xchg %%ebx, %[save]+4 \n\t"
"xchg %%ecx, %[save]+8 \n\t"
"xchg %%edx, %[save]+12 \n\t"
"xchg %%esi, %[save]+16 \n\t"
"xchg %%edi, %[save]+20 \n\t"
"xchg %%esp, %[save]+24 \n\t"
"xchg %%ebp, %[save]+28 \n\t"
"test_insn: . = . + 16\n\t"
"test_insn_end: \n\t"
"xchg %%eax, %[save]+0 \n\t"
"xchg %%ebx, %[save]+4 \n\t"
"xchg %%ecx, %[save]+8 \n\t"
"xchg %%edx, %[save]+12 \n\t"
"xchg %%esi, %[save]+16 \n\t"
"xchg %%edi, %[save]+20 \n\t"
"xchg %%esp, %[save]+24 \n\t"
"xchg %%ebp, %[save]+28 \n\t"
/* Save EFLAGS in outregs*/
"pushfl \n\t"
"popl %[save]+36 \n\t"
"xor %[tmp], %[tmp] \n\t"
"mov %[tmp], %%gs \n\t"
: [tmp]"=&r"(tmp), [save]"+m"(save)
: [gdt_descr]"m"(gdt_descr), [bigseg]"r"((short)16)
: "cc", "memory"
);
*outregs = save;
}
#define R_AX 1
#define R_BX 2
#define R_CX 4
#define R_DX 8
#define R_SI 16
#define R_DI 32
#define R_SP 64
#define R_BP 128
int regs_equal(const struct regs *r1, const struct regs *r2, int ignore)
{
const u32 *p1 = &r1->eax, *p2 = &r2->eax; // yuck
int i;
for (i = 0; i < 8; ++i)
if (!(ignore & (1 << i)) && p1[i] != p2[i])
return 0;
return 1;
}
#define MK_INSN(name, str) \
asm ( \
".pushsection \".text\" \n\t" \
"insn_" #name ": " str " \n\t" \
"insn_" #name "_end: \n\t" \
".popsection \n\t" \
); \
extern u8 insn_##name[], insn_##name##_end[]
void test_shld(void)
{
struct regs inregs = { .eax = 0xbe, .edx = 0xef000000 }, outregs;
MK_INSN(shld_test, "shld $8,%edx,%eax\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_shld_test,
insn_shld_test_end - insn_shld_test);
if (outregs.eax != 0xbeef)
print_serial("shld: failure\n");
else
print_serial("shld: success\n");
}
void test_mov_imm(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(mov_r32_imm_1, "mov $1234567890, %eax");
MK_INSN(mov_r16_imm_1, "mov $1234, %ax");
MK_INSN(mov_r8_imm_1, "mov $0x12, %ah");
MK_INSN(mov_r8_imm_2, "mov $0x34, %al");
MK_INSN(mov_r8_imm_3, "mov $0x12, %ah\n\t" "mov $0x34, %al\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_mov_r16_imm_1,
insn_mov_r16_imm_1_end - insn_mov_r16_imm_1);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 1234)
print_serial("mov test 1: FAIL\n");
/* test mov $imm, %eax */
exec_in_big_real_mode(&inregs, &outregs,
insn_mov_r32_imm_1,
insn_mov_r32_imm_1_end - insn_mov_r32_imm_1);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 1234567890)
print_serial("mov test 2: FAIL\n");
/* test mov $imm, %al/%ah */
exec_in_big_real_mode(&inregs, &outregs,
insn_mov_r8_imm_1,
insn_mov_r8_imm_1_end - insn_mov_r8_imm_1);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1200)
print_serial("mov test 3: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_mov_r8_imm_2,
insn_mov_r8_imm_2_end - insn_mov_r8_imm_2);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x34)
print_serial("mov test 4: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_mov_r8_imm_3,
insn_mov_r8_imm_3_end - insn_mov_r8_imm_3);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1234)
print_serial("mov test 5: FAIL\n");
}
void test_cmp_imm(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(cmp_test1, "mov $0x34, %al\n\t"
"cmp $0x34, %al\n\t");
MK_INSN(cmp_test2, "mov $0x34, %al\n\t"
"cmp $0x39, %al\n\t");
MK_INSN(cmp_test3, "mov $0x34, %al\n\t"
"cmp $0x24, %al\n\t");
/* test cmp imm8 with AL */
/* ZF: (bit 6) Zero Flag becomes 1 if an operation results
* in a 0 writeback, or 0 register
*/
exec_in_big_real_mode(&inregs, &outregs,
insn_cmp_test1,
insn_cmp_test1_end - insn_cmp_test1);
if ((outregs.eflags & (1<<6)) != (1<<6))
print_serial("cmp test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_cmp_test2,
insn_cmp_test2_end - insn_cmp_test2);
if ((outregs.eflags & (1<<6)) != 0)
print_serial("cmp test 2: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_cmp_test3,
insn_cmp_test3_end - insn_cmp_test3);
if ((outregs.eflags & (1<<6)) != 0)
print_serial("cmp test 3: FAIL\n");
}
void test_add_imm(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(add_test1, "mov $0x43211234, %eax \n\t"
"add $0x12344321, %eax \n\t");
MK_INSN(add_test2, "mov $0x12, %eax \n\t"
"add $0x21, %al\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_add_test1,
insn_add_test1_end - insn_add_test1);
if (outregs.eax != 0x55555555)
print_serial("add test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_add_test2,
insn_add_test2_end - insn_add_test2);
if (outregs.eax != 0x33)
print_serial("add test 2: FAIL\n");
}
void test_eflags_insn(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(clc, "clc");
MK_INSN(cli, "cli");
MK_INSN(sti, "sti");
MK_INSN(cld, "cld");
MK_INSN(std, "std");
exec_in_big_real_mode(&inregs, &outregs,
insn_clc,
insn_clc_end - insn_clc);
if (outregs.eflags & 1)
print_serial("clc test: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_cli,
insn_cli_end - insn_cli);
if (outregs.eflags & (1 << 9))
print_serial("cli test: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_sti,
insn_sti_end - insn_sti);
if (!(outregs.eflags & (1 << 9)))
print_serial("sti test: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_cld,
insn_cld_end - insn_cld);
if (outregs.eflags & (1 << 10))
print_serial("cld test: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_std,
insn_std_end - insn_std);
if (!(outregs.eflags & (1 << 10)))
print_serial("std test: FAIL\n");
}
void test_io(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(io_test1, "mov $0xff, %al \n\t"
"out %al, $0x10 \n\t"
"in $0x10, %al \n\t");
MK_INSN(io_test2, "mov $0xffff, %ax \n\t"
"out %ax, $0x10 \n\t"
"in $0x10, %ax \n\t");
MK_INSN(io_test3, "mov $0xffffffff, %eax \n\t"
"out %eax, $0x10 \n\t"
"in $0x10, %eax \n\t");
MK_INSN(io_test4, "mov $0x10, %dx \n\t"
"mov $0xff, %al \n\t"
"out %al, %dx \n\t"
"in %dx, %al \n\t");
MK_INSN(io_test5, "mov $0x10, %dx \n\t"
"mov $0xffff, %ax \n\t"
"out %ax, %dx \n\t"
"in %dx, %ax \n\t");
MK_INSN(io_test6, "mov $0x10, %dx \n\t"
"mov $0xffffffff, %eax \n\t"
"out %eax, %dx \n\t"
"in %dx, %eax \n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test1,
insn_io_test1_end - insn_io_test1);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0xff)
print_serial("I/O test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test2,
insn_io_test2_end - insn_io_test2);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0xffff)
print_serial("I/O test 2: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test3,
insn_io_test3_end - insn_io_test3);
if (!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0xffffffff)
print_serial("I/O test 3: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test4,
insn_io_test4_end - insn_io_test4);
if (!regs_equal(&inregs, &outregs, R_AX|R_DX) || outregs.eax != 0xff)
print_serial("I/O test 4: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test5,
insn_io_test5_end - insn_io_test5);
if (!regs_equal(&inregs, &outregs, R_AX|R_DX) || outregs.eax != 0xffff)
print_serial("I/O test 5: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_io_test6,
insn_io_test6_end - insn_io_test6);
if (!regs_equal(&inregs, &outregs, R_AX|R_DX) || outregs.eax != 0xffffffff)
print_serial("I/O test 6: FAIL\n");
}
void test_call(void)
{
struct regs inregs = { 0 }, outregs;
u32 esp[16];
inregs.esp = (u32)esp;
MK_INSN(call1, "mov $test_function, %eax \n\t"
"call *%eax\n\t");
MK_INSN(call_near1, "jmp 2f\n\t"
"1: mov $0x1234, %eax\n\t"
"ret\n\t"
"2: call 1b\t");
MK_INSN(call_near2, "call 1f\n\t"
"jmp 2f\n\t"
"1: mov $0x1234, %eax\n\t"
"ret\n\t"
"2:\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_call1,
insn_call1_end - insn_call1);
if(!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1234)
print_serial("Call Test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_call_near1, insn_call_near1_end - insn_call_near1);
if(!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1234)
print_serial("Call near Test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_call_near2, insn_call_near2_end - insn_call_near2);
if(!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1234)
print_serial("Call near Test 2: FAIL\n");
}
void test_jcc_short(void)
{
struct regs inregs = { 0 }, outregs;
MK_INSN(jnz_short1, "jnz 1f\n\t"
"mov $0x1234, %eax\n\t"
"1:\n\t");
MK_INSN(jnz_short2, "1:\n\t"
"cmp $0x1234, %eax\n\t"
"mov $0x1234, %eax\n\t"
"jnz 1b\n\t");
MK_INSN(jmp_short1, "jmp 1f\n\t"
"mov $0x1234, %eax\n\t"
"1:\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_jnz_short1, insn_jnz_short1_end - insn_jnz_short1);
if(!regs_equal(&inregs, &outregs, 0))
print_serial("JNZ sort Test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_jnz_short2, insn_jnz_short2_end - insn_jnz_short2);
if(!regs_equal(&inregs, &outregs, R_AX) || !(outregs.eflags & (1 << 6)))
print_serial("JNZ sort Test 2: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_jmp_short1, insn_jmp_short1_end - insn_jmp_short1);
if(!regs_equal(&inregs, &outregs, 0))
print_serial("JMP sort Test 1: FAIL\n");
}
void test_jcc_near(void)
{
struct regs inregs = { 0 }, outregs;
/* encode near jmp manually. gas will not do it if offsets < 127 byte */
MK_INSN(jnz_near1, ".byte 0x0f, 0x85, 0x06, 0x00\n\t"
"mov $0x1234, %eax\n\t");
MK_INSN(jnz_near2, "cmp $0x1234, %eax\n\t"
"mov $0x1234, %eax\n\t"
".byte 0x0f, 0x85, 0xf0, 0xff\n\t");
MK_INSN(jmp_near1, ".byte 0xE9, 0x06, 0x00\n\t"
"mov $0x1234, %eax\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_jnz_near1, insn_jnz_near1_end - insn_jnz_near1);
if(!regs_equal(&inregs, &outregs, 0))
print_serial("JNZ near Test 1: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_jnz_near2, insn_jnz_near2_end - insn_jnz_near2);
if(!regs_equal(&inregs, &outregs, R_AX) || !(outregs.eflags & (1 << 6)))
print_serial("JNZ near Test 2: FAIL\n");
exec_in_big_real_mode(&inregs, &outregs,
insn_jmp_near1, insn_jmp_near1_end - insn_jmp_near1);
if(!regs_equal(&inregs, &outregs, 0))
print_serial("JMP near Test 1: FAIL\n");
}
void test_long_jmp()
{
struct regs inregs = { 0 }, outregs;
u32 esp[16];
inregs.esp = (u32)esp;
MK_INSN(long_jmp, "call 1f\n\t"
"jmp 2f\n\t"
"1: jmp $0, $test_function\n\t"
"2:\n\t");
exec_in_big_real_mode(&inregs, &outregs,
insn_long_jmp,
insn_long_jmp_end - insn_long_jmp);
if(!regs_equal(&inregs, &outregs, R_AX) || outregs.eax != 0x1234)
print_serial("Long JMP Test: FAIL\n");
}
void test_null(void)
{
struct regs inregs = { 0 }, outregs;
exec_in_big_real_mode(&inregs, &outregs, 0, 0);
if (!regs_equal(&inregs, &outregs, 0))
print_serial("null test: FAIL\n");
}
void start(void)
{
test_null();
test_shld();
test_mov_imm();
test_cmp_imm();
test_add_imm();
test_io();
test_eflags_insn();
test_jcc_short();
test_jcc_near();
/* test_call() uses short jump so call it after testing jcc */
test_call();
/* long jmp test uses call near so test it after testing call */
test_long_jmp();
exit(0);
}
asm(
".data \n\t"
". = . + 4096 \n\t"
"stacktop: \n\t"
".text \n\t"
"init: \n\t"
"xor %ax, %ax \n\t"
"mov %ax, %ds \n\t"
"mov %ax, %es \n\t"
"mov %ax, %ss \n\t"
"mov $0x4000, %cx \n\t"
"xor %esi, %esi \n\t"
"mov %esi, %edi \n\t"
"rep/addr32/cs/movsl \n\t"
"mov $stacktop, %sp\n\t"
"ljmp $0, $start \n\t"
".pushsection .boot, \"ax\" \n\t"
"ljmp $0xf000, $init \n\t"
".popsection"
);