fpc/compiler/x86_64/win64unw.pas
svenbarth 2b59000d56 + implement compiler support for SEH on Win64
Note: due to the way we access variables in a nested function (which in this case includes exception filters) we can not extract the finally handlers and call them (like we do on i386 and x86_64, but instead we duplicate the finally code)

git-svn-id: trunk@44941 -
2020-04-21 06:06:05 +00:00

413 lines
11 KiB
ObjectPascal

{
Copyright (c) 2011 by Free Pascal development team
Support for win64 unwind data
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., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************
}
unit win64unw;
{$i fpcdefs.inc}
interface
uses
cclasses,globtype,aasmbase,aasmdata,aasmtai,cgbase,ogbase;
type
TWin64CFI=class
private
FFrameOffs, FFrameReg: Integer;
FFlags: Integer;
FHandler: TObjSymbol;
FCount: Integer;
FElements:TLinkedList;
FFrameStartSym:TObjSymbol;
FFrameStartSec:TObjSection;
FXdataSym:TObjSymbol;
FXdataSec:TObjSection;
FPrologueEndPos:aword;
FPrologueEndSeen:Boolean;
FName: pshortstring;
procedure AddElement(objdata:TObjData;aCode,aInfo:Integer;aOffs:dword);
public
constructor create;
destructor destroy;override;
procedure generate_prologue_data(objdata:TObjData);
procedure start_frame(objdata:TObjData; const name: string);
procedure end_frame(objdata:TObjData);
procedure end_prologue(objdata:TObjData);
procedure push_reg(objdata:TObjData;reg:tregister);
procedure save_reg(objdata:TObjData;reg:tregister;ofs:dword);
procedure save_xmm(objdata:TObjData;reg:tregister;ofs:dword);
procedure set_frame(objdata:TObjData; reg:tregister;ofs:dword);
procedure stack_alloc(objdata:TObjData;ofs:dword);
procedure switch_to_handlerdata(objdata:TObjData);
end;
implementation
uses
cutils,globals,verbose,cpubase;
const
UWOP_PUSH_NONVOL = 0; { info = register number }
UWOP_ALLOC_LARGE = 1; { no info, alloc size in next 2 slots }
UWOP_ALLOC_SMALL = 2; { info = size of allocation / 8 - 1 }
UWOP_SET_FPREG = 3; { no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 }
UWOP_SAVE_NONVOL = 4; { info = register number, offset in next slot }
UWOP_SAVE_NONVOL_FAR = 5; { info = register number, offset in next 2 slots }
UWOP_SAVE_XMM = 6;
UWOP_SAVE_XMM_FAR = 7;
UWOP_SAVE_XMM128 = 8; { info = XMM reg number, offset in next slot }
UWOP_SAVE_XMM128_FAR = 9; { info = XMM reg number, offset in next 2 slots }
UWOP_PUSH_MACHFRAME = 10; { info = 0: no error-code, 1: error-code }
UNW_FLAG_EHANDLER = $01; { exceptiion handler }
UNW_FLAG_UHANDLER = $02; { termination handler }
UNW_FLAG_FHANDLER = UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER;
UNW_FLAG_CHAININFO = $04; { mutually exclusive with the above }
type
tai_seh_directive_x64=class(tai_seh_directive)
procedure generate_code(objdata:TObjData);override;
end;
TPrologueElement=class(TLinkedListItem)
public
opcode: Integer; { =(info shl 4) or code }
ofs: dword;
addr: aword;
end;
var
current_unw: TWin64Cfi;
function EncodeReg(r: TRegister): integer;
begin
case r of
NR_RAX: result:=0;
NR_RCX: result:=1;
NR_RDX: result:=2;
NR_RBX: result:=3;
NR_RSP: result:=4;
NR_RBP: result:=5;
NR_RSI: result:=6;
NR_RDI: result:=7;
NR_R8: result:=8;
NR_R9: result:=9;
NR_R10: result:=10;
NR_R11: result:=11;
NR_R12: result:=12;
NR_R13: result:=13;
NR_R14: result:=14;
NR_R15: result:=15;
else
InternalError(2011072305);
end;
end;
function EncodeXMM(r: TRegister): integer;
begin
if getregtype(r)=R_MMREGISTER then
result:=getsupreg(r)
else
InternalError(2011072308);
end;
{ TWin64CFI }
constructor TWin64CFI.create;
begin
inherited create;
FElements:=TLinkedList.Create;
end;
destructor TWin64CFI.destroy;
begin
FElements.Free;
stringdispose(FName);
inherited destroy;
end;
procedure TWin64CFI.AddElement(objdata:TObjData;aCode,aInfo:Integer;aOffs:dword);
var
el:TPrologueElement;
begin
el:=TPrologueElement.Create;
FElements.concat(el);
el.opcode:=(aInfo shl 4) or aCode;
el.ofs:=aOffs;
el.addr:=objdata.CurrObjSec.Size;
{ a single element may occupy 1,2 or 3 word-sized slots }
case aCode of
UWOP_ALLOC_LARGE:
Inc(FCount,2+ord(aInfo<>0));
UWOP_SAVE_NONVOL_FAR,
UWOP_SAVE_XMM128_FAR:
Inc(FCount,3);
UWOP_SAVE_NONVOL,
UWOP_SAVE_XMM128:
Inc(FCount,2);
else
inc(FCount);
end;
end;
{ Changes objdata.CurrObjSec to .xdata, so generation of
handler data may continue }
procedure TWin64CFI.generate_prologue_data(objdata:TObjData);
var
hp: TPrologueElement;
uwcode: array [0..1] of byte;
uwdata: array [0..3] of byte;
zero: word;
begin
if FCount>255 then
InternalError(2011072301);
if not FPrologueEndSeen then
CGMessage(asmw_e_missing_endprologue);
if (FPrologueEndPos-FFrameStartSym.address) > 255 then
CGMessage(asmw_e_prologue_too_large);
if codegenerror then
exit;
FXdataSec:=objdata.createsection('.xdata.n_'+lower(FName^),4,[oso_data,oso_load]);
FXdataSym:=objdata.symboldefine('$unwind$'+FName^,AB_GLOBAL,AT_DATA);
uwdata[0]:=(FFlags shl 3) or 1;
uwdata[1]:=FPrologueEndPos-FFrameStartSym.address;
uwdata[2]:=FCount;
{ Offset is multiple of 16, so it is already shifted into correct position }
uwdata[3]:=FFrameOffs or FFrameReg;
objdata.writebytes(uwdata,4);
{ write elements in reverse order (offset descending) }
hp:=TPrologueElement(FElements.Last);
while Assigned(hp) do
begin
uwcode[0]:=hp.addr-FFrameStartSym.address;
uwcode[1]:=hp.opcode;
objdata.writebytes(uwcode,2);
case hp.opcode and $0F of
UWOP_PUSH_NONVOL,
UWOP_ALLOC_SMALL,
UWOP_SET_FPREG,
UWOP_PUSH_MACHFRAME: ; { These have no extra data }
UWOP_ALLOC_LARGE:
if (hp.opcode and $F0)<>0 then
objdata.writebytes(hp.ofs,4)
else
objdata.writebytes(hp.ofs,2);
UWOP_SAVE_NONVOL_FAR,
UWOP_SAVE_XMM128_FAR:
objdata.writebytes(hp.ofs,4);
UWOP_SAVE_NONVOL,
UWOP_SAVE_XMM128:
objdata.writebytes(hp.ofs,2);
else
InternalError(2011072302);
end;
hp:=TPrologueElement(hp.Previous);
end;
{ pad with zeros to dword boundary }
zero:=0;
if odd(FCount) then
objdata.writebytes(zero,2);
if Assigned(FHandler) then
objdata.writereloc(0,sizeof(longint),FHandler,RELOC_RVA);
end;
procedure TWin64CFI.start_frame(objdata:TObjData;const name:string);
begin
if assigned(FName) then
internalerror(2011072306);
FName:=stringdup(name);
FFrameStartSym:=objdata.symbolref(name);
FFrameStartSec:=objdata.CurrObjSec;
FCount:=0;
FFrameReg:=0;
FFrameOffs:=0;
FPrologueEndPos:=0;
FPrologueEndSeen:=false;
FHandler:=nil;
FXdataSec:=nil;
FXdataSym:=nil;
FFlags:=0;
end;
procedure TWin64CFI.switch_to_handlerdata(objdata:TObjData);
begin
if not assigned(FName) then
internalerror(2011072310);
if FHandler=nil then
CGMessage(asmw_e_handlerdata_no_handler);
if FXdataSec=nil then
generate_prologue_data(objdata)
else
objdata.SetSection(FXdataSec);
end;
procedure TWin64CFI.end_frame(objdata:TObjData);
var
pdatasec:TObjSection;
begin
if not assigned(FName) then
internalerror(2011072307);
if FXdataSec=nil then
generate_prologue_data(objdata);
if not codegenerror then
begin
pdatasec:=objdata.createsection(sec_pdata,lower(FName^));
objdata.writereloc(0,4,FFrameStartSym,RELOC_RVA);
objdata.writereloc(FFrameStartSec.Size,4,FFrameStartSym,RELOC_RVA);
objdata.writereloc(0,4,FXdataSym,RELOC_RVA);
{ restore previous state }
objdata.SetSection(FFrameStartSec);
{ create a dummy relocation, so pdata is not smartlinked away }
FFrameStartSec.AddSectionReloc(0,pdatasec,RELOC_NONE);
end;
FElements.Clear;
FFrameStartSym:=nil;
FHandler:=nil;
FXdataSec:=nil;
FXdataSym:=nil;
FFlags:=0;
stringdispose(FName);
end;
procedure TWin64CFI.end_prologue(objdata:TObjData);
begin
if not assigned(FName) then
internalerror(2011072312);
FPrologueEndPos:=objdata.CurrObjSec.Size;
FPrologueEndSeen:=true;
end;
procedure TWin64CFI.push_reg(objdata:TObjData;reg:tregister);
begin
AddElement(objdata,UWOP_PUSH_NONVOL,EncodeReg(reg),0);
end;
procedure TWin64CFI.save_reg(objdata:TObjData;reg:tregister;ofs:dword);
var
info: Integer;
begin
info:=EncodeReg(reg);
if ((ofs and 7) = 0) and (ofs<=$ffff*8) then
AddElement(objdata,UWOP_SAVE_NONVOL,info,ofs shr 3)
else
AddElement(objdata,UWOP_SAVE_NONVOL_FAR,info,ofs);
end;
procedure TWin64CFI.save_xmm(objdata:TObjData;reg:tregister;ofs:dword);
var
info: Integer;
begin
info:=EncodeXMM(reg);
if ((ofs and 15)=0) and (ofs<=$ffff*16) then
AddElement(objdata,UWOP_SAVE_XMM128, info, ofs shr 4)
else
AddElement(objdata,UWOP_SAVE_XMM128_FAR, info, ofs);
end;
procedure TWin64CFI.set_frame(objdata:TObjData;reg:tregister;ofs:dword);
var
info: Integer;
begin
info:=EncodeReg(reg);
if FFrameReg<>0 then
InternalError(2011072303);
if info=0 then { frame register cannot be RAX }
InternalError(2011072304);
if (ofs>240) or ((ofs and 15)<>0) then
InternalError(2011072310);
FFrameReg:=info;
FFrameOffs:=ofs;
{ !! looks like docs aren't correct and info should be set to register }
AddElement(objdata,UWOP_SET_FPREG,0,0);
end;
procedure TWin64CFI.stack_alloc(objdata:TObjData;ofs:dword);
begin
if ((ofs and 7)=0) and (ofs<=128) then
AddElement(objdata,UWOP_ALLOC_SMALL,(ofs-8) shr 3,0)
else if ((ofs and 7) = 0) and (ofs<=$ffff * 8) then
AddElement(objdata,UWOP_ALLOC_LARGE,0,ofs shr 3)
else
AddElement(objdata,UWOP_ALLOC_LARGE,1,ofs);
end;
procedure tai_seh_directive_x64.generate_code(objdata:TObjData);
begin
case kind of
ash_proc:
current_unw.start_frame(objdata,data.name^);
ash_endproc:
current_unw.end_frame(objdata);
ash_endprologue:
current_unw.end_prologue(objdata);
ash_handler:
begin
current_unw.FHandler:=objdata.symbolref(data.name^);
current_unw.FFlags:=data.flags;
end;
ash_handlerdata:
current_unw.switch_to_handlerdata(objdata);
ash_eh,ash_32,ash_no32: ; { these are not for x86_64 }
ash_setframe:
current_unw.set_frame(objdata,data.reg,data.offset);
ash_stackalloc:
current_unw.stack_alloc(objdata,data.offset);
ash_pushreg:
current_unw.push_reg(objdata,data.reg);
ash_savereg:
current_unw.save_reg(objdata,data.reg,data.offset);
ash_savexmm:
current_unw.save_xmm(objdata,data.reg,data.offset);
ash_pushframe: {TBD};
ash_pushnv,
ash_savenv:
internalerror(2019050712);
else
internalerror(2020041901);
end;
end;
initialization
cai_seh_directive:=tai_seh_directive_x64;
current_unw:=TWin64CFI.Create;
finalization
current_unw.Free;
end.