首页 > 编程语言 >C++ 使用终端GDB调试复杂项目中Segmentation Fault 和 std::bad_alloc问题

C++ 使用终端GDB调试复杂项目中Segmentation Fault 和 std::bad_alloc问题

时间:2024-09-05 14:55:49浏览次数:6  
标签:std ... Segmentation 函数 Fault gdb GDB test 断点

        近期在公司虚拟机上写代码遇到Segmentation Fault 和 std::bad_alloc问题,但是项目庞大,在不了解功能、代码连接关系的时候很难追踪具体是什么地方出了问题。网络上许多关于GDB的教程仅仅停留在简单的示例中的调试,对于复杂的项目结构(多文件,多作用域,......)来说显得有些不够用,因此记录一下终端GDB调试复杂项目的经验,也希望可以帮助到被同样问题困扰的朋友。

1. 修改编译选项以生成支持GDB的可执行文件

        首先在项目编译时我们需要开启debug模式,对于大型项目来说,通常我们使用Makefile或CMakeList来进行文件管理,要让编译得到的可执行文件能够被gdb处理,我们需要在编译时增加命令。

        对于Makefile来说,我们一般通过修改CXXFLAG后的参数来增加对GDB的支持,对于GDB来说,我们主要需要考虑的参数有两个:

        (必需)-g : 在编译项目时,使用 -g 选项来生成调试信息。这个选项告诉编译器在生成的二进制文件中包含足够的调试信息,方便GDB进行调试。

        (可选)-O0 : 这个选项可以禁用编译器的自动优化,否则优化可能会改变代码的执行顺序或使某些变量在调试器中不可见,或是导致GDB的报错位置与文件中实际位置不一致,增大debug难度。

        这里是一个修改Makefile以生成支持GDB的可执行文件的例子:

CXXFLAGS = -std=c++17 -Wall -O0 -g

        对于CMakeList来说,实现方式是类似的,替换成修改CMAKE_CXX_FLAGS_DEBUG:

set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")

2. 使用GDB进行调试

2.1 进入GDB

        在完成第一步以后,我们就可以得到一个可执行文件了,我们就假设它的名字是exec。接下来我们要在GDB中打开它:

> gdb .../exec
Reading symbols from .../exec...

        或者这样:

> gdb
> (gdb) file .../exec
Reading symbols from .../exec...
> (gdb)

        在GDB读取好可执行文件的信息后,我们就可以正式开始打断点debug了。

2.2 设置断点

        要在GDB中打断点,我们需要传入两个参数:需要打断点的文件,以及设置断点的位置/函数。对于结构较复杂的项目,打断点往往需要传递完整的路径信息,在此演示两种打断点的方式,并附上查询特定函数的方法。

如果项目中的文件的名称是唯一的,那么我们可以直接给GDB提供文件名,然后把断点设置在我们想要的位置:

> (gdb) b test.hh:35
Breakpoint 1 at 0x87e76f4: file path/to/your/file/test.hh:35.

        或者我们也可以通过把断点设置在特定函数的开头。GDB似乎不支持直接查找特定函数中包含的函数,所以我们通过正则匹配的方式把函数名传递给GDB进行查找:

> (gdb) info functions testFunction  //这种方式能够查询项目的所有文件中,名字内包含testFunction的函数:
File path/to/func/test.hh:
41:        void testFunction();  //我们想要的函数
File another/path/to/anotherFile/other.cc:
189:       int testFunctionAnother(int, float);  //其他文件中的函数

        在查找完成后,可以用list来查看该函数所在位置的信息。我们把断点设置在函数testFunction的入口处:

> (gdb) list path/to/func/test.hh:testFunctions
 ###################################
 ### 此处会展示testFunctions的信息 ###
 ###################################
> (gdb) b path/to/func/test.hh:testFunctions
Breakpoint 2 at 0x99e6e22: path/to/func/test.hh:testFunctions.

        关于断点的设置和操作,网上有许多介绍更加细致的教程,这里就只介绍一下复杂项目中函数的查询方式,更进一步的断点操作就不做过多赘述了。

2.3 运行GDB

       在GDB读取好可执行文件的信息后,我们就可以正式开始打断点debug了。我个人面对的项目是需要设置传入参数的,因此并不像网上许多教程那样只是简单地输入run就可以开始运行。

        如果你的项目代码运行时不需要传入参数,只需要输入run,GDB就会开始运行程序直到断点,或是遇到导致程序crash的问题(比如Segmentation Fault)为止:

> (gdb) run

        而对于需要传入参数的项目,我们需要在运行时指定要传入的指令。这里我们假设文件需要通过 -s 来读取位于 ~/test/case1/路径下的文件file。为了展示GDB下改变路径和传入指令的过程,我们先通过cd命令来移动到~/test/路径下,再读取file文件:

> (gdb) cd ~/test
Working directory /home/test
> (gdb) run .../exec -s ./file

        前文简单提到了关于设置断点的操作。对于知道想要追踪的代码位置的情况来说,如果你设置了断点,那么程序会在运行到断点位置后停下,之后我们可以尝试单步运行代码或是查询当前变量的值。但是在面对Segmentation Fault之类的问题时,往往我们并不清楚代码中是什么位置出了问题,而通过打断点来进行单步调试对于复杂项目来说会显得有些不太现实。

        然而,好消息是,当GDB在run的过程中遇到会导致程序崩溃的问题时能够自动停下。也就是说,我们不需要设置任何断点,GDB能够自动停止在出现问题的位置:

Thread 1 "exec" received signal SIGSEGV, Segmentation fault.

        接下来我们输入where, 能够看到导致程序crash的代码和调用关系。通常来说这里可能会报出很多信息,因为GDB会完整地展示整个函数的调用过程。例如,main() -> func1() -> func2()导致了Segmentation Fault, 那么GDB会把这3个函数都抓取出来:

> (gdb) where
#0 0x000000005841628 in func2 ()
#1 0x0000000005845d7 in func1 ()
#2 0x00000000565ea43 in main ()

        使用frame命令可以仔细查看特定报错的信息,而用backtrace命令可以查看函数之间调用时传递的参数和函数的内部变量值:

> (gdb) frame 0  //对应上方的#0, 展示func1中的详细信息
...
> (gdb) backtrace full
...

        在通过以上操作得到报错函数信息后,我们就可以通过进一步查询相关函数、追踪特定变量或在可疑的位置打断点来实现精确的问题追踪。对于我个人遇到的情况而言,GDB在这里并没有给我明确指出具体是哪一行导致程序崩溃,但GDB告诉了我导致崩溃的函数,于是我通过调用的函数来进一步排查出现问题的地方(例如,析构函数报错,所以我会选择优先排查class中的clear功能)。

标签:std,...,Segmentation,函数,Fault,gdb,GDB,test,断点
From: https://blog.csdn.net/m0_52437597/article/details/141865857

相关文章

  • C++: std::once_flag 和 std::call_once
    std::once_flag和std::call_oncestd::once_flag和std::call_once是C++11引入的同步原语,用于确保某个函数在多线程环境中只被执行一次。它们位于头文件中,主要用于实现线程安全的初始化操作。std::once_flag概述类型:std::once_flag是一个结构体,用于记录某个函数......
  • 【Moveit2】MoveGroupInterface设置目标姿态,然后创建一个计划到该姿态的运动路径,stati
    PlanandExecuteusingMoveGroupInterface//CreatetheMoveItMoveGroupInterfaceusingmoveit::planning_interface::MoveGroupInterface;automove_group_interface=MoveGroupInterface(node,"panda_arm");//SetatargetPoseautoconsttarget_p......
  • 遥感影像-语义分割数据集:Postdam数据集详细介绍及训练样本处理流程
    原始数据集详情Potsdam数据集是一个有着2D语义分割内容标注的城市遥感数据集。KeyValue卫星类型未知覆盖区域一个典型的历史城市,有着大的建筑群、狭窄的街道和密集的聚落结构-Potsdam场景城市分辨率5cm数量38张单张尺寸6000*6000原始影像位深8位标签图片位深8位原始影像......
  • Python 默认列表(Default List):一种灵活的数据结构
    Python中的默认列表(DefaultList)是一种特殊的数据结构,它允许我们创建一个包含特定元素类型的列表,并在需要时动态地添加或删除元素。这种灵活性使得默认列表成为了处理一些不确定或变化的数据的有力工具。创建列表时指定元素类型在Python中,我们可以在创建列表时指定元素类型,如果......
  • MyBatis 源码解析:DefaultSqlSession 功能解析
    摘要DefaultSqlSession是MyBatis中的核心类,负责执行SQL语句和管理事务。在日常开发中,我们经常会通过SqlSession来执行数据库的增删改查操作。你是否想深入了解DefaultSqlSession的内部实现机制?本文将通过自定义实现一个DefaultSqlSession类,带你全面解析MyBatis......
  • JavaScript中的export、export default、exports和module.exports(export、export defa
    简介:在JavaScript中,export和exportdefault是ES6模块系统的核心部分,用于从文件中导出函数、关键字,对象或值,使其可以在其他文件中通过import语句导入和使用,而exports和module.exports是CommonJS模块系统的一部分,在Node.js环境中,你可以使用exports或module.exports......
  • C++:std::thread 和 pthread
            在C++中,线程的实现主要有两种方式:使用C++11标准库中的std::thread和POSIX线程库(pthread)。这两种方式各有优缺点,适用于不同的场景。以下是对这两种方式的详细比较和示例代码。std::thread示例代码#include<iostream>#include<thread>#include<chrono>......
  • C++:std::this_thread::sleep_for 和 sleep
            在C++中,std::this_thread::sleep_for和sleep函数都可以用来使当前线程暂停执行一段时间,但它们有一些重要的区别。以下是对这两种方法的详细比较:std::this_thread::sleep_for定义:std::this_thread::sleep_for是C++11标准库中的一个函数,用于使当前线程暂停执......
  • JavaScript中的`event.preventDefault()`和`event.stopPropagation()`有什么区别?
    在JavaScript中,event.preventDefault()和event.stopPropagation()是两个常用于事件处理的重要方法,它们各自扮演着不同的角色,在控制Web页面交互行为时发挥着关键作用。下面将详细阐述这两个方法的区别,包括它们的作用、使用场景以及影响。一、event.preventDefault()1.定义与......
  • stdio.h及字符串输入输出
    这里只简单介绍常用的C语言常见的输入输出及字符串的输入输出,可以作为常用C语言字符串的速记收藏。#include<stdio.h>scanf //与空格,tab键及换行就阻断缓冲区printf //格式输入输出gets(数组名) //直到遇到换行键停止chararr[n];gets(arr);puts(数组名) //......