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

What is the process of handling variables in the Linux debugger

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

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

This article mainly explains "what is the process of dealing with variables in the Linux debugger". The content of the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "what is the process of dealing with variables in the Linux debugger".

Before you begin, please make sure that the version of libelfin you are using is fbreg on my branch. This includes some hack to support getting the base address of the current stack frame and evaluating the location list, which are not provided by the native libelfin. You may need to pass the-gdwarf-2 parameter to GCC to generate compatible DWARF messages. But before I implement it, I'll elaborate on the location encoding in the latest DWARF 5 specification.

DWARF location

The location of the variable in memory at a given time is encoded in the DWARF information using the DW_AT_location attribute. A location description can be a single location description, a composite location description, or a location list.

Simple location description: describes the location of a contiguous part of an object (usually all parts). A simple location description can describe a location in an addressable memory or register, or a missing location (with or without a known value). For example, DW_OP_fbreg-32: an entire stored variable-32 bytes starting from the stack frame base address.

Compound location description: depending on the fragment description object, each object can be contained in a portion of the register or stored in a memory location independent of other fragments. For example, DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2: the first four bytes are in register 3 and the last two bytes are in a variable in register 10.

Location list: describes objects that have a limited lifetime or change location during the lifetime. For example:

[0] DW_OP_reg0 [1] DW_OP_reg3 [2] DW_OP_reg2

A variable whose position moves between registers according to the current value of the program counter.

Depending on the type of location description, DW_AT_location is encoded in three different ways. Exprloc encodes a simple and composite location description. They consist of a byte length followed by an DWARF expression or location description. A list of encoded locations for loclist and loclistptr, which provide indexes or offsets in the .debug _ loclists section, which describes the actual list of locations.

DWARF expression

Use the DWARF expression to calculate the actual location of the variable. This includes a series of operations that manipulate stack values. There are many DWARF operations available, so I won't explain them in detail. Instead, I will give you some examples from each expression to give you something available. Also, don't be afraid of this; libelfin will handle all these complexities for us.

Literal coding

Push unsigned values into the stack, address operands into the stack, literals into the stack DW_OP_lit0, DW_OP_lit1... DW_OP_lit31DW_OP_addrDW_OP_constu

Register value

Press the contents of the given register plus the given offset into the stack and the value found at the stack frame base address, offset the given values DW_OP_fbregDW_OP_breg0, DW_OP_breg1... DW_OP_breg31

Stack operation

Treat the top of the stack as a memory address and replace it with the contents of that address copy the value DW_OP_dupDW_OP_deref at the top of the stack

Arithmetic and logical operation

Same as DW_OP_and, but add values to pop up the two values at the top of the stack and press back their logical ANDDW_OP_andDW_OP_plus

Control flow operation

Conditional branch: if the top of the stack is not 0, the first two values are compared by skipping backward or backward in the expression through offset, and if the condition is true, press 1, otherwise it is 0DW_OP_le, DW_OP_eq, DW_OP_gt, etc. DW_OP_bra

Input conversion

Converts the value at the top of the stack to a different type, which describes the DW_OP_convert by the DWARF information entry for a given offset

Special operation

Do nothing! DW_OP_nopDWARF Typ

The representation of the DWARF type needs to be strong enough to provide useful variable representations for debugger users. Users often want to be able to debug at the application level rather than at the machine level, and they need to know what their variables are doing.

The DWARF type is encoded in DIE along with most other debugging information. They can have properties that indicate their name, encoding, size, bytes, and so on. Countless type tags can be used to represent pointers, arrays, structures, typedef, and anything else you can see in a C or C++ program.

Take this simple structure as an example:

Struct test {int ifloat float Jittint k [42]; test* next;}

The parent DIE of this structure looks like this:

DW_TAG_structure_typeDW_AT_name "test" DW_AT_byte_size 0x000000b8DW_AT_decl_file 0x00000001 test.cppDW_AT_decl_line 0x00000001

What it says above is that we have a structure called test, the size of which is 0xb8, declared on line 1 of test.cpp. Then there are many child DIE that describe the member.

DW_TAG_memberDW_AT_name "I" DW_AT_type DW_AT_decl_file 0x00000001 test.cppDW_AT_decl_line 0x00000002DW_AT_data_member_location 0 DW_TAG_memberDW_AT_name "j" DW_AT_type DW_AT_decl_file 0x00000001 test.cppDW_AT_decl_line 0x00000003DW_AT_data_member_location 4 DW_TAG_memberDW_AT_name "k" DW_AT_type DW_AT_decl_file 0x00000001 test.cppDW_AT_decl _ line 0x00000004DW_AT_data_member_location 8 DW_TAG_memberDW_AT_name "next" DW_AT_type DW_AT_decl_file 0x00000001 test.cppDW_AT_decl_line 0x00000005DW_AT_data_member_location 176 (as signed =-80)

Each member has a name, a type (which is an DIE offset), a declaration file and line, and a byte offset that points to the structure in which its member is located. Its type points to the following.

DW_TAG_base_typeDW_AT_name "int" DW_AT_encoding DW_ATE_signedDW_AT_byte_size 0x00000004 DW_TAG_base_typeDW_AT_name "float" DW_AT_encoding DW_ATE_floatDW_AT_byte_size 0x00000004 DW_TAG_array_typeDW_AT_type DW_TAG_subrange_typeDW_AT_type DW_AT_count 0x0000002a DW_TAG_base_typeDW_AT_name "sizetype" DW_AT_byte_size 0x00000008DW_AT_encoding DW_ATE_unsigned DW_TAG_pointer_typeDW_AT_type

As you can see, int on my laptop is a 4-byte signed integer type, and float is a 4-byte floating-point number. The integer array type points to the int type as its element type, and sizetype (which can be thought of as size_t) as the index type, which has 2a elements. The test * type is DW_TAG_pointer_type, which refers to test DIE.

Implement a simple variable reader

As mentioned above, libelfin will handle most of the complexity for us. However, it does not implement all the methods for representing variable locations, and it will be very complicated to deal with them in our code. Therefore, I now choose to support only exprloc. Please add support for more types of expressions as needed. If you really have the courage, please submit a patch to libelfin to help complete the necessary support!

Processing variables is mainly to locate different parts in memory or registers, reading or writing the same as before. For simplicity, I will only show you how to implement reading.

First we need to tell libelfin how to read the registers from our process. We create a class that inherits from expr_context and use ptrace to handle everything:

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 sizereturn ptrace (PTRACE_PEEKDATA, m_pid, address, nullptr);} private:pid_t masks id;}

Reading will be handled by the read_variables function in our debugger class:

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

The first thing we do above is to find the function we are currently entering, and then we need to iterate through the entries in that function to find variables:

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

We get the location information by looking for the DW_AT_location entry in DIE:

Auto loc_val = De [DW _ AT::location]

Then we make sure it is an exprloc and ask libelfin to evaluate our expression:

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

Now that we have evaluated the expression, we need to read the contents of the variable. It can be in memory or in registers, so we will deal with these two cases:

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

As you can see, I printed out the value without explanation based on the type of variable. Hopefully, through this code, you can see how to support writing variables, or search for variables with a given name.

Finally, we can add it to our command parser:

Else if (is_prefix (command, "variables")) {read_variables ();} test it

Write some small features with some variables, do not optimize and compile it with debugging information, and then see if you can read the value of the variable. Try to write to the memory address where the variable is stored and see how the program changes.

Thank you for reading, the above is the content of "what is the process of dealing with variables in the Linux debugger". After the study of this article, I believe you have a deeper understanding of the process of dealing with variables in the Linux debugger, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

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

Development

Wechat

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

12
Report