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;
}
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.
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.
I suspect that unicorn doesn't clean up
env->old_exception
after the hook function returns, and so when a following exception occurs and thecheck_exception
function (inqemu/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.