| |
| ##--------------------------------------------------------------------## |
| ##--- The core dispatch loop, for jumping to a code address. ---## |
| ##--- vg_dispatch.S ---## |
| ##--------------------------------------------------------------------## |
| |
| /* |
| This file is part of Valgrind, an x86 protected-mode emulator |
| designed for debugging and profiling binaries on x86-Unixes. |
| |
| Copyright (C) 2000-2002 Julian Seward |
| jseward@acm.org |
| Julian_Seward@muraroa.demon.co.uk |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License as |
| published by the Free Software Foundation; either version 2 of the |
| License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307, USA. |
| |
| The GNU General Public License is contained in the file LICENSE. |
| */ |
| |
| #include "vg_constants.h" |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- The normal-case dispatch machinery. ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* To transfer to an (original) code address, load it into %eax and |
| jump to vg_dispatch. This fragment of code tries to find the |
| address of the corresponding translation by searching the translation |
| table. If it fails, a new translation is made, added to the |
| translation table, and then jumped to. Almost all the hard |
| work is done by C routines; this code simply handles the |
| common case fast -- when the translation address is found in |
| the translation cache. |
| |
| At entry, %eax is the only live (real-machine) register; the |
| entire simulated state is tidily saved in vg_m_state. |
| */ |
| |
| |
| /* The C world needs a way to get started simulating. So we provide |
| a function void vg_run_innerloop ( void ), which starts running |
| from vg_m_eip, and exits when the counter reaches zero. This loop |
| can also exit if vg_oursignalhandler() catches a non-resumable |
| signal, for example SIGSEGV. It then longjmp()s back past here. |
| */ |
| |
| .globl VG_(run_innerloop) |
| VG_(run_innerloop): |
| #OYNK(1000) |
| # ----- entry point to VG_(run_innerloop) ----- |
| pushal |
| # Set up the baseBlock pointer |
| movl $VG_(baseBlock), %ebp |
| |
| # fetch m_eip into %eax |
| movl VGOFF_(m_eip), %esi |
| movl (%ebp, %esi, 4), %eax |
| |
| # fall thru to vg_dispatch |
| |
| .globl VG_(dispatch) |
| VG_(dispatch): |
| # %eax holds destination (original) address |
| # To signal any kind of interruption, set vg_dispatch_ctr |
| # to 1, and vg_interrupt_reason to the appropriate value |
| # before jumping here. |
| |
| # %ebp indicates further details of the control transfer |
| # requested to the address in %eax. The idea is that we |
| # want to check all jump targets to see if they are either |
| # VG_(signalreturn_bogusRA) or VG_(trap_here), both of which |
| # require special treatment. However, testing all branch |
| # targets is expensive, and anyway in most cases JITter knows |
| # that a jump cannot be to either of these two. We therefore |
| # adopt the following trick. |
| # |
| # If ebp == & VG_(baseBlock), which is what it started out as, |
| # this is a jump for which the JITter knows no check need be |
| # made. |
| # |
| # If it is ebp == VG_EBP_DISPATCH_CHECKED, we had better make |
| # the check. |
| # |
| # If %ebp has any other value, we panic. |
| # |
| # What the JITter assumes is that VG_(signalreturn_bogusRA) can |
| # only be arrived at from an x86 ret insn, and dually that |
| # VG_(trap_here) can only be arrived at from an x86 call insn. |
| # The net effect is that all call and return targets are checked |
| # but straightforward jumps are not. |
| # |
| # Thinks ... is this safe if the client happens to tailcall |
| # VG_(trap_here) ? I dont think that can happen -- if it did |
| # it would be a problem. |
| # |
| cmpl $VG_(baseBlock), %ebp |
| jnz dispatch_checked_maybe |
| |
| dispatch_unchecked: |
| # save the jump address at VG_(baseBlock)[VGOFF_(m_eip)], |
| # so that if this block takes a fault, we later know where we were. |
| movl VGOFF_(m_eip), %esi |
| movl %eax, (%ebp, %esi, 4) |
| |
| # do we require attention? |
| # this check has to be after the call/ret transfer checks, because |
| # we have to ensure that any control transfer following a syscall |
| # return is an ordinary transfer. By the time we get here, we have |
| # established that the next transfer, which might get delayed till |
| # after a syscall return, is an ordinary one. |
| # All a bit subtle ... |
| #OYNK(1001) |
| decl VG_(dispatch_ctr) |
| jz counter_is_zero |
| |
| #OYNK(1002) |
| # try a fast lookup in the translation cache |
| movl %eax, %ebx |
| andl $VG_TT_FAST_MASK, %ebx |
| # ebx = tt_fast index |
| movl VG_(tt_fast)(,%ebx,4), %ebx |
| # ebx points at a tt entry |
| # now compare target with the tte.orig_addr field (+0) |
| cmpl %eax, (%ebx) |
| jnz full_search |
| # Found a match. Set the tte.mru_epoch field (+8) |
| # and call the tte.trans_addr field (+4) |
| movl VG_(current_epoch), %ecx |
| movl %ecx, 8(%ebx) |
| call *4(%ebx) |
| jmp VG_(dispatch) |
| |
| full_search: |
| #no luck? try the full table search |
| pushl %eax |
| call VG_(search_transtab) |
| addl $4, %esp |
| |
| # %eax has trans addr or zero |
| cmpl $0, %eax |
| jz need_translation |
| # full table search also zeroes the tte.last_use field, |
| # so we dont have to do so here. |
| call *%eax |
| jmp VG_(dispatch) |
| |
| need_translation: |
| OYNK(1003) |
| movl $VG_Y_TRANSLATE, VG_(interrupt_reason) |
| counter_is_zero: |
| OYNK(1004) |
| popal |
| # ----- (the only) exit point from VG_(run_innerloop) ----- |
| # ----- unless of course vg_oursignalhandler longjmp()s |
| # ----- back through it, due to an unmanagable signal |
| ret |
| |
| |
| /* The normal way to get back to the translation loop is to put |
| the address of the next (original) address and return. |
| However, simulation of a RET insn requires a check as to whether |
| the next address is vg_signalreturn_bogusRA. If so, a signal |
| handler is returning, so we need to invoke our own mechanism to |
| deal with that, by calling vg_signal_returns(). This restores |
| the simulated machine state from the VgSigContext structure on |
| the stack, including the (simulated, of course) %eip saved when |
| the signal was delivered. We then arrange to jump to the |
| restored %eip. |
| */ |
| dispatch_checked_maybe: |
| # Possibly a checked dispatch. Sanity check ... |
| cmpl $VG_EBP_DISPATCH_CHECKED, %ebp |
| jz dispatch_checked |
| # ebp has an invalid value ... crap out. |
| pushl $panic_msg_ebp |
| call VG_(panic) |
| # (never returns) |
| |
| dispatch_checked: |
| OYNK(2000) |
| # first off, restore %ebp -- since it is currently wrong |
| movl $VG_(baseBlock), %ebp |
| |
| # see if we need to mess with stack blocks |
| pushl %ebp |
| pushl %eax |
| call VG_(delete_client_stack_blocks_following_ESP_change) |
| popl %eax |
| popl %ebp |
| |
| # is this a signal return? |
| cmpl $VG_(signalreturn_bogusRA), %eax |
| jz dispatch_to_signalreturn_bogusRA |
| # should we intercept this call? |
| cmpl $VG_(trap_here), %eax |
| jz dispatch_to_trap_here |
| # ok, its not interesting. Handle the normal way. |
| jmp dispatch_unchecked |
| |
| dispatch_to_signalreturn_bogusRA: |
| OYNK(2001) |
| pushal |
| call VG_(signal_returns) |
| popal |
| # %EIP will now point to the insn which should have followed |
| # the signal delivery. Jump to it. Since we no longer have any |
| # hint from the JITter about whether or not it is checkable, |
| # go via the conservative route. |
| movl VGOFF_(m_eip), %esi |
| movl (%ebp, %esi, 4), %eax |
| jmp dispatch_checked |
| |
| |
| /* Similarly, check CALL targets to see if it is the ultra-magical |
| vg_trap_here(), and, if so, act accordingly. See vg_clientmalloc.c. |
| Be careful not to get the real and simulated CPUs, |
| stacks and regs mixed up ... |
| */ |
| dispatch_to_trap_here: |
| OYNK(111) |
| /* Considering the params to vg_trap_here(), we should have: |
| 12(%ESP) is what_to_do |
| 8(%ESP) is arg2 |
| 4(%ESP) is arg1 |
| 0(%ESP) is return address |
| */ |
| movl VGOFF_(m_esp), %esi |
| movl (%ebp, %esi, 4), %ebx |
| # %ebx now holds simulated %ESP |
| cmpl $0x4000, 12(%ebx) |
| jz handle_malloc |
| cmpl $0x4001, 12(%ebx) |
| jz handle_malloc |
| cmpl $0x4002, 12(%ebx) |
| jz handle_malloc |
| cmpl $0x5000, 12(%ebx) |
| jz handle_free |
| cmpl $0x5001, 12(%ebx) |
| jz handle_free |
| cmpl $0x5002, 12(%ebx) |
| jz handle_free |
| cmpl $6666, 12(%ebx) |
| jz handle_calloc |
| cmpl $7777, 12(%ebx) |
| jz handle_realloc |
| cmpl $8888, 12(%ebx) |
| jz handle_memalign |
| push $panic_msg_trap |
| call VG_(panic) |
| # vg_panic never returns |
| |
| handle_malloc: |
| # %ESP is in %ebx |
| pushl 12(%ebx) |
| pushl 8(%ebx) |
| call VG_(client_malloc) |
| addl $8, %esp |
| # returned value is in %eax |
| jmp save_eax_and_simulate_RET |
| |
| handle_free: |
| # %ESP is in %ebx |
| pushl 12(%ebx) |
| pushl 8(%ebx) |
| call VG_(client_free) |
| addl $8, %esp |
| jmp simulate_RET |
| |
| handle_calloc: |
| # %ESP is in %ebx |
| pushl 8(%ebx) |
| pushl 4(%ebx) |
| call VG_(client_calloc) |
| addl $8, %esp |
| # returned value is in %eax |
| jmp save_eax_and_simulate_RET |
| |
| handle_realloc: |
| # %ESP is in %ebx |
| pushl 8(%ebx) |
| pushl 4(%ebx) |
| call VG_(client_realloc) |
| addl $8, %esp |
| # returned value is in %eax |
| jmp save_eax_and_simulate_RET |
| |
| handle_memalign: |
| # %ESP is in %ebx |
| pushl 8(%ebx) |
| pushl 4(%ebx) |
| call VG_(client_memalign) |
| addl $8, %esp |
| # returned value is in %eax |
| jmp save_eax_and_simulate_RET |
| |
| save_eax_and_simulate_RET: |
| movl VGOFF_(m_eax), %esi |
| movl %eax, (%ebp, %esi, 4) # %eax -> %EAX |
| # set %EAX bits to VALID |
| movl VGOFF_(sh_eax), %esi |
| movl $0x0 /* All 32 bits VALID */, (%ebp, %esi, 4) |
| # fall thru ... |
| simulate_RET: |
| # standard return |
| movl VGOFF_(m_esp), %esi |
| movl (%ebp, %esi, 4), %ebx # %ESP -> %ebx |
| movl 0(%ebx), %eax # RA -> %eax |
| addl $4, %ebx # %ESP += 4 |
| movl %ebx, (%ebp, %esi, 4) # %ebx -> %ESP |
| jmp dispatch_checked # jump to %eax |
| |
| .data |
| panic_msg_trap: |
| .ascii "dispatch_to_trap_here: unknown what_to_do" |
| .byte 0 |
| panic_msg_ebp: |
| .ascii "vg_dispatch: %ebp has invalid value!" |
| .byte 0 |
| .text |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- A helper for delivering signals when the client is ---*/ |
| /*--- (presumably) blocked in a system call. ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* Returns, in %eax, the next orig_addr to run. |
| The caller needs to decide whether the returned orig_addr |
| requires special handling. |
| |
| extern Addr VG_(run_singleton_translation) ( Addr trans_addr ) |
| */ |
| |
| /* should we take care to save the FPU state here? */ |
| |
| .globl VG_(run_singleton_translation) |
| VG_(run_singleton_translation): |
| movl 4(%esp), %eax # eax = trans_addr |
| pushl %ebx |
| pushl %ecx |
| pushl %edx |
| pushl %esi |
| pushl %edi |
| pushl %ebp |
| |
| # set up ebp correctly for translations |
| movl $VG_(baseBlock), %ebp |
| |
| # run the translation |
| call *%eax |
| |
| # next orig_addr is correctly in %eax already |
| |
| popl %ebp |
| popl %edi |
| popl %esi |
| popl %edx |
| popl %ecx |
| popl %ebx |
| |
| ret |
| |
| ##--------------------------------------------------------------------## |
| ##--- end vg_dispatch.S ---## |
| ##--------------------------------------------------------------------## |