当前位置: 首页 > 科技观察

开发Linux调试器(9):处理变量

时间:2023-03-16 17:41:05 科技观察

变量是偷偷摸摸的。有时他们会很高兴地留在寄存器中,但一转身就弹出堆栈。出于优化目的,编译器可能会将它们完全抛出窗口。无论变量在内存中如何移动,我们都需要一些方法在调试器中跟踪和操作它们。这篇文章将教您如何在调试器中处理变量,并演示使用libelfin的简单实现。系列文章索引准备环境断点寄存器和内存ELF和DWARF源代码和信号源代码级步进源代码级断点堆栈展开处理变量高级主题在开始之前,请确保您使用的是我的fork中的fbreg版本的libelfin.这包括一些hack来支持获取当前堆栈帧的基地址和评估位置列表,这两者都不是由本机libelfin提供的。您可能需要将-gdwarf-2传递给GCC以生成兼容的DWARF消息。但在实施之前,我将详细说明位置在DWARF5***规范中是如何编码的。如果您想了解更多,可以从此处获取标准。DWARFLocation使用DW_AT_location属性在DWARF消息中编码给定时刻变量在内存中的位置。位置描述可以是单个位置描述、复合位置描述或位置列表。简单位置描述:描述对象的连续部分(通常是所有部分)的位置。简单的位置描述可以描述可寻址存储器或寄存器中的位置或缺少位置(有或没有已知值)。例如,DW_OP_fbreg-32:一个完全存储的变量-从堆栈帧的底部算起32个字节。复合位置描述:对象是用片段来描述的,每个对象可以包含在寄存器的一部分中,也可以存储在与其他片段无关的内存位置中。例如,DW_OP_reg3DW_OP_piece4DW_OP_reg10DW_OP_piece2:前四个字节在寄存器3中,后两个字节在寄存器10中的变量中。位置列表:描述具有有限生命周期或在其生命周期中改变位置的对象。例如:[0]DW_OP_reg0[1]DW_OP_reg3[2]DW_OP_reg2变量,其位置根据程序计数器的当前值在寄存器之间移动。DW_AT_location以三种不同的方式编码,具体取决于位置描述的种类。exprloc编码简单和复合的位置描述。它们由一个字节长度和一个DWARF表达式或位置描述组成。loclist和loclistptr的编码位置列表,为描述实际位置列表的.debug_loclists部分提供索引或偏移量。DWARF表达式使用DWARF表达式计算变量的实际位置。这包括一系列操作堆栈值的操作。有很多DWARF操作可用,所以我不会详细解释它们。相反,我会给出每个表达式的示例,以便为您提供一些有用的东西。另外,不要害怕那个;libelfin将为我们处理所有这些复杂问题。文字编码DW_OP_lit0,DW_OP_lit1...DW_OP_lit31将文字压入堆栈DW_OP_addr将地址操作数压入堆栈DW_OP_constu将无符号值压入堆栈寄存器值DW_OP_fbreg压入栈帧基址DW_OP_breg0,DW_OP_breg1...DW_OP_breg31将给定寄存器的内容加上给定的偏移量压入堆栈堆栈操作DW_OP_dup复制堆栈顶部的值DW_OP_deref将堆栈顶部视为内存地址,并用该地址的内容替换它算术和逻辑操作DW_OP_and弹出栈顶的两个值并将它们的逻辑AND推回DW_OP_plus与DW_OP_and相同,但添加值控制流操作DW_OP_le、DW_OP_eq、DW_OP_gt等在弹出两个值之前,比较它们,如果条件为真则压入1,否则压入0DW_OP_bra条件分支:如果栈顶不为0,则跳过表达式中向后或向后的输入,通过offset转换DW_OP_convert将栈顶的值转换为不同的类型,即由DWARFoffset信息条目给出描述特殊操作DW_OP_nop什么都不做!DWARF类型DWARF类型表示需要足够强大,以便为调试器用户级别的调试提供有用的变量,并且他们需要了解他们的变量在做什么。DWARF类型与大多数其他调试信息一起编码在DIE中。它们可以具有指示其名称、编码、大小、字节等的属性。无数类型标签可用于表示指针、数组、结构、typedef以及您在C或C++程序中可以看到的任何其他内容。以这个简单的结构为例:structtest{inti;floatj;intk[42];test*next;};这个结构体的parentDIE是这样的:<1><0x0000002a>DW_TAG_structure_typeDW_AT_name"test"DW_AT_byte_size0x000000b8DW_AT_decl_file0x00000001AT0000de_above据说我们有一个叫做test的结构体,大小为0xb8,在test.cpp的第一行声明。接下来是一些描述成员的子DIE。<2><0x00000032>DW_TAG_memberDW_AT_name"i"DW_AT_type<0x00000063>DW_AT_decl_file0x00000001test.cppDW_AT_decl_line0x00000002DW_AT_data_member_location0<2><0x0000003e>DW_TAG_memberDW_AT_name"j"DW_AT_type<0x0000006a>DW_AT_decl_file0x00000001test.cppDW_AT_decl_line0x00000003DW_AT_data_member_location4<2><0x0000004a>DW_TAG_memberDW_AT_name"k"DW_AT_type<0x00000071>DW_AT_decl_file0x00000001test.cppDW_AT_decl_line0x00000004DW_AT_data_member_location8<2><0x00000056>DW_TAG_memberDW_AT_name"next"DW_AT_type<0x00000084>DW_AT_decl_file0x00000001test.cppDW_AT_decl_line0x00000005DW_AT_data_member_location176(assigned=-80)每个成员都有一个名称、一个类型(它是一个DIE偏移量)、一个声明文件和行,andabyteoffsetintothestructurewhereitsmembersreside.Itstypepointstothefollowing.<1><0x00000063>DW_TAG_base_typeDW_AT_name"int"DW_AT_encodingDW_ATE_signedDW_AT_byte_size0x00000004<1><0x0000006a>DW_TAG_base_typeDW_AT_name"float"DW_AT_encodingDW_ATE_floatDW_AT_byte_size0x00000004<1><0x00000071>DW_TAG_array_typeDW_AT_type<0x00000063><2><0x00000076>DW_TAG_subrange_typeDW_AT_type<0x0000007d>DW_AT_count0x0000002a<1><0x0000007d>DW_TAG_base_typeDW_AT_name"sizetype"DW_AT_byte_size0x00000008DW_AT_encodingDW_ATE_unsigned<1><0x00000084>DW_TAG_pointer_typeDW_AT_type<0x0000002a>可以看到,我笔记本上的int是4字节的有符号整数类型,float是4字节的浮点数。整数数组类型将指向int类型的指针作为其元素类型传递,并将sizetype(可以认为是size_t)作为索引类型传递,它们具有2a个元素。测试*类型为DW_TAG_pointer_type,指的是测试DIE。实现简单的变量读取器如上所述,libelfin将为我们处理大部分的复杂性。但是,它并没有实现所有表示可变位置的方法,在我们的代码中处理这些方法会变得非常复杂。因此,我选择暂时只支持exprloc。请根据需要添加对更多类型表达式的支持。如果您真的很勇敢,请向libelfin提交补丁以帮助完成必要的支持!处理变量基本上就是在内存或者寄存器中定位不同的部分,读或者写跟以前一样。为了简单起见,我将只向您展示如何实现读取。首先,我们需要告诉libelfin如何从我们的进程中读取寄存器。我们创建一个继承自expr_context的类,并使用ptrace来处理所有事情:);}dwarf::taddrpc()override{structuser_regs_structregs;ptrace(PTRACE_GETREGS,m_pid,nullptr,®s);returnregs.rip;}dwarf::taddrderef_size(dwarf::taddraddress,unsignedsize)override{//TODOtakeintoaccountsizereptrace(EPTA_returnptrace,,address,nullptr);}private:pid_tm_pid;};读取将由我们调试器类中的read_variables函数处理:voiddebugger::read_variables(){usingnamespacedwarf;autofunc=get_function_from_pc(get_pc());//...}我们上面做的第一件事是找到我们当前所在的函数,然后我们需要遍历该函数中的条目以找到变量:for(constauto&die:func){if(die.tag==DW_TAG::variable){//...}}我们通过查找DW_A来获取位置DIE中的T_location条目:autoloc_val=die[DW_AT::location];然后我们确保它是一个exprloc并要求libelfin评估我们的表达式:if(loc_val.get_type()==value::type::exprloc){ptrace_expr_contextcontext{m_pid};autoresult=loc_val.as_exprloc().evaluate(&context);现在我们已经计算了表达式,我们需要读取变量的内容。它可以在内存或寄存器中,所以我们将处理这两种情况:)<<"(0x"<