/*ident "@(#)cls4:lib/task/task/fudge.c.386 1.3" */ /******************************************************************************* C++ source for the C++ Language System, Release 3.0. This product is a new release of the original cfront developed in the computer science research center of AT&T Bell Laboratories. Copyright (c) 1993 UNIX System Laboratories, Inc. Copyright (c) 1991, 1992 AT&T and UNIX System Laboratories, Inc. Copyright (c) 1984, 1989, 1990 AT&T. All Rights Reserved. THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE of AT&T and UNIX System Laboratories, Inc. The copyright notice above does not evidence any actual or intended publication of such source code. *******************************************************************************/ #include #include "hw_stack.h" // Intel 386 frame fudger /* careful -- stack frame not self-describing */ // STACK GROWS DOWN // We define a mask in which FrameLayout can record which regs are saved. // We only care about the non-scratch regs edi, esi, ebx. const short REG_EDI = 0x1; const short REG_ESI = 0x2; const short REG_EBX = 0x4; #define ALL_REGS_SAVED(m) ((short)(m) == 0x0007) const int OPCODE_SIZE = 1; const MACHINE_BYTE CALL_INSTR = 0xe8; const int CALL_SIZE = 5; const MACHINE_BYTE JMP_SHORT = 0xeb; const MACHINE_BYTE JMP_DISPL = 0xe9; // assumes we don't need to worry about jump near indirect (0xff) #define IS_JMP_INSTR(instr) \ ((MACHINE_BYTE)(instr) == (MACHINE_BYTE)JMP_SHORT || \ (MACHINE_BYTE)(instr) == (MACHINE_BYTE)JMP_DISPL) const MACHINE_BYTE PUSH_EBP_INSTR = 0x55; const int PUSH_SIZE = 1; const MACHINE_BYTE MOV_INSTR = 0x8b; const int MOV_SIZE = 2; const MACHINE_BYTE SUB_INSTR = 0x83; const int SUB_SIZE = 3; const MACHINE_BYTE PUSH_EDI_INSTR = 0x57; const MACHINE_BYTE PUSH_ESI_INSTR = 0x56; const MACHINE_BYTE PUSH_EBX_INSTR = 0x53; #define IS_PUSH_REG_INSTR(instp) \ ((*((MACHINE_BYTE*)(instp)) & 0xf0) == 0x50) #define IS_SET_EBP_INSTR(instp) \ (((*(instp)) == MOV_INSTR) && (*((MACHINE_BYTE*)(instp) + 1) == 0xec)) #define IS_SUB_ESP_INSTR(instp) \ ((*((MACHINE_BYTE*)(instp)) == SUB_INSTR) \ && (*((MACHINE_BYTE*)(instp) + 1) == 0xec)) int* Skip_pc_p; // global to hold fudged return pc. // See comments in hw_stack.c. /* Given a pointer to a call instruction, yield a pointer to * the called function */ unsigned int call_dst_ptr(MACHINE_BYTE* callp) { /* * On the Intel 386, the call instruction is: call displ * where the displacement is always relative to the pc. */ MACHINE_BYTE* dst_op_p = callp + OPCODE_SIZE; int offset = 0; // bytes are in reverse order, must flip positions offset = dst_op_p[0] | dst_op_p[1] << 8 | dst_op_p[2] << 16 | dst_op_p[3] << 24; return (unsigned int)(callp + CALL_SIZE + offset); } FrameLayout::FrameLayout(int* fp) { mask = 0; unsigned int* return_pc = (unsigned int*)OLD_PC(fp); /* * find the starting address of the function. The idea is that the * instruction immediately before the return address must be the * function call. */ MACHINE_BYTE* callp = (MACHINE_BYTE*)return_pc - CALL_SIZE; if (*callp != CALL_INSTR) object::task_error(E_FUNCS, (object*)0); MACHINE_BYTE* func_addr = (MACHINE_BYTE*) call_dst_ptr(callp); /* If first instruction is jmp (-O not used), add displacement to pc * to get to function prologue. * Function prologue should be: * pushl %ebp * movl %esp,%ebp * subl XXX,%esp / XXX is the offset we want * pushl %eXX / a push instruction for each saved reg * / (sometimes first push is of scratch * / reg, and then that is the offset) * / stop when no more push instructions, or have saved all regs */ if (IS_JMP_INSTR(*func_addr)) { int displ; if (*func_addr == JMP_SHORT) { // 1 byte displacement displ = func_addr[1]; func_addr += 2; // next instruction } else { // JMP_DISPL, 4 byte displacement // bytes reversed displ = func_addr[1] | func_addr[2] << 8 | func_addr[3] << 16 | func_addr[4] << 24; func_addr += 5; // next instruction } func_addr += displ; // jmp destination } if ((*func_addr == PUSH_EBP_INSTR) && (IS_SET_EBP_INSTR(func_addr + PUSH_SIZE))) { func_addr += MOV_SIZE + PUSH_SIZE; if (IS_SUB_ESP_INSTR(func_addr)) { offset = func_addr[2] / 4; func_addr += SUB_SIZE; } else { // assume no automatics to be allocated offset = 0; } } else { object::task_error(E_FUNCS, (object*)0); } while (IS_PUSH_REG_INSTR(func_addr) && (! ALL_REGS_SAVED(mask))) { if (*func_addr == PUSH_EDI_INSTR) { mask |= REG_EDI; func_addr += PUSH_SIZE; } else if (*func_addr == PUSH_ESI_INSTR) { mask |= REG_ESI; func_addr += PUSH_SIZE; } else if (*func_addr == PUSH_EBX_INSTR) { mask |= REG_EBX; func_addr += PUSH_SIZE; } else { // Compiler might push a scratch reg before any non- // scratch regs to reserve space for automatics. if (mask != 0) { // if non-scratch regs already saved, // then this is not an offset break; } else { offset += 1; func_addr += PUSH_SIZE; } } } } /* * Fudge frame of function-defined-by-f_fp (called "f" below) * so that that function returns to its grandparent, * in particular, so a parent task returns to the function that * called the derived constructor, skipping the derived constructor; * the child will return to the derived constructor, which is its "main." * To do this we will overwrite the old fp and pc (those saved by * f) with the old-old ones (those saved by f's caller), * and we will overwrite the register save area with registers saved by * f's caller (referred to as "skip" below). * * There are 2 register-save cases to deal with: * 1. skip_n_saved <= f_n_saved * 3. skip_n_saved > f_n_saved * * These are handled as follows: * 1. copy the saved skip_regs over the corresponding f_regs, * leaving any additional saved f_regs intact. * f's epilogue instructions will be correct. * 2. f's epilogue instructions will restore too few regs, * must take special care to see that the extras are restored properly. * -Copy saved skip_regs over any corresponding f_regs, * -If fudge_return saved more regs than f did, then * copy saved extra saved skip_regs over any corresponding fudge_regs, * -If more extra skip_regs (not saved by either f or fudge_return, * and therefore not used by either) remain, restore them explicitly. * They will not be disturbed by the return from fudge_return or f. */ void task::fudge_return(int* f_fp) { register int* fp = f_fp; // fp of frame-to-be-fudged FrameLayout lo(fp); // frame to be fudged register int* skip_fp = (int*)OLD_FP(fp); // fp for f's caller (skip) FrameLayout skip_lo(skip_fp); // frame for skip register int* fr_fp = FP(); // fp for fudge_return FrameLayout fr_lo(fr_fp); // frame for fudge_return OLD_PC(fp) = (int)&fudge_sp; // task::task will return through // fudge_sp, which will reset the sp // to point at the return-pc in // skip's frame // copy old fp OLD_FP(fp) = OLD_FP(skip_fp); // now copy saved registers // copy any saved skip regs over corresponding f regs; if there isn't // a corresponding f reg, copy over corresponding fudge_return reg; // if there isn't a corresponding fr_reg, restore it explicitly. register int* to = FIRST_SAVED_REG_P(fp, lo.offset); register int* from = FIRST_SAVED_REG_P(skip_fp, skip_lo.offset); register int* fr_to = FIRST_SAVED_REG_P(fr_fp, fr_lo.offset); for (register short m = 1; m != 0x08; m <<=1) { if (m & lo.mask) { if (m & skip_lo.mask) { *to-- = *from--; if (m & fr_lo.mask) fr_to--; } else { // nothing to copy to--; if (m & fr_lo.mask) fr_to--; } } else if (m & skip_lo.mask) { // No slot for *from in f's frame if (m & fr_lo.mask) { // copy to fudge_return's frame *fr_to-- = *from--; } else { // Not used in f or fudge_return; // restore explicitly switch(m) { case REG_EDI: set_edi(from--); break; case REG_ESI: set_esi(from--); break; case REG_EBX: set_ebx(from--); break; default: // Oops--don't expect other regs // to be saved from--; task_error(E_REGMASK, this); break; } } } } }