Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize the breakpoint of source code level in Linux

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly shows you "how to achieve source-level breakpoints in Linux", the content is easy to understand, clear, hope to help you solve your doubts, the following let Xiaobian lead you to study and learn "how to achieve source-level breakpoints in Linux" this article.

Breakpoint

DWARF

Elves and dwarves describe how DWARF debugging information works and how it can be used to map machine code to high-level source code. Recall that DWARF contains the address range of the function and a row table that allows you to switch the location of the code between abstraction layers. We will use these features to implement our breakpoints.

Function entry

If you consider overloading, member functions, etc., setting breakpoints on function names may be a bit complicated, but we will traverse all the compilation units and search for functions that match the name we are looking for. The DWARF information is as follows:

< 0>

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>

DW_TAG_subprogram DW_AT_low_pc 0x00400670 DW_AT_high_pc 0x0040069c DW_AT_name foo...... DW_TAG_subprogram DW_AT_low_pc 0x00400700 DW_AT_high_pc 0x004007a0 DW_AT_name bar...

We want to match DW_AT_name and use DW_AT_low_pc (the starting address of the function) to set our breakpoint.

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);}

The only thing about this code that looks a little strange is + + entry. The problem is that the function's DW_AT_low_pc does not point to the starting address of the function's user code, it points to the beginning of the prologue. The compiler usually outputs the prologue and epilogue of a function, which are used to save and restore the stack, manipulate stack pointers, and so on. This is not very useful for us, so we added the entry lines to get the * lines of user code instead of prologue. The DWARF row table actually has some functionality for marking the entry as the * * line after the function prologue, but not all compilers output it, so I took the original approach.

Source line

To set a breakpoint on a line of high-level source code, we need to convert the line number to an address in DWARF. We will traverse the compilation unit, looking for a compilation unit whose name matches the given file, and then looking for the entry corresponding to the given line.

DWARF looks a bit like this:

.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 [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

So if we want to set a breakpoint on the fifth line of ab.cpp, we will look for the entry associated with the row (0x004004e3) and set a breakpoint.

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;}

I have done is_suffix hack here so that you can type c.cpp to represent a/b/c.cpp. Of course you should actually use case-sensitive paths to deal with libraries or other things, but I'm lazy. Entry.is_stmt checks whether the entry to the row table is marked as the beginning of a statement, which is set by the compiler based on the address of the target it considers to be a breakpoint.

Symbol search

When we are in the object file layer, the symbol is king. Functions are named by symbols, global variables are named by symbols, you get a symbol, we get a symbol, everyone gets a symbol. In a given object file, some symbols may refer to other object files or shared libraries, and the linker will create an executable program from the symbol reference.

You can find the symbol in the correctly named symbol table, which is stored in the ELF section of the binary file. Fortunately, libelfin has a good interface to do this, so we don't have to deal with all the ELF things ourselves. To let you know what we are dealing with, here is a dump of the .symtab part of a binary file, which is generated by 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 23: 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 78: 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

You can see many symbols used to set up the environment in the object file, and you can also see the main symbol.

We are interested in the type, name, and value (address) of symbols. We have a symbol_type enumeration of this type with a std::string as the name and std::uintptr_t as the address:

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;}

We need to map the symbol types obtained from libelfin to our enumeration because we don't want the dependency to break this interface. Fortunately, I chose the same name for everything, so it's simple:

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 }}

* We need to look for symbols. For illustrative purposes, I loop through the ELF section of the symbol table and collect any symbols I find in it into std::vector. A smarter implementation can establish a mapping from names to symbols so that you only need to look at the data once.

Std::vector debugger::lookup_symbol (const std::string& name) {std::vector 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;}

Add command

As always, we need to add some more commands to expose the functionality to the user. For breakpoints, I use a GDB-style interface, where the breakpoint type is inferred from the parameters you pass, rather than requiring explicit switching:

0x-> breakpoint address

:-> breakpoint line number

-> breakpoint function name

Else if (is_prefix (command, "break") {if (args [1] [0] = ='0' & & args [1] [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]);}}

For symbols, we will look for symbols and print out any matches we find:

Else if (is_prefix (command, "symbol")) {auto syms = lookup_symbol (args [1]); for (auto&& s: syms) {std::cout

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report