iis服务器助手广告
返回顶部
首页 > 资讯 > 操作系统 >Linux中如何实现源码级断点
  • 357
分享到

Linux中如何实现源码级断点

2023-06-16 13:06:01 357人浏览 薄情痞子
摘要

这篇文章主要为大家展示了“linux中如何实现源码级断点”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux中如何实现源码级断点”这篇文章吧。断点DWARFElves 和 dwarves 这

这篇文章主要为大家展示了“linux中如何实现源码级断点”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux中如何实现源码级断点”这篇文章吧。

断点

DWARF

Elves 和 dwarves 这篇文章,描述了 DWARF 调试信息是如何工作的,以及如何用它来将机器码映射到高层源码中。回想一下,DWARF  包含了函数的地址范围和一个允许你在抽象层之间转换代码位置的行表。我们将使用这些功能来实现我们的断点。

函数入口

如果你考虑重载、成员函数等等,那么在函数名上设置断点可能有点复杂,但是我们将遍历所有的编译单元,并搜索与我们正在寻找的名称匹配的函数。DWARF  信息如下所示:

< 0><0x0000000b>  DW_TAG_compile_unit                     DW_AT_producer              clang version 3.9.1 (tags/RELEASE_391/final)                     DW_AT_language              DW_LANG_C_plus_plus                     DW_AT_name                  /super/secret/path/MiniDbg/examples/variable.cpp                     DW_AT_stmt_list             0x00000000                     DW_AT_comp_dir              /super/secret/path/MiniDbg/build                     DW_AT_low_pc                0x00400670                     DW_AT_high_pc               0x0040069c LOCAL_SYMBOLS: < 1><0x0000002e>    DW_TAG_subprogram                       DW_AT_low_pc                0x00400670                       DW_AT_high_pc               0x0040069c                       DW_AT_name                  foo                       ... ... <14><0x000000b0>    DW_TAG_subprogram                       DW_AT_low_pc                0x00400700                       DW_AT_high_pc               0x004007a0                       DW_AT_name                  bar                       ...

我们想要匹配 DW_AT_name 并使用 DW_AT_low_pc(函数的起始地址)来设置我们的断点。

void debugger::set_breakpoint_at_function(const std::string& name) {     for (const auto& cu : m_dwarf.compilation_units()) {         for (const auto& die : cu.root()) {             if (die.has(dwarf::DW_AT::name) && at_name(die) == name) {                 auto low_pc = at_low_pc(die);                 auto entry = get_line_entry_from_pc(low_pc);                 ++entry; //skip prologue                 set_breakpoint_at_address(entry->address);             }         }     } }

这代码看起来有点奇怪的唯一一点是 ++entry。 问题是函数的 DW_AT_low_pc 不指向该函数的用户代码的起始地址,它指向 prologue  的开始。编译器通常会输出一个函数的 prologue 和  epilogue,它们用于执行保存和恢复堆栈、操作堆栈指针等。这对我们来说不是很有用,所以我们将入口行加一来获取用户代码的***行而不是  prologue。DWARF 行表实际上具有一些功能,用于将入口标记为函数 prologue  之后的***行,但并不是所有编译器都输出它,因此我采用了原始的方法。

源码行

要在高层源码行上设置一个断点,我们要将这个行号转换成 DWARF  中的一个地址。我们将遍历编译单元,寻找一个名称与给定文件匹配的编译单元,然后查找与给定行对应的入口。

DWARF 看上去有点像这样:

.debug_line: line number info for a single cu Source lines (from CU-DIE at .debug_info offset 0x0000000b): NS new statement, BB new basic block, ET end of text sequence PE prologue end, EB epilogue begin IS=val ISA number, DI=val discriminator value <pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath" 0x004004a7  [   1, 0] NS uri: "/super/secret/path/a.hpp" 0x004004ab  [   2, 0] NS 0x004004b2  [   3, 0] NS 0x004004b9  [   4, 0] NS 0x004004c1  [   5, 0] NS 0x004004c3  [   1, 0] NS uri: "/super/secret/path/b.hpp" 0x004004c7  [   2, 0] NS 0x004004ce  [   3, 0] NS 0x004004d5  [   4, 0] NS 0x004004dd  [   5, 0] NS 0x004004df  [   4, 0] NS uri: "/super/secret/path/ab.cpp" 0x004004e3  [   5, 0] NS 0x004004e8  [   6, 0] NS 0x004004ed  [   7, 0] NS 0x004004f4  [   7, 0] NS ET

所以如果我们想要在 ab.cpp 的第五行设置一个断点,我们将查找与行 (0x004004e3) 相关的入口并设置一个断点。

void debugger::set_breakpoint_at_source_line(const std::string& file, unsigned line) {     for (const auto& cu : m_dwarf.compilation_units()) {         if (is_suffix(file, at_name(cu.root()))) {             const auto& lt = cu.get_line_table();             for (const auto& entry : lt) {                 if (entry.is_stmt && entry.line == line) {                     set_breakpoint_at_address(entry.address);                     return;                 }             }         }     } }

我这里做了 is_suffix hack,这样你可以输入 c.cpp 代表 a/b/c.cpp  。当然你实际上应该使用大小写敏感路径处理库或者其它东西,但是我比较懒。entry.is_stmt  是检查行表入口是否被标记为一个语句的开头,这是由编译器根据它认为是断点的***目标的地址设置的。

符号查找

当我们在对象文件层时,符号是王者。函数用符号命名,全局变量用符号命名,你得到一个符号,我们得到一个符号,每个人都得到一个符号。  在给定的对象文件中,一些符号可能引用其他对象文件或共享库,链接器将从符号引用创建一个可执行程序。

可以在正确命名的符号表中查找符号,它存储在二进制文件的 ELF 部分中。幸运的是,libelfin  有一个不错的接口来做这件事,所以我们不需要自己处理所有的 ELF 的事情。为了让你知道我们在处理什么,下面是一个二进制文件的 .symtab 部分的转储,它由  readelf 生成:

Num:    Value          Size Type    Bind   Vis      Ndx Name  0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND  1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1  2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2  3: 0000000000400278     0 SECTION LOCAL  DEFAULT    3  4: 00000000004002c8     0 SECTION LOCAL  DEFAULT    4  5: 0000000000400430     0 SECTION LOCAL  DEFAULT    5  6: 00000000004004e4     0 SECTION LOCAL  DEFAULT    6  7: 0000000000400508     0 SECTION LOCAL  DEFAULT    7  8: 0000000000400528     0 SECTION LOCAL  DEFAULT    8  9: 0000000000400558     0 SECTION LOCAL  DEFAULT    9 10: 0000000000400570     0 SECTION LOCAL  DEFAULT   10 11: 0000000000400714     0 SECTION LOCAL  DEFAULT   11 12: 0000000000400720     0 SECTION LOCAL  DEFAULT   12 13: 0000000000400724     0 SECTION LOCAL  DEFAULT   13 14: 0000000000400750     0 SECTION LOCAL  DEFAULT   14 15: 0000000000600e18     0 SECTION LOCAL  DEFAULT   15 16: 0000000000600e20     0 SECTION LOCAL  DEFAULT   16 17: 0000000000600e28     0 SECTION LOCAL  DEFAULT   17 18: 0000000000600e30     0 SECTION LOCAL  DEFAULT   18 19: 0000000000600ff0     0 SECTION LOCAL  DEFAULT   19 20: 0000000000601000     0 SECTION LOCAL  DEFAULT   20 21: 0000000000601018     0 SECTION LOCAL  DEFAULT   21 22: 0000000000601028     0 SECTION LOCAL  DEFAULT   22 23: 0000000000000000     0 SECTION LOCAL  DEFAULT   23 24: 0000000000000000     0 SECTION LOCAL  DEFAULT   24 25: 0000000000000000     0 SECTION LOCAL  DEFAULT   25 26: 0000000000000000     0 SECTION LOCAL  DEFAULT   26 27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27 28: 0000000000000000     0 SECTION LOCAL  DEFAULT   28 29: 0000000000000000     0 SECTION LOCAL  DEFAULT   29 30: 0000000000000000     0 SECTION LOCAL  DEFAULT   30 31: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS init.c 32: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c 33: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   17 __JCR_LIST__ 34: 00000000004005a0     0 FUNC    LOCAL  DEFAULT   10 dereGISter_tm_clones 35: 00000000004005e0     0 FUNC    LOCAL  DEFAULT   10 register_tm_clones 36: 0000000000400620     0 FUNC    LOCAL  DEFAULT   10 __do_global_dtors_aux 37: 0000000000601028     1 OBJECT  LOCAL  DEFAULT   22 completed.6917 38: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   16 __do_global_dtors_aux_fin 39: 0000000000400640     0 FUNC    LOCAL  DEFAULT   10 frame_dummy 40: 0000000000600e18     0 OBJECT  LOCAL  DEFAULT   15 __frame_dummy_init_array_ 41: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS /super/secret/path/MiniDbg/ 42: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c 43: 0000000000400818     0 OBJECT  LOCAL  DEFAULT   14 __FRAME_END__ 44: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   17 __JCR_END__ 45: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 46: 0000000000400724     0 NOTYPE  LOCAL  DEFAULT   13 __GNU_EH_FRAME_HDR 47: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   20 _GLOBAL_OFFSET_TABLE_ 48: 0000000000601028     0 OBJECT  LOCAL  DEFAULT   21 __TMC_END__ 49: 0000000000601020     0 OBJECT  LOCAL  DEFAULT   21 __dso_handle 50: 0000000000600e20     0 NOTYPE  LOCAL  DEFAULT   15 __init_array_end 51: 0000000000600e18     0 NOTYPE  LOCAL  DEFAULT   15 __init_array_start 52: 0000000000600e30     0 OBJECT  LOCAL  DEFAULT   18 _DYNAMIC 53: 0000000000601018     0 NOTYPE  WEAK   DEFAULT   21 data_start 54: 0000000000400710     2 FUNC    GLOBAL DEFAULT   10 __libc_csu_fini 55: 0000000000400570    43 FUNC    GLOBAL DEFAULT   10 _start 56: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__ 57: 0000000000400714     0 FUNC    GLOBAL DEFAULT   11 _fini 58: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_ 59: 0000000000400720     4 OBJECT  GLOBAL DEFAULT   12 _IO_stdin_used 60: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT   21 __data_start 61: 00000000004006a0   101 FUNC    GLOBAL DEFAULT   10 __libc_csu_init 62: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start 63: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT   22 _end 64: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT   21 _edata 65: 0000000000400670    44 FUNC    GLOBAL DEFAULT   10 main 66: 0000000000400558     0 FUNC    GLOBAL DEFAULT    9 _init

你可以在对象文件中看到用于设置环境的很多符号,***还可以看到 main 符号。

我们对符号的类型、名称和值(地址)感兴趣。我们有一个该类型的 symbol_type 枚举,并使用一个 std::string  作为名称,std::uintptr_t 作为地址:

enum class symbol_type {     notype,            // No type (e.g., absolute symbol)     object,            // Data object     func,              // Function entry point     section,           // Symbol is associated with a section     file,              // Source file associated with the };                     // object file std::string to_string (symbol_type st) {     switch (st) {     case symbol_type::notype: return "notype";     case symbol_type::object: return "object";     case symbol_type::func: return "func";     case symbol_type::section: return "section";     case symbol_type::file: return "file";     } } struct symbol {     symbol_type type;     std::string name;     std::uintptr_t addr; };

我们需要将从 libelfin  获得的符号类型映射到我们的枚举,因为我们不希望依赖关系破环这个接口。幸运的是,我为所有的东西选了同样的名字,所以这样很简单:

symbol_type to_symbol_type(elf::stt sym) {     switch (sym) {     case elf::stt::notype: return symbol_type::notype;     case elf::stt::object: return symbol_type::object;     case elf::stt::func: return symbol_type::func;     case elf::stt::section: return symbol_type::section;     case elf::stt::file: return symbol_type::file;     default: return symbol_type::notype;     } };

***我们要查找符号。为了说明的目的,我循环查找符号表的 ELF 部分,然后收集我在其中找到的任意符号到 std::vector  中。更智能的实现可以建立从名称到符号的映射,这样你只需要查看一次数据就行了。

std::vector<symbol> debugger::lookup_symbol(const std::string& name) {     std::vector<symbol> syms;     for (auto &sec : m_elf.sections()) {         if (sec.get_hdr().type != elf::sht::symtab && sec.get_hdr().type != elf::sht::dynsym)             continue;         for (auto sym : sec.as_symtab()) {             if (sym.get_name() == name) {                 auto &d = sym.get_data();                 syms.push_back(symbol{to_symbol_type(d.type()), sym.get_name(), d.value});             }         }     }     return syms; }

添加命令

一如往常,我们需要添加一些更多的命令来向用户暴露功能。对于断点,我使用 GDB  风格的接口,其中断点类型是通过你传递的参数推断的,而不用要求显式切换:

  • 0x<hexadecimal> -> 断点地址

  • <line>:<filename> -> 断点行号

  • <anything else> -> 断点函数名

else if(is_prefix(command, "break")) {     if (args[1][0] == '0' && args[1][1] == 'x') {         std::string addr {args[1], 2};         set_breakpoint_at_address(std::stol(addr, 0, 16));     }     else if (args[1].find(':') != std::string::npos) {         auto file_and_line = split(args[1], ':');         set_breakpoint_at_source_line(file_and_line[0], std::stoi(file_and_line[1]));     }     else {         set_breakpoint_at_function(args[1]);     } }

对于符号,我们将查找符号并打印出我们发现的任何匹配项:

else if(is_prefix(command, "symbol")) {     auto syms = lookup_symbol(args[1]);     for (auto&& s : syms) {         std::cout << s.name << ' ' << to_string(s.type) << " 0x" << std::hex << s.addr << std::endl;     } }

以上是“Linux中如何实现源码级断点”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网操作系统频道!

--结束END--

本文标题: Linux中如何实现源码级断点

本文链接: https://www.lsjlt.com/news/283651.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Linux中如何实现源码级断点
    这篇文章主要为大家展示了“Linux中如何实现源码级断点”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux中如何实现源码级断点”这篇文章吧。断点DWARFElves 和 dwarves 这...
    99+
    2023-06-16
  • Linux调试器中如何实现断点
    这篇文章将为大家详细讲解有关Linux调试器中如何实现断点,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。系列索引随着后面文章的发布,这些链接会逐渐生效。准备环境断点寄存器和内存Elves 和 dwarve...
    99+
    2023-06-28
  • Flutter实现资源下载断点续传的示例代码
    目录协议梳理实现步骤写在最后协议梳理 一般情况下,下载的功能模块,至少需要提供如下基础功能:资源下载、取消当前下载、资源是否下载成功、资源文件的大小、清除缓存文件。而断点续传主要体现...
    99+
    2024-04-02
  • python如何实现断点调试
    这篇文章主要讲解了“python如何实现断点调试”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python如何实现断点调试”吧!❤  什么是bug(缺陷)软件缺陷就是通常说的bug...
    99+
    2023-07-05
  • JavaScript如何实现自动断点
    这篇文章主要为大家展示了“JavaScript如何实现自动断点”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JavaScript如何实现自动断点”这篇文章吧。自动断点有时候在执行 JavaScr...
    99+
    2023-06-27
  • FileZilla如何实现断点续传
    这篇文章主要介绍了FileZilla如何实现断点续传,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。FileZilla断点续传功能可以说几乎是每个FTP软件必备的功能,也可以说...
    99+
    2023-06-16
  • IDEA Debug如何实现条件断点
    小编给大家分享一下IDEA Debug如何实现条件断点,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!条件断点条件断点就是当满足某个条件(通常是表达式)时,debu...
    99+
    2023-06-27
  • IDEA Debug如何实现回退断点
    这篇文章将为大家详细讲解有关IDEA Debug如何实现回退断点,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。回退断点不知道大伙有没有遇到这样一个问题,当我们进行debug时,通常不会进入某个方法一步一步...
    99+
    2023-06-27
  • IDEA Debug如何实现临时断点
    这篇文章将为大家详细讲解有关IDEA Debug如何实现临时断点,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。临时断点所谓临时断点就是只断一次,IDEA默认断点会一致存在。如果你只需要暂停一次,那么使用临...
    99+
    2023-06-27
  • CAS实现单点登录实例源码
    修改server.xml文件,如下: 注意: 这里使用的是https的认证方式,需要将这个配置放开,并做如下修改:port="8443" protocol="org.apache.coyote.htt...
    99+
    2023-06-05
  • php如何实现下载断点续传
    本篇内容介绍了“php如何实现下载断点续传”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!php实现下载断点续传的方法:1、创建一个php示例...
    99+
    2023-07-04
  • linux如何罗列实时中断
    ...
    99+
    2024-04-02
  • linux如何实现ADSL断线重拨
    这篇文章主要介绍“linux如何实现ADSL断线重拨”,在日常操作中,相信很多人在linux如何实现ADSL断线重拨问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”linux如何实现ADSL断线重拨”的疑惑有所...
    99+
    2023-06-10
  • 调试JavaScript代码中如何手动断点
    小编给大家分享一下调试JavaScript代码中如何手动断点,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!手动断点断点是程序员调试代码时非常好用的利器,通过断点调...
    99+
    2023-06-27
  • JAVA中如何实现表达式计算源码
    这篇文章主要为大家展示了“JAVA中如何实现表达式计算源码”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JAVA中如何实现表达式计算源码”这篇文章吧。支持运算符:+-*/%><][!...
    99+
    2023-06-03
  • vue中的代码如何进行断点调试
    目录vue代码进行断点调试下面开始调试总结vue代码进行断点调试 对于这样的代码,我们打两个断点,想要调试的话,需要在项目根目录,新建一个文件:vue.config.js,然后根据...
    99+
    2023-05-17
    vue代码 vue代码断点调试 vue断点调试
  • linux源码如何安装git
    小编给大家分享一下linux源码如何安装git,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!linux源码安装git的方法:1、下载git源码上传至Linux服务器;2、登录目标机器,解压并安装;3、安装“gcc gcc-...
    99+
    2023-06-21
  • C++中出现断点无效如何解决
    本篇文章给大家分享的是有关C++中出现断点无效如何解决,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。 某个修改后,断点无法工作,之前版本依然正常 只是某个文件中断点无法工作 断...
    99+
    2023-06-17
  • HTML5如何实现文件断点续传功能
    这篇文章主要为大家展示了“HTML5如何实现文件断点续传功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“HTML5如何实现文件断点续传功能”这篇文章吧。HTM...
    99+
    2024-04-02
  • 在Linux系统中如何源码安装Git
    在Linux系统中,可以使用源码自行编译安装Git。相较于使用二进制包安装,源码安装的优势是可以自定义安装选项,安装更适合自己的Git版本。本文将介绍在Linux系统中如何源码安装Git。第一步:下载Git源码首先打开Git官网 https...
    99+
    2023-10-22
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作