gdb debugging tips
Displaying Julia variables
gdb içinde herhangi bir jl_value_t* nesnesi obj, aşağıdaki gibi görüntülenebilir:
(gdb) call jl_(obj)Nesne, julia oturumunda görüntülenecek, gdb oturumunda değil. Bu, Julia'nın C kodu tarafından işlenen nesnelerin türlerini ve değerlerini keşfetmek için yararlı bir yoldur.
Benzer şekilde, Julia'nın iç işleyişini (örneğin, compiler.jl) hata ayıklıyorsanız, obj'yi şu şekilde yazdırabilirsiniz:
ccall(:jl_, Cvoid, (Any,), obj)Bu, Julia'nın çıktı akışlarının başlatılma sırasından kaynaklanan sorunları aşmanın iyi bir yoludur.
Julia'nın flisp yorumlayıcısı value_t nesnelerini kullanır; bunlar call fl_print(fl_ctx, ios_stdout, obj) ile görüntülenebilir.
Useful Julia variables for Inspecting
Birçok değişkenin, tekil nesneler gibi, adreslerinin yazdırılması birçok hata için yararlı olabilir, ancak daha da yararlı olan bir dizi ek değişken vardır (tam liste için julia.h dosyasına bakın).
- (when in
jl_apply_generic)mfuncvejl_uncompress_ast(mfunc->def, mfunc->code):: çağrı yığını hakkında biraz bilgi edinmek için jl_linenovejl_filename:: bir testte nereden hata ayıklamaya başlayacağınızı (veya bir dosyanın ne kadarının ayrıştırıldığını) anlamak için.$1:: gerçekten bir değişken değil, ama son gdb komutunun (örneğinprint) sonucuna atıfta bulunmak için hala yararlı bir kısayol.jl_options:: bazen faydalı, çünkü başarıyla ayrıştırılan tüm komut satırı seçeneklerini listeler.jl_uv_stderr:: çünkü kim stdio ile etkileşimde bulunmayı sevmez ki
Useful Julia functions for Inspecting those variables
jl_print_task_backtraces(0):: gdb'ninthread apply all btveya lldb'ninthread backtrace allkomutuna benzer. Tüm mevcut görevler için geri izleri yazdırırken tüm iş parçacıklarını çalıştırır.jl_gdblookup($pc):: Mevcut işlevi ve satırı bulmak için.jl_gdblookupinfo($pc):: Mevcut yöntem örneği nesnesini aramak için.jl_gdbdumpcode(mi):: REPL düzgün çalışmadığındacode_typed/code_llvm/code_asm'nin tamamını dökmek için.jlbacktrace():: Hata yığınını stderr'ye dökmek için. Sadecerecord_backtrace()çağrıldıktan sonra kullanılabilir.jl_dump_llvm_value(Value*)::Value->dump()'ı gdb'de çağırmak için, burada yerel olarak çalışmıyor. Örneğin,f->linfo->functionObject,f->linfo->specFunctionObjectveto_function(f->linfo).jl_dump_llvm_module(Module*):: GDB'de yerel olarak çalışmadığı içinModule->dump()'ı çağırmak için.Type->dump():: sadece lldb'de çalışır. Not: lldb'nin çıktının üzerine istemini yazdırmasını önlemek için;1gibi bir şey ekleyin.jl_eval_string("expr"):: mevcut durumu değiştirmek veya sembolleri aramak için yan etkileri tetiklemek amacıyla kullanılır.jl_typeof(jl_value_t*):: bir Julia değerinin tür etiketini çıkarmak için (gdb'de öncemacro define jl_typeof jl_typeofçağırın veya tanımlamak için ilk argüman olarak kısa bir şey seçin, örneğinty)
Inserting breakpoints for inspection from gdb
gdb oturumunuzda jl_breakpoint içinde bir kesme noktası ayarlayın şöyle:
(gdb) break jl_breakpointSonra Julia kodunuzun içine jl_breakpoint çağrısını ekleyin.
ccall(:jl_breakpoint, Cvoid, (Any,), obj)obj herhangi bir değişken veya kesme noktasında erişilebilir olmasını istediğiniz bir demet olabilir.
jl_apply çerçevesine geri dönmek özellikle faydalıdır; buradan bir işlevin argümanlarını görüntülemek için, örneğin,
(gdb) call jl_(args[0])Başka yararlı bir çerçeve to_function(jl_method_instance_t *li, bool cstyle)'dir. jl_method_instance_t* argümanı, derleyiciye gönderilen nihai AST'ye bir referans içeren bir yapıdır. Ancak, bu noktada AST genellikle sıkıştırılmış olacaktır; AST'yi görüntülemek için jl_uncompress_ast çağrısını yapın ve ardından sonucu jl_'ye iletin:
#2 0x00007ffff7928bf7 in to_function (li=0x2812060, cstyle=false) at codegen.cpp:584
584 abort();
(gdb) p jl_(jl_uncompress_ast(li, li->ast))Inserting breakpoints upon certain conditions
Loading a particular file
Dosya sysimg.jl olarak adlandırılsın:
(gdb) break jl_load if strcmp(fname, "sysimg.jl")==0Calling a particular method
(gdb) break jl_apply_generic if strcmp((char*)(jl_symbol_name)(jl_gf_mtable(F)->name), "method_to_break")==0Bu işlev her çağrıda kullanıldığı için, bunu yaparsanız her şeyi 1000 kat daha yavaş hale getireceksiniz.
Dealing with signals
Julia düzgün çalışmak için birkaç sinyale ihtiyaç duyar. Profiler, örnekleme için SIGUSR2 kullanır ve çöp toplayıcı, iş parçacıkları senkronizasyonu için SIGSEGV kullanır. Profiler veya birden fazla iş parçacığı kullanan bir kodu hata ayıklıyorsanız, hata ayıklayıcının bu sinyalleri göz ardı etmesini isteyebilirsiniz çünkü normal işlemler sırasında çok sık tetiklenebilirler. Bunu GDB'de yapmak için komut (göz ardı etmek istediğiniz SIGSEGV yerine SIGUSR2 veya diğer sinyalleri koyun):
(gdb) handle SIGSEGV noprint nostop passİlgili LLDB komutu (işlem başlatıldıktan sonra):
(lldb) pro hand -p true -s false -n false SIGSEGVEğer çok iş parçacıklı kodda bir segfault'u hata ayıklıyorsanız, yalnızca gerçek segfault'u yakalamak için jl_critical_error üzerinde bir kesme noktası ayarlayabilirsiniz (Linux ve BSD'de sigdie_handler da işe yarayabilir).
Debugging during Julia's build process (bootstrap)
make sırasında meydana gelen hatalar özel bir işleme ihtiyaç duyar. Julia iki aşamada inşa edilir, sys0 ve sys.ji oluşturulur. Hata anında hangi komutların çalıştığını görmek için make VERBOSE=1 kullanın.
Bu yazının yazıldığı sırada, base dizininden sys0 aşamasında yapı hatalarını hata ayıklayabilirsiniz:
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys0 sysimg.jlusr/lib/julia/ dizinindeki tüm dosyaları silmeniz gerekebilir, bunun çalışması için.
sys.ji aşamasasını şu şekilde hata ayıklayabilirsiniz:
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys -J ../usr/lib/julia/sys0.ji sysimg.jlVarsayılan olarak, herhangi bir hata Julia'nın çıkmasına neden olur, gdb altında bile. Bir hatayı "eylemde" yakalamak için jl_error içinde bir kesme noktası ayarlayın (belirli türdeki hatalar için birkaç başka yararlı nokta da vardır, bunlar arasında: jl_too_few_args, jl_too_many_args ve jl_throw bulunmaktadır).
Bir hata yakalandığında, yararlı bir teknik, yığın üzerinde yukarı yürümek ve jl_apply ile ilgili çağrıyı inceleyerek fonksiyonu gözlemlemektir. Gerçek bir dünya örneği almak için:
Breakpoint 1, jl_throw (e=0x7ffdf42de400) at task.c:802
802 {
(gdb) p jl_(e)
ErrorException("auto_unbox: unable to determine argument type")
$2 = void
(gdb) bt 10
#0 jl_throw (e=0x7ffdf42de400) at task.c:802
#1 0x00007ffff65412fe in jl_error (str=0x7ffde56be000 <_j_str267> "auto_unbox:
unable to determine argument type")
at builtins.c:39
#2 0x00007ffde56bd01a in julia_convert_16886 ()
#3 0x00007ffff6541154 in jl_apply (f=0x7ffdf367f630, args=0x7fffffffc2b0, nargs=2) at julia.h:1281
...En son jl_apply çerçeve #3'te, bu yüzden oraya geri dönebiliriz ve julia_convert_16886 fonksiyonu için AST'ye bakabiliriz. Bu, convert'in bazı yöntemleri için benzersiz bir isimdir. Bu çerçevede f, bir jl_function_t* olduğundan, specTypes alanından tür imzasına, varsa, bakabiliriz:
(gdb) f 3
#3 0x00007ffff6541154 in jl_apply (f=0x7ffdf367f630, args=0x7fffffffc2b0, nargs=2) at julia.h:1281
1281 return f->fptr((jl_value_t*)f, args, nargs);
(gdb) p f->linfo->specTypes
$4 = (jl_tupletype_t *) 0x7ffdf39b1030
(gdb) p jl_( f->linfo->specTypes )
Tuple{Type{Float32}, Float64} # <-- type signature for julia_convert_16886Sonra, bu fonksiyonun AST'sine bakabiliriz:
(gdb) p jl_( jl_uncompress_ast(f->linfo, f->linfo->ast) )
Expr(:lambda, Array{Any, 1}[:#s29, :x], Array{Any, 1}[Array{Any, 1}[], Array{Any, 1}[Array{Any, 1}[:#s29, :Any, 0], Array{Any, 1}[:x, :Any, 0]], Array{Any, 1}[], 0], Expr(:body,
Expr(:line, 90, :float.jl)::Any,
Expr(:return, Expr(:call, :box, :Float32, Expr(:call, :fptrunc, :Float32, :x)::Any)::Any)::Any)::Any)::AnySon olarak, belki de en kullanışlı olanı, işlevin yeniden derlenmesini zorlayarak kod oluşturma sürecinde adım adım ilerleyebiliriz. Bunu yapmak için, jl_lamdbda_info_t* içindeki önbelleğe alınmış functionObject'ı temizleyin:
(gdb) p f->linfo->functionObject
$8 = (void *) 0x1289d070
(gdb) set f->linfo->functionObject = NULLSonra, faydalı bir yere bir kesme noktası ayarlayın (örneğin, emit_function, emit_expr, emit_call vb.) ve kod üretimini çalıştırın:
(gdb) p jl_compile(f)
... # your breakpoint hereDebugging precompilation errors
Modül ön derlemesi, her modülü ön derlemek için ayrı bir Julia süreci başlatır. Bir ön derleme işçisinde bir kesme noktası ayarlamak veya hataları yakalamak, işçiye bir hata ayıklayıcı bağlamayı gerektirir. En kolay yaklaşım, hata ayıklayıcının belirli bir adı eşleşen yeni süreç başlatmalarını izlemesini ayarlamaktır. Örneğin:
(gdb) attach -w -n julia-debugveya:
(lldb) process attach -w -n julia-debugSonra önceden derlemeyi başlatmak için bir betik/komut çalıştırın. Daha önce açıklandığı gibi, belirli dosya yükleme olaylarını yakalamak ve hata ayıklama penceresini daraltmak için ana süreçte koşullu kesme noktaları kullanın. (bazı işletim sistemleri, ana süreçten her fork'u takip etmek gibi alternatif yaklaşımlar gerektirebilir)
Mozilla's Record and Replay Framework (rr)
Julia artık Mozilla'nın hafif kayıt ve deterministik hata ayıklama çerçevesi rr ile kutudan çıkar çıkmaz çalışıyor. Bu, bir yürütmenin izini deterministik olarak yeniden oynamanızı sağlar. Yeniden oynatılan yürütmenin adres alanları, kayıt içeriği, sistem çağrısı verileri vb. her çalıştırmada tam olarak aynıdır.
Gerekli olan rr'nin en son sürümü (3.1.0 veya daha yüksek)dır.
Reproducing concurrency bugs with rr
rr, varsayılan olarak tek iş parçacıklı bir makineyi simüle eder. Eşzamanlı kodu hata ayıklamak için rr record --chaos kullanabilirsiniz; bu, rr'nin rastgele seçilen bir ila sekiz çekirdek arasında simülasyon yapmasına neden olur. Bu nedenle, JULIA_NUM_THREADS=8 ayarlamak ve kodunuzu rr altında yeniden çalıştırmak isteyebilirsiniz, ta ki hatanızı yakalayana kadar.