Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x86 divide-by-zero exception causes nested exception #1883

Open
zouxianyu opened this issue Sep 18, 2023 · 0 comments
Open

x86 divide-by-zero exception causes nested exception #1883

zouxianyu opened this issue Sep 18, 2023 · 0 comments

Comments

@zouxianyu
Copy link

While emulating x86 code, I tried to handle the divide-by-zero exception by setting the UC_HOOK_INTR hook. However, when the hook function returns, unicorn does not consider the exception handled, and when the next exception is encountered, unicorn calls the hook function with a double fault as an argument, and when the exception is encountered for the third time, the simulation execution terminates because of the triple fault.

The following code will reproduce the problem.

#include <unicorn/unicorn.h>

// code to be emulated
#define X86_CODE32 "\xF7\xF1\xF7\xF1\xF7\xF1" // DIV ecx * 3

// memory address where emulation starts
#define ADDRESS 0x1000000

void intr_callback(uc_engine *uc, uint32_t intno, void *user_data) {
    // ignore the exception
    printf("Interrupt %d\n", intno);

    // set EIP to next instruction
    uint32_t eip;
    uc_reg_read(uc, UC_X86_REG_EIP, &eip);
    eip += 2; // div length
    uc_reg_write(uc, UC_X86_REG_EIP, &eip);
}

int main(int argc, char **argv, char **envp)
{
    uc_engine *uc;
    uc_err err;
    int r_eax = 1;
    int r_ecx = 0;
    int r_edx = 0;

    printf("Emulate i386 code\n");

    // Initialize emulator in X86-32bit mode
    err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
    if (err != UC_ERR_OK) {
        printf("Failed on uc_open() with error returned: %u\n", err);
        return -1;
    }

    // map 2MB memory for this emulation
    uc_mem_map(uc, ADDRESS, 2 * 1024 * 1024, UC_PROT_ALL);

    // write machine code to be emulated to memory
    if (uc_mem_write(uc, ADDRESS, X86_CODE32, sizeof(X86_CODE32) - 1)) {
        printf("Failed to write emulation code to memory, quit!\n");
        return -1;
    }

    // initialize machine registers
    uc_reg_write(uc, UC_X86_REG_EAX, &r_eax);
    uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx);
    uc_reg_write(uc, UC_X86_REG_EDX, &r_edx);

    // add hook
    uc_hook intr_registry;
    if (uc_hook_add(uc, &intr_registry, UC_HOOK_INTR, intr_callback, NULL, 1, 0)) {
        printf("Failed to add interrupt hook\n");
        return -1;
    }

    // emulate code in infinite time & unlimited instructions
    err=uc_emu_start(uc, ADDRESS, ADDRESS + sizeof(X86_CODE32) - 1, 0, 0);
    if (err) {
        printf("Failed on uc_emu_start() with error returned %u: %s\n",
               err, uc_strerror(err));
    }

    // delete hook
    uc_hook_del(uc, intr_registry);

    uc_close(uc);

    return 0;
}

Here's the output. 0 indicates a divide-by-zero exception, 8 indicates a double fault, and when the exception is encountered for the third time, a triple fault occurs and the emulation ends, so the third div instruction is not executed.

Emulate i386 code
Interrupt 0
Interrupt 8

I suspect that unicorn doesn't clean up env->old_exception after the hook function returns, and so when a following exception occurs and the check_exception function (in qemu/target/i386/excp_helper.c) is called, it will result in a misclassification as a nested exception. I'm not sure what the special meaning of the exception numbers 0, 10 to 13 in this code is.

/*
 * Check nested exceptions and change to double or triple fault if
 * needed. It should only be called, if this is not an interrupt.
 * Returns the new exception number.
 */
static int check_exception(CPUX86State *env, int intno, int *error_code,
                           uintptr_t retaddr)
{
    int first_contributory = env->old_exception == 0 ||
                              (env->old_exception >= 10 &&
                               env->old_exception <= 13);
    int second_contributory = intno == 0 ||
                               (intno >= 10 && intno <= 13);

    qemu_log_mask(CPU_LOG_INT, "check_exception old: 0x%x new 0x%x\n",
                env->old_exception, intno);

    if (env->old_exception == EXCP08_DBLE) {
        if (env->hflags & HF_GUEST_MASK) {
            cpu_vmexit(env, SVM_EXIT_SHUTDOWN, 0, retaddr); /* does not return */
        }

        qemu_log_mask(CPU_LOG_RESET, "Triple fault\n");

        qemu_system_reset_request(env->uc);
        return EXCP_HLT;
    }

    if ((first_contributory && second_contributory)
        || (env->old_exception == EXCP0E_PAGE &&
            (second_contributory || (intno == EXCP0E_PAGE)))) {
        intno = EXCP08_DBLE;
        *error_code = 0;
    }

    if (second_contributory || (intno == EXCP0E_PAGE) ||
        (intno == EXCP08_DBLE)) {
        env->old_exception = intno;
    }

    return intno;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant