Detalhes sobre a implementação da instrução CALL / RET no QEMU (x86_64)

1

No arquivo target/i386/translate.c , a instrução CALL (opcode 0xe8) possui esta implementação:

    case 0xe8: /* call im */
    {
        if (dflag != MO_16) {
            tval = (int32_t)insn_get(env, s, MO_32);
        } else {
            tval = (int16_t)insn_get(env, s, MO_16);
        }
        next_eip = s->pc - s->cs_base;
        tval += next_eip;
        if (dflag == MO_16) {
            tval &= 0xffff;
        } else if (!CODE64(s)) {
            tval &= 0xffffffff;
        }
        tcg_gen_movi_tl(cpu_T0, next_eip);
        gen_push_v(s, cpu_T0);
        gen_bnd_jmp(s);
        gen_jmp(s, tval);
    }
    break;

O valor em next_eip é salvo pelas seguintes chamadas:

        tcg_gen_movi_tl(cpu_T0, next_eip);
        gen_push_v(s, cpu_T0);

Mas não consigo encontrar como esse valor ( next_eip ) é usado na implementação RET :

    case 0xc3: /* ret */
    ot = gen_pop_T0(s);
    gen_pop_update(s, ot);
    /* Note that gen_pop_T0 uses a zero-extending load.  */
    gen_op_jmp_v(cpu_T0);
    gen_bnd_jmp(s);
    gen_jr(s, cpu_T0);
    break;

Enquanto procuro a implementação de CALL , vejo o código onde o endereço de retorno é usado:

void tcg_gen_op2(TCGOpcode opc, TCGArg a1, TCGArg a2) { TCGOp *op = tcg_emit_op(opc); // INDEX_op_movi_i64 op->args[0] = a1; // address of register op->args[1] = a2; // **REAL RETURN ADDRESS** }

Mas não consigo encontrar o REAL RETURN ADDRESS ao rastrear a implementação de RET .

Alguém pode me dizer onde exatamente a implementação da instrução RET usa o valor em next_eip .

    
por puzzlestorage 28.06.2018 / 09:12

1 resposta

0

O valor em next_eip é retirado da pilha:

ot = gen_pop_T0(s);

Como efeito colateral, isso atualiza cpu_T0 , que é então usado para o salto; veja a implementação de gen_pop_T0 :

TCGMemOp d_ot = mo_pushpop(s, s->dflag);

gen_lea_v_seg(s, mo_stacksize(s), cpu_regs[R_ESP], R_SS, -1);
gen_op_ld_v(s, d_ot, cpu_T0, cpu_A0);

return d_ot;

É assim que RET recupera o valor que foi enviado por CALL :

tcg_gen_movi_tl(cpu_T0, next_eip);
gen_push_v(s, cpu_T0);

Ao interpretar uma instrução RET , o emulador não pode confiar no conhecimento interno: ele deve se comportar exatamente como a instrução RET , recuperando seu endereço de retorno da pilha. (No código real, há muitos casos de RET s seguindo um JMP com uma pilha configurada manualmente em vez de um CALL ou um CALL nunca resultando em RET ou código alterando o endereço de retorno para o RET alterando o valor na pilha.) Isso é o que a implementação RET do QEMU faz: ele aparece o endereço de retorno da pilha ( gen_pop_T0 ) e processa isso.

    
por 28.06.2018 / 09:20

Tags