unit md_thread; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} {$DEFINE NT_THREAD} {$DEFINE CSRSRV} interface {$IFDEF NT_THREAD} // {$ELSE} {$UNDEF CSRSRV} {$ENDIF} uses ntapi, windows, kern_thr, sysutils; Const SYS_STACK_RSRV=64*1024; SYS_STACK_SIZE=16*1024; SYS_GUARD_SIZE= 4*1024; function cpu_thread_alloc(pages:Word):p_kthread; function cpu_thread_free(td:p_kthread):Integer; function BaseQueryInfo(td:p_kthread):Integer; function cpu_thread_create(td:p_kthread; stack_base:Pointer; stack_size:QWORD; start_func:Pointer; arg :Pointer):Integer; procedure cpu_thread_terminate(td:p_kthread); function cpu_sched_add(td:p_kthread):Integer; procedure cpu_sched_throw; function cpu_thread_finished(td:p_kthread):Boolean; function cpuset_setaffinity(td:p_kthread;new:Ptruint):Integer; function cpu_set_base_priority(td:p_kthread;prio:Integer):Integer; function cpu_set_priority(td:p_kthread;prio:Integer):Integer; function cpu_thread_set_name(td:p_kthread;const name:shortstring):Integer; function md_suspend(td:p_kthread):Integer; function md_resume (td:p_kthread):Integer; procedure seh_wrapper_before(td:p_kthread;var func:Pointer); procedure seh_wrapper_after (td:p_kthread;func:Pointer); implementation uses md_context, vmparam; // var size_of_umtx_q:Integer; external; // function cpu_thread_alloc(pages:Word):p_kthread; var td:p_kthread; stack_size:ULONG_PTR; headr_size:ULONG_PTR; padding :ULONG_PTR; data:Pointer; size:ULONG_PTR; R:DWORD; begin Result:=nil; stack_size:=ULONG_PTR(pages)*PAGE_SIZE; if (stack_size0) then Exit; //header R:=NtAllocateVirtualMemory( NtCurrentProcess, @data, 0, @headr_size, MEM_COMMIT, PAGE_READWRITE ); if (R<>0) then Exit; td:=data; td^.td_umtxq:=Pointer(td+1); //footer data:=data+size-stack_size; //kernel stack R:=NtAllocateVirtualMemory( NtCurrentProcess, @data, 0, @stack_size, MEM_COMMIT, PAGE_READWRITE ); if (R<>0) then Exit; td^.td_kstack.sttop:=data; td^.td_kstack.stack:=data+stack_size; td^.td_padding.addr:=Pointer(td)+(headr_size-padding); td^.td_padding.size:=padding; Result:=td; end; function cpu_thread_free(td:p_kthread):Integer; var data:Pointer; size:ULONG_PTR; begin if (td=nil) then Exit(0); data:=td; size:=0; Result:=NtFreeVirtualMemory( NtCurrentProcess, @data, @size, MEM_RELEASE ); end; function BaseQueryInfo(td:p_kthread):Integer; var data:array[0..SizeOf(THREAD_BASIC_INFORMATION)-1+7] of Byte; P_TBI:PTHREAD_BASIC_INFORMATION; begin P_TBI:=Align(@data,8); P_TBI^:=Default(THREAD_BASIC_INFORMATION); Result:=NtQueryInformationThread( td^.td_handle, ThreadBasicInformation, P_TBI, SizeOf(THREAD_BASIC_INFORMATION), nil); if (Result<>0) then Exit; if (P_TBI^.TebBaseAddress=nil) then Exit(-1); td^.td_teb :=P_TBI^.TebBaseAddress; td^.td_cpuset:=P_TBI^.AffinityMask; td^.td_teb^.thread:=td; //self td^.td_teb^.DeallocationStack:=nil; //dont free memory end; procedure BaseInitializeStack(InitialTeb :PINITIAL_TEB; StackAddress:Pointer; StackSize :Ptruint); inline; begin InitialTeb^.PreviousStackBase :=nil; InitialTeb^.PreviousStackLimit:=nil; InitialTeb^.StackBase :=StackAddress+StackSize; //start addr InitialTeb^.StackLimit :=StackAddress; //lo addr InitialTeb^.AllocatedStackBase:=StackAddress; //DeallocationStack end; procedure BaseInitializeContext(Context :PCONTEXT; Parameter :Pointer; StartAddress:Pointer; StackAddress:Pointer); inline; begin Context^:=Default(TCONTEXT); Context^.Rsp:=ptruint(StackAddress); Context^.Rbp:=ptruint(StackAddress); Context^.Rdi:=ptruint(Parameter); Context^.Rip:=ptruint(StartAddress); Context^.SegGs:=KGDT64_R3_DATA or RPL_MASK; Context^.SegEs:=KGDT64_R3_DATA or RPL_MASK; Context^.SegDs:=KGDT64_R3_DATA or RPL_MASK; Context^.SegCs:=KGDT64_R3_CODE or RPL_MASK; Context^.SegSs:=KGDT64_R3_DATA or RPL_MASK; Context^.SegFs:=KGDT64_R3_CMTEB or RPL_MASK; Context^.EFlags:=$3000 or EFLAGS_INTERRUPT_MASK; Context^.MxCsr:=__INITIAL_MXCSR__; Context^.FltSave.ControlWord:=__INITIAL_FPUCW__; //Context^.FltSave.StatusWord: WORD; Context^.FltSave.MxCsr :=__INITIAL_MXCSR__; Context^.FltSave.MxCsr_Mask :=__INITIAL_MXCSR_MASK__; Context^.ContextFlags:=CONTEXT_THREAD; end; {$IFDEF CSRSRV} type t_GetProcAddressForCaller=function(hModule :HINST; lpProcName:LPCSTR; Param3 :Pointer):Pointer; //KernelBase.dll t_CsrCreateRemoteThread=function(hThread :THANDLE; ClientId:PCLIENT_ID):DWORD; //csrsrv function _CsrCreateRemoteThread(hThread:THANDLE;ClientId:PCLIENT_ID):DWORD; var GetProcAddressForCaller:t_GetProcAddressForCaller; CsrCreateRemoteThread :t_CsrCreateRemoteThread; begin Result:=0; Pointer(GetProcAddressForCaller):=GetProcAddress(GetModuleHandle('kernelbase.dll'),'GetProcAddressForCaller'); CsrCreateRemoteThread :=nil; //Writeln('csrsrv.dll:0x',HexStr(GetModuleHandle('csrsrv.dll'),16)); //Writeln('csrsrv:0x',HexStr(GetModuleHandle('csrsrv'),16)); if (GetProcAddressForCaller<>nil) then begin Pointer(CsrCreateRemoteThread):=GetProcAddressForCaller(GetModuleHandle('csrsrv.dll'),'CsrCreateRemoteThread',nil); end; if (CsrCreateRemoteThread=nil) then begin Pointer(CsrCreateRemoteThread):=GetProcAddress(GetModuleHandle('csrsrv.dll'),'CsrCreateRemoteThread'); end; if (CsrCreateRemoteThread<>nil) then begin Result:=CsrCreateRemoteThread(hThread,ClientId); end; //Writeln('GetProcAddressForCaller:0x',HexStr(GetProcAddressForCaller)); //Writeln('CsrCreateRemoteThread :0x',HexStr(CsrCreateRemoteThread)); end; {$ENDIF} function cpu_thread_create(td:p_kthread; stack_base:Pointer; stack_size:QWORD; start_func:Pointer; arg :Pointer):Integer; {$IFDEF NT_THREAD} var _ClientId :array[0..SizeOf(TCLIENT_ID )+14] of Byte; _InitialTeb:array[0..SizeOf(TINITIAL_TEB)+14] of Byte; _Context :array[0..SizeOf(TCONTEXT )+14] of Byte; ClientId :PCLIENT_ID; InitialTeb:PINITIAL_TEB; Context :PCONTEXT; Stack :Pointer; {$ENDIF} begin if (td=nil) then Exit(-1); {$IFDEF NT_THREAD} ClientId :=Align(@_ClientId ,16); InitialTeb:=Align(@_InitialTeb,16); Context :=Align(@_Context ,16); ClientId^.UniqueProcess:=NtCurrentProcess; ClientId^.UniqueThread :=NtCurrentThread; BaseInitializeStack(InitialTeb,stack_base,stack_size); Stack:=stack_base+stack_size; Stack:=Pointer((ptruint(Stack) and (not $F))); BaseInitializeContext(Context, arg, start_func, Stack); {$ENDIF} {$IFDEF NT_THREAD} //Writeln('NtCreateThread'); Result:=NtCreateThread( @td^.td_handle, THREAD_ALL_ACCESS, nil, NtCurrentProcess, ClientId, Context, InitialTeb, True); {$ELSE} //Writeln('CreateThread'); td^.td_handle:=CreateThread(nil,4*1024,start_func,arg,CREATE_SUSPENDED,PDWORD(@td^.td_tid)^); if (td^.td_handle<>0) then begin Result:=0; end else begin Result:=GetLastError; end; {$ENDIF} if (Result=0) then begin {$IFDEF NT_THREAD} td^.td_tid:=DWORD(ClientId^.UniqueThread); {$ENDIF} //CSRSRV {$IFDEF CSRSRV} Result:=_CsrCreateRemoteThread(td^.td_handle,ClientId); {$ENDIF} //CSRSRV if (Result=0) then begin Result:=BaseQueryInfo(td); end; if (Result<>0) then begin cpu_thread_terminate(td); end; end; end; procedure cpu_thread_terminate(td:p_kthread); begin if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit; NtTerminateThread(td^.td_handle,0); NtClose(td^.td_handle); td^.td_handle:=0; td^.td_tid:=0; end; function cpu_sched_add(td:p_kthread):Integer; begin if (td=nil) then Exit(-1); if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit(-1); td^.td_state:=TDS_RUNNING; Result:=NtResumeThread(td^.td_handle,nil); if (Result<>0) then begin td^.td_state:=TDS_INACTIVE; end; end; procedure cpu_sched_throw; begin RtlExitUserThread(0); end; function cpu_thread_finished(td:p_kthread):Boolean; var R:DWORD; T:QWORD; begin Result:=True; if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit; T:=0; R:=NtWaitForSingleObject(td^.td_handle,False,@T); Result:=(R=STATUS_WAIT_0); if Result then begin NtClose(td^.td_handle); td^.td_handle:=0; td^.td_tid:=0; end; end; function cpuset_setaffinity(td:p_kthread;new:Ptruint):Integer; var info:SYSTEM_INFO; i,m,t,n:Integer; data:array[0..SizeOf(Ptruint)-1+7] of Byte; p_mask:PPtruint; begin if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit(-1); new:=new and $FF; info.dwNumberOfProcessors:=1; GetSystemInfo(info); if (info.dwNumberOfProcessors<8) then begin //remap m:=0; for i:=0 to 7 do begin t:=(new shr i) and 1; n:=(i mod info.dwNumberOfProcessors); m:=m or (t shl n); end; new:=m; end; td^.td_cpuset:=new; p_mask:=Align(@data,8); p_mask^:=new; Result:=NtSetInformationThread(td^.td_handle,ThreadAffinityMask,p_mask,SizeOf(Ptruint)); end; function cpu_set_base_priority(td:p_kthread;prio:Integer):Integer; begin if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit(-1); Case prio of 0..255:prio:= 16; 256..496:prio:= 2; 497..526:prio:= 1; 527..556:prio:= 0; 557..586:prio:=-1; 587..767:prio:=-2; else prio:=-16; end; Result:=NtSetInformationThread(td^.td_handle,ThreadBasePriority,@prio,SizeOf(Integer)); end; function cpu_set_priority(td:p_kthread;prio:Integer):Integer; begin if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit(-1); Case prio of 0..263:prio:=15; 264..299:prio:=14; 300..335:prio:=13; 336..371:prio:=12; 372..407:prio:=11; 408..443:prio:=10; 444..479:prio:= 9; 480..515:prio:= 8; 516..551:prio:= 7; 552..587:prio:= 6; 588..623:prio:= 5; 624..659:prio:= 4; 660..695:prio:= 3; 696..731:prio:= 2; 732..767:prio:= 1; else prio:= 1; end; Result:=NtSetInformationThread(td^.td_handle,ThreadPriority,@prio,SizeOf(Integer)); end; function cpu_thread_set_name(td:p_kthread;const name:shortstring):Integer; Const MAX_LEN=256; var W:array[0..MAX_LEN-1+7] of WideChar; P_W:PWideChar; data:array[0..SizeOf(UNICODE_STRING)-1+7] of Byte; P_UNAME:PUNICODE_STRING; L:DWORD; begin Result:=0; if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit; P_W:=Align(@W,8); FillWord(P_W^,MAX_LEN,0); L:=Utf8ToUnicode(P_W,MAX_LEN,@name[1],length(name)); P_UNAME:=Align(@data,8); P_UNAME^.Length :=L*SizeOf(WideChar); P_UNAME^.MaximumLength:=P_UNAME^.Length; P_UNAME^._Align :=0; P_UNAME^.Buffer :=P_W; Result:=NtSetInformationThread(td^.td_handle,ThreadNameInformation,P_UNAME,SizeOf(UNICODE_STRING)); end; function md_suspend(td:p_kthread):Integer; var count:ULONG; begin Result:=0; if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit; count:=0; NtSuspendThread(td^.td_handle,@count); Result:=count; end; function md_resume(td:p_kthread):Integer; var count:ULONG; begin Result:=0; if (td=nil) then Exit; if (td^.td_handle=0) or (td^.td_handle=THandle(-1)) then Exit; count:=0; NtResumeThread(td^.td_handle,@count); Result:=count; end; procedure main_wrapper; assembler; nostackframe; asm subq $40, %rsp .seh_stackalloc 40 .seh_endprologue call %gs:teb.jitcall nop addq $40, %rsp .seh_handler __FPC_default_handler,@except,@unwind end; procedure seh_wrapper_before(td:p_kthread;var func:Pointer); begin func:=@main_wrapper; end; procedure seh_wrapper_after(td:p_kthread;func:Pointer); begin td^.td_teb^.jitcall:=func; end; end.