------------------------------------------------------------------------------ -- -- -- GNAT COMPILER COMPONENTS -- -- -- -- SYSTEM.MACHINE_STATE_OPERATIONS -- -- -- -- B o d y -- -- (Version for x86) -- -- -- -- Copyright (C) 1999-2004 Ada Core Technologies, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- -- ware Foundation; either version 2, or (at your option) any later ver- -- -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- -- OUT 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 distributed with GNAT; see file COPYING. If not, write -- -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, -- -- MA 02111-1307, USA. -- -- -- -- As a special exception, if other files instantiate generics from this -- -- unit, or you link this unit with other files to produce an executable, -- -- this unit does not by itself cause the resulting executable to be -- -- covered by the GNU General Public License. This exception does not -- -- however invalidate any other reasons why the executable file might be -- -- covered by the GNU Public License. -- -- -- -- GNAT was originally developed by the GNAT team at New York University. -- -- Extensive contributions were provided by Ada Core Technologies Inc. -- -- -- ------------------------------------------------------------------------------ -- Note: it is very important that this unit not generate any exception -- tables of any kind. Otherwise we get a nasty rtsfind recursion problem. -- This means no subprograms, including implicitly generated ones. with Unchecked_Conversion; with System.Storage_Elements; with System.Machine_Code; use System.Machine_Code; with System.Memory; package body System.Machine_State_Operations is function "+" (Left, Right : Address) return Address; pragma Import (Intrinsic, "+"); -- Provide addition operation on type Address (this may not be directly -- available if type System.Address is non-private and the operations on -- the type are made abstract to hide them from public users of System). use System.Exceptions; type Uns8 is mod 2 ** 8; type Uns32 is mod 2 ** 32; type Bits5 is mod 2 ** 5; type Bits6 is mod 2 ** 6; function To_Address is new Unchecked_Conversion (Uns32, Address); type Uns32_Ptr is access all Uns32; function To_Uns32_Ptr is new Unchecked_Conversion (Uns32, Uns32_Ptr); -- Note: the type Uns32 has an alignment of 4. However, in some cases -- values of type Uns32_Ptr will not be aligned (notably in the case -- where we get the immediate field from an instruction). However this -- does not matter in practice, since the x86 does not require that -- operands be aligned. ---------------------- -- General Approach -- ---------------------- -- For the x86 version of this unit, the Subprogram_Info_Type values -- are simply the starting code address for the subprogram. Popping -- of stack frames works by analyzing the code in the prolog, and -- deriving from this analysis the necessary information for restoring -- the registers, including the return point. --------------------------- -- Description of Prolog -- --------------------------- -- If a frame pointer is present, the prolog looks like -- pushl %ebp -- movl %esp,%ebp -- subl $nnn,%esp omitted if nnn = 0 -- pushl %edi omitted if edi not used -- pushl %esi omitted if esi not used -- pushl %ebx omitted if ebx not used -- If a frame pointer is not present, the prolog looks like -- subl $nnn,%esp omitted if nnn = 0 -- pushl %ebp omitted if ebp not used -- pushl %edi omitted if edi not used -- pushl %esi omitted if esi not used -- pushl %ebx omitted if ebx not used -- Note: any or all of the save over call registers may be used and -- if so, will be saved using pushl as shown above. The order of the -- pushl instructions will be as shown above for gcc generated code, -- but the code in this unit does not assume this. ------------------------- -- Description of Call -- ------------------------- -- A call looks like: -- pushl ... push parameters -- pushl ... -- call ... perform the call -- addl $nnn,%esp omitted if no parameters -- Note that we are not absolutely guaranteed that the call is always -- followed by an addl operation that readjusts %esp for this particular -- call. There are two reasons for this: -- 1) The addl can be delayed and combined in the case where more than -- one call appears in sequence. This can be suppressed by using the -- switch -fno-defer-pop and for Ada code, we automatically use -- this switch, but we could still be dealing with C code that was -- compiled without using this switch. -- 2) Scheduling may result in moving the addl instruction away from -- the call. It is not clear if this actually can happen at the -- current time, but it is certainly conceptually possible. -- The addl after the call is important, since we need to be able to -- restore the proper %esp value when we pop the stack. However, we do -- not try to compensate for either of the above effects. As noted above, -- case 1 does not occur for Ada code, and it does not appear in practice -- that case 2 occurs with any significant frequency (we have never seen -- an example so far for gcc generated code). -- Furthermore, it is only in the case of -fomit-frame-pointer that we -- really get into trouble from not properly restoring %esp. If we have -- a frame pointer, then the worst that happens is that %esp is slightly -- more depressed than it should be. This could waste a bit of space on -- the stack, and even in some cases cause a storage leak on the stack, -- but it will not affect the functional correctness of the processing. ---------------------------------------- -- Definitions of Instruction Formats -- ---------------------------------------- type Rcode is (eax, ecx, edx, ebx, esp, ebp, esi, edi); pragma Warnings (Off, Rcode); -- Code indicating which register is referenced in an instruction -- The following define the format of a pushl instruction Op_pushl : constant Bits5 := 2#01010#; type Ins_pushl is record Op : Bits5 := Op_pushl; Reg : Rcode; end record; for Ins_pushl use record Op at 0 range 3 .. 7; Reg at 0 range 0 .. 2; end record; Ins_pushl_ebp : constant Ins_pushl := (Op_pushl, Reg => ebp); type Ins_pushl_Ptr is access all Ins_pushl; -- For the movl %esp,%ebp instruction, we only need to know the length -- because we simply skip past it when we analyze the prolog. Ins_movl_length : constant := 2; -- The following define the format of addl/subl esp instructions Op_Immed : constant Bits6 := 2#100000#; Op2_addl_Immed : constant Bits5 := 2#11100#; pragma Unreferenced (Op2_addl_Immed); Op2_subl_Immed : constant Bits5 := 2#11101#; type Word_Byte is (Word, Byte); pragma Unreferenced (Byte); type Ins_addl_subl_byte is record Op : Bits6; -- Set to Op_Immed w : Word_Byte; -- Word/Byte flag (set to 1 = byte) s : Boolean; -- Sign extension bit (1 = extend) Op2 : Bits5; -- Secondary opcode Reg : Rcode; -- Register Imm8 : Uns8; -- Immediate operand end record; for Ins_addl_subl_byte use record Op at 0 range 2 .. 7; w at 0 range 1 .. 1; s at 0 range 0 .. 0; Op2 at 1 range 3 .. 7; Reg at 1 range 0 .. 2; Imm8 at 2 range 0 .. 7; end record; type Ins_addl_subl_word is record Op : Bits6; -- Set to Op_Immed w : Word_Byte; -- Word/Byte flag (set to 0 = word) s : Boolean; -- Sign extension bit (1 = extend) Op2 : Bits5; -- Secondary opcode Reg : Rcode; -- Register Imm32 : Uns32; -- Immediate operand end record; for Ins_addl_subl_word use record Op at 0 range 2 .. 7; w at 0 range 1 .. 1; s at 0 range 0 .. 0; Op2 at 1 range 3 .. 7; Reg at 1 range 0 .. 2; Imm32 at 2 range 0 .. 31; end record; type Ins_addl_subl_byte_Ptr is access all Ins_addl_subl_byte; type Ins_addl_subl_word_Ptr is access all Ins_addl_subl_word; --------------------- -- Prolog Analysis -- --------------------- -- The analysis of the prolog answers the following questions: -- 1. Is %ebp used as a frame pointer? -- 2. How far is SP depressed (i.e. what is the stack frame size) -- 3. Which registers are saved in the prolog, and in what order -- The following data structure stores the answers to these questions subtype SOC is Rcode range ebx .. edi; -- Possible save over call registers SOC_Max : constant := 4; -- Max number of SOC registers that can be pushed type SOC_Push_Regs_Type is array (1 .. 4) of Rcode; -- Used to hold the register codes of pushed SOC registers type Prolog_Type is record Frame_Reg : Boolean; -- This is set to True if %ebp is used as a frame register, and -- False otherwise (in the False case, %ebp may be saved in the -- usual manner along with the other SOC registers). Frame_Length : Uns32; -- Amount by which ESP is decremented on entry, includes the effects -- of push's of save over call registers as indicated above, e.g. if -- the prolog of a routine is: -- -- pushl %ebp -- movl %esp,%ebp -- subl $424,%esp -- pushl %edi -- pushl %esi -- pushl %ebx -- -- Then the value of Frame_Length would be 436 (424 + 3 * 4). A -- precise definition is that it is: -- -- %esp on entry minus %esp after last SOC push -- -- That definition applies both in the frame pointer present and -- the frame pointer absent cases. Num_SOC_Push : Integer range 0 .. SOC_Max; -- Number of save over call registers actually saved by pushl -- instructions (other than the initial pushl to save the frame -- pointer if a frame pointer is in use). SOC_Push_Regs : SOC_Push_Regs_Type; -- The First Num_SOC_Push entries of this array are used to contain -- the codes for the SOC registers, in the order in which they were -- pushed. Note that this array excludes %ebp if it is used as a frame -- register, since although %ebp is still considered an SOC register -- in this case, it is saved and restored by a separate mechanism. -- Also we will never see %esp represented in this list. Again, it is -- true that %esp is saved over call, but it is restored by a separate -- mechanism. end record; procedure Analyze_Prolog (A : Address; Prolog : out Prolog_Type); -- Given the address of the start of the prolog for a procedure, -- analyze the instructions of the prolog, and set Prolog to contain -- the information obtained from this analysis. ---------------------------------- -- Machine_State_Representation -- ---------------------------------- -- The type Machine_State is defined in the body of Ada.Exceptions as -- a Storage_Array of length 1 .. Machine_State_Length. But really it -- has structure as defined here. We use the structureless declaration -- in Ada.Exceptions to avoid this unit from being implementation -- dependent. The actual definition of Machine_State is as follows: type SOC_Regs_Type is array (SOC) of Uns32; type MState is record eip : Uns32; -- The instruction pointer location (which is the return point -- value from the next level down in all cases). Regs : SOC_Regs_Type; -- Values of the save over call registers end record; for MState use record eip at 0 range 0 .. 31; Regs at 4 range 0 .. 5 * 32 - 1; end record; -- Note: the routines Enter_Handler, and Set_Machine_State reference -- the fields in this structure non-symbolically. type MState_Ptr is access all MState; function To_MState_Ptr is new Unchecked_Conversion (Machine_State, MState_Ptr); ---------------------------- -- Allocate_Machine_State -- ---------------------------- function Allocate_Machine_State return Machine_State is use System.Storage_Elements; begin return Machine_State (Memory.Alloc (MState'Max_Size_In_Storage_Elements)); end Allocate_Machine_State; -------------------- -- Analyze_Prolog -- -------------------- procedure Analyze_Prolog (A : Address; Prolog : out Prolog_Type) is Ptr : Address; Ppl : Ins_pushl_Ptr; Pas : Ins_addl_subl_byte_Ptr; function To_Ins_pushl_Ptr is new Unchecked_Conversion (Address, Ins_pushl_Ptr); function To_Ins_addl_subl_byte_Ptr is new Unchecked_Conversion (Address, Ins_addl_subl_byte_Ptr); function To_Ins_addl_subl_word_Ptr is new Unchecked_Conversion (Address, Ins_addl_subl_word_Ptr); begin Ptr := A; Prolog.Frame_Length := 0; if Ptr = Null_Address then Prolog.Num_SOC_Push := 0; Prolog.Frame_Reg := True; return; end if; if To_Ins_pushl_Ptr (Ptr).all = Ins_pushl_ebp then Ptr := Ptr + 1 + Ins_movl_length; Prolog.Frame_Reg := True; else Prolog.Frame_Reg := False; end if; Pas := To_Ins_addl_subl_byte_Ptr (Ptr); if Pas.Op = Op_Immed and then Pas.Op2 = Op2_subl_Immed and then Pas.Reg = esp then if Pas.w = Word then Prolog.Frame_Length := Prolog.Frame_Length + To_Ins_addl_subl_word_Ptr (Ptr).Imm32; Ptr := Ptr + 6; else Prolog.Frame_Length := Prolog.Frame_Length + Uns32 (Pas.Imm8); Ptr := Ptr + 3; -- Note: we ignore sign extension, since a sign extended -- value that was negative would imply a ludicrous frame size. end if; end if; -- Now scan push instructions for SOC registers Prolog.Num_SOC_Push := 0; loop Ppl := To_Ins_pushl_Ptr (Ptr); if Ppl.Op = Op_pushl and then Ppl.Reg in SOC then Prolog.Num_SOC_Push := Prolog.Num_SOC_Push + 1; Prolog.SOC_Push_Regs (Prolog.Num_SOC_Push) := Ppl.Reg; Prolog.Frame_Length := Prolog.Frame_Length + 4; Ptr := Ptr + 1; else exit; end if; end loop; end Analyze_Prolog; ------------------- -- Enter_Handler -- ------------------- procedure Enter_Handler (M : Machine_State; Handler : Handler_Loc) is begin Asm ("mov %0,%%edx", Inputs => Machine_State'Asm_Input ("r", M)); Asm ("mov %0,%%eax", Inputs => Handler_Loc'Asm_Input ("r", Handler)); Asm ("mov 4(%%edx),%%ebx"); -- M.Regs (ebx) Asm ("mov 12(%%edx),%%ebp"); -- M.Regs (ebp) Asm ("mov 16(%%edx),%%esi"); -- M.Regs (esi) Asm ("mov 20(%%edx),%%edi"); -- M.Regs (edi) Asm ("mov 8(%%edx),%%esp"); -- M.Regs (esp) Asm ("jmp %*%%eax"); end Enter_Handler; ---------------- -- Fetch_Code -- ---------------- function Fetch_Code (Loc : Code_Loc) return Code_Loc is begin return Loc; end Fetch_Code; ------------------------ -- Free_Machine_State -- ------------------------ procedure Free_Machine_State (M : in out Machine_State) is begin Memory.Free (Address (M)); M := Machine_State (Null_Address); end Free_Machine_State; ------------------ -- Get_Code_Loc -- ------------------ function Get_Code_Loc (M : Machine_State) return Code_Loc is Asm_Call_Size : constant := 2; -- Minimum size for a call instruction under ix86. Using the minimum -- size is safe here as the call point computed from the return point -- will always be inside the call instruction. MS : constant MState_Ptr := To_MState_Ptr (M); begin if MS.eip = 0 then return To_Address (MS.eip); else -- When doing a call the return address is pushed to the stack. -- We want to return the call point address, so we subtract -- Asm_Call_Size from the return address. This value is set -- to 5 as an asm call takes 5 bytes on x86 architectures. return To_Address (MS.eip - Asm_Call_Size); end if; end Get_Code_Loc; -------------------------- -- Machine_State_Length -- -------------------------- function Machine_State_Length return System.Storage_Elements.Storage_Offset is begin return MState'Max_Size_In_Storage_Elements; end Machine_State_Length; --------------- -- Pop_Frame -- --------------- procedure Pop_Frame (M : Machine_State; Info : Subprogram_Info_Type) is MS : constant MState_Ptr := To_MState_Ptr (M); PL : Prolog_Type; SOC_Ptr : Uns32; -- Pointer to stack location after last SOC push Rtn_Ptr : Uns32; -- Pointer to stack location containing return address begin Analyze_Prolog (Info, PL); -- Case of frame register, use EBP, safer than ESP if PL.Frame_Reg then SOC_Ptr := MS.Regs (ebp) - PL.Frame_Length; Rtn_Ptr := MS.Regs (ebp) + 4; MS.Regs (ebp) := To_Uns32_Ptr (MS.Regs (ebp)).all; -- No frame pointer, use ESP, and hope we have it exactly right! else SOC_Ptr := MS.Regs (esp); Rtn_Ptr := SOC_Ptr + PL.Frame_Length; end if; -- Get saved values of SOC registers for J in reverse 1 .. PL.Num_SOC_Push loop MS.Regs (PL.SOC_Push_Regs (J)) := To_Uns32_Ptr (SOC_Ptr).all; SOC_Ptr := SOC_Ptr + 4; end loop; MS.eip := To_Uns32_Ptr (Rtn_Ptr).all; MS.Regs (esp) := Rtn_Ptr + 4; end Pop_Frame; ----------------------- -- Set_Machine_State -- ----------------------- procedure Set_Machine_State (M : Machine_State) is N : constant Asm_Output_Operand := No_Output_Operands; begin Asm ("mov %0,%%edx", N, Machine_State'Asm_Input ("r", M)); -- At this stage, we have the following situation (note that we -- are assuming that the -fomit-frame-pointer switch has not been -- used in compiling this procedure. -- (value of M) -- return point -- old ebp <------ current ebp/esp value -- The values of registers ebx/esi/edi are unchanged from entry -- so they have the values we want, and %edx points to the parameter -- value M, so we can store these values directly. Asm ("mov %%ebx,4(%%edx)"); -- M.Regs (ebx) Asm ("mov %%esi,16(%%edx)"); -- M.Regs (esi) Asm ("mov %%edi,20(%%edx)"); -- M.Regs (edi) -- The desired value of ebp is the old value Asm ("mov 0(%%ebp),%%eax"); Asm ("mov %%eax,12(%%edx)"); -- M.Regs (ebp) -- The return point is the desired eip value Asm ("mov 4(%%ebp),%%eax"); Asm ("mov %%eax,(%%edx)"); -- M.eip -- Finally, the desired %esp value is the value at the point of -- call to this routine *before* pushing the parameter value. Asm ("lea 12(%%ebp),%%eax"); Asm ("mov %%eax,8(%%edx)"); -- M.Regs (esp) end Set_Machine_State; ------------------------------ -- Set_Signal_Machine_State -- ------------------------------ procedure Set_Signal_Machine_State (M : Machine_State; Context : System.Address) is pragma Warnings (Off, M); pragma Warnings (Off, Context); begin null; end Set_Signal_Machine_State; end System.Machine_State_Operations;