unit kern_thread; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses sysutils, mqueue, kern_param, kern_thr, ucontext, signal, signalvar, time, rtprio, hamt; procedure thread_reap(); function thread_alloc(pages:Word):p_kthread; procedure thread_free(td:p_kthread); function sys_thr_new (_param:Pointer;_size:Integer):Integer; function sys_thr_create (ctx:Pointer;id:PDWORD;flags:Integer):Integer; function sys_thr_self (id:PDWORD):Integer; procedure sys_thr_exit (state:PDWORD); function sys_thr_kill (id,sig:Integer):Integer; function sys_thr_kill2 (pid,id,sig:Integer):Integer; function sys_thr_suspend (timeout:Pointer):Integer; function sys_thr_wake (id:DWORD):Integer; function sys_thr_set_name(id:DWORD;pname:PChar):Integer; function sys_thr_get_name(id:DWORD;pname:PChar):Integer; function sys_amd64_set_fsbase(base:Pointer):Integer; function sys_amd64_get_fsbase(base:PPointer):Integer; function sys_amd64_set_gsbase(base:Pointer):Integer; function sys_amd64_get_gsbase(base:PPointer):Integer; procedure thread_inc_ref(td:p_kthread); procedure thread_dec_ref(td:p_kthread); procedure thread_lock_assert(td:p_kthread); procedure thread_lock (td:p_kthread); procedure thread_unlock (td:p_kthread); function tdfind(tid:DWORD):p_kthread; procedure threads_lock; function threads_trylock:Boolean; procedure threads_unlock; procedure KernSetThreadDebugName(newtd:p_kthread;prefix:PChar); function SIGPENDING(td:p_kthread):Boolean; procedure threadinit; //SYSINIT function kthread_add (func,arg:Pointer;newtdp:pp_kthread;pages:Word;name:PChar):Integer; procedure kthread_exit(); procedure thread_suspend_all(exclude:p_kthread); procedure thread_resume_all (exclude:p_kthread); var init_tty_cb:Tprocedure; implementation uses errno, systm, kern_mtx, md_context, machdep, md_thread, kern_rwlock, kern_sig, kern_proc, kern_rangelock, sched_ule, sys_sleepqueue; // procedure umtx_thread_init(td:p_kthread); external; procedure umtx_thread_exit(td:p_kthread); external; procedure umtx_thread_fini(td:p_kthread); external; function kern_umtx_wake (td:p_kthread;umtx:Pointer;n_wake,priv:Integer):Integer; external; function umtx_copyin_timeout(addr:Pointer;tsp:p_timespec):Integer; external; procedure jit_ctx_free(td:p_kthread); external; // var p_threads :TAILQ_HEAD=(tqh_first:nil;tqh_last:@p_threads.tqh_first); p_numthreads:Integer=0; tidhashtbl :TSTUB_HAMT32; tidhash_lock:Pointer=nil; zombie_threads:TAILQ_HEAD=(tqh_first:nil;tqh_last:@zombie_threads.tqh_first); zombie_lock :Pointer=nil; const max_threads_per_proc=1500; function SIGPENDING(td:p_kthread):Boolean; public; begin Result:=SIGNOTEMPTY(@td^.td_sigqueue.sq_signals) and sigsetmasked(@td^.td_sigqueue.sq_signals,@td^.td_sigmask); end; function get_p_threads:Pointer; public; begin Result:=@p_threads; end; // function _thread_null(parameter:pointer):ptrint; register; begin Result:=0; end; var _t_init:Integer=0; procedure threadinit; begin if (System.InterlockedExchange(_t_init,1)<>0) then Exit; //init internals BeginThread(@_thread_null); end; { * Place an unused thread on the zombie list. * Use the slpq as that must be unused by now. } procedure thread_zombie(td:p_kthread); begin rw_wlock(zombie_lock); TAILQ_INSERT_HEAD(@zombie_threads,td,@td^.td_zombie); rw_wunlock(zombie_lock); end; { * Reap zombie resources. } procedure thread_reap(); var td_first,td_next:p_kthread; begin if rw_try_wlock(zombie_lock) then begin if (not TAILQ_EMPTY(@zombie_threads)) then begin td_first:=TAILQ_FIRST(@zombie_threads); while (td_first<>nil) do begin td_next:=TAILQ_NEXT(td_first,@td_first^.td_slpq); if cpu_thread_finished(td_first) then begin //Writeln('thread_reap:',HexStr(td_first)); TAILQ_REMOVE(@zombie_threads,td_first,@td_first^.td_zombie); thread_free(td_first); end else begin Break; end; td_first:=td_next; end; end; rw_wunlock(zombie_lock); end; end; function thread_alloc(pages:Word):p_kthread; begin thread_reap(); Result:=cpu_thread_alloc(pages); mtx_init(Result^.tdq_lock,'tdq_lock'); Result^.td_lock:=@Result^.tdq_lock; Result^.td_state:=TDS_INACTIVE; Result^.td_lend_user_pri:=PRI_MAX; Result^.td_sleepqueue:=sleepq_alloc(); umtx_thread_init(Result); end; procedure thread_free(td:p_kthread); begin mtx_destroy(td^.tdq_lock); sleepq_free(td^.td_sleepqueue); rlqentry_free(td^.td_rlqe); umtx_thread_fini(td); cpu_thread_free(td); end; procedure thread_inc_ref(td:p_kthread); public; begin System.InterlockedIncrement(td^.td_ref); end; procedure thread_dec_ref(td:p_kthread); public; begin if (System.InterlockedDecrement(td^.td_ref)=0) then begin thread_free_local_buffer(td); thread_zombie(td); end; end; procedure thread_lock_assert(td:p_kthread); public; begin mtx_assert(td^.td_lock^); end; procedure thread_lock(td:p_kthread); public; var m:p_mtx; begin repeat m:=td^.td_lock; Assert(m<>nil,'thread_lock'); mtx_lock(m^); if (m=td^.td_lock) then begin Break; end; mtx_unlock(m^); until false; end; procedure thread_unlock(td:p_kthread); public; begin mtx_unlock(td^.td_lock^); end; procedure thread_link(td:p_kthread); begin td^.td_state:=TDS_INACTIVE; td^.td_flags:=TDF_INMEM; sigqueue_init(@td^.td_sigqueue); System.InterlockedIncrement(p_numthreads); end; procedure thread_unlink(td:p_kthread); begin System.InterlockedDecrement(p_numthreads) end; function tdfind(tid:DWORD):p_kthread; public; Var data:PPointer; begin Result:=nil; rw_rlock(tidhash_lock); data:=HAMT_search32(@tidhashtbl,tid); if (data<>nil) then begin Result:=data^; end; if (Result<>nil) then begin thread_inc_ref(Result); end; rw_runlock(tidhash_lock); end; procedure tidhash_add(td:p_kthread); var data:PPointer; begin rw_wlock(tidhash_lock); TAILQ_INSERT_HEAD(@p_threads, td, @td^.td_plist); data:=HAMT_insert32(@tidhashtbl,td^.td_tid,td); if (data<>nil) then begin if (data^=td) then begin thread_inc_ref(td); end; end; rw_wunlock(tidhash_lock); end; procedure tidhash_remove(td:p_kthread); var data:Pointer; begin data:=nil; rw_wlock(tidhash_lock); TAILQ_REMOVE(@p_threads, td, @td^.td_plist); HAMT_delete32(@tidhashtbl,td^.td_tid,@data); rw_wunlock(tidhash_lock); if (data=td) then begin thread_dec_ref(td); end; end; procedure threads_lock; public; begin rw_wlock(tidhash_lock); end; function threads_trylock:Boolean; public; begin Result:=rw_try_wlock(tidhash_lock); end; procedure threads_unlock; public; begin rw_wunlock(tidhash_lock); end; procedure KernSetThreadDebugName(newtd:p_kthread;prefix:PChar); var //td:p_kthread; //backup:array[0..1] of QWORD; name:shortstring; begin name:=shortstring(prefix)+shortstring(newtd^.td_name)+':'+IntToStr(newtd^.td_tid); cpu_thread_set_name(newtd,name); { td:=curkthread; if (td<>nil) then begin //prevent bullshit in ntdll:__chkstk backup[0]:=PQWORD(td^.td_kstack.stack)[-1]; backup[1]:=PQWORD(td^.td_kstack.stack)[-2]; PQWORD(td^.td_kstack.stack)[-1]:=0; PQWORD(td^.td_kstack.stack)[-2]:=0; end; SetThreadDebugName(newtd^.td_tid,name); if (td<>nil) then begin //restore stack PQWORD(td^.td_kstack.stack)[-1]:=backup[0]; PQWORD(td^.td_kstack.stack)[-2]:=backup[1]; end; } end; procedure before_start(td:p_kthread); begin td:=curkthread; InitThread(td^.td_ustack.stack-td^.td_ustack.sttop); Set8087CW(__INITIAL_FPUCW__); SetMXCSR (__INITIAL_MXCSR__); if (init_tty_cb<>nil) then begin init_tty_cb(); end; //switch set_pcb_flags(curkthread,PCB_IS_JIT); //force JIT mode ipi_sigreturn; Writeln(stderr,'I''m a teapot!'); end; procedure before_start_kern(td:p_kthread); type t_cb=procedure(arg:QWORD); begin td:=curkthread; InitThread(td^.td_ustack.stack-td^.td_ustack.sttop); Set8087CW(__INITIAL_FPUCW__); SetMXCSR (__INITIAL_MXCSR__); if (init_tty_cb<>nil) then begin init_tty_cb(); end; //call t_cb(td^.td_frame.tf_rip)(td^.td_frame.tf_rdi); kthread_exit(); end; procedure thread0_param(td:p_kthread); begin td^.td_base_user_pri:=700; td^.td_lend_user_pri:=1023; td^.td_base_pri :=68; td^.td_priority :=68; td^.td_pri_class :=10; td^.td_user_pri :=700; end; function create_thread(td :p_kthread; //calling thread ctx :p_mcontext_t; start_func:Pointer; arg :Pointer; stack_base:Pointer; stack_size:QWORD; tls_base :Pointer; child_tid :PDWORD; parent_tid:PDWORD; rtp :p_rtprio; name :PChar ):Integer; var newtd:p_kthread; stack:stack_t; wrap:Pointer; n:Integer; begin Result:=0; if (p_numthreads>=max_threads_per_proc) then begin Exit(EPROCLIM); end; writeln('create_thread[',name,']'#13#10, ' start_func:0x',HexStr(start_func),#13#10, ' arg :0x',HexStr(arg),#13#10, ' stack_base:0x',HexStr(stack_base),#13#10, ' stack_size:0x',HexStr(stack_size,16),#13#10, ' tls_base :0x',HexStr(tls_base) ); thread_reap(); if (rtp<>nil) then begin Case (rtp^._type and $fff7) of //RTP_PRIO_BASE RTP_PRIO_IDLE: begin if (rtp^._prio<>960) then Exit(EINVAL); end; RTP_PRIO_NORMAL: begin if (rtp^._prio>959) then Exit(EINVAL); end; PRI_REALTIME: begin if (rtp^._prio>767) then Exit(EINVAL); end; else Exit(EINVAL) end; end; if (stack_size<$1000) then begin if (ctx<>nil) then begin stack_size:=$1000; end else begin Exit(EINVAL); end; end; if (ptruint(stack_base)<$1000) then begin if (ctx<>nil) then begin stack_base:=Pointer(ctx^.mc_rsp-stack_size); //re check if (ptruint(stack_base)<$1000) then begin Exit(EINVAL); end; end else begin Exit(EINVAL); end; end; newtd:=thread_alloc(0); if (newtd=nil) then Exit(ENOMEM); thread0_param(newtd); //user stack newtd^.td_ustack.stack:=stack_base+stack_size; newtd^.td_ustack.sttop:=stack_base; //user stack //seh wrapper wrap:=@before_start; seh_wrapper_before(newtd,wrap); //seh wrapper stack.ss_sp :=newtd^.td_kstack.sttop; stack.ss_size:=(ptruint(newtd^.td_kstack.stack)-ptruint(newtd^.td_kstack.sttop)); n:=cpu_thread_create(newtd, stack.ss_sp, stack.ss_size, wrap, Pointer(newtd)); if (n<>0) then begin thread_free(newtd); Exit(EINVAL); end; writeln('create_thread[',name,']'#13#10, ' newtd:0x',HexStr(newtd),#13#10, ' tid:',newtd^.td_tid ); if (child_tid<>nil) then begin n:=suword32(child_tid^,newtd^.td_tid); if (n<>0) then begin cpu_thread_terminate(newtd); thread_free(newtd); Exit(EFAULT); end; end; if (parent_tid<>nil) then begin n:=suword32(parent_tid^,newtd^.td_tid); if (n<>0) then begin cpu_thread_terminate(newtd); thread_free(newtd); Exit(EFAULT); end; end; if (ctx<>nil) then begin // old way to set user context n:=set_mcontext(newtd,ctx); if (n<>0) then begin cpu_thread_terminate(newtd); thread_free(newtd); Exit(n); end; end else begin // Set up our machine context. stack.ss_sp :=stack_base; stack.ss_size:=stack_size; // Set upcall address to user thread entry function. cpu_set_upcall_kse(newtd,start_func,arg,@stack); // Setup user TLS address and TLS pointer register. cpu_set_fsbase(newtd,tls_base); Writeln('set_fsbase=0x',HexStr(tls_base)); //init FPU fpuinit(newtd); end; //seh wrapper wrap:=@before_start; seh_wrapper_after(newtd,wrap); //seh wrapper if (td<>nil) then begin newtd^.td_sigmask:=td^.td_sigmask; end; thread_link(newtd); if (name<>nil) then begin Move(name^,newtd^.td_name,SizeOf(t_td_name)); end; KernSetThreadDebugName(newtd,'ps4:'); sched_fork_thread(td,newtd); tidhash_add(newtd); if (rtp<>nil) then begin if (td=nil) then begin rtp_to_pri(rtp,newtd); sched_prio(newtd,newtd^.td_user_pri); end else if (td^.td_pri_class<>PRI_TIMESHARE) then begin rtp_to_pri(rtp,newtd); sched_prio(newtd,newtd^.td_user_pri); end; end else begin sched_prio(newtd,newtd^.td_user_pri); end; n:=cpu_sched_add(newtd); if (n<>0) then begin cpu_thread_terminate(newtd); thread_free(newtd); Exit(EFAULT); end; end; function kthread_add(func,arg:Pointer;newtdp:pp_kthread;pages:Word;name:PChar):Integer; public; var td:p_kthread; newtd:p_kthread; stack:stack_t; wrap:Pointer; n:Integer; begin Result:=0; threadinit; if (func=nil) or (newtdp=nil) then begin Exit(EINVAL); end; td:=curkthread; if (td<>nil) then begin thread_reap(); end; newtd:=thread_alloc(pages); if (newtd=nil) then Exit(ENOMEM); thread0_param(newtd); stack.ss_sp :=newtd^.td_kstack.sttop; stack.ss_size:=(ptruint(newtd^.td_kstack.stack)-ptruint(newtd^.td_kstack.sttop)); //user stack newtd^.td_ustack.stack:=newtd^.td_kstack.stack; newtd^.td_ustack.sttop:=newtd^.td_kstack.sttop; //user stack //seh wrapper wrap:=@before_start_kern; seh_wrapper_before(newtd,wrap); //seh wrapper n:=cpu_thread_create(newtd, stack.ss_sp, stack.ss_size, wrap, Pointer(newtd)); if (n<>0) then begin Writeln(StdErr,'failed cpu_thread_create:0x',HexStr(n,8)); thread_free(newtd); Exit(EINVAL); end; cpu_set_upcall_kse(newtd,func,arg,@stack); //init FPU fpuinit(newtd); //seh wrapper wrap:=@before_start_kern; seh_wrapper_after(newtd,wrap); //seh wrapper if (td<>nil) then begin newtd^.td_sigmask:=td^.td_sigmask; end; thread_link(newtd); newtd^.td_pflags:=newtd^.td_pflags or TDP_KTHREAD; if (name<>nil) then begin Move(name^,newtd^.td_name,SizeOf(t_td_name)); end; KernSetThreadDebugName(newtd,'kern:'); sched_fork_thread(td,newtd); tidhash_add(newtd); thread_inc_ref(newtd); n:=cpu_sched_add(newtd); if (n<>0) then begin cpu_thread_terminate(newtd); thread_dec_ref(newtd); thread_free(newtd); Exit(EFAULT); end; newtdp^:=newtd; end; function kern_thr_new(td:p_kthread;param:p_thr_param):Integer; var rtp:t_rtprio; rtpp:p_rtprio; name:t_td_name; begin Result:=0; rtpp:=nil; if (param^.rtp<>nil) then begin Result:=copyin(param^.rtp,@rtp,Sizeof(t_rtprio)); if (Result<>0) then Exit; rtpp:=@rtp; end; name:=Default(t_td_name); if (param^.name<>nil) then begin Result:=copyinstr(param^.name,@name,32,nil); if (Result<>0) then Exit; end; Result:=create_thread(td, nil, param^.start_func, param^.arg, param^.stack_base, param^.stack_size, param^.tls_base, param^.child_tid, param^.parent_tid, rtpp, @name); end; function sys_thr_new(_param:Pointer;_size:Integer):Integer; var param:thr_param; begin if (_size<0) or (_size>Sizeof(thr_param)) then Exit(EINVAL); param:=Default(thr_param); Result:=copyin(_param,@param,_size); if (Result<>0) then Exit; Result:=kern_thr_new(curkthread,@param); end; function sys_thr_create(ctx:Pointer;id:PDWORD;flags:Integer):Integer; var _ctx:ucontext_t; begin Result:=copyin(ctx,@_ctx,sizeof(ucontext_t)); if (Result<>0) then Exit; //flags ignored Result:=create_thread(curkthread, @_ctx.uc_mcontext, nil, nil, nil, 0, nil, id, nil, nil, nil); end; procedure thread_exit; var td:p_kthread; begin td:=curkthread; if (td=nil) then Exit; ASSERT(TAILQ_EMPTY(@td^.td_sigqueue.sq_list),'signal pending'); td^.td_state:=TDS_INACTIVE; tidhash_remove(td); thread_unlink(td); umtx_thread_exit(td); jit_ctx_free(td); //free thread_dec_ref(td); DoneThread; cpu_sched_throw; end; function sys_thr_self(id:PDWORD):Integer; var td:p_kthread; begin if (id=nil) then Exit(EINVAL); td:=curkthread; if (td=nil) then Exit(EFAULT); Result:=suword32(id^,td^.td_tid); if (Result<>0) then Exit(EFAULT); Result:=0; end; procedure sys_thr_exit(state:PDWORD); var td:p_kthread; begin td:=curkthread; if (td=nil) then Exit; if (state<>nil) then begin //join wakeup suword32(state^, 1); kern_umtx_wake(td,Pointer(state),High(Integer),0); end; tdsigcleanup(td); //thread_stopped(p); thread_reap(); thread_exit(); // NOTREACHED end; procedure kthread_exit(); public; begin thread_reap(); thread_exit(); // NOTREACHED end; procedure thread_suspend_all(exclude:p_kthread); public; var td,ttd:p_kthread; begin td:=curkthread; threads_lock; ttd:=TAILQ_FIRST(@p_threads); while (ttd<>nil) do begin if (ttd<>td) and (ttd<>exclude) then begin md_suspend(ttd); end; ttd:=TAILQ_NEXT(ttd,@ttd^.td_plist) end; threads_unlock; end; procedure thread_resume_all(exclude:p_kthread); public; var td,ttd:p_kthread; begin td:=curkthread; threads_lock; ttd:=TAILQ_FIRST(@p_threads); while (ttd<>nil) do begin if (ttd<>td) and (ttd<>exclude) then begin md_resume(ttd); end; ttd:=TAILQ_NEXT(ttd,@ttd^.td_plist) end; threads_unlock; end; function sys_thr_kill(id,sig:Integer):Integer; var td,ttd:p_kthread; ksi:ksiginfo_t; begin td:=curkthread; thread_reap(); ksiginfo_init(@ksi); ksi.ksi_info.si_signo:=sig; ksi.ksi_info.si_code :=SI_LWP; ksi.ksi_info.si_pid :=p_proc.p_pid; if (id=-1) then //all begin if (sig<>0) and (not _SIG_VALID(sig)) then begin Result:=EINVAL; end else begin Result:=ESRCH; PROC_LOCK; threads_lock; ttd:=TAILQ_FIRST(@p_threads); while (ttd<>nil) do begin if (ttd<>td) then begin Result:=0; if (sig=0) then Break; tdksignal(ttd,sig,@ksi); end; ttd:=TAILQ_NEXT(ttd,@ttd^.td_plist) end; threads_unlock; PROC_UNLOCK; end; end else begin Result:=0; td:=tdfind(DWORD(id)); if (td=nil) then Exit(ESRCH); if (sig=0) then begin // end else if (not _SIG_VALID(sig)) then Result:=EINVAL else begin PROC_LOCK; tdksignal(td,sig,@ksi); PROC_UNLOCK; end; thread_dec_ref(td); end; end; function sys_thr_kill2(pid,id,sig:Integer):Integer; begin if (pid<>0) and (pid<>p_proc.p_pid) then begin Exit(ESRCH); end; Result:=sys_thr_kill(id,sig); end; function kern_thr_suspend(td:p_kthread;tsp:p_timespec):Integer; var tv:Int64; begin Result:=0; thread_reap(); if ((td^.td_pflags and TDP_WAKEUP)<>0) then begin td^.td_pflags:=td^.td_pflags and (not TDP_WAKEUP); Exit(0); end; tv:=0; if (tsp<>nil) then begin if (tsp^.tv_sec=0) and (tsp^.tv_nsec=0) then begin Result:=EWOULDBLOCK; end else begin tv:=TIMESPEC_TO_UNIT(tsp); tv:=tvtohz(tv); end; end; PROC_LOCK; if (Result=0) and ((td^.td_flags and TDF_THRWAKEUP)=0) then begin Result:=msleep(td,@p_proc.p_mtx,PCATCH,'lthr',tv); end; if ((td^.td_flags and TDF_THRWAKEUP)<>0) then begin thread_lock(td); td^.td_flags:=td^.td_flags and (not TDF_THRWAKEUP); thread_unlock(td); PROC_UNLOCK; Exit(0); end; PROC_UNLOCK; if (Result=EWOULDBLOCK) then begin Result:=ETIMEDOUT; end else if (Result=ERESTART) then begin if (tv<>0) then begin Result:=EINTR; end; end; end; function sys_thr_suspend(timeout:Pointer):Integer; var td:p_kthread; ts:timespec; tsp:p_timespec; begin td:=curkthread; if (td=nil) then Exit(-1); tsp:=nil; if (timeout<>nil) then begin Result:=umtx_copyin_timeout(timeout,@ts); if (Result<>0) then Exit; tsp:=@ts; end; Result:=kern_thr_suspend(td,tsp); end; function sys_thr_wake(id:DWORD):Integer; var td:p_kthread; begin Result:=0; td:=curkthread; thread_reap(); if (td<>nil) then if (id=td^.td_tid) then begin td^.td_pflags:=td^.td_pflags or TDP_WAKEUP; Exit(0); end; td:=tdfind(DWORD(id)); if (td=nil) then Exit(ESRCH); thread_lock(td); td^.td_flags:=td^.td_flags or TDF_THRWAKEUP; thread_unlock(td); wakeup(td); //broadcast thread_dec_ref(td); end; function sys_thr_set_name(id:DWORD;pname:PChar):Integer; var td:p_kthread; name:t_td_name; begin Result:=0; thread_reap(); name:=Default(t_td_name); if (pname<>nil) then begin Result:=copyinstr(pname,@name,32,nil); if (Result<>0) then Exit; end; if (Integer(id)=-1) then begin kern_proc.p_proc.p_comm:=name; Exit; end; td:=tdfind(DWORD(id)); if (td=nil) then Exit(ESRCH); thread_lock(td); td^.td_name:=name; KernSetThreadDebugName(td,'ps4:'); thread_unlock(td); thread_dec_ref(td); end; function strnlen(s:PChar;maxlen:ptrint):ptrint; var len:size_t; begin For len:=0 to maxlen-1 do begin if (s^=#0) then Break; Inc(s); end; Exit(len); end; function sys_thr_get_name(id:DWORD;pname:PChar):Integer; var td:p_kthread; name:t_td_name; len:ptrint; begin Result:=0; thread_reap(); td:=tdfind(DWORD(id)); if (td=nil) then begin //sceSblACMgrIsSystemUcred Exit(ESRCH); end; thread_lock(td); name:=td^.td_name; thread_unlock(td); thread_dec_ref(td); len:=strnlen(name,31); Result:=copyout(@name,pname,len+1); end; function sys_amd64_set_fsbase(base:Pointer):Integer; var td:p_kthread; begin Result:=0; td:=curkthread; if (td=nil) then Exit(-1); cpu_set_fsbase(td,base); end; function sys_amd64_get_fsbase(base:PPointer):Integer; var td:p_kthread; begin Result:=0; td:=curkthread; if (td=nil) or (base=nil) then Exit(-1); base^:=td^.pcb_fsbase; end; function sys_amd64_set_gsbase(base:Pointer):Integer; var td:p_kthread; begin Result:=0; td:=curkthread; if (td=nil) then Exit(-1); cpu_set_gsbase(td,base); end; function sys_amd64_get_gsbase(base:PPointer):Integer; var td:p_kthread; begin Result:=0; td:=curkthread; if (td=nil) or (base=nil) then Exit(-1); base^:=td^.pcb_gsbase; end; end.