blob: d0a60005fa0376cc8a60ef6a061f34d5051f4646 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2000-2008 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
/* x86/steal_reg.c */
#include "../globals.h"
#include "arch.h"
#include "instr.h"
#include "instrlist.h"
#include "steal_reg.h"
#include "../fragment.h"
/* N.B.:
* For debugging reg stealing, it's nice to be able to only do reg stealing
* on selected fragments. To support this we allow STEAL_REGISTER to be defined
* even when DCONTEXT_IN_EDI is not.
*/
/* around whole file: */
#ifdef STEAL_REGISTER
/* In the following functions, edi is assumed to be a pointer to the
* dynamo context object. The application's version of edi is kept
* in memory, and we use ebx as a scratch register. We use esi as
* an alternative scratch register. */
/* x86 notes:
* cti instruction reg usage:
* jmp,jcc direct: 'J' = immed only = no regs used
* jmp indirect: 'E' = either single reg or base or base + index
* = max 2 regs
* ret/lret: 'I' = immed only = no regs used
* jcxz and loop*: 'J' + ecx = uses ecx only
* calls are just like jmps
* other instructions:
* string instrs use esi and edi
* rep uses ecx
* xlat uses al, ebx
* wrmsr uses eax, ecx, and edx -- but requires kernel mode
* eax and edx are used for call return values
* (edx for 64-bit values)
*
* Must treat 8-bit registers separately!
* 16-bit versions (e.g., w/ data size prefix) can be treated just
* like 32-bit since in terms of modrm bits DI==EDI and we'll change
* it to use BX/SI/DX just like in 32-bit mode we'd use EBX/ESI/EDX.
*/
/* you must pass in the NEXT instruction!
* restore_state restores state prior to the instruction passed in!
* you can pass in null, in which case restore_state will append
* to ilist.
*/
void
restore_state(dcontext_t *dcontext, instr_t *instr, instrlist_t *ilist)
{
if (ilist->flags == EDI_VAL_IN_MEM)
return;
if (instr != NULL) {
/* insert store if edi's value in ebx doesn't match dcontext->xdi */
if (ilist->flags != EDI_VAL_IN_EBX_AND_MEM) {
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EBX, XDI_OFFSET));
}
/* restore application's value for ebx in ebx */
instrlist_preinsert(ilist, instr,
instr_create_restore_from_dcontext(dcontext, REG_EBX, XBX_OFFSET));
#ifndef DCONTEXT_IN_EDI
/* restore app's edi */
instrlist_preinsert(ilist, instr,
instr_create_restore_from_dcontext(dcontext, REG_EDI, XDI_OFFSET));
#endif
} else {
if (ilist->flags != EDI_VAL_IN_EBX_AND_MEM) {
instrlist_append(ilist,
instr_create_save_to_dcontext(dcontext, REG_EBX, XDI_OFFSET));
}
instrlist_append(ilist,
instr_create_restore_from_dcontext(dcontext, REG_EBX, XBX_OFFSET));
#ifndef DCONTEXT_IN_EDI
/* restore app's edi */
instrlist_append(ilist,
instr_create_restore_from_dcontext(dcontext, REG_EDI, XDI_OFFSET));
#endif
}
ilist->flags = EDI_VAL_IN_MEM;
}
static void
expand_pusha(dcontext_t *dcontext, instr_t *instr, instrlist_t *ilist)
{
/* Convert into the following sequence:
pusha
addl $4, %esp # %esp = %esp + 4
push edi_offset(%edi) # push real edi value onto stack
*/
if (ilist->flags != EDI_VAL_IN_MEM)
restore_state(dcontext, instr, ilist);
#ifndef DCONTEXT_IN_EDI
return;
#endif
/* reverse order! */
/* build and insert push instruction as binary */
instrlist_postinsert(ilist, instr,
instr_create_raw_3bytes(dcontext,
0xff,
0x77,/* %edi + 8-bit offset + /6 */
XDI_OFFSET));
/* build and insert addl instruction as binary */
instrlist_postinsert(ilist, instr,
instr_create_raw_3bytes(dcontext,
0x83,
0xc4, /* %esp + 8-bit immed + /0 */
4));
}
static void
expand_popa(dcontext_t *dcontext, instr_t *instr, instrlist_t *ilist)
{
#ifndef DCONTEXT_IN_EDI
return;
#endif
/* Convert into the following sequence:
movl (%esp), %ebx # get edi value from stack
movl %ebx, edi_offset(%edi) # save it in context
movl %edi, 12(%esp) # squirrel away context ptr in esp location
popa # NOTE: doesn't restore esp from stack
movl -20(%esp), %edi # restore context ptr
*/
instrlist_preinsert(ilist, instr,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_EBX),
OPND_CREATE_MEM32(REG_ESP, 0)));
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EBX, XDI_OFFSET));
instrlist_preinsert(ilist, instr,
INSTR_CREATE_mov_st(dcontext,
OPND_CREATE_MEM32(REG_ESP, 12),
opnd_create_reg(REG_EDI)));
instrlist_postinsert(ilist, instr,
INSTR_CREATE_mov_ld(dcontext, opnd_create_reg(REG_EDI),
OPND_CREATE_MEM32(REG_ESP, -20)));
ilist->flags = EDI_VAL_IN_MEM;
}
/* Handle implicit references to edi. This routine assumes that
* edi is both read and written, that no explicit operands exist
* in the instruction, and that ebx is not used. */
/*
example:
0x00405178 f3 a5 repz movs %ds:%esi,%es:%edi
becomes
0x0142cb94 89 5f 04 mov %ebx,0x4(%edi)
0x0142cb97 89 fb mov %edi,%ebx
0x0142cb99 8b 7b 14 mov 0x14(%ebx),%edi
0x0142cb9c f3 a5 repz movs %ds:%esi,%es:%edi
0x0142cb9e 87 fb xchg %edi,%ebx
0x0142cba0 89 5f 14 mov %ebx,0x14(%edi)
0x0142cba3 8b 5f 04 mov 0x4(%edi),%ebx
*/
static void
use_edi(dcontext_t *dcontext,
instr_t *instr, instrlist_t *ilist)
{
#ifndef DCONTEXT_IN_EDI
/* save cur edi, then bring dcontext into edi */
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EDI, XDI_OFFSET));
instrlist_preinsert(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EDI),
opnd_create_immed_int((int)dcontext,
OPSZ_PTR)));
#endif
if (ilist->flags == EDI_VAL_IN_MEM) {
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EBX, XBX_OFFSET));
instrlist_preinsert(ilist, instr,
XINST_CREATE_move(dcontext, opnd_create_reg(REG_EBX),
opnd_create_reg(REG_EDI));
instrlist_preinsert(ilist, instr,
load_instr(dcontext, REG_EDI, REG_EBX, XDI_OFFSET));
} else {
/* create 'xchg %edi, %ebx' */
instrlist_preinsert(ilist, instr,
INSTR_CREATE_xchg(dcontext, opnd_create_reg(REG_EDI),
opnd_create_reg(REG_EBX)));
}
/* instruction can now use edi */
/* create 'xchg %edi, %ebx' */
instrlist_postinsert(ilist, instr,
INSTR_CREATE_xchg(dcontext, opnd_create_reg(REG_EDI),
opnd_create_reg(REG_EBX)));
ilist->flags = EDI_VAL_IN_EBX;
}
/* An alternative rewriter: Rewrite the modrm byte using esi or edx, since
* ebx is unavailable (i.e., simultaneously used).
*
example, with following instr that uses edi too (but not ebx):
0x0040a492 8a 1f mov (%edi),%bl
0x0040a494 47 inc %edi
becomes:
0x0142faba 89 77 10 mov %esi,0x10(%edi)
0x0142fabd 8b 77 14 mov 0x14(%edi),%esi
0x0142fac0 8a 1e mov (%esi),%bl
0x0142fac2 8b 77 10 mov 0x10(%edi),%esi
0x0142fac5 89 5f 04 mov %ebx,0x4(%edi)
0x0142fac8 8b 5f 14 mov 0x14(%edi),%ebx
0x0142facb 43 inc %ebx
0x0142facc 89 5f 14 mov %ebx,0x14(%edi)
0x0142facf 8b 5f 04 mov 0x4(%edi),%ebx
*/
static void
use_different_reg(dcontext_t *dcontext,
instr_t *instr, instrlist_t *ilist, int reg, int offs,
bool read, bool write)
{
if (ilist->flags != EDI_VAL_IN_MEM)
restore_state(dcontext, instr, ilist); /* cannot use ebx */
#ifndef DCONTEXT_IN_EDI
/* save cur edi, then bring shared_dcontext into edi */
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EDI, XDI_OFFSET));
instrlist_preinsert(ilist, instr,
INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_EDI),
opnd_create_immed_int((int)dcontext,
OPSZ_PTR)));
#endif
/* save current reg */
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, reg, offs));
if (read) {
/* bring in current value of edi */
instrlist_preinsert(ilist, instr,
instr_create_restore_from_dcontext(dcontext, reg, XDI_OFFSET));
}
/* instr goes here */
/* add to ilist in reverse order */
#ifndef DCONTEXT_IN_EDI
/* restore app's edi */
instrlist_postinsert(ilist, instr,
load_abs_instr(dcontext, REG_EDI,
((int)(&shared_dcontext)) + XDI_OFFSET));
#endif
/* restore previous value of reg */
instrlist_postinsert(ilist, instr,
instr_create_restore_from_dcontext(dcontext, reg, offs));
if (write) {
/* copy value of edi back into memory location */
instrlist_postinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, reg, XDI_OFFSET));
}
}
/*###########################################################################
*
* Here are a bunch of (old) examples
* Note that the current code will put in a direct memory reference when
* it can, so push %edi => push 0x14(%edi)
*/
/* Instruction directly reads and writes edi, e.g. INC %edi.
Register specifier is encoded directly in opcode byte. */
/*
example:
0x00405d2a 47 inc %edi
becomes:
0x0142b8c9 89 5f 04 mov %ebx,0x4(%edi)
0x0142b8cc 8b 5f 14 mov 0x14(%edi),%ebx
0x0142b8cf 43 inc %ebx
0x0142b8d0 89 5f 14 mov %ebx,0x14(%edi)
0x0142b8d3 8b 5f 04 mov 0x4(%edi),%ebx
example, with prev instr that uses edi:
0x00409e49 0f b6 37 movzx (%edi),%esi
0x00409e4c 47 inc %edi
becomes:
0x0142fe80 89 5f 04 mov %ebx,0x4(%edi)
0x0142fe83 8b 5f 14 mov 0x14(%edi),%ebx
0x0142fe86 0f b6 33 movzx (%ebx),%esi
0x0142fe89 43 inc %ebx
0x0142fe8a 89 5f 14 mov %ebx,0x14(%edi)
0x0142fe8d 8b 5f 04 mov 0x4(%edi),%ebx
*/
/* Instruction directly reads edi, e.g. PUSH %edi.
Register specifier is encoded directly in opcode byte. */
/*
example:
0x00409d60 57 push %edi
becomes:
0x0142fbd1 89 5f 04 mov %ebx,0x4(%edi)
0x0142fbd4 8b 5f 14 mov 0x14(%edi),%ebx
0x0142fbd7 53 push %ebx
0x0142fbd8 8b 5f 04 mov 0x4(%edi),%ebx
example, with following instr that uses edi as well:
0x00409e16 57 push %edi
0x00409e17 8b 7c 24 14 mov 0x14(%esp),%edi
becomes:
0x0142ff15 89 5f 04 mov %ebx,0x4(%edi)
0x0142ff18 8b 5f 14 mov 0x14(%edi),%ebx
0x0142ff1b 53 push %ebx
0x0142ff1c 8b 5c 24 14 mov 0x14(%esp),%ebx
0x0142ff20 89 5f 14 mov %ebx,0x14(%edi)
0x0142ff23 8b 5f 04 mov 0x4(%edi),%ebx
*/
/* Instruction directly writes edi, e.g. POP %edi.
Register specifier is encoded directly in opcode byte. */
/*
example:
0x00409e99 5f pop %edi
becomes:
0x0142fce0 89 5f 04 mov %ebx,0x4(%edi)
0x0142fce3 5b pop %ebx
0x0142fce4 89 5f 14 mov %ebx,0x14(%edi)
0x0142fce7 8b 5f 04 mov 0x4(%edi),%ebx
*/
/*
example:
0x00409e33 0f b6 07 movzx (%edi),%eax
becomes:
0x0142fec0 89 5f 04 mov %ebx,0x4(%edi)
0x0142fec3 8b 5f 14 mov 0x14(%edi),%ebx
0x0142fec6 0f b6 03 movzx (%ebx),%eax
0x0142fec9 8b 5f 04 mov 0x4(%edi),%ebx
new example:
0x00403de3 8b 3e mov (%esi) -> %edi
0x00403de5 2b f8 sub %eax %edi -> %edi
becomes:
0x0536e663 89 5f 04 mov %ebx -> 0x4(%edi)
0x0536e666 8b 1e mov (%esi) -> %ebx
0x0536e668 2b d8 sub %eax %ebx -> %ebx
0x0536e66a 89 5f 14 mov %ebx -> 0x14(%edi)
0x0536e66d 8b 5f 04 mov 0x4(%edi) -> %ebx
here's a good example of reg steal + indirect jmp mangling:
0x77f831ff ff 24 bd c0 32 f8 77 jmp 0x77f832c0(,%edi,4)
becomes:
0x052de580 89 57 0c mov %edx -> 0xc(%edi)
0x052de583 89 5f 04 mov %ebx -> 0x4(%edi)
0x052de586 8b 5f 14 mov 0x14(%edi) -> %ebx
0x052de589 8b 14 bd c0 32 f8 77 mov 0x77f832c0(,%edi,4) -> %edx
0x052de590 8b 5f 04 mov 0x4(%edi) -> %ebx
0x052de593 e9 00 00 00 00 jmp 0x52de598 <exit stub 0>
*/
/*
* end of examples
*
*###########################################################################*/
static void
use_ebx(dcontext_t *dcontext,
instr_t *instr, instrlist_t *ilist, bool read, bool write)
{
if (ilist->flags == EDI_VAL_IN_MEM) {
#ifndef DCONTEXT_IN_EDI
/* save cur edi, then bring shared_dcontext into edi */
instrlist_preinsert(ilist, instr,
store_abs_instr(dcontext, REG_EDI,
((int)(&shared_dcontext)) + XDI_OFFSET));
instrlist_preinsert(ilist, instr,
move_immed_instr(dcontext, (int)&shared_dcontext, REG_EDI));
#endif
/* save current ebx */
instrlist_preinsert(ilist, instr,
instr_create_save_to_dcontext(dcontext, REG_EBX, XBX_OFFSET));
if (read) {
/* bring value of edi into ebx */
instrlist_preinsert(ilist, instr,
instr_create_restore_from_dcontext(dcontext, REG_EBX, XDI_OFFSET));
ilist->flags = EDI_VAL_IN_EBX_AND_MEM;
}
}
/* instr goes here */
if (write) {
/* record that memory location of edi is stale */
ilist->flags = EDI_VAL_IN_EBX;
}
}
void
steal_reg(dcontext_t *dcontext, instr_t *instr, instrlist_t *ilist)
{
opnd_t * uses[5];
#ifdef DCONTEXT_IN_EDI
opnd_t save[5];
#endif
int i, num_uses;
bool writes, reads;
#ifndef DCONTEXT_IN_EDI
#if 0
i = GLOBAL_STAT(num_fragments);
/* use this to narrow location of reg steal bug */
if (i < 0 || i > 1000000)
return;
#endif
#endif
/* special cases */
switch (instr_get_opcode(instr)) {
case OP_pusha:
expand_pusha(dcontext, instr, ilist);
return;
case OP_popa:
expand_popa(dcontext, instr, ilist);
return;
case OP_ins: case OP_rep_ins:
case OP_outs: case OP_rep_outs:
case OP_movs: case OP_rep_movs:
case OP_stos: case OP_rep_stos:
case OP_lods: case OP_rep_lods:
case OP_cmps: case OP_rep_cmps: case OP_repne_cmps:
case OP_scas: case OP_rep_scas: case OP_repne_scas:
/* these all use edi implicitly, there's no way around it */
use_edi(dcontext, instr, ilist);
return;
}
if (!instr_uses_reg(instr, REG_EDI) && !uses_reg(instr, REG_DI)) {
if (ilist->flags != EDI_VAL_IN_MEM)
restore_state(dcontext, instr, ilist);
return;
}
/* if we get to here we know we're going to change the operands,
* possibly by directly writing to operand bytes, so we have to explicitly
* set original bits invalid:
*/
instr_set_raw_bits_valid(instr, false);
/* gather edi usage info */
num_uses = 0;
writes = reads = false;
for (i=0; i<instr_num_dsts(instr); i++) {
if (opnd_uses_reg(instr_get_dst(instr, i), REG_EDI) ||
opnd_uses_reg(instr_get_dst(instr, i), REG_DI)) {
/* FIXME: this code was written back when accessed instr fields
* directly...need to fix this up
*/
#error FIXME -- OLD CODE
uses[num_uses++] = &instr->dsts[i];
if (opnd_is_memory_reference(instr_get_dst(instr, i)))
reads = true;
else if (opnd_uses_reg(instr_get_dst(instr, i), REG_DI)) {
reads = true; /* need to copy in upper 16 bits of edi */
writes = true;
} else
writes = true;
}
}
for (i=0; i<instr_num_src(instr); i++) {
if (opnd_uses_reg(instr_get_src(instr, i), REG_EDI) ||
opnd_uses_reg(instr_get_src(instr, i), REG_DI)) {
/* FIXME: this code was written back when accessed instr fields
* directly...need to fix this up
*/
#error FIXME -- OLD CODE
if (i == 0)
uses[num_uses++] = &instr->src0;
else
uses[num_uses++] = &instr->srcs[i];
reads = true;
}
}
ASSERT(num_uses > 0);
/* Try replacing register edi with direct memory access
* FIXME: try to use memory access to replace di?
* need to select 16-bit data size, + 16 bit offset from 0x14(%edi)?
* no 16-bit offset b/c of little-endian-ness!
*/
#ifdef DCONTEXT_IN_EDI
if (ilist->flags == EDI_VAL_IN_MEM) {
for (i=0; i<num_uses; i++) {
save[i] = *uses[i];
if (opnd_is_reg(save[i]) && opnd_get_reg(save[i]) == REG_EDI) {
*uses[i] = opnd_create_base_disp(REG_EDI, REG_NULL, 0, XDI_OFFSET,
reg_get_size(REG_EDI));
} else {
/* give up */
int j;
for (j=0; j<i; j++)
*uses[j] = save[j];
i = 0;
break;
}
}
if (i > 0) { /* we didn't give up */
/* switch to store if currently reg-reg "load" */
if (instr_get_opcode(instr) == OP_mov_ld && num_uses == 1 && writes) {
instr_set_opcode(instr, OP_mov_st);
i = 0;
}
if (instr_is_encoding_possible(instr)) {
/* It worked, we're done */
LOG(THREAD, LOG_INTERP, 3,
"*** Successfully used memory to replace edi!\n");
return;
}
/* Else, restore */
if (i == 0) /* flag saying "changed OP_mov_ld to OP_mov_st" */
instr_set_opcode(instr, OP_mov_ld);
for (i=0; i<num_uses; i++) {
*uses[i] = save[i];
}
}
}
#endif
if (!instr_uses_reg(instr, REG_EBX) && !instr_uses_reg(instr, REG_BX) &&
!instr_uses_reg(instr, REG_BL) && !instr_uses_reg(instr, REG_BH)) {
/* use ebx */
use_ebx(dcontext, instr, ilist, reads, writes);
for (i=0; i<num_uses; i++) {
opnd_replace_reg(uses[i], REG_EDI, REG_EBX);
opnd_replace_reg(uses[i], REG_DI, REG_BX);
}
}
else if (!instr_uses_reg(instr, REG_ESI) && !instr_uses_reg(instr, REG_SI)) {
/* use esi */
use_different_reg(dcontext, instr, ilist, REG_ESI, XSI_OFFSET, reads, writes);
for (i=0; i<num_uses; i++) {
opnd_replace_reg(uses[i], REG_EDI, REG_ESI);
opnd_replace_reg(uses[i], REG_DI, REG_SI);
}
}
else {
ASSERT(!instr_uses_reg(instr, REG_EDX) && !instr_uses_reg(instr, REG_DX) &&
!instr_uses_reg(instr, REG_DL) && !instr_uses_reg(instr, REG_DH));
/* use edx */
use_different_reg(dcontext, instr, ilist, REG_EDX, XDX_OFFSET, reads, writes);
for (i=0; i<num_uses; i++) {
opnd_replace_reg(uses[i], REG_EDI, REG_EDX);
opnd_replace_reg(uses[i], REG_DI, REG_DX);
}
}
}
/*
sequence to study for better stealing:
0x77fca2da 0f b7 38 movzx (%eax) -> %edi
0x77fca2dd 2b fb sub %ebx %edi -> %edi
0x77fca2df 89 7d a8 mov %edi -> 0xffffffa8(%ebp)
becomes:
0x0265e0cd 89 5f 04 mov %ebx -> 0x4(%edi)
0x0265e0d0 0f b7 18 movzx (%eax) -> %ebx
0x0265e0d3 89 5f 14 mov %ebx -> 0x14(%edi)
0x0265e0d6 8b 5f 04 mov 0x4(%edi) -> %ebx
0x0265e0d9 89 77 10 mov %esi -> 0x10(%edi)
0x0265e0dc 8b 77 14 mov 0x14(%edi) -> %esi
0x0265e0df 2b f3 sub %ebx %esi -> %esi
0x0265e0e1 89 77 14 mov %esi -> 0x14(%edi)
0x0265e0e4 8b 77 10 mov 0x10(%edi) -> %esi
0x0265e0e7 89 5f 04 mov %ebx -> 0x4(%edi)
0x0265e0ea 8b 5f 14 mov 0x14(%edi) -> %ebx
0x0265e0ed 89 5d a8 mov %ebx -> 0xffffffa8(%ebp)
0x0265e0f0 8b 5f 04 mov 0x4(%edi) -> %ebx
*/
#endif /* STEAL_REGISTER (around whole file) */