unit subr_backtrace; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses mqueue, kern_thr; procedure print_frame(var f:text;frame:Pointer); procedure print_backtrace(var f:text;rip,rbp:Pointer;skipframes:sizeint); procedure print_backtrace_td(var f:text); procedure print_error_td(const str:shortstring); implementation uses vmparam, systm, sys_bootparam, kern_named_id, subr_dynlib, elf_nid_utils, ps4libdoc; function IS_TRAP_FUNC(rip:qword):Boolean; external; function IS_JIT_FUNC (rip:qword):Boolean; external; function CaptureBacktrace(td:p_kthread;rbp:PPointer;skipframes,count:sizeint;frames:PCodePointer):sizeint; label _next; var adr:Pointer; procedure push; inline; begin if (skipframes<>0) then begin Dec(skipframes); end else if (count<>0) then begin frames[0]:=adr; Dec(count); Inc(frames); Inc(Result); end; end; begin Result:=0; while (rbp<>nil) and (rbp<>Pointer(-1)) and (count<>0) do begin adr:=fuptr(rbp[1]); rbp:=fuptr(rbp[0]); _next: if (adr<>nil) then begin push; end else begin Break; end; if (td<>nil) and (IS_TRAP_FUNC(QWORD(adr)) or IS_JIT_FUNC(QWORD(adr))) then begin adr:=Pointer(td^.td_frame.tf_rip); rbp:=Pointer(td^.td_frame.tf_rbp); goto _next; end; end; end; type TLQRec=record Base :Pointer; Addr :Pointer; LastAdr:Pointer; LastNid:QWORD; end; Function trav_proc(h_entry:p_sym_hash_entry;var r:TLQRec):Integer; var adr:Pointer; begin Result:=0; adr:=r.Base+fuptr(h_entry^.sym.st_value); if (adr<=r.Addr) then if (adr>r.LastAdr) then begin r.LastAdr:=adr; r.LastNid:=fuptr(h_entry^.nid); Result:=1; end; end; Function find_proc_lib_entry(lib_entry:p_Lib_Entry;var r:TLQRec):Integer; var h_entry:p_sym_hash_entry; begin Result:=0; h_entry:=fuptr(lib_entry^.syms.tqh_first); while (h_entry<>nil) do begin Result:=Result+trav_proc(h_entry,r); h_entry:=fuptr(h_entry^.link.tqe_next); end; end; Function find_proc_obj(obj:p_lib_info;var r:TLQRec):Integer; var lib_entry:p_Lib_Entry; begin Result:=0; lib_entry:=fuptr(obj^.lib_table.tqh_first); while (lib_entry<>nil) do begin Result:=Result+find_proc_lib_entry(lib_entry,r); lib_entry:=fuptr(lib_entry^.link.tqe_next); end; end; type TDynlibLineInfo=record func :shortstring; source :t_id_name; base_addr:ptruint; func_addr:ptruint; end; function GetDynlibLineInfo(addr:ptruint;var info:TDynlibLineInfo):boolean; var obj:p_lib_info; r:TLQRec; adr:QWORD; lock:Boolean; begin Result:=False; lock:=False; if not dynlibs_locked then begin dynlibs_lock; lock:=True; end; obj:=find_obj_by_addr_safe(Pointer(addr)); if (obj<>nil) then begin r.LastAdr:=nil; r.LastNid:=0; r.Addr:=Pointer(addr); r.Base:=fuptr(obj^.map_base); info.base_addr:=QWORD(r.Base); info.source:=Default(t_id_name); copyin_nofault(@obj^.name,@info.source,SizeOf(t_id_name)); if (info.source='') then begin copyin_nofault(obj^.lib_path,@info.source,SizeOf(t_id_name)); end; if (find_proc_obj(obj,r)<>0) then begin info.func:=ps4libdoc.GetFunctName(r.LastNid); if (info.func='Unknow') then begin info.func:=EncodeValue64(r.LastNid); end; info.func_addr:=QWORD(r.LastAdr); Result:=True; end else begin info.func_addr:=0; adr:=QWORD(obj^.init_proc_addr); if (adr<=QWORD(r.Addr)) then if (adr>info.func_addr) then begin info.func:='dtInit'; info.func_addr:=adr; Result:=True; end; adr:=QWORD(obj^.fini_proc_addr); if (adr<=QWORD(r.Addr)) then if (adr>info.func_addr) then begin info.func:='dtFini'; info.func_addr:=adr; Result:=True; end; adr:=QWORD(obj^.entry_addr); if (adr<=QWORD(r.Addr)) then if (adr>info.func_addr) then begin info.func:='Entry'; info.func_addr:=adr; Result:=True; end; end; end; if lock then begin dynlibs_unlock; end; end; function find_obj_by_handle(id:Integer):p_lib_info; var obj:p_lib_info; begin Result:=nil; obj:=TAILQ_FIRST(@dynlibs_info.obj_list); while (obj<>nil) do begin if (obj^.id=id) then begin Exit(obj); end; // obj:=TAILQ_NEXT(obj,@obj^.link); end; end; procedure print_frame(var f:text;frame:Pointer); var info:TDynlibLineInfo; offset1:QWORD; offset2:QWORD; begin if is_guest_addr(ptruint(frame)) then begin info:=Default(TDynlibLineInfo); if GetDynlibLineInfo(ptruint(frame),info) then begin offset1:=QWORD(frame)-QWORD(info.base_addr); offset2:=QWORD(frame)-QWORD(info.func_addr); Writeln(f,' offset $',HexStr(offset1 shr 24,5),'|',HexStr(offset1,6),' ',info.source,':',info.func,'+$',HexStr(offset2,6)); end else begin if (info.base_addr<>0) then begin offset1:=QWORD(frame)-QWORD(info.base_addr); Writeln(f,' offset $',HexStr(offset1 shr 24,5),'|',HexStr(offset1,6),' ',info.source); end else begin Writeln(f,' $',HexStr(frame),' ',info.source); end; end; end else if (BackTraceStrFunc<>nil) then begin Writeln(f,BackTraceStrFunc(frame)); end else begin Writeln(f,' $',HexStr(frame)); end; end; procedure print_backtrace(var f:text;rip,rbp:Pointer;skipframes:sizeint); var i,count:sizeint; frames:array [0..255] of codepointer; begin count:=max_frame_dump; count:=30; if (skipframes>0) then begin Dec(skipframes); end else begin print_frame(f,rip); end; count:=CaptureBacktrace(curkthread,rbp,skipframes,count,@frames[0]); if (count<>0) then for i:=0 to count-1 do begin print_frame(f,frames[i]); end; end; procedure print_backtrace_td(var f:text); var td:p_kthread; begin td:=curkthread; if (td=nil) then Exit; // print_backtrace(stderr,Pointer(td^.td_frame.tf_rip),Pointer(td^.td_frame.tf_rbp),0); end; procedure print_error_td(const str:shortstring); begin thread_suspend_all(p_host_ipc_td); Writeln(StdErr,str); p_host_ipc.error(str); print_backtrace_td(StdErr); end; end.