【GDB自定义指令】core analyzer结合gdb的调试及自定义gdb指令详情

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【GDB自定义指令】core analyzer结合gdb的调试及自定义gdb指令详情

🌊前言

说明:为了方便对比和分析,我创建了两个core analyzer的环境,分别是

  1. core_analyzer
  2. core_analyzer_Test

其中core_analyzer 是初始环境【保持不动】,core_analyzer_Test 是测试环境【用作自定义gdb指令的环境】。


🌞1. 程序分析

程序修改的主要位置:

/root/host/core_analyzer_Test/core_analyzer/gdbplus/gdb-12.1/gdb/

可以看见heapcmd.c文件,其中包含一些特定GDB的函数和命令【GDB扩展】,该文件定义的功能是原来gdb12.1所没有的,引入了一些新的命令和功能,用于堆内存分析、对象搜索、内存段显示等。

heapcmd.c文件分析:

  1. 命令函数
  • 文件定义了多个函数,对应于调试器可以执行的命令。这些命令包括与堆内存检查、对象搜索、内存段显示等相关的操作。
  • 每个函数通常接受一个字符串参数args和一个整数参数from_tty,这表示命令的来源是否是终端。
  1. 命令实现
  • heap_commandref_commandpattern_commandsegment_command等函数实现了特定的调试器命令。
  • 例如,heap_command似乎处理与堆内存相关的各种操作,如堆遍历、内存使用统计、泄漏检查等。
  • 同样,ref_command用于搜索给定对象的引用,pattern_command用于显示内存模式,segment_command用于显示内存段。
  1. 命令参数解析
  • 这些命令接收的参数似乎在函数内部进行解析。例如,使用ca_parse_options函数将args字符串解析为标记。
  • 然后使用这些标记来确定要执行的特定操作或提取必要的信息,如内存地址或选项。
  1. 初始化函数
  • 存在一个初始化函数_initialize_heapcmd,它将这些命令注册到调试器中。
  • 这个函数使用add_cmd函数将命令添加到调试器的命令列表中。
  1. 辅助函数
  • 文件中有多个辅助函数,如内存分配包装器unique_xmalloc_ptr,解析函数parse_and_eval_address和打印函数CA_PRINT
  1. 帮助消息
  • 存在一个帮助消息ca_help_msg,提供了可用命令的摘要以及它们的使用说明。
  • 当用户使用ca_help命令请求帮助时,将显示此消息。
  1. 其他
  • 其他函数如display_help_commandswitch_heap_commandinfo_local_command等提供了调试器环境中的其他功能或设置。

🌞2. 案例说明

🌍2.1 修改内容

测试目的:使用自定义指令打印出二叉树的所有节点【固定写死的内容】。

根据上述的分析,这里我打算编写三个文件:

  • addstructcmd.c     -----编写cmd内容
  • addstruct.h            -----编写结构体定义+扩展接口
  • add_Struct.c          -----编写具体的接口实现

详细步骤如下:

在/root/host/core_analyzer_Test/core_analyzer/gdbplus/gdb-12.1/gdb/目录下创建3个文件+内容:


vim addstructcmd.c
/*
 * addstructcmd.c
 *
 *  Created on: Feb 17, 2024
 *      Author: sym
 */
#include "ref.h"
#include "addstruct.h"
 
/***************************************************************************
* gdb commands
***************************************************************************/
static void
addstructmem_command (const char *args, int from_tty)
{
  CA_PRINT("addstructmem_command\n");
}
 
// 打印二叉树的所有节点
static void
addstruct_tree_command (const char *args, int from_tty)
{
    TreeNode* root = buildTree();
    print_Tree(root);
    CA_PRINT("\n");
}
 
static char addstruct_help_msg[] = "Commands of addstruct v1.0\n"
  "addstructmem    -- print memory pool info.\n"
    "addstruct_tree  -- print binary tree node values.\n" // 添加新命令的帮助信息
  "type 'help <command>' to get more detail and usage info\n";
 
static void
display_addstruct_help_command (const char *args, int from_tty)
{
    CA_PRINT("%s", addstruct_help_msg);
}
 
void _initialize_addstructcmd ();
 
void
_initialize_addstructcmd ()
{
  add_cmd("addstructmem", class_info, addstructmem_command, _("Search for references to a given object"), &cmdlist);
    // 打印二叉树
    add_cmd("addstruct_tree", class_info, addstruct_tree_command, _("Display binary tree node values"), &cmdlist);
  // help list
  add_cmd("addstruct_help", class_info, display_addstruct_help_command, _("Display core analyzer help"), &cmdlist);
}

vim addstruct.h
/*
 * addstruct.h
 *
 *  Created on: Feb 17, 2024
 *      Author: sym
 */
#include <stdio.h>
#include <stdlib.h>
 
#ifndef TREENODE_H
#define TREENODE_H
/*
 * Data structures for reference
 */
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;
 
/
// Import functions (required from  etc.)
/
// 创建一个新的树节点
extern TreeNode* createNode(int data);
// 建立树节点数据:构建四层树
extern TreeNode* buildTree();
// 递归遍历树并打印节点数据
extern void print_Tree(TreeNode* root);
 
#endif /* TREENODE_H */
 

vim add_Struct.c
/*
 * add_Struct.c
 *
 *  Created on: Feb 17, 2024
 *      Author: sym
 */
#include "addstruct.h"
 
// 创建一个新的树节点
TreeNode* 
createNode(int data) {
    TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed.\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}
 
// 建立树节点数据:构建四层树
TreeNode* 
buildTree() {
    TreeNode* root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->left = createNode(6);
    root->right->right = createNode(7);
    root->left->left->left = createNode(8);
    root->left->left->right = createNode(9);
    return root;
}
 
// 递归遍历树并打印节点数据
void 
print_Tree(TreeNode* root) {
    if (root != NULL) {
        printf("%d ", root->data);
        print_Tree(root->left);
        print_Tree(root->right);
    }
}
 

给上述三个文件加入rwx权限:

chmod 777 addstructcmd.c 
chmod 777 addstruct.h 
chmod 777 add_Struct.c

然后打开Makefile.in文件,在1102行加入

    addstructcmd.c \
    add_Struct.c \


🌍2.2 测试自定义指令

回到/root/host/core_analyzer_Test/core_analyzer目录,终端输入

./build_gdb.sh

编译完成后在当前路径下进行终端输入

./build/gdb-12.1/build/gdb/gdb

测试gdb的自定义指令

addstructmem

addstruct_help

addstruct_tree

显示如上图则表明添加自定义gdb指令初步成功!


🌞3. 实战内容

前面案例实现了几个简单的自定义gdb指令,但缺陷在于都是基于写死的内容打印输出,实际情况使用gdb是为了去调试自己的程序是否存在问题,所以需要加上用户调试的参数以完善自定义gdb指令,使其更加灵活。


🌳3.1 修改内容

根据上述的案例,这里我打算编写文件:

  • addstructcmd.c     -----编写cmd内容

详细步骤如下:

在/root/host/core_analyzer_Test/core_analyzer/gdbplus/gdb-12.1/gdb/目录下创建addstructcmd.c 文件+内容:


vi addstructcmd.c
/*
 * addstructcmd.c
 *
 *  Created on: Feb 17, 2024
 *      Author: sym
 */
#include "ref.h"
 
/***************************************************************************
* new structs and print structs
***************************************************************************/
// 定义数据结构:树
struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
};
 
// 打印数据:树【先序遍历】
static void 
print_tree_nodes(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
 
    printf("%d ", root->data);
 
    if (root->left != NULL) {
        print_tree_nodes(root->left);
    }
 
    if (root->right != NULL) {
        print_tree_nodes(root->right);
    }
}
/***************************************************************************
* gdb commands
***************************************************************************/
static void
addstructmem_command (const char *args, int from_tty)
{
  CA_PRINT("addstructmem_command\n");
}
 
// 测试内容
static void
addstruct_test_command (const char *args, int from_tty)
{
  printf("args=%s\n", args);
    printf("from_tty=%d\n", from_tty);
}
 
// 打印以指定根节点地址为根的二叉树的所有节点
static void 
addstruct_tree_command(const char *args, int from_tty) 
{
    if (!args || *args == '\0') {
        printf("Please provide the root node address.\n");
        return;
    }
 
    // 将 args 存储的地址内容 转换为 root 的地址
    struct TreeNode *root = (struct TreeNode *)strtoul(args, NULL, 0);
   
    // 如果找到了指定的根节点,则打印以该节点为根的二叉树的所有节点
    if (root != NULL) {
        printf("Here is test root info:add and data\n");
        // 对指针进行有效性检查
        printf("root node address: %p\n", (void *)root); // 打印传入的根节点地址,用于验证
        printf("root node data: %d\n\n", root->data); // 打印传入的根节点数据,用于验证
        
        printf("Printing tree nodes:\n");
        print_tree_nodes(root);
        printf("\n");
    } else {
        printf("Specified root node not found.\n");
    }
}
 
 
static char addstruct_help_msg[] = "Commands of addstruct v1.0\n"
    "addstructmem    -- print memory pool info.\n"
    "addstruct_test <txt>    -- test get user inputTxt\n"
    "addstruct_tree <root>  -- print binary tree node values starting from specified root node.\n"
    "type 'help <command>' to get more detail and usage info\n";
 
 
static void
display_addstruct_help_command (const char *args, int from_tty)
{
    CA_PRINT("%s", addstruct_help_msg);
}
 
void _initialize_addstructcmd ();
 
void
_initialize_addstructcmd ()
{
    add_cmd("addstructmem", class_info, addstructmem_command, _("Search for references to a given object"), &cmdlist);
    // 测试获取输入内容
    add_cmd("addstruct_test", class_info, addstruct_test_command, _("Display user inputTxt"), &cmdlist);
    // 打印二叉树
    add_cmd("addstruct_tree", class_info, addstruct_tree_command, _("Display binary tree node values"), &cmdlist);
    // help list
    add_cmd("addstruct_help", class_info, display_addstruct_help_command, _("Display core analyzer help"), &cmdlist);
}
 
 

给上述文件加入rwx权限:

chmod 777 addstructcmd.c

然后打开Makefile.in文件,在1102行加入

addstructcmd.c \


🌳3.2 测试自定义指令

回到/root/host/core_analyzer_Test/core_analyzer目录,终端输入

./build_gdb.sh

编译完成后在当前路径下进行终端输入【说明:tree3_01.c 文件内容参考:【gdb调试】在ubuntu环境使用gdb调试一棵四层二叉树的数据结构详解

./build/gdb-12.1/build/gdb/gdb  /root/host/my_program/tree3_01

测试gdb的自定义指令

addstructmem

addstruct_help

addstruct_tree + 地址

显示如下图则表明添加自定义gdb指令初步成功!

这里也反馈了一个信息:地址传递给 args,通过变量 args 成功将 root 的地址设置成了传递的地址【用户传递参数的处理过程是没问题的】,显示段错误是因为这个传递的地址空间未开辟。


🌳3.2 自定义gdb指令错误纠察

纠错思路:使用gdb调试修改的gdb进行查错,由于gdb中没有构建一颗完整的二叉树,所以我打算手动开辟一棵两层的二叉树用作测试。

🍃测试一:手动开辟空间测试

在目录:/root/host/core_analyzer_Test/core_analyzer 使用gdb调试gdb:

./build/gdb-12.1/build/gdb/gdb ./build/gdb-12.1/build/gdb/gdb

打开后依次执行:

start
b addstruct_tree_command
 
jump /root/host/core_analyzer_Test/core_analyzer/build/gdb-12.1/build/../gdb/addstructcmd.c:55
 
set *((int *)0x5555555592a0) = 1
set ((struct TreeNode *)0x5555555592a0)->left = (struct TreeNode *)0x5555555592c0
set ((struct TreeNode *)0x5555555592a0)->right = (struct TreeNode *)0x5555555592e0
 
set *((int *)0x5555555592c0) = 2
set ((struct TreeNode *)0x5555555592c0)->left = (struct TreeNode *)0x0
set ((struct TreeNode *)0x5555555592c0)->right = (struct TreeNode *)0x0
 
set *((int *)0x5555555592e0) = 3
set ((struct TreeNode *)0x5555555592e0)->left = (struct TreeNode *)0x0
set ((struct TreeNode *)0x5555555592e0)->right = (struct TreeNode *)0x0
 
set variable args = "0x5555555592a0"
 
f

此时地址空间开辟的地址和值如下图:

调试可以正常打印:


🍃测试二:程序开辟,修改addstructcmd.c

修改addstructcmd.c 内容如下:

/*
 * addstructcmd.c
 *
 *  Created on: Feb 17, 2024
 *      Author: sym
 */
#include "ref.h"
 
/***************************************************************************
* new structs and print structs
***************************************************************************/
// 定义数据结构:树
struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
};
 
 
// 创建一个新的树节点
TreeNode* createNode(int data) {
    TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed.\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}
 
// 构建四层树
struct TreeNode* buildTree() {
    TreeNode* root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->left = createNode(6);
    root->right->right = createNode(7);
    root->left->left->left = createNode(8);
    root->left->left->right = createNode(9);
    return root;
}
 
 
// 打印数据:树【先序遍历】
static void 
print_tree_nodes(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
 
    printf("%d ", root->data);
 
    if (root->left != NULL) {
        print_tree_nodes(root->left);
    }
 
    if (root->right != NULL) {
        print_tree_nodes(root->right);
    }
}
/***************************************************************************
* gdb commands
***************************************************************************/
static void
addstructmem_command (const char *args, int from_tty)
{
  CA_PRINT("addstructmem_command\n");
}
 
// 测试内容
static void
addstruct_test_command (const char *args, int from_tty)
{
  printf("args=:%s\n", args);
    printf("args=:%d\n", from_tty);
}
 
// 打印以指定根节点地址为根的二叉树的所有节点
static void 
addstruct_tree_command(const char *args, int from_tty) 
{
    if (!args || *args == '\0') {
        printf("Please provide the root node address.\n");
        return;
    }
 
    // 构建树
    struct TreeNode* root_frist = buildTree();
    // 将 args 存储的地址内容 转换为 root 的地址
    struct TreeNode *root = (struct TreeNode *)strtoul(args, NULL, 0);
   
    // 如果找到了指定的根节点,则打印以该节点为根的二叉树的所有节点
    if (root != NULL) {
        printf("Here is test root info:add and data\n");
        // 对指针进行有效性检查
        printf("root node address: %p\n", (void *)root); // 打印传入的根节点地址,用于验证
        printf("root node data: %d\n\n", root->data); // 打印传入的根节点数据,用于验证
        
        printf("Printing tree nodes:\n");
        print_tree_nodes(root);
        printf("\n");
    } else {
        printf("Specified root node not found.\n");
    }
}
 
 
static char addstruct_help_msg[] = "Commands of addstruct v1.0\n"
    "addstructmem    -- print memory pool info.\n"
    "addstruct_test <txt>    -- test get user inputTxt\n"
    "addstruct_tree <root>  -- print binary tree node values starting from specified root node.\n"
    "type 'help <command>' to get more detail and usage info\n";
 
 
static void
display_addstruct_help_command (const char *args, int from_tty)
{
    CA_PRINT("%s", addstruct_help_msg);
}
 
void _initialize_addstructcmd ();
 
void
_initialize_addstructcmd ()
{
    add_cmd("addstructmem", class_info, addstructmem_command, _("Search for references to a given object"), &cmdlist);
    // 测试获取输入内容
    add_cmd("addstruct_test", class_info, addstruct_test_command, _("Display user inputTxt"), &cmdlist);
    // 打印二叉树
    add_cmd("addstruct_tree", class_info, addstruct_tree_command, _("Display binary tree node values"), &cmdlist);
    // help list
    add_cmd("addstruct_help", class_info, display_addstruct_help_command, _("Display core analyzer help"), &cmdlist);
}
 
 

然后回到章节 3.2 进行编译。

在目录:/root/host/core_analyzer_Test/core_analyzer 使用gdb调试gdb:

./build/gdb-12.1/build/gdb/gdb ./build/gdb-12.1/build/gdb/gdb

打开后依次执行【这里赋给args的值是随意的,防止 if 语句那里直接 return】:

start
b addstruct_tree_command
 
jump /root/host/core_analyzer/build/gdb-12.1/build/../gdb/addstructcmd.c:84
 
set variable args = "0x5555555592a0"
f

使用p root_frist 打印 root_frist 的地址,然后将其重新赋给args

在gdb写死的数据结构:树【调用方法 struct TreeNode* buildTree() 】。获取地址可以正常输出:


🍃测试总结

上面两轮测试都在说明==>程序没问题,传参的设置与转换没问题。

问题:无法知道用户开辟的地址在哪,解决思路是通过输入变量名然后搜索堆栈获取信息,再打印。类似(比如p root会输出地址信息,这里的root就是变量名),而后面要做的就是获取这个显示的地址。

补充说明:

./xxx  每次执行开辟的地址空间不同,而gdb调试每次的开辟地址都一样。


相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
3月前
|
NoSQL Linux C语言
Linux GDB 调试
Linux GDB 调试
62 10
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
|
3月前
|
NoSQL
技术分享:如何使用GDB调试不带调试信息的可执行程序
【8月更文挑战第27天】在软件开发和调试过程中,我们有时会遇到需要调试没有调试信息的可执行程序的情况。这可能是由于程序在编译时没有加入调试信息,或者调试信息被剥离了。然而,即使面对这样的挑战,GDB(GNU Debugger)仍然提供了一些方法和技术来帮助我们进行调试。以下将详细介绍如何使用GDB调试不带调试信息的可执行程序。
99 0
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
42 1
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
31 0
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试正在运行的程序
Linux C/C++ gdb调试正在运行的程序
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试core文件
Linux C/C++ gdb调试core文件
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试
Linux C/C++ gdb调试
|
6月前
|
NoSQL 搜索推荐 openCL
【C/C++ 调试 GDB指南 】gdb调试基本操作
【C/C++ 调试 GDB指南 】gdb调试基本操作
378 2
|
6月前
|
NoSQL Linux 开发工具
【深入解析git和gdb:版本控制与调试利器的终极指南】(下)
【深入解析git和gdb:版本控制与调试利器的终极指南】