GDB调试技巧
显示Julia变量
在’gdb`中,任何对象(obj
)`jl_value_t*'都可以显示如下。
(gdb) call jl_(obj)
对象将显示在’julia’会话中,而不是gdb会话中。 这是确定由Julia的C代码操作的对象的类型和值的好方法。
此外,如果您正在调试一些内部Julia组件(例如,compiler.jl
),可以使用函数输出一个对象(obj
)
ccall(:jl_, Cvoid, (Any,), obj)
这是解决由于julia输出流的初始化顺序而出现的问题的最佳选择。
Julia中的flisp解释器使用’value_t’对象。 它们可以使用`call fl_print(fl_ctx,ios_stdout,obj)'显示。
Julia用于检查的有用变量
虽然输出许多变量的地址,例如单个实例,对于许多故障可能很有用,但还有许多其他变量(有关完整列表,请参阅`julia’的描述。h')更相关。
-
(when in`jl_apply_generic')`mfunc`and'jl_uncompress_ast(mfunc->def,mfunc->code)':确定调用栈的一些信息
-
'jl_lineno`和’jl_filename':确定测试中应该从哪个行开始调试(或确定文件被分析的程度)
-
'$1':不完全是一个变量,但仍然是引用最后一个gdb命令结果的有用简写(例如,'print'`
-
'jl_options':有时很有用,因为它列出了已成功分析的所有命令行参数
-
'jl_uv_stderr':用作与stdio交互的替代方法
Julia用于检查这些变量的有用函数
-
'jl_print_task_backtraces(0)`:类似于gdb中的`thread apply all bt`或lldb中的`thread backtrace all'。
-
'jl_gdblookup(pc pc)':搜索当前函数和字符串。
-
'jl_gdblookupinfo`pc pc)':搜索当前方法实例对象。
-
'jl_gdbdumpcode`mi)`:当REPL不能正常工作时重置整个`code_typed/code_llvm/code_asm'。
-
'jlbacktrace()':将当前Julia backtrace堆栈保存到stderr。 它仅在调用`record_backtrace()'后使用。
-
'jl_dump_llvm_value(Value*)
:在gdb中调用函数
+Value→dump()',它最初不起作用。 例如`'+f->linfo->functionObject',+f→linfo→specFunctionObject+`和
+to_function(f→linfo)+'。 -
jl_dump_llvm_module(Module*)
:在gdb中调用函数'Module->dump()',它最初不起作用。 -
'Type->dump()
:仅在lldb中工作。 注意。 添加类似
;1’的内容以防止lldb在输出期间显示其窗口。 -
`jl_eval_string("expr")':调用副作用以更改当前状态或搜索字符。
-
'jl_typeof(jl_value_t*)':要提取Julia值类型标签(在gdb中,首先调用`macro define jl_typeof jl_typeof`或选择像`ty`这样的简短内容作为定义缩写的第一个参数)。
从gdb插入验证的断点
在’gdb’会话中,在`jl_breakpoint`函数中设置断点,如下所示。
(gdb) break jl_breakpoint
然后,在Julia的代码中,通过添加插入对`jl_breakpoint`函数的调用:
ccall(:jl_breakpoint, Cvoid, (Any,), obj)
'obj’可以是任何应该在断点处可用的变量或元组。
返回’jl_apply’框架特别有用,从中可以显示函数的参数,例如如下所示。
(gdb) call jl_(args[0])
另一个有用的帧是’to_function(jl_method_instance_t*li,bool cstyle)'。 `Jl_method_instance_t*'参数是一个结构,引用发送到编译器的最终AST树。 然而,AST通常在该阶段被压缩。 要查看AST,请调用’jl_uncompress_ast',然后将结果传递给’jl_'。
#2 0x00007ffff7928bf7 in to_function (li=0x2812060, cstyle=false) at codegen.cpp:584 584 abort(); (gdb) p jl_(jl_uncompress_ast(li, li->ast))
使用信号
Julia正常工作需要几个信号。 探查器使用`SIGUSR2’进行采样,垃圾收集器使用’SIGSEGV’进行线程同步。 如果您正在调试由探查器或多个线程使用的代码,则可以允许调试器忽略这些信号,因为它们可以在正常操作期间经常被激活。 为此,GDB使用以下命令(将`SIGSEGV`替换为`SIGUSR2`或其他需要忽略的信号)。
(gdb) handle SIGSEGV noprint nostop pass
相应的LLDB命令(在进程启动后执行)如下所示。
(lldb) pro hand -p true -s false -n false SIGSEGV
如果您正在使用流式代码调试紧急情况,则可以在`jl_critical_error`(`sigdie_handler`也应该在Linux和BSD上工作)中设置断点,以仅拦截实际的紧急情况,而不是垃圾收集器的同步点。
在Julia构建期间进行调试(bootstrap)
构建(make
)期间发生的错误需要特殊处理。 Julia分两个阶段构建`sys0’和’sys。纪'。 要查看崩溃时正在运行的命令,请使用’make VERBOSE=1'。
在编写本文档时,您可以从基本目录调试sys0阶段的构建错误,如下所示。
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys0 sysimg.jl
您可能需要删除`usr/lib/julia/`中的所有文件才能使其工作。
你可以调试’sys。纪’如下。
julia/base$ gdb --args ../usr/bin/julia-debug -C native --build ../usr/lib/julia/sys -J ../usr/lib/julia/sys0.ji sysimg.jl
默认情况下,任何错误都会导致Julia关闭,即使在gdb中也是如此。 要捕获过程中的错误,请将断点设置为’jl_error`(对于特定类型的故障,有几个更有用的断点,包括`jl_too_few_args`,`jl_too_many_args`和’jl_throw')。
在捕获错误后,建议通过检查关联的函数调用`jl_apply`来向上堆栈并检查函数。 让我们举一个真实的例子。
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 ...
最后一个函数’jl_apply’在第3帧中,所以我们可以回到那里并查看函数`julia_convert_16886’的AST。 这是某些转换方法(`convert')的唯一名称。 此帧中的’f’是’jl_function_t’的函数,因此您可以从’specTypes’字段查看类型签名(如果有的话)。
(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} # <-- сигнатура типа для julia_convert_16886
然后我们可以看看这个函数的AST。
(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)::Any
最后,您可以对函数执行强制重新编译以完成代码生成过程。 为此,请从`jl_lamdbda_info_t*`中清除缓存对象`functionObject'。
(gdb) p f->linfo->functionObject $8 = (void *) 0x1289d070 (gdb) set f->linfo->functionObject = NULL
然后在正确的位置设置断点(例如,emit_function
,emit_expr
,`emit_call`等。)并开始代码生成。
(gdb) p jl_compile(f) ... # Здесь находится ваша точка останова