Linux如何处理变量

发布时间:2021-10-28 17:12:58 作者:小新
来源:亿速云 阅读:128

这篇文章主要介绍了Linux如何处理变量,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

DWARF 位置

某一给定时刻的内存中变量的位置使用 DW_AT_location 属性编码在 DWARF  信息中。位置描述可以是单个位置描述、复合位置描述或位置列表。

根据位置描述的种类,DW_AT_location 以三种不同的方式进行编码。exprloc 编码简单和复合的位置描述。它们由一个字节长度组成,后跟一个  DWARF 表达式或位置描述。loclist 和 loclistptr 的编码位置列表,它们在 .debug_loclists  部分中提供索引或偏移量,该部分描述了实际的位置列表。

DWARF 表达式

使用 DWARF 表达式计算变量的实际位置。这包括操作堆栈值的一系列操作。有很多 DWARF  操作可用,所以我不会详细解释它们。相反,我会从每一个表达式中给出一些例子,给你一个可用的东西。另外,不要害怕这些;libelfin  将为我们处理所有这些复杂性。

DWARF 类型

DWARF  类型的表示需要足够强大来为调试器用户提供有用的变量表示。用户经常希望能够在应用程序级别进行调试,而不是在机器级别进行调试,并且他们需要了解他们的变量正在做什么。

DWARF 类型与大多数其他调试信息一起编码在 DIE  中。它们可以具有指示其名称、编码、大小、字节等的属性。无数的类型标签可用于表示指针、数组、结构体、typedef 以及 C 或 C++  程序中可以看到的任何其他内容。

以这个简单的结构体为例:

struct test{     int i;     float j;     int k[42];     test* next; };

这个结构体的父 DIE 是这样的:

< 1><0x0000002a>    DW_TAG_structure_type                       DW_AT_name                  "test"                       DW_AT_byte_size             0x000000b8                       DW_AT_decl_file             0x00000001 test.cpp                       DW_AT_decl_line             0x00000001

上面说的是我们有一个叫做 test 的结构体,大小为 0xb8,在 test.cpp 的第 1 行声明。接下来有许多描述成员的子 DIE。

< 2><0x00000032>      DW_TAG_member                         DW_AT_name                  "i"                         DW_AT_type                  <0x00000063>                         DW_AT_decl_file             0x00000001 test.cpp                         DW_AT_decl_line             0x00000002                         DW_AT_data_member_location  0 < 2><0x0000003e>      DW_TAG_member                         DW_AT_name                  "j"                         DW_AT_type                  <0x0000006a>                         DW_AT_decl_file             0x00000001 test.cpp                         DW_AT_decl_line             0x00000003                         DW_AT_data_member_location  4 < 2><0x0000004a>      DW_TAG_member                         DW_AT_name                  "k"                         DW_AT_type                  <0x00000071>                         DW_AT_decl_file             0x00000001 test.cpp                         DW_AT_decl_line             0x00000004                         DW_AT_data_member_location  8 < 2><0x00000056>      DW_TAG_member                         DW_AT_name                  "next"                         DW_AT_type                  <0x00000084>                         DW_AT_decl_file             0x00000001 test.cpp                         DW_AT_decl_line             0x00000005                         DW_AT_data_member_location  176(as signed = -80)

每个成员都有一个名称、一个类型(它是一个 DIE 偏移量)、一个声明文件和行,以及一个指向其成员所在的结构体的字节偏移。其类型指向如下。

< 1><0x00000063>    DW_TAG_base_type                       DW_AT_name                  "int"                       DW_AT_encoding              DW_ATE_signed                       DW_AT_byte_size             0x00000004 < 1><0x0000006a>    DW_TAG_base_type                       DW_AT_name                  "float"                       DW_AT_encoding              DW_ATE_float                       DW_AT_byte_size             0x00000004 < 1><0x00000071>    DW_TAG_array_type                       DW_AT_type                  <0x00000063> < 2><0x00000076>      DW_TAG_subrange_type                         DW_AT_type                  <0x0000007d>                         DW_AT_count                 0x0000002a < 1><0x0000007d>    DW_TAG_base_type                       DW_AT_name                  "sizetype"                       DW_AT_byte_size             0x00000008                       DW_AT_encoding              DW_ATE_unsigned < 1><0x00000084>    DW_TAG_pointer_type                       DW_AT_type                  <0x0000002a>

如你所见,我笔记本电脑上的 int 是一个 4 字节的有符号整数类型,float是一个 4 字节的浮点数。整数数组类型通过指向 int  类型作为其元素类型,sizetype(可以认为是 size_t)作为索引类型,它具有 2a 个元素。 test * 类型是  DW_TAG_pointer_type,它引用 test DIE。

实现简单的变量读取器

如上所述,libelfin  将为我们处理大部分复杂性。但是,它并没有实现用于表示可变位置的所有方法,并且在我们的代码中处理这些将变得非常复杂。因此,我现在选择只支持  exprloc。请根据需要添加对更多类型表达式的支持。如果你真的有勇气,请提交补丁到 libelfin 中来帮助完成必要的支持!

处理变量主要是将不同部分定位在存储器或寄存器中,读取或写入与之前一样。为了简单起见,我只会告诉你如何实现读取。

首先我们需要告诉 libelfin 如何从我们的进程中读取寄存器。我们创建一个继承自 expr_context 的类并使用 ptrace  来处理所有内容:

class ptrace_expr_context : public dwarf::expr_context { public:     ptrace_expr_context (pid_t pid) : m_pid{pid} {}     dwarf::taddr reg (unsigned regnum) override {         return get_register_value_from_dwarf_register(m_pid, regnum);     }     dwarf::taddr pc() override {         struct user_regs_struct regs;         ptrace(PTRACE_GETREGS, m_pid, nullptr, &regs);         return regs.rip;     }     dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override {         //TODO take into account size         return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr);     } private:     pid_t m_pid; };

读取将由我们 debugger 类中的 read_variables 函数处理:

void debugger::read_variables() {     using namespace dwarf;     auto func = get_function_from_pc(get_pc());     //... }

我们上面做的***件事是找到我们目前进入的函数,然后我们需要循环访问该函数中的条目来寻找变量:

for (const auto& die : func) {     if (die.tag == DW_TAG::variable) {         //...     } }

我们通过查找 DIE 中的 DW_AT_location 条目获取位置信息:

auto loc_val = die[DW_AT::location];

接着我们确保它是一个 exprloc,并请求 libelfin 来评估我们的表达式:

if (loc_val.get_type() == value::type::exprloc) {     ptrace_expr_context context {m_pid};     auto result = loc_val.as_exprloc().evaluate(&context);

现在我们已经评估了表达式,我们需要读取变量的内容。它可以在内存或寄存器中,因此我们将处理这两种情况:

switch (result.location_type) {                 case expr_result::type::address:                 {                     auto value = read_memory(result.value);                     std::cout << at_name(die) << " (0x" << std::hex << result.value << ") = "                               << value << std::endl;                     break;                 }                 case expr_result::type::reg:                 {                     auto value = get_register_value_from_dwarf_register(m_pid, result.value);                     std::cout << at_name(die) << " (reg " << result.value << ") = "                               << value << std::endl;                     break;                 }                 default:                     throw std::runtime_error{"Unhandled variable location"};                 }

你可以看到,我根据变量的类型,打印输出了值而没有解释。希望通过这个代码,你可以看到如何支持编写变量,或者用给定的名字搜索变量。

***我们可以将它添加到我们的命令解析器中:

else if(is_prefix(command, "variables")) {     read_variables(); }

感谢你能够认真阅读完这篇文章,希望小编分享的“Linux如何处理变量”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

推荐阅读:
  1. Linux怎么查看环境变量
  2. Linux中如何定义变量脚本

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

linux

上一篇:如何用 Python背着女朋友偷偷抓取了她的行踪

下一篇:Mysql数据分组排名实现的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》