search
HomeSystem TutorialLINUXExplore variable handling techniques in Linux debuggers!

Explore variable handling techniques in Linux debuggers!

Jan 15, 2024 pm 11:09 PM
linuxlinux tutorialRed Hatlinux systemlinux commandlinux certificationred hat linuxlinux video

Introduction Variables are sneaky. Sometimes they'll happily sit in the register, only to end up on the stack as soon as they turn around. For optimization purposes, the compiler may throw them out of the window entirely. No matter how variables move through memory, we need some way to track and manipulate them in the debugger. This article will teach you how to handle variables in the debugger and demonstrate a simple implementation using libelfin.
Series Article Index
  1. Preparing the environment
  2. Breakpoint
  3. Registers and Memory
  4. ELF and DWARF
  5. Source code and signals
  6. Source code level step by step execution
  7. Source level breakpoints
  8. Stack expansion
  9. Handling variables
  10. Advanced Topics

Before starting, please make sure you are using the version of libelfin fbreg on my branch. This contains a few hacks to support getting the base address of the current stack frame and evaluating a list of locations, neither of which is provided by native libelfin. You may need to pass the -gdwarf-2 parameter to GCC to generate compatible DWARF messages. But before implementing that, I'll detail how positional encoding works in the latest DWARF 5 specification. If you want to know more, you can get the standard here.

DWARF Location

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

  • Simple position description: Describes the position of a continuous part (usually all parts) of the object. A simple location description can describe a location in addressable memory or a register, or the lack thereof (with or without a known value). For example, DW_OP_fbreg -32: An entire stored variable - 32 bytes starting from the stack frame base.
  • Composite location description: Describe objects in terms of fragments, each object can be contained in a portion of a 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 limited lifetime or change location during lifetime. for example:
      • [ 0]DW_OP_reg0
      • [ 1]DW_OP_reg3
      • [ 2]DW_OP_reg2
  • Variables that move between registers based on the current value of the program counter.

DW_AT_location is encoded in three different ways depending on the type of location description. exprloc encodes simple and composite position descriptions. They consist of a byte length followed by a DWARF expression or location description. Encoded location lists for loclist and loclistptr, which provide the index or offset in the .debug_loclists section, which describes the actual location list.

DWARF expression

Use a DWARF expression to calculate the actual position of a 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'll give some examples from each expression to give you something to work with. Also, don't be afraid of this; libelfin will handle all this complexity for us.

  • Literal encoding
    • DW_OP_lit0, DW_OP_lit1……DW_OP_lit31
      • Push literals onto the stack
    • DW_OP_addr
      • Push the address operand onto the stack
    • DW_OP_constu
      • Push unsigned value onto stack
  • Register value
    • DW_OP_fbreg
      • Push the value found at the base of the stack frame, offset by the given value
    • DW_OP_breg0, DW_OP_breg1... DW_OP_breg31
      • Push the contents of the given register plus the given offset onto the stack
  • Stack operations
    • DW_OP_dup
      • Copy the value at the top of the stack
    • DW_OP_deref
      • Treat the top of the stack as a memory address and replace it with the contents of that address
  • Arithmetic and logical operations
    • DW_OP_and
      • Pop the two values ​​at the top of the stack and push back their logical AND
    • DW_OP_plus
      • Same as DW_OP_and, but adds value
  • Control flow operations
    • DW_OP_le, DW_OP_eq, DW_OP_gt, etc.
      • Pop the first two values, compare them, and push 1 if the condition is true, 0 otherwise
    • DW_OP_bra
      • Conditional branch: If the top of the stack is not 0, skip forward or backward in the expression via offset
  • Input conversion
    • DW_OP_convert
      • Convert the value at the top of the stack to a different type, which is described by the DWARF info entry at the given offset
  • Special operations
    • DW_OP_nop
      • do nothing!
DWARF type

DWARF type representations need to be powerful enough to provide useful variable representations to debugger users. Users often want to be able to debug at the application level rather than at the machine level, and they need to understand what their variables are doing.

The DWARF type is encoded in DIE along with most other debugging information. They can have properties indicating their name, encoding, size, bytes, etc. A myriad of type tags are available to represent pointers, arrays, structures, typedefs, and anything else you might see in C or a C program.

Take this simple structure as an example:

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

The parent DIE of this structure is like this:

 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

The above is that we have a structure called test, with a size of 0xb8, declared on line 1 of test.cpp. Next there are a number of sub-DIEs describing the members.

 DW_TAG_member
DW_AT_name "i"
DW_AT_type 
DW_AT_decl_file 0x00000001 test.cpp
DW_AT_decl_line 0x00000002
DW_AT_data_member_location 0
 DW_TAG_member
DW_AT_name "j"
DW_AT_type 
DW_AT_decl_file 0x00000001 test.cpp
DW_AT_decl_line 0x00000003
DW_AT_data_member_location 4
 DW_TAG_member
DW_AT_name "k"
DW_AT_type 
DW_AT_decl_file 0x00000001 test.cpp
DW_AT_decl_line 0x00000004
DW_AT_data_member_location 8
 DW_TAG_member
DW_AT_name "next"
DW_AT_type 
DW_AT_decl_file 0x00000001 test.cpp
DW_AT_decl_line 0x00000005
DW_AT_data_member_location 176(as signed = -80)

Each member has a name, a type (which is a DIE offset), a declaration file and line, and a byte offset pointing to the structure in which its member resides. Its type points are as follows.

 DW_TAG_base_type
DW_AT_name "int"
DW_AT_encoding DW_ATE_signed
DW_AT_byte_size 0x00000004
 DW_TAG_base_type
DW_AT_name "float"
DW_AT_encoding DW_ATE_float
DW_AT_byte_size 0x00000004
 DW_TAG_array_type
DW_AT_type 
 DW_TAG_subrange_type
DW_AT_type 
DW_AT_count 0x0000002a
 DW_TAG_base_type
DW_AT_name "sizetype"
DW_AT_byte_size 0x00000008
DW_AT_encoding DW_ATE_unsigned
 DW_TAG_pointer_type
DW_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 has 2a elements by pointing to type int as its element type and sizetype (think of it as size_t) as the index type. The test * type is DW_TAG_pointer_type, which refers to the test DIE.

Implementing a simple variable reader

As mentioned above, libelfin will handle most of the complexity for us. However, it does not implement all methods for representing variable positions, and handling these in our code will become very complex. Therefore, I now choose to only support exprloc. Please add support for more types of expressions as needed. If you're really brave, please submit a patch to libelfin to help complete the necessary support!

Processing variables mainly involves locating different parts in memory or registers, and reading or writing is the same as before. To keep things simple, I'll just tell you how to implement reading.

First we need to tell libelfin how to read 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 size
return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr);
}
private:
pid_t m_pid;
};

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 did above is find the function we are currently in, then we need to iterate through the entries in that function to find the variables:

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

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

auto loc_val = die[DW_AT::location];

Next we make sure it's 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 registers, so we'll handle both cases:

switch (result.location_type) {
case expr_result::type::address:
{
auto value = read_memory(result.value);
std::cout 
<p>You can see that I printed out the value without explanation based on the type of the variable. Hopefully with this code you can see how there is support for writing variables, or searching for variables with a given name. </p>
<p>Finally we can add this to our command parser: </p>
<pre class="brush:php;toolbar:false">else if(is_prefix(command, "variables")) {
read_variables();
}
have a test

Write some small function with some variables, compile it without optimization and with debug information, and then see if you can read the value of the variable. Try writing to the memory address where the variable is stored and see how the program changes behavior.

There are already nine articles, and the last one is left! Next time I will discuss some more advanced concepts that may be of interest to you. Now you can find the code for this post here.

The above is the detailed content of Explore variable handling techniques in Linux debuggers!. For more information, please follow other related articles on the PHP Chinese website!

Statement
This article is reproduced at:Linux就该这么学. If there is any infringement, please contact admin@php.cn delete
How to Use 'next' Command with Awk in Linux - Part 6How to Use 'next' Command with Awk in Linux - Part 6May 15, 2025 am 10:43 AM

In this sixth installment of our Awk series, we will explore the next command, which is instrumental in enhancing the efficiency of your script executions by skipping redundant processing steps.What is the next Command?The next command in awk instruc

How to Efficiently Transfer Files in LinuxHow to Efficiently Transfer Files in LinuxMay 15, 2025 am 10:42 AM

Transferring files in Linux systems is a common task that every system administrator should master, especially when it comes to network transmission between local or remote systems. Linux provides two commonly used tools to accomplish this task: SCP (Secure Replication) and Rsync. Both provide a safe and convenient way to transfer files between local or remote machines. This article will explain in detail how to use SCP and Rsync commands to transfer files, including local and remote file transfers. Understand the scp (Secure Copy Protocol) in Linux scp command is a command line program used to securely copy files and directories between two hosts via SSH (Secure Shell), which means that when files are transferred over the Internet, the number of

10 Most Popular Linux Desktop Environments of All Time10 Most Popular Linux Desktop Environments of All TimeMay 15, 2025 am 10:35 AM

One fascinating feature of Linux, in contrast to Windows and Mac OS X, is its support for a variety of desktop environments. This allows desktop users to select the most suitable and fitting desktop environment based on their computing requirements.A

How to Install LibreOffice 24.8 in Linux DesktopHow to Install LibreOffice 24.8 in Linux DesktopMay 15, 2025 am 10:15 AM

LibreOffice stands out as a robust and open-source office suite, tailored for Linux, Windows, and Mac platforms. It boasts an array of advanced features for handling word documents, spreadsheets, presentations, drawings, calculations, and mathematica

How to Work with PDF Files Using ONLYOFFICE Docs in LinuxHow to Work with PDF Files Using ONLYOFFICE Docs in LinuxMay 15, 2025 am 09:58 AM

Linux users who manage PDF files have a wide array of programs at their disposal. Specifically, there are numerous specialized PDF tools designed for various functions.For instance, you might opt to install a PDF viewer for reading files or a PDF edi

How to Filter Command Output Using Awk and STDINHow to Filter Command Output Using Awk and STDINMay 15, 2025 am 09:53 AM

In the earlier segments of the Awk command series, our focus was primarily on reading input from files. However, what if you need to read input from STDIN?In Part 7 of the Awk series, we will explore several examples where you can use the output of o

Clifm - Lightning-Fast Terminal File Manager for LinuxClifm - Lightning-Fast Terminal File Manager for LinuxMay 15, 2025 am 09:45 AM

Clifm stands out as a distinctive and incredibly swift command-line file manager, designed on the foundation of a shell-like interface. This means that users can engage with their file system using commands they are already familiar with.The choice o

How to Upgrade from Linux Mint 21.3 to Linux Mint 22How to Upgrade from Linux Mint 21.3 to Linux Mint 22May 15, 2025 am 09:44 AM

If you prefer not to perform a new installation of Linux Mint 22 Wilma, you have the option to upgrade from a previous version.In this guide, we will detail the process to upgrade from Linux Mint 21.3 (the most recent minor release of the 21.x series

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

VSCode Windows 64-bit Download

VSCode Windows 64-bit Download

A free and powerful IDE editor launched by Microsoft

MantisBT

MantisBT

Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

SecLists

SecLists

SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.