unit vm_patch_link;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 mqueue,
 kern_stub;

type
 t_patch_type=(pt_none,pt_fsbase,pt_gsbase,pt_syscall,pt_unresolve,pt_jit_prolog,pt_jit_frame);

 p_patch_info=^t_patch_info;
 t_patch_info=record
  vaddr:Pointer;
  vsize:Integer;
  ptype:t_patch_type;
  stub :p_stub_chunk;
 end;

 p_patch_node=^t_patch_node;
 t_patch_node=record
  link:TAILQ_ENTRY;
  page:TAILQ_ENTRY;
  info:t_patch_info;
 end;

function  vm_get_patch_link(vaddr:Pointer;vsize:Integer;ptype:t_patch_type):t_patch_info;
function  vm_patch_exist(vaddr:Pointer;vsize:Integer):Boolean;

procedure vm_add_patch_link     (_obj,vaddr:Pointer;vsize:Integer;ptype:t_patch_type;stub:p_stub_chunk);
procedure vm_free_patch_link    (_obj:Pointer;node:p_patch_node);
procedure vm_object_patch_remove(_obj:Pointer;start,__end:DWORD);
function  vm_get_patch_link     (_obj,vaddr:Pointer):p_stub_chunk;
procedure vm_rem_patch_link     (_obj,vaddr:Pointer);

implementation

uses
 hamt,
 kern_rwlock,
 vmparam,
 sys_vm_object;

type
 p_patch_page=^t_patch_page;
 t_patch_page=TAILQ_HEAD;

var
 hamt_lock:Pointer=nil;
 hamt_page:TSTUB_HAMT32;

function OFF_TO_IDX(x:Pointer):DWORD; inline;
begin
 Result:=QWORD(x) shr PAGE_SHIFT;
end;

procedure hamt_insert_link(node:p_patch_node);
var
 data:PPointer;
 page:p_patch_page;
begin
 rw_wlock(hamt_lock);

 data:=HAMT_search32(@hamt_page,OFF_TO_IDX(node^.info.vaddr));

 if (data=nil) then
 begin
  page:=AllocMem(SizeOf(t_patch_page));
  TAILQ_INIT(page);
  data:=HAMT_insert32(@hamt_page,OFF_TO_IDX(node^.info.vaddr),page);
 end;

 page:=data^;

 TAILQ_INSERT_TAIL(page,node,@node^.page);

 rw_wunlock(hamt_lock);
end;

procedure hamt_remove_link(node:p_patch_node);
var
 data:PPointer;
 page:p_patch_page;
begin
 rw_wlock(hamt_lock);

 data:=HAMT_search32(@hamt_page,OFF_TO_IDX(node^.info.vaddr));

 if (data=nil) then
 begin
  rw_wunlock(hamt_lock);
  Exit;
 end;

 page:=data^;

 TAILQ_REMOVE(page,node,@node^.page);

 if TAILQ_EMPTY(page) then
 begin
  HAMT_delete32(@hamt_page,OFF_TO_IDX(node^.info.vaddr),nil);
  FreeMem(page);
 end;

 rw_wunlock(hamt_lock);
end;

function info_cross(info:p_patch_info;vaddr:Pointer;vsize:Integer):Boolean; inline;
begin
 Result:=((vaddr+vsize)>info^.vaddr) and (vaddr<(info^.vaddr+info^.vsize));
end;

function _vm_get_patch_link(page:p_patch_page;vaddr:Pointer;ptype:t_patch_type):t_patch_info;
var
 entry,next:p_patch_node;
begin
 Result:=Default(t_patch_info);
 if (page=nil) then Exit;

 entry:=TAILQ_FIRST(page);
 while (entry<>nil) do
 begin
  next:=TAILQ_NEXT(entry,@entry^.link);
  //
  if (entry^.info.vaddr=vaddr) and
     ((ptype=pt_none) or (entry^.info.ptype=ptype)) then
  begin
   Result:=entry^.info;
   p_inc_ref(Result.stub);
  end;
  //
  entry:=next;
 end;
end;

function _vm_get_patch_link(page:p_patch_page;vaddr:Pointer;vsize:Integer;ptype:t_patch_type):t_patch_info;
var
 entry,next:p_patch_node;
begin
 Result:=Default(t_patch_info);
 if (page=nil) then Exit;

 entry:=TAILQ_FIRST(page);
 while (entry<>nil) do
 begin
  next:=TAILQ_NEXT(entry,@entry^.link);
  //
  if info_cross(@entry^.info,vaddr,vsize) and
     ((ptype=pt_none) or (entry^.info.ptype=ptype)) then
  begin
   Result:=entry^.info;
   p_inc_ref(Result.stub);
  end;
  //
  entry:=next;
 end;
end;

function vm_get_patch_link(vaddr:Pointer;vsize:Integer;ptype:t_patch_type):t_patch_info;
var
 off1,off2:DWORD;
 data:PPointer;
 page:p_patch_page;
begin
 Result:=Default(t_patch_info);

 off1:=OFF_TO_IDX(vaddr);

 rw_wlock(hamt_lock);

 data:=HAMT_search32(@hamt_page,off1);

 if (data=nil) then
 begin
  page:=nil;
 end else
 begin
  page:=data^;
 end;

 if (vsize=0) then
 begin
  Result:=_vm_get_patch_link(page,vaddr,ptype);
 end else
 begin
  Result:=_vm_get_patch_link(page,vaddr,vsize,ptype);

  off2:=OFF_TO_IDX(vaddr+vsize-1);

  if (Result.stub=nil) and (off1<>off2) then
  begin
   data:=HAMT_search32(@hamt_page,off2);

   if (data=nil) then
   begin
    page:=nil;
   end else
   begin
    page:=data^;
   end;

   Result:=_vm_get_patch_link(page,vaddr,vsize,ptype);
  end;
 end;

 rw_wunlock(hamt_lock);
end;

function vm_patch_exist(vaddr:Pointer;vsize:Integer):Boolean;
var
 info:t_patch_info;
begin
 info:=vm_get_patch_link(vaddr,vsize,pt_none);

 if (info.stub<>nil) then
 begin
  p_dec_ref(info.stub);
  Result:=True;
 end else
 begin
  Result:=False;
 end;
end;

procedure vm_add_patch_link(_obj,vaddr:Pointer;vsize:Integer;ptype:t_patch_type;stub:p_stub_chunk);
var
 obj:vm_object_t;
 node:p_patch_node;
begin
 //Writeln('patch:vaddr=0x',HexStr(vaddr),' type:',ptype);

 obj:=_obj;
 node:=AllocMem(SizeOf(t_patch_node));
 node^.info.vaddr:=vaddr;
 node^.info.vsize:=vsize;
 node^.info.ptype:=ptype;
 node^.info.stub :=stub;

 p_inc_ref(stub);

 VM_OBJECT_LOCK(obj);
 TAILQ_INSERT_TAIL(@obj^.patchq,node,@node^.link);
 VM_OBJECT_UNLOCK(obj);

 hamt_insert_link(node);
end;

procedure vm_free_patch_link(_obj:Pointer;node:p_patch_node);
var
 obj:vm_object_t;
begin
 obj:=_obj;
 TAILQ_REMOVE(@obj^.patchq,node,@node^.link);

 hamt_remove_link(node);

 p_dec_ref(node^.info.stub);
 FreeMem(node);
end;

procedure vm_object_patch_remove(_obj:Pointer;start,__end:DWORD); public;
var
 obj:vm_object_t;
 entry,next:p_patch_node;
begin
 obj:=_obj;

 VM_OBJECT_LOCK(obj);

 entry:=TAILQ_FIRST(@obj^.patchq);
 while (entry<>nil) do
 begin
  next:=TAILQ_NEXT(entry,@entry^.link);
  //
  if ((start=0) or (OFF_TO_IDX(entry^.info.vaddr)>=start)) and
     ((__end=0) or (OFF_TO_IDX(entry^.info.vaddr)< __end)) then
  begin
   vm_free_patch_link(_obj,entry);
  end;
  //
  entry:=next;
 end;

 VM_OBJECT_UNLOCK(obj);
end;

function vm_get_patch_link(_obj,vaddr:Pointer):p_stub_chunk;
var
 obj:vm_object_t;
 entry,next:p_patch_node;
begin
 Result:=nil;
 obj:=_obj;

 VM_OBJECT_LOCK(obj);

 entry:=TAILQ_FIRST(@obj^.patchq);
 while (entry<>nil) do
 begin
  next:=TAILQ_NEXT(entry,@entry^.link);
  //
  if (entry^.info.vaddr=vaddr) then
  begin
   Result:=entry^.info.stub;
   p_inc_ref(Result);
   //
   VM_OBJECT_UNLOCK(obj);
   Exit;
  end;
  //
  entry:=next;
 end;

 VM_OBJECT_UNLOCK(obj);
end;

procedure vm_rem_patch_link(_obj,vaddr:Pointer);
var
 obj:vm_object_t;
 entry,next:p_patch_node;
begin
 obj:=_obj;

 VM_OBJECT_LOCK(obj);

 entry:=TAILQ_FIRST(@obj^.patchq);
 while (entry<>nil) do
 begin
  next:=TAILQ_NEXT(entry,@entry^.link);
  //
  if (entry^.info.vaddr=vaddr) then
  begin
   vm_free_patch_link(_obj,entry);
  end;
  //
  entry:=next;
 end;

 VM_OBJECT_UNLOCK(obj);
end;

end.

