gcc为formal parm生成dwarf调试信息

gcc为formal parm生成dwarf调试信息本次问题从一个bug入手,源文件非常简单,只包含一个函数:intcall2b(charc,floatf1,short2,doubled1,inti,floatf2,longl,doubled2){ c=’a’;f1=0.0;s=5;d1=0.0; i=6; f2=0.1;l=7;d2=0.2returni;}该函数使用

大家好,欢迎来到IT知识分享网。

本次问题从一个bug入手,源文件非常简单,只包含一个函数:

int call2b(char c, float f1, short 2, double d1, int i, float f2, long l, double d2)

{

  c=’a’;f1=0.0;s=5;d1=0.0;

  i=6;

  f2 = 0.1;l=7;d2=0.2

return i;

}

该函数使用gcc编译(参数‘-g -O0’)之后会产生dwarf调试信息,其中每个formal parm都会有一个die。每个die描述的信息有:name、file、line、type以及location。但是问题来了:

(1)前面四个parm的调试信息都是完整的,但是从第五个参数int i开始,后面的parm的调试信息中都没有location信息。

(2)把i=6该语句注释掉,再重新编译,此时所有的parm的调试信息都是完整的。

这么两个问题让我百思不得其解,但是calm down。仔细分析一下,就会发现,调试信息是从int i开始不正常的,而使用的gcc的体系结构后端的abi中规定,传参的寄存器有5个,当传参寄存器用完之后那么就使用堆栈传参。那么我们可以知道,前面四个参数刚好把5个传参寄存器用完了(double d1使用两个)。也就是说堆栈传参导致的问题。

那么接下来进行debug。

首先需要找到dwarf调试信息是在哪个pass中生成的。我们找到一个名为“final”的pass,该pass负责将rtl转化为assembly,并且输出dwarf调试信息。具体流程如图所示。

gcc为formal parm生成dwarf调试信息

可见,前面部分负责输出assembly insns,而dwarf调试信息输出的工作主要在dwarf2out_function_decl(fndecl)函数中完成,该函数根据一个function的tree_node fndecl进行分析,输出该函数中所有变量的die。自然也会为该函数的formal parm生成调试信息,该功能通过调用gen_formal_paremeter_die()函数完成。该函数大致的工作是为该formal parm产生一个die,然后根据rtl中的信息,为die的各个attr赋值,包括name、line、type、location。其中location信息是由add_location_or_const_attribute()完成。

那么跟进去,可以看到,首先获取parm_decl->decl_with_rtl.rtl,该rtl表示了该parm的location,是一个mem类型的rtx,使用regno+offset的形式表示。而对于前面四个寄存器传递的参数,它们的regno都是fp,而后面从int i开始的参数的regno是一个pseudo register,而不是一个hardware register,所以gcc选择不输出此location信息(pseudo register没意义的,并不能真正的指示parm的location)。此处又产生两个疑问:

(3)前四个寄存器传递的参数,传入的时候应该是register表示,而此处看到的rtl确实mem类型的sp+offset。可以理解,因为寄存器传入的参数在函数的prologue段会被压入stack,此处的疑问是参数的location由register到sp+offset这么一个过程是在哪里完成的?

(4)为什么int i开始的寄存器传递的参数的rtl的regno+offset还是pseudo register呢?

思路就是找到这么一个表示location的rtl是在哪里赋值的(可以使用watchpoint来实现),最终我们定位到assign_parms(fndecl)函数。该函数的作用是为函数的parms赋值rtl。parm_decl->decl_with_rtl.rtl就是在此处赋值的。该函数的输入是tree_node fndecl,fndecl中包含该函数的所有的parms信息,使用DECL_ARGUMENTS(fndecl)访问。该函数在名为“expand”的pass中被执行。assign_parms()的流程如图所示:

gcc为formal parm生成dwarf调试信息

从图中可以看到寄存器传递的参数和堆栈传递的参数最后产生的描述location的rtl是不同的,那么就可以解释为什么寄存器传递的参数的location信息完整,而堆栈传递的参数的location的信息缺失了,至于为什么会导致缺失,稍后解释。这里先对流程图做几个解释:

a)assign_parm_find_entry_rtl()为parm生成描述输入的rtl,entry_parm。是通过一个宏函数FUNCTION_ARG()实现的,该宏由后端体系结构实现。具体的机制是:判断abi中传参寄存器是否使用完毕,如果没有的话,为该parm生成一个reg的rtl表示该parm使用寄存器传递,比如“reg:SI 2 r2”;如果使用完毕,表示该参数需要使用堆栈传递,那么entry_parm为0.

b)对于virtual_stack_vars_rtx,这是一个宏,定义在rtl.h中“#define virtual_stack_vars_rtx (global_rtl[GR_VIRTUAL_STACK_ARGS])”。其中global_rtl[]是一个rtl的数组,里面存储了所有特殊用途的rtl,比如这里的virtual_stack_vars_rtx为“reg/f :SI 70 virtual-stack-vars”。而GR_VIRTUAL_STACK_ARGS是一个枚举类型的成员,用来表征相应的特殊的rtl在global_rtl[]中的下标如

下面分析堆栈传递的参数的location信息缺失的原因

上述分析可知,堆栈传递的参数的location缺失的主要原因是对应rtl的regno为pseudo register,那么关键就在regno上,由上图可知,stack传递的parm的location rtl的regno是crtl->args.internal_arg_pointer,此时为‘reg: SI 89’。是一个随机而又普通的pseudo register。那么我们不禁要想,这个是在哪里赋值的呢?在assign_parms()开始时会调用default_internal_arg_pointer()函数,代码如图所示。

gcc为formal parm生成dwarf调试信息

可以知道,此处由于fixed_regs[ARG_POINTER_REGNUM]为0,所以不是返回virtual_incoming_args_rtx,而是返回copy_to_reg(virtual_incoming_args_rtx),其中virtual_incoming_args_rtx和virtual_stack_vars_rtx一样是global_rtl[]数组中一个特殊的rtx,‘reg/f:SI 69 virtual-incoming-args’,该rtl的功能是用来指示堆栈传递参数的位置的。而fixed_regs[ARG_POINTER_REGNUM]为0表示该体系结构中没有分配一个特殊的寄存器作为ARG_POINTER_REGNUM,所以返回的copy_to_reg()则会调用gen_reg_RTX()分配当前可用的一个pseudo register来指示堆栈传递的参数的位置,而这个值是不确定的。

所以综上关键是fixed_regs[]数组中关于ARG_POINTER_REGNUM的定义。这个是在gcc/config/目录下体系结构实现文件中定义的。实践表明,我把它置位1之后,重新编译gcc,然后去编译call2b,最后产生的dwarf调试信息中所有参数的location信息都正确了。修改之后,在assign_parms()中ctrl->args.internal_arg_pointer变为‘reg/f:SI 69 virtual-incoming-args’。而不是一个随机的pseudo register了。

那么这就可以解释本文之前提出的问题(1)和问题(4)。还有问题(2)和问题(3)呢,怎么解释呢?

问题(2)virtual_incoming_args_rtx和virtual_stack_vars_rtx的转化

通过上述分析可知,正确情况下assign_parms()函数过后,寄存器传递的参数使用virtual_stack_vars_rtx指示,堆栈传递的参数使用virtual_incoming_args_rtx指示。而这么两个特殊的rtx本质上只是两个特殊的pseudo register而已,那么我们最终要得到的是hardware register,这个转换在什么时候完成的呢?(相应的体系结构必须为virtual_incoming_args_rtx和virtual_stack_vars_rtx留出两个hardware register)。通过跟踪调试分析,可知,在instantiate_virtual_regs()函数中实现的。该函数在名为“vregs”的pass中执行。该函数遍历所有的insns,然后进行分析,对所有用到的特殊virtual register进行替换。virtual_stack_vars_rtx和virtual_incoming_args_rtx的替换也在这里完成。

问题(3)为何i=6这个语句删除之后,产生的parms的location信息都是完整的?

在未修改fixed_regs[ARG_POINTER_REGNUM]之前,assign_parms()函数为堆栈传递的参数分配的location的rtl是用gen_reg_RTX()动态产生的一个pseudo register。其实,在编译的阶段,一开始分配的寄存器都是pseudo register,virtual_incoming_args_rtx和virtual_stack_vars_rtx也是,只不过二者是特殊的pseudo register。其它地方,比如运算中用到的寄存器此处也是分配的pseudo register,那么为什么正常运算用到的pseudo register能够在最后映射到hardware register,而用来指示堆栈传递参数的pseudo register不能呢?这个过程在哪里实现呢?

在ira_build()函数中,会对当前所有的insns进行扫描分析。该函数在名为‘ira’的pass中执行。该函数对当前函数的insn进行扫描分析,将pseudo register,通过分析insn中的set语句来知道哪些pseudo register被使用了,并且为其分配hardware register,并且将这个映射关系记录到ira_allocnos[]数组中。然后在reload()函数中根据记录的ira_allocnos[]数组进行reload操作,将pseudo寄存器映射为hardware 寄存器,并且修改相应的用到该pseudo register的所有rtl。

那么,描述堆栈参数的location的pseudo register之所以不会在reload()时进行映射,原因就是在ira_build()扫描的insns中并没有描述参数的insns,所以也就没有该pseudo register的set语句。

那么为什么压根就没有描述parms的呢?是因为call2b()函数中只是对parm进行了赋值操作,所以并不需要描述每个parm,相应的,gcc只需要描述这几个赋值语句即可。因为没别的地方要用到这些parm了。这一点在gimplify_function_tree()时,可以找到证据,该函数会调用gimpify_expr()函数来分析当前fndecl的generic exprs,以此来决定是否需要为fndecl中的expr生成相应的gimple表达式。对于call2b我们可以看到,它的generic expr仅仅是一些stmt,描述这几个赋值语句,而没有描述parm的generic expr。否则的话,在gimplify_expr()函数时会调用get_formal_tmp_var()为parms产生gimple,即后来换转换成相应的rtl。

那么,你可能会疑惑,call2b()中除了赋值语句,还有其它地方用到了parm的,“return i;”不就是么。但是很遗憾,gcc非常聪明,它知道即将会给i赋值6,那么return时,它直接return 6,就不需要care这个参数i了。

把“i=6;”语句删除掉后,gcc在解析函数call2b()的时候,就不知道“return i;”语句究竟要返回的什么值了,因为这取决于传入的什么值。这时候就需要描述这个参数i。在生成的insns中我们可以看到,删除“i=6;”语句之后,多了一个insn“(insn 34 33 35 2 para_test.c:20(set SI 74 [D.2000])(mem/c/i:SI(reg/f:SI 76)[0 i+0 S4 A32]))”,使用pseudo register 76来描述参数i的location。

所以在ira_build()扫描insns时,当分析到该insn时,就会为该pseudo register分配一个hardware register,比如r5. 这样最后产生的dwarf调试信息中,就可以使用r5来寻址堆栈传递的这些参数了。

可能有人还会有疑问,这里只是用到了i,为什么其他参数的location信息也正确了呢。我们回到assign_parms()函数,所有的堆栈传递的参数的寻址方式都是regno+offset,而regno都是crtl->args.internal_arg_pointer,只是offset不同而已。而只要有一个参数,比如i,将crtl->args.internal_arg_pointer映射到了hardware register,比如r5,那么其它的参数的location也就可以用r5来指示了。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/11659.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信