From: Julian Seward Date: Thu, 7 Jul 2005 01:32:16 +0000 (+0000) Subject: Basic support for self-checking translations. It fits quite neatly X-Git-Tag: svn/VALGRIND_3_0_1^2~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f5b3cfa9e45e9c3fa7410673fbd4a685efb67553;p=thirdparty%2Fvalgrind.git Basic support for self-checking translations. It fits quite neatly into the IR: if a translation self-check fails, the translation exits passing VEX_TRC_JMP_TINVAL to the despatcher and with the guest_TISTART/guest_TILEN pseudo-registers indicating what area of the guest code needs to be invalidated. The actual checksumming is done by a helper function which does (a variant of) the Adler32 checksum. Space/time overhead, whilst substantial, looks tolerable. There's a little room for optimisation of the basic scheme. It would certainly be viable to run with self-checking for all translations to support Valgrinding JITs (including V itself) without any assistance from the JIT. git-svn-id: svn://svn.valgrind.org/vex/trunk@1262 --- diff --git a/VEX/priv/guest-generic/bb_to_IR.c b/VEX/priv/guest-generic/bb_to_IR.c index d8dfae08fb..2263580441 100644 --- a/VEX/priv/guest-generic/bb_to_IR.c +++ b/VEX/priv/guest-generic/bb_to_IR.c @@ -41,6 +41,10 @@ #include "guest-generic/bb_to_IR.h" +/* Forwards .. */ +static UInt genericg_compute_adler32 ( HWord addr, UInt len ); + + /* Disassemble a complete basic block, starting at guest_IP_start, returning a new IRBB. The disassembler may chase across basic block boundaries if it wishes and if chase_into_ok allows it. @@ -51,6 +55,13 @@ dis_instr_fn is the arch-specific fn to disassemble on function; it is this that does the real work. + + do_self_check indicates that the caller needs a self-checking + translation. + + offB_TIADDR and offB_TILEN are the offsets of guest_TIADDR and + guest_TILEN. Since this routine has to work for any guest state, + without knowing what it is, those offsets have to passed in. */ static Bool const_False ( Addr64 a ) { return False; } @@ -62,7 +73,10 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, /*IN*/ Bool (*chase_into_ok)(Addr64), /*IN*/ Bool host_bigendian, /*IN*/ VexArchInfo* archinfo_guest, - /*IN*/ IRType guest_word_type ) + /*IN*/ IRType guest_word_type, + /*IN*/ Bool do_self_check, + /*IN*/ Int offB_TISTART, + /*IN*/ Int offB_TILEN ) { Long delta; Int i, n_instrs, first_stmt_idx; @@ -71,6 +85,7 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, IRStmt* imark; static Int n_resteers = 0; Int d_resteers = 0; + Int selfcheck_idx = 0; IRBB* irbb; Addr64 guest_IP_curr_instr; @@ -78,9 +93,15 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, debug_print = toBool(vex_traceflags & VEX_TRACE_FE); + /* Note: for adler32 to work without % operation for the self + check, need to limit length of stuff it scans to 5552 bytes. + Therefore limiting the max bb len to 100 insns seems generously + conservative. */ + /* check sanity .. */ + vassert(sizeof(HWord) == sizeof(void*)); vassert(vex_control.guest_max_insns >= 1); - vassert(vex_control.guest_max_insns < 500); + vassert(vex_control.guest_max_insns < 100); vassert(vex_control.guest_chase_thresh >= 0); vassert(vex_control.guest_chase_thresh < vex_control.guest_max_insns); vassert(guest_word_type == Ity_I32 || guest_word_type == Ity_I64); @@ -98,6 +119,17 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, delta = 0; n_instrs = 0; + /* If asked to make a self-checking translation, leave a 3 spaces + in which to put the check statements. We'll fill them in later + when we know the length and adler32 of the area to check. */ + if (do_self_check) { + selfcheck_idx = irbb->stmts_used; + addStmtToIRBB( irbb, IRStmt_NoOp() ); + addStmtToIRBB( irbb, IRStmt_NoOp() ); + addStmtToIRBB( irbb, IRStmt_NoOp() ); + } + + /* Process instructions. */ while (True) { vassert(n_instrs < vex_control.guest_max_insns); @@ -106,6 +138,11 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, resteerOK = toBool( n_instrs < vex_control.guest_chase_thresh + /* If making self-checking translations, don't chase + .. it makes the checks too complicated. We only want + to scan just one sequence of bytes in the check, not + a whole bunch. */ + && !do_self_check /* we can't afford to have a resteer once we're on the last extent slot. */ && vge->n_used < 3 @@ -182,11 +219,11 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, } /* Update the VexGuestExtents we are constructing. */ - /* If vex_control.guest_max_insns is required to be < 500 and - each insn is at max 15 bytes long, this limit of 10000 then + /* If vex_control.guest_max_insns is required to be < 100 and + each insn is at max 20 bytes long, this limit of 5000 then seems reasonable since the max possible extent length will be - 500 * 15 == 7500. */ - vassert(vge->len[vge->n_used-1] < 10000); + 100 * 20 == 2000. */ + vassert(vge->len[vge->n_used-1] < 5000); vge->len[vge->n_used-1] = toUShort(toUInt( vge->len[vge->n_used-1] + dres.len )); n_instrs++; @@ -209,12 +246,12 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, ? IRConst_U32(toUInt(guest_IP_bbstart+delta)) : IRConst_U64(guest_IP_bbstart+delta) ); - return irbb; + goto done; } break; case Dis_StopHere: vassert(irbb->next != NULL); - return irbb; + goto done; case Dis_Resteer: /* Check that we actually allowed a resteer .. */ vassert(resteerOK); @@ -238,9 +275,114 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, vpanic("bb_to_IR"); } } + /*NOTREACHED*/ + vassert(0); + + done: + /* We're done. The only thing that might need attending to is that + a self-checking preamble may need to be created. */ + if (do_self_check) { + + UInt len2check, adler32; + IRConst* guest_IP_bbstart_IRConst; + + vassert(vge->n_used == 1); + len2check = vge->len[0]; + if (len2check == 0) + len2check = 1; + + adler32 = genericg_compute_adler32( (HWord)guest_code, len2check ); + + guest_IP_bbstart_IRConst + = guest_word_type==Ity_I32 + ? IRConst_U32(guest_IP_bbstart) + : IRConst_U64(guest_IP_bbstart); + + /* Set TISTART and TILEN. These will describe to the despatcher + the area of guest code to invalidate should we exit with a + self-check failure. */ + + irbb->stmts[selfcheck_idx+0] + = IRStmt_Put( offB_TILEN, + guest_word_type==Ity_I32 + ? IRExpr_Const(IRConst_U32(len2check)) + : IRExpr_Const(IRConst_U64(len2check)) ); + + irbb->stmts[selfcheck_idx+1] + = IRStmt_Put( offB_TISTART, IRExpr_Const(guest_IP_bbstart_IRConst) ); + + irbb->stmts[selfcheck_idx+2] + = IRStmt_Exit( + IRExpr_Binop( + Iop_CmpNE32, + mkIRExprCCall( + Ity_I32, + 0/*regparms*/, + "genericg_compute_adler32", + &genericg_compute_adler32, + mkIRExprVec_2( + mkIRExpr_HWord( (HWord)guest_code ), + IRExpr_Const(IRConst_U32(len2check)) + ) + ), + IRExpr_Const(IRConst_U32(adler32)) + ), + Ijk_TInval, + guest_IP_bbstart_IRConst + ); + } + + return irbb; } +/*------------------------------------------------------------- + A support routine for doing self-checking translations. + -------------------------------------------------------------*/ + +/* CLEAN HELPER */ +/* CALLED FROM GENERATED CODE */ + +/* Compute the Adler32 checksum of host memory at [addr + .. addr+len-1]. This presumably holds guest code. Note this is + not a proper implementation of Adler32 in that it fails to mod the + counts with 65521 every 5552 bytes, but we really never expect to + get anywhere near that many bytes to deal with. This fn is called + once for every use of a self-checking translation, so it needs to + be as fast as possible. */ +static UInt genericg_compute_adler32 ( HWord addr, UInt len ) +{ + UInt i; + UInt s1 = 1; + UInt s2 = 0; + UChar* buf = (UChar*)addr; + for (i = 0; i < len; i++) { + s1 += (UInt)buf[i]; + s2 += s1; + } +#if 0 + while (len >= 4) { + s1 += buf[0]; + s2 += s1; + s1 += buf[1]; + s2 += s1; + s1 += buf[2]; + s2 += s1; + s1 += buf[3]; + s2 += s1; + buf += 4; + len -= 4; + } + while (len > 0) { + s1 += buf[0]; + s2 += s1; + len--; + buf++; + } +#endif + return (s2 << 16) + s1; +} + /*--------------------------------------------------------------------*/ /*--- end guest-generic/bb_to_IR.c ---*/ diff --git a/VEX/priv/guest-generic/bb_to_IR.h b/VEX/priv/guest-generic/bb_to_IR.h index 3d26938dfd..91a8aae0b5 100644 --- a/VEX/priv/guest-generic/bb_to_IR.h +++ b/VEX/priv/guest-generic/bb_to_IR.h @@ -146,7 +146,10 @@ IRBB* bb_to_IR ( /*OUT*/VexGuestExtents* vge, /*IN*/ Bool (*chase_into_ok)(Addr64), /*IN*/ Bool host_bigendian, /*IN*/ VexArchInfo* archinfo_guest, - /*IN*/ IRType guest_word_type ); + /*IN*/ IRType guest_word_type, + /*IN*/ Bool do_self_check, + /*IN*/ Int offB_TISTART, + /*IN*/ Int offB_TILEN ); #endif /* ndef GENERIC_BB_TO_IR_H */ diff --git a/VEX/priv/host-x86/hdefs.c b/VEX/priv/host-x86/hdefs.c index eb74feb5c4..d2720826ef 100644 --- a/VEX/priv/host-x86/hdefs.c +++ b/VEX/priv/host-x86/hdefs.c @@ -2151,6 +2151,9 @@ Int emit_X86Instr ( UChar* buf, Int nbuf, X86Instr* i ) case Ijk_NoDecode: *p++ = 0xBD; p = emit32(p, VEX_TRC_JMP_NODECODE); break; + case Ijk_TInval: + *p++ = 0xBD; + p = emit32(p, VEX_TRC_JMP_TINVAL); break; case Ijk_Ret: case Ijk_Call: case Ijk_Boring: diff --git a/VEX/priv/main/vex_main.c b/VEX/priv/main/vex_main.c index 52e66b059c..3145608f7d 100644 --- a/VEX/priv/main/vex_main.c +++ b/VEX/priv/main/vex_main.c @@ -221,6 +221,7 @@ VexTranslateResult LibVEX_Translate ( HInstrArray* vcode; HInstrArray* rcode; Int i, j, k, out_used, guest_sizeB; + Int offB_TISTART, offB_TILEN; UChar insn_bytes[32]; IRType guest_word_type; IRType host_word_type; @@ -242,6 +243,8 @@ VexTranslateResult LibVEX_Translate ( disInstrFn = NULL; guest_word_type = Ity_INVALID; host_word_type = Ity_INVALID; + offB_TISTART = 0; + offB_TILEN = 0; vex_traceflags = traceflags; @@ -322,9 +325,13 @@ VexTranslateResult LibVEX_Translate ( guest_sizeB = sizeof(VexGuestX86State); guest_word_type = Ity_I32; guest_layout = &x86guest_layout; + offB_TISTART = offsetof(VexGuestX86State,guest_TISTART); + offB_TILEN = offsetof(VexGuestX86State,guest_TILEN); vassert(archinfo_guest->subarch == VexSubArchX86_sse0 || archinfo_guest->subarch == VexSubArchX86_sse1 || archinfo_guest->subarch == VexSubArchX86_sse2); + vassert(sizeof( ((VexGuestX86State*)0)->guest_TISTART ) == 4); + vassert(sizeof( ((VexGuestX86State*)0)->guest_TILEN ) == 4); break; case VexArchAMD64: @@ -334,7 +341,11 @@ VexTranslateResult LibVEX_Translate ( guest_sizeB = sizeof(VexGuestAMD64State); guest_word_type = Ity_I64; guest_layout = &amd64guest_layout; + offB_TISTART = offsetof(VexGuestAMD64State,guest_TISTART); + offB_TILEN = offsetof(VexGuestAMD64State,guest_TILEN); vassert(archinfo_guest->subarch == VexSubArch_NONE); + vassert(sizeof( ((VexGuestAMD64State*)0)->guest_TISTART ) == 8); + vassert(sizeof( ((VexGuestAMD64State*)0)->guest_TILEN ) == 8); break; case VexArchARM: @@ -344,6 +355,8 @@ VexTranslateResult LibVEX_Translate ( guest_sizeB = sizeof(VexGuestARMState); guest_word_type = Ity_I32; guest_layout = &armGuest_layout; + offB_TISTART = 0; /* hack ... arm has bitrot */ + offB_TILEN = 0; /* hack ... arm has bitrot */ vassert(archinfo_guest->subarch == VexSubArchARM_v4); break; @@ -354,8 +367,12 @@ VexTranslateResult LibVEX_Translate ( guest_sizeB = sizeof(VexGuestPPC32State); guest_word_type = Ity_I32; guest_layout = &ppc32Guest_layout; + offB_TISTART = offsetof(VexGuestPPC32State,guest_TISTART); + offB_TILEN = offsetof(VexGuestPPC32State,guest_TILEN); vassert(archinfo_guest->subarch == VexSubArchPPC32_noAV || archinfo_guest->subarch == VexSubArchPPC32_AV); + vassert(sizeof( ((VexGuestPPC32State*)0)->guest_TISTART ) == 4); + vassert(sizeof( ((VexGuestPPC32State*)0)->guest_TILEN ) == 4); break; default: @@ -382,7 +399,10 @@ VexTranslateResult LibVEX_Translate ( chase_into_ok, host_is_bigendian, archinfo_guest, - guest_word_type ); + guest_word_type, + False/*selfcheck*/, + offB_TISTART, + offB_TILEN ); if (irbb == NULL) { /* Access failure. */ diff --git a/VEX/pub/libvex.h b/VEX/pub/libvex.h index 871da1aad3..eec566a308 100644 --- a/VEX/pub/libvex.h +++ b/VEX/pub/libvex.h @@ -331,9 +331,15 @@ extern void LibVEX_ShowStats ( void ); stack tags will not be as expected, and (2) after returning to generated code, the generated code is likely to go wrong. This really should be fixed. -*/ - + ALL GUEST ARCHITECTURES + ~~~~~~~~~~~~~~~~~~~~~~~ + The architecture must contain two pseudo-registers, guest_TISTART + and guest_TILEN. These are used to pass the address of areas of + guest code, translations of which are to be invalidated, back to + the despatcher. Both pseudo-regs must have size equal to the guest + word size. +*/ #endif /* ndef __LIBVEX_H */ /*---------------------------------------------------------------*/ diff --git a/VEX/pub/libvex_ir.h b/VEX/pub/libvex_ir.h index eeaac93cf6..5e5fbf4022 100644 --- a/VEX/pub/libvex_ir.h +++ b/VEX/pub/libvex_ir.h @@ -778,12 +778,12 @@ extern Bool eqIRAtom ( IRExpr*, IRExpr* ); /* This describes hints which can be passed to the dispatcher at guest control-flow transfer points. - Re Ijk_Invalidate: typically the guest state will have two - pseudo-registers, guest_TISTART and guest_TILEN, which - specify the start and length of the region to be invalidated. - It is the responsibility of the relevant toIR.c to ensure that - these are filled in with suitable values before issuing a jump - of kind Ijk_TInval. + Re Ijk_Invalidate: the guest state _must_ have two + pseudo-registers, guest_TISTART and guest_TILEN, which specify the + start and length of the region to be invalidated. These are both + the size of a guest word. It is the responsibility of the relevant + toIR.c to ensure that these are filled in with suitable values + before issuing a jump of kind Ijk_TInval. */ typedef enum {