unit vm_nt_sub_map;

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

interface

uses
 sysutils,
 vm,
 vm_pmap_prot;

type
 pp_vm_nt_sub_entry=^p_vm_nt_sub_entry;
 p_vm_nt_sub_entry=^vm_nt_sub_entry;
 vm_nt_sub_entry=packed record
  prev :p_vm_nt_sub_entry; // previous entry
  next :p_vm_nt_sub_entry; // next entry
  left :p_vm_nt_sub_entry; // left child in binary search tree
  right:p_vm_nt_sub_entry; // right child in binary search tree
  start:vm_offset_t;       // start address
  __end:vm_offset_t;       // end address
  //
  decl_prot:Byte;          // declared protection (with tracking bits)
  real_prot:Byte;          // real page protection
  align    :Word;
 end;

 p_vm_nt_sub_map=^t_vm_nt_sub_map;
 t_vm_nt_sub_map=object
  header:vm_nt_sub_entry;   // List of entries
  root  :p_vm_nt_sub_entry; // Root of a binary search tree
  property  min_offset:vm_offset_t read header.start write header.start;
  property  max_offset:vm_offset_t read header.__end write header.__end;
 end;

procedure vm_nt_sub_map_init(map:p_vm_nt_sub_map;min,max:vm_offset_t);

function  vm_nt_sub_map_insert(
            map  :p_vm_nt_sub_map;
            start:vm_offset_t;
            __end:vm_offset_t;
            prot :Byte):Integer;

// Move nodes from one submap to another
procedure vm_nt_sub_map_move(dst,src:p_vm_nt_sub_map);
procedure vm_nt_sub_map_clip(dst,src:p_vm_nt_sub_map);

procedure vm_nt_sub_map_free(map:p_vm_nt_sub_map);

//Change protection taking into account tracking
function  vm_nt_sub_map_protect(map  :p_vm_nt_sub_map;
                                start:vm_offset_t;
                                __end:vm_offset_t;
                                prot :Byte):Integer;

//Change protection tracking
function vm_nt_sub_map_tracking(map  :p_vm_nt_sub_map;
                                start:vm_offset_t;
                                __end:vm_offset_t;
                                prot :Byte):Integer;

//Update page protection by mode
procedure vm_nt_sub_map_prot_fixup(map  :p_vm_nt_sub_map;
                                   start:vm_offset_t;
                                   __end:vm_offset_t;
                                   mode :Integer);

implementation

uses
 md_map;

procedure vm_nt_sub_map_init(map:p_vm_nt_sub_map;min,max:vm_offset_t); inline;
begin
 map^:=Default(t_vm_nt_sub_map);
 map^.header.next:=@map^.header;
 map^.header.prev:=@map^.header;
 map^.min_offset :=min;
 map^.max_offset :=max;
end;

function vm_nt_sub_entry_create(map:p_vm_nt_sub_map):p_vm_nt_sub_entry;
var
 new_entry:p_vm_nt_sub_entry;
begin
 new_entry:=AllocMem(SizeOf(vm_nt_sub_entry));
 Assert((new_entry<>nil),'vm_nt_sub_entry_create: kernel resources exhausted');
 Result:=new_entry;
end;

procedure vm_nt_sub_entry_dispose(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry); inline;
begin
 FreeMem(entry);
end;

function vm_nt_sub_entry_splay(addr:vm_offset_t;root:p_vm_nt_sub_entry):p_vm_nt_sub_entry;
var
 llist,rlist:p_vm_nt_sub_entry;
 ltree,rtree:p_vm_nt_sub_entry;
 y          :p_vm_nt_sub_entry;
begin
 if (root=nil) then Exit(root);

 llist:=nil;
 rlist:=nil;
 repeat
  { root is never nil in here. }
  if (addr<root^.start) then
  begin
   y:=root^.left;
   if (y=nil) then break;
   if (addr<y^.start) and (y^.left<>nil) then
   begin
    { Rotate right and put y on rlist. }
    root^.left:=y^.right;
    y^.right:=root;
    root:=y^.left;
    y^.left:=rlist;
    rlist:=y;
   end else
   begin
    { Put root on rlist. }
    root^.left:=rlist;
    rlist:=root;
    root:=y;
   end;
  end else
  if (addr>=root^.__end) then
  begin
   y:=root^.right;
   if (y=nil) then break;
   if (addr>=y^.__end) and (y^.right<>nil) then
   begin
    { Rotate left and put y on llist. }
    root^.right:=y^.left;
    y^.left:=root;
    root:=y^.right;
    y^.right:=llist;
    llist:=y;
   end else
   begin
    { Put root on llist. }
    root^.right:=llist;
    llist:=root;
    root:=y;
   end;
  end else
  begin
   break;
  end;
 until false;

 ltree:=root^.left;
 while (llist<>nil) do
 begin
  y:=llist^.right;
  llist^.right:=ltree;
  ltree:=llist;
  llist:=y;
 end;
 rtree:=root^.right;
 while (rlist<>nil) do
 begin
  y:=rlist^.left;
  rlist^.left:=rtree;
  rtree:=rlist;
  rlist:=y;
 end;

 root^.left :=ltree;
 root^.right:=rtree;

 Result:=(root);
end;

procedure vm_nt_sub_entry_link(
           map        :p_vm_nt_sub_map;
           after_where:p_vm_nt_sub_entry;
           entry      :p_vm_nt_sub_entry);
begin
 entry^.prev:=after_where;
 entry^.next:=after_where^.next;
 entry^.next^.prev:=entry;
 after_where^.next:=entry;

 if (after_where<>@map^.header) then
 begin
  if (after_where<>map^.root) then
  begin
   vm_nt_sub_entry_splay(after_where^.start, map^.root);
  end;
  entry^.right:=after_where^.right;
  entry^.left :=after_where;
  after_where^.right:=nil;
 end else
 begin
  entry^.right:=map^.root;
  entry^.left :=nil;
 end;

 map^.root:=entry;
end;

procedure vm_nt_sub_entry_unlink(
           map  :p_vm_nt_sub_map;
           entry:p_vm_nt_sub_entry);
var
 next,prev,root:p_vm_nt_sub_entry;
begin
 if (entry<>map^.root) then
 begin
  vm_nt_sub_entry_splay(entry^.start, map^.root);
 end;
 if (entry^.left=nil) then
 begin
  root:=entry^.right;
 end else
 begin
  root:=vm_nt_sub_entry_splay(entry^.start, entry^.left);
  root^.right:=entry^.right;
 end;

 map^.root:=root;

 prev:=entry^.prev;
 next:=entry^.next;
 next^.prev:=prev;
 prev^.next:=next;
end;

function vm_nt_sub_map_lookup_entry(
           map    :p_vm_nt_sub_map;
           address:vm_offset_t;
           entry  :pp_vm_nt_sub_entry):Boolean;
var
 cur:p_vm_nt_sub_entry;
begin
 cur:=map^.root;
 if (cur=nil) then
 begin
  entry^:=@map^.header;
 end else
 if (address>=cur^.start) and (cur^.__end>address) then
 begin
  entry^:=cur;
  Exit(TRUE);
 end else
 begin
  cur:=vm_nt_sub_entry_splay(address,cur);
  map^.root:=cur;

  if (address>=cur^.start) then
  begin
   entry^:=cur;
   if (cur^.__end>address) then
   begin
    Exit(TRUE);
   end;
  end else
  begin
   entry^:=cur^.prev;
  end;
 end;
 Result:=(FALSE);
end;

procedure vm_nt_sub_simplify_entry(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry); forward;

function  vm_nt_sub_map_insert(
            map  :p_vm_nt_sub_map;
            start:vm_offset_t;
            __end:vm_offset_t;
            prot :Byte):Integer;
var
 new_entry  :p_vm_nt_sub_entry;
 prev_entry :p_vm_nt_sub_entry;
 temp_entry :p_vm_nt_sub_entry;

begin
 if (start<map^.min_offset) or (__end>map^.max_offset) or (start>=__end) then
 begin
  Exit(KERN_INVALID_ADDRESS);
 end;

 if vm_nt_sub_map_lookup_entry(map,start,@temp_entry) then
 begin
  Exit(KERN_NO_SPACE);
 end;

 prev_entry:=temp_entry;

 if (prev_entry^.next<>@map^.header) and
    (prev_entry^.next^.start<__end) then
 begin
  Exit(KERN_NO_SPACE);
 end;

 if (prev_entry<>@map^.header) and
    (prev_entry^.decl_prot=prot) and
    (prev_entry^.real_prot=0) and
    (prev_entry^.__end=start) then
 begin
  prev_entry^.__end:=__end;

  vm_nt_sub_simplify_entry(map, prev_entry);

  Exit(KERN_SUCCESS);
 end;

 new_entry:=vm_nt_sub_entry_create(map);
 new_entry^.start:=start;
 new_entry^.__end:=__end;

 new_entry^.decl_prot:=prot;
 new_entry^.real_prot:=0;   //do fixup

 vm_nt_sub_entry_link(map, prev_entry, new_entry);

 vm_nt_sub_simplify_entry(map, new_entry);

 Result:=KERN_SUCCESS;
end;

procedure vm_nt_sub_map_entry_delete(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry); inline;
begin
 vm_nt_sub_entry_unlink (map, entry);
 vm_nt_sub_entry_dispose(map, entry);
end;

procedure vm_nt_sub_simplify_entry(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry);
var
 next,prev:p_vm_nt_sub_entry;
begin
 prev:=entry^.prev;
 if (prev<>@map^.header) then
 begin
  if (prev^.__end=entry^.start) and
     (prev^.decl_prot=entry^.decl_prot) and
     (prev^.real_prot=entry^.real_prot) then
  begin
   vm_nt_sub_entry_unlink(map, prev);
   entry^.start:=prev^.start;

   vm_nt_sub_entry_dispose(map, prev);
  end;
 end;

 next:=entry^.next;
 if (next<>@map^.header) then
 begin
  if (entry^.__end=next^.start) and
     (next^.decl_prot=entry^.decl_prot) and
     (next^.real_prot=entry^.real_prot) then
  begin
   vm_nt_sub_entry_unlink(map, next);
   entry^.__end:=next^.__end;

   vm_nt_sub_entry_dispose(map, next);
  end;
 end;
end;

procedure _vm_nt_sub_map_clip_start(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry;start:vm_offset_t);
var
 new_entry:p_vm_nt_sub_entry;
begin
 Assert(start<entry^.__end);

 vm_nt_sub_simplify_entry(map, entry);

 new_entry:=vm_nt_sub_entry_create(map);
 new_entry^:=entry^;

 new_entry^.__end:=start;
 entry^.start    :=start;

 vm_nt_sub_entry_link(map, entry^.prev, new_entry);
end;

procedure vm_nt_sub_map_clip_start(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry;start:vm_offset_t); inline;
begin
 if (start>entry^.start) then
 begin
  _vm_nt_sub_map_clip_start(map,entry,start);
 end;
end;

procedure _vm_nt_sub_map_clip_end(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry;__end:vm_offset_t);
var
 new_entry:p_vm_nt_sub_entry;
begin
 Assert(__end>entry^.start);

 new_entry:=vm_nt_sub_entry_create(map);
 new_entry^:=entry^;

 new_entry^.start:=__end;
 entry^.__end    :=__end;

 vm_nt_sub_entry_link(map, entry, new_entry);
end;

procedure vm_nt_sub_map_clip_end(map:p_vm_nt_sub_map;entry:p_vm_nt_sub_entry;__end:vm_offset_t); inline;
begin
 if (__end<entry^.__end) then
 begin
  _vm_nt_sub_map_clip_end(map,entry,__end);
 end;
end;

procedure vm_nt_sub_map_move(dst,src:p_vm_nt_sub_map);
var
 entry,next,after:p_vm_nt_sub_entry;
begin
 {
 Writeln('[vm_nt_sub_map_move]');
 Writeln(' dst:',HexStr(dst^.min_offset,16),':',HexStr(dst^.max_offset,16));
 Writeln(' src:',HexStr(src^.min_offset,16),':',HexStr(src^.max_offset,16));
 }

 entry:=src^.header.next;
 //
 after:=nil;
 //
 while (entry<>@src^.header) do
 begin
  //
  if (after=nil) then
  begin
   vm_nt_sub_map_lookup_entry(dst, entry^.__end, @after); //get first position of insert
  end;
  //
  next:=entry^.next;
  //
  vm_nt_sub_entry_unlink  (src,entry);
  vm_nt_sub_entry_link    (dst,after,entry);
  vm_nt_sub_simplify_entry(dst,entry);
  //
  after:=entry;
  //
  entry:=next;
 end;
end;

procedure vm_nt_sub_map_clip(dst,src:p_vm_nt_sub_map);
var
 entry,next,after:p_vm_nt_sub_entry;
begin
 Assert((dst^.min_offset=src^.min_offset) or (dst^.max_offset=src^.max_offset),'vm_nt_sub_map_clip');

 {
 Writeln('[vm_nt_sub_map_clip]');
 Writeln(' dst:',HexStr(dst^.min_offset,16),':',HexStr(dst^.max_offset,16));
 Writeln(' src:',HexStr(src^.min_offset,16),':',HexStr(src^.max_offset,16));
 }

 entry:=nil;
 if (vm_nt_sub_map_lookup_entry(src, dst^.min_offset, @entry)) then
 begin
  vm_nt_sub_map_clip_start(src, entry, dst^.min_offset); //devide src by dst
 end else
 begin
  entry:=entry^.next;
 end;
 //
 after:=nil;
 //
 while (entry<>@src^.header) and (entry^.start<dst^.max_offset) do
 begin
  //
  vm_nt_sub_map_clip_end(src, entry, dst^.max_offset); //devide src by dst
  //
  if (after=nil) then
  begin
   vm_nt_sub_map_lookup_entry(dst, entry^.__end, @after); //get first position of insert
  end;
  //
  next:=entry^.next;
  //
  vm_nt_sub_entry_unlink  (src,entry);
  vm_nt_sub_entry_link    (dst,after,entry);
  vm_nt_sub_simplify_entry(dst,entry);
  //
  after:=entry;
  //
  entry:=next;
 end;
end;

procedure vm_nt_sub_map_free(map:p_vm_nt_sub_map);
var
 entry,next:p_vm_nt_sub_entry;
begin
 entry:=map^.header.next;
 //
 while (entry<>@map^.header) do
 begin
  next:=entry^.next;
  //
  vm_nt_sub_map_entry_delete(map,entry);
  //
  entry:=next;
 end;
end;

//

function vm_nt_sub_map_protect(map  :p_vm_nt_sub_map;
                               start:vm_offset_t;
                               __end:vm_offset_t;
                               prot :Byte):Integer;
var
 entry:p_vm_nt_sub_entry;

 base,size:vm_size_t;
 mask:Integer;
 r:Integer;
begin
 if (start=__end) then
 begin
  Exit(KERN_SUCCESS);
 end;

 if (vm_nt_sub_map_lookup_entry(map, start, @entry)) then
 begin
  vm_nt_sub_map_clip_start(map, entry, start);
 end else
 begin
  entry:=entry^.next;
 end;

 while (entry<>@map^.header) and (entry^.start<__end) do
 begin
  vm_nt_sub_map_clip_end(map, entry, __end);

  //save trac, change prot
  prot:=(prot and VM_RW) or (entry^.decl_prot and PAGE_TRACK_RWX);
  entry^.decl_prot:=prot;

  //masking
  mask:=not (prot shr PAGE_TRACK_SHIFT);
  prot:=(prot and VM_RW) and mask;

  if (prot<>(entry^.real_prot and VM_RW)) then
  begin
   base:=entry^.start;
   size:=entry^.__end-entry^.start;

   r:=md_protect(Pointer(base),size,prot);
   if (r<>0) then
   begin
    Writeln('failed md_protect(',HexStr(base,11),',',HexStr(base+size,11),'):0x',HexStr(r,8));
    Assert(false,'vm_nt_sub_map_protect');
   end;
   //save changes
   entry^.real_prot:=prot;
  end;

  vm_nt_sub_simplify_entry(map, entry);
  entry:=entry^.next;
 end;

 Result:=(KERN_SUCCESS);
end;

function vm_nt_sub_map_tracking(map  :p_vm_nt_sub_map;
                                start:vm_offset_t;
                                __end:vm_offset_t;
                                prot :Byte):Integer;
var
 entry:p_vm_nt_sub_entry;

 base,size:vm_size_t;
 mask:Integer;
 r:Integer;
begin
 if (start=__end) then
 begin
  Exit(KERN_SUCCESS);
 end;

 if (vm_nt_sub_map_lookup_entry(map, start, @entry)) then
 begin
  vm_nt_sub_map_clip_start(map, entry, start);
 end else
 begin
  entry:=entry^.next;
 end;

 while (entry<>@map^.header) and (entry^.start<__end) do
 begin
  vm_nt_sub_map_clip_end(map, entry, __end);

  //save prot, change track
  prot:=(prot and PAGE_TRACK_RWX) or (entry^.decl_prot and VM_RW);
  entry^.decl_prot:=prot;

  //masking
  mask:=not (prot shr PAGE_TRACK_SHIFT);
  prot:=(prot and VM_RW) and mask;

  if (prot<>(entry^.real_prot and VM_RW)) then
  begin
   base:=entry^.start;
   size:=entry^.__end-entry^.start;

   r:=md_protect(Pointer(base),size,prot);
   if (r<>0) then
   begin
    Writeln('failed md_protect(',HexStr(base,11),',',HexStr(base+size,11),'):0x',HexStr(r,8));
    Assert(false,'vm_nt_sub_map_protect');
   end;
   //save changes
   entry^.real_prot:=prot;
  end;

  vm_nt_sub_simplify_entry(map, entry);
  entry:=entry^.next;
 end;

 Result:=(KERN_SUCCESS);
end;

procedure vm_nt_sub_map_prot_fixup(map  :p_vm_nt_sub_map;
                                   start:vm_offset_t;
                                   __end:vm_offset_t;
                                   mode :Integer);
var
 entry  :p_vm_nt_sub_entry;
 e_start:vm_offset_t;
 e___end:vm_offset_t;

 base,size:vm_size_t;
 prot:Integer;
 mask:Integer;
 r:Integer;
begin
 if (start=__end) then Exit;

 if (vm_nt_sub_map_lookup_entry(map, start, @entry)) then
 begin
  //
 end else
 begin
  entry:=entry^.next;
 end;

 while (entry<>@map^.header) and (entry^.start<__end) do
 begin

  e_start:=entry^.start;
  e___end:=entry^.__end;

  if (e_start<start) then
  begin
   e_start:=start;
  end;

  if (e___end>__end) then
  begin
   e___end:=__end;
  end;

  if (e_start<>e___end) then
  begin
   prot:=entry^.decl_prot;

   //TRACK_PROT Take tracking bits into account
   if ((mode and TRACK_PROT)=0) then
   begin
    prot:=(prot and VM_RW);
   end else
   begin
    mask:=not (prot shr PAGE_TRACK_SHIFT);
    prot:=(prot and VM_RW) and mask;
   end;

   //REMAP_PROT Ignore protect bit checking
   if ((mode and REMAP_PROT)<>0) or (prot<>(entry^.real_prot and VM_RW)) then
   begin
    base:=e_start;
    size:=e___end-e_start;

    r:=md_protect(Pointer(base),size,prot);
    if (r<>0) then
    begin
     Writeln('failed md_protect(',HexStr(base,11),',',HexStr(base+size,11),'):0x',HexStr(r,8));
     Assert(false,'vm_nt_sub_map_prot_fixup');
    end;
    //save changes
    entry^.real_prot:=prot;
   end;

  end;

  entry:=entry^.next;
 end;
end;


end.

