首页 > 其他分享 >gdb基本使用介绍

gdb基本使用介绍

时间:2023-12-18 14:32:22浏览次数:32  
标签:基本 std name 程序 GDB 介绍 gdb 内存 include

GDB介绍

GDB是GNU Debugger的简称,其作用是可以在程序运行时,检测程序正在做什么。GDB程序自身是使用C/C++程序编写的,但可以支持除C/Cpp之外很多编程语言的调试。GDB原生支持调试的语言包含:C/Cpp/D/Go/Object-C/OpenCL C/Fortran/Rust等等。

使用GDB,我们可以方便地进行如下任务:

  1. 如果程序崩溃后产生了core dump文件,gdb可以通过分析core dump文件,找到程序crash的位置,调用堆栈等用于找到问题原因的关键信息;
  2. 在程序运行时,GDB可以检测当前检测程序正在干什么事情;
  3. 在程序运行的时,修改变量的值,这个对于提高工作效率很有用处;
  4. 可以使的程序在特定条件下中断;
  5. 监视内存地址变动;

GDB不适合做什么事情:

  1. GDB可以用来辅助调试内存泄露问题,但GDB不能用于内存泄露检测;
  2. GDB可以用来辅助程序性能调优,但是GDB不能用来程序性能问题分析;
  3. GDB不是编译器,不能运行有编译问题的程序,也不能用来调试编译问题;

GDB使了解三方中间件、无源码程序、解决程序疑难杂症的利器。GDB有着日志无法比拟的优势,此外,GDB还非常适合对多种开发语言混合的程序进行调试。

案例实操汇总

  1. Segmentation Fualt问题排查
  2. 程序阻塞问题排查
  3. 数据纂改问题排查
  4. 堆内存重复释放问题排查

++Segmentation Fault问题排查++ 定义:Segmentation Fault是进程访问了由操作系统内存保护机制规定的受限的内存区域触发的。当发生Segmentation Fault异常时,操作系统通过发起一个“SIGSEGV”信号来终止进程,而且,Segmentation Fault不能被异常捕获代码捕获,是导致程序Crash的常见诱因。 对于C/Cpp等贴近操作系统的开发语言,由于提供了灵活的内存访问机制,所以自然成为了Segmentation Fault异常的重灾区,由于默认的Segmentation Fault异常几乎没有详细的错误信息,所以开发人员处理此类异常时变得更为棘手。 范畴:在实际开发中,使用了未初始化的指针,空指针,已经被回收的内存指针,栈/堆溢出(越界)等方面,都会引发Segmentation Fault。 案例: simulateSegmentationFault.cpp

#include <iostream>
class Employee{
public:
	std::string name;
};
void simulateSegmentationFault(const std::string& name) {
	try {
		Employee *employee = new Employee();
		employee->name = name;
		std::cout << "Employee name is " << employee->name << std::endl;
		delete employee;
		std::cout << "After deletion, employee name is " << employee->name << std::endl;
	} catch(...) {
		std::cout << "Error occurred!" << std::endl;
	}
}

int main (int argc, char **argv) {
	std::string text = "Hello world";
	std::cout << text << std::endl;
	simulateSegmentationFault(text);
	return 0;
}

编译程序并运行程序,如下图: image.png 从结果上来看,我们的异常捕获代码对于Segmentation Fault无能为力。其次,发生异常时没有打印任何对我们有帮助的提示信息。 成功加载core文件后,我们首先使用bt命令来查看Crash位置的错误堆栈。从堆栈信息中,可以看到__GI__IO_fwrite方法的buf参数的值是0x0,这显然不是一个合法的数值。序号为5的栈帧,是发生异常前,我们自己的代码压入的最后一个栈帧,信息中甚至给出了发生问题时的调用位置在simulateSegmentationFault.cpp 文件的第13行(simulateSegmentationFault.cpp:13),我们使用up 3 命令向前移动3个栈帧,使得当前处理的栈帧移动到编码为3的栈帧。 image.png 此时可以看到传入的参数name是没有问题的,使得list命令查看下问题调用部分的上下文,再使用info locals命令查看调用时的局部变量的情况。最后使用p *employee命令,查看employee指针指向的数据。 image.png 此时可以看到第13行,使用std::cout输出Employee的name属性时,employee指针指向的地址的name属性已经是一个无效地址了(0x0)。 ++程序阻塞问题排查++ 范围:程序阻塞一般包括:

  • 并发程序中产生了死锁,线程无法获取到锁对象
  • 远程调用长时间阻塞无法返回
  • 程序长时间等待某个时间通知
  • 程序产生了死循环
  • 访问了受限的资源和IO,处于排队阻塞状态 对于大多数阻塞来说,被阻塞的线程会处于休眠状态,放置在等待队列,并不会占用系统的CPU时间。但是如果这种行为不符合程序的预期,那么我们就需要查明程序当前在等待哪一个锁对象,程序阻塞在哪个方法,程序在访问哪个资源时卡住了等问题。 案例:
#include <thread>
#include <mutex>
#include <iostream>

std::mutex my_mu;
void thread1_func() {
	for (int i = 0; i < 5; ++i) {
		my_mu.lock();
		std::cout << "thread1 lock mutex succeed!" << std::endl;
		std::this_thread::yield();
	}
}

void thread2_func() {
	for (int i = 0; i < 5; ++i) {
		my_mu.unlock();
		std::cout << "thread2 unlock mutex succeed!" << std::endl;
		std::this_thread::yield();
	}
}

void simulateBlocking() {
	std::thread thread1(thread1_func);
	std::thread thread2(thread2_func);	
	thread1.join();
	thread2.join();
}
int main (int argc, char **argv) {
	std::string text = "Hello world";
	std::cout << text << std::endl;
	simulateBlocking();
	return 0;
}

编译并运行程序,如下图: image.png 为了调查程序阻塞的原因,我们使用命令把gdb关联到运行中的进程;步骤如下: 1. gdb simulationBlocking,运行bt查看堆栈; 2. 直接跳转到f 2,查看代码list;此时我们通过查看堆栈信息,知道阻塞的位置是在simulateBlocking.cpp的31行,即thread1.join()并没有完成,继续往下面查; image.png 3. 执行info threads 查看所有运行的线程; 4. 查看编号为2的线程的堆栈:thread apply 2 bt;切换到线程2:thread 2; 5. 查看thread1的堆栈:bt;直接条找到我们代码所在的栈帧:f 4; 6. 查看锁对象: p my_mu; 7. 确认持有锁的线程:info threads; image.png ++数据篡改问题排查++ 定义:数据被篡改不一定会引起异常,但很有可能会导致业务结果不符合预期,对于大量使用了三方库的项目来说,想知道数据在哪里被修改成了什么,并不是一件容易的事情。对于C/Cpp来说,还存在着指针被修改后,导致指针原来指向的对象可能无法回收的问题。单纯从日志来看,很难发现一个变量何时被哪个变量修改成了什么,所以需要借助GDB监控断点。 案例: simulateDataChanged.cpp

#include <thread>
#include <mutex>
#include<syscall.h>
#include<sys/types.h>
#include<unistd.h>
#include <iostream>

std::mutex my_mu;

class Employee{
public:
	Employee(const std::string& name) {_name = name;}
	std::string _name;
};

pid_t gettid(){
    return static_cast<pid_t>(syscall(SYS_gettid));
}

void check_func(Employee& e) {
	auto tid = gettid();
	std::cout << "thread " << tid << " stated." << std::endl;
	while(true) {
		if (e._name.compare("origin employee name") != 0) {
			std::cout << "Error occurred, Employee name changed, new value is: 		"<< e._name << std::endl; 
			break;
		}
	}
	std::this_thread::yield();
} 

void modify_func(Employee& e) {
	auto tid = gettid();
	std::cout << "thread " << tid << " started." << std::endl;
	std::this_thread::sleep_for(std::chrono::milliseconds(0));
	e._name = std::string("employee name changed");
}

void simulateDataChanged() {
	Employee e("Origin employee name");
	std::thread thread1(check_func, std::ref(e));
	std::thread thread2(modify_func, std::ref(e));
	thread1.join();
	thread2.join();
}
int main (int argc, char **argv) {
	std::string text = "Hello world";
	std::cout << text << std::endl;
	simulateDataChanged();
	return 0;
}

编译,运行程序; gdb调试,步骤如下: 1. gdb simulateDataChanged 2. 在simulateDataChanged方法上添加断点,b simulateDataChanged.cpp:simulateDataChanged运行程序 r; 3. 使得程序执行到e对象创建完成之后,执行两次n; 4. 监视e._name:watch -location e._name; 5. 继续执行c,在触发了watch中断后,查看终端所在位置的堆栈,bt; 6. 直接跳转到我们的代码所在的栈帧, f 2; image.png ++堆内存重复释放问题排查++ 定义:堆内存的重复释放,会导致内存泄露,被破坏的内存可以被攻击者利用,从而产生更为严重的安全问题。目标流行的C函数库(比如libc),会在内存重复释放时,抛出“double free or corruption (fasttop)”错误,并终止程序运行。为了修复堆内存重复释放问题,我们需要找到所有释放对应堆内存的代码位置,用来判断哪一个释放堆内存的操作是不正确的。

使用GDB可以解决我们知道哪一个变量产生了内存重复释放,但我们不知道都在哪里对此变量释放了内存空间的问题。如果我们对产生内存重复释放问题的变量一无所知,那么还需要借助其它的工具来辅助定位。

下面我们使用两个线程,在其中释放同一块堆内存,用来模拟堆内存重复释放问题; 案例: simulateDoubleFree.cpp

#include <thread>
#include <mutex>
#include<syscall.h>
#include<sys/types.h>
#include<unistd.h>
#include <iostream>

std::mutex my_mu;

class Employee{
public:
	Employee(const std::string& name) {_name = name;}
	std::string _name;
};

pid_t gettid(){
    return static_cast<pid_t>(syscall(SYS_gettid));
}

void thread1_func(Employee* e) {
	auto tid = gettid();
	std::cout << "thread " << tid << " stated." << std::endl;
	e->_name = "new employee name1";
	delete e;
	e = nullptr;
} 

void thread2_func(Employee* e) {
	auto tid = gettid();
	std::cout << "thread " << tid << " started." << std::endl;
	e->_name = "new employee name2";
	delete e;
	e = nullptr;
}

void simulateDoubleFree() {
	Employee *e = new Employee("Origin employee name");
	std::thread thread1(thread1_func, e);
	std::thread thread2(thread2_func, e);
	thread1.join();
	thread2.join();
}
int main (int argc, char **argv) {
	std::string text = "Hello world";
	std::cout << text << std::endl;
	simulateDoubleFree();
	return 0;
}

编译并运行程序,如下图: image.png gdb调试,步骤如下: 1. 首先在创建好e变量之后的地方,加入断点:b simulateDoubleFree.cpp:38,随后执行 r; 2. 创建好变量e之后,再执行 p &e,获取到e的指针地址, 随后执行bt; 3. 之后在所有free e内存的地方,加断点,b __GI_libc_free if mem == 0x7fffffffdb68,随后运行c;执行bt就可以看到第一个free e的地方;如下图: image.png 4. 继续执行c和bt可以看到第二个free e的地方。如下图: image.png 现在我们使用GDB来找到所有释放employee变量堆内存的代码位置,以便决定哪个释放操作不是需要的:

常用的GDB命令

image.png image.png

标签:基本,std,name,程序,GDB,介绍,gdb,内存,include
From: https://blog.51cto.com/u_15804342/8873459

相关文章

  • 天猫商品详情接口 json 格式返回介绍
    天猫商品详情数据接口返回的JSON格式数据通常包含以下字段:num_iid:商品IDtitle:商品标题desc_short:商品简短描述price:商品价格total_price:商品总价(如有优惠券等)suggestive_price:推荐价格orginal_price:原价nick:卖家昵称num:库存数量detail_url:商品详情链接pic_url:商品图片链接brand:......
  • conan 基本使用
    原文:https://docs.conan.io/2/tutorial/consuming_packages.htmlBasic安装conanpipinstallconan打印conan依赖安装路径conanconfighome查看profileconanprofileshowinstall时指定profileconaninstall.--build=missing--profile=defaultProfile首次安装......
  • 45、Flink 的指标体系介绍及验证(2)-指标的scope、报告、系统指标以及追踪、api集成示例
    文章目录Flink系列文章一、Flink指标体系2、Scope范围1)、用户范围2)、系统范围SystemScope3)、所有变量列表4)、用户变量3、Reporter4、Systemmetrics1)、CPU2)、Memory3)、Threads4)、GarbageCollection5)、ClassLoader6)、Network7)、Defaultshuffleservice8)、Cluster9)、Availabili......
  • 45、Flink 的指标体系介绍及验证(3)- 完整版
    文章目录Flink系列文章一、Flink指标体系1、Registeringmetrics注册指标1)、指标类型2)、计数器3)、Gauge4)、Histogram5)、Meter2、Scope范围1)、用户范围2)、系统范围SystemScope3)、所有变量列表4)、用户变量3、Reporter4、Systemmetrics1)、CPU2)、Memory3)、Threads4)、GarbageColl......
  • 47、Flink 的指标报告介绍(graphite、influxdb、prometheus、statsd和datalog)及示例(jmx
    文章目录Flink系列文章一、MetricReporters1、概述及示例2、入门示例0)、特别说明1)、配置2)、验证3)、自定义的指标收集器3、基于标志符格式vs.基于tags格式4、Pushvs.Pull5、发送器1)、JMX2)、Graphite2)、InfluxDB4)、Prometheus5)、PrometheusPushGateway6)、StatsD7)、Datadog8)......
  • jmeter 基本请求
    jmeter get请求普通的get请求,是客户端去服务器获取资源的,可以直接在浏览器中访问,获取到服务端的响应的。 post请求,参数为k=v的POST接口post请求方式常用数据格式第一种:content-type:x-www-form-urlencoded,content-type:x-www-form-urlencoded即表单形式,数据格式类型:u......
  • 数据结构之<树>的介绍
    树的基本概念在数据结构中,树(Tree)是一种层次结构,由节点和边组成。树的基本概念包括根节点、子节点、父节点、兄弟节点等。节点拥有零个或多个子节点,除了根节点外,每个节点有且仅有一个父节点。树的层数称为树的高度。子节点以及它后续节点所形成的数称为子树。1.二叉树(BinaryTree)二......
  • Kubernetes 网络之 DNS 介绍
    一、服务发现Pod提供的服务可以通过Service生成的ClusterIP(VIP)来访问。怎么知道某个应用的VIP呢?k8s主要有两种Service发现机制:环境变量和DNS。没有DNS服务的时候,k8s会采用环境变量的形式,但一旦有多个service,环境变量会变复杂,为解决该问题,我们使用DNS服务。环境变量在之......
  • 【python入门之OS模块介绍】---OS模块介绍
    title:【python入门之OS模块介绍】---OS模块介绍date:2023-12-1615:54:06updated:2023-12-1616:20:00description:【python入门之OS模块介绍】---OS模块介绍cover:https://home.cnblogs.com/u/dream-ze/【一】OS模块的介绍os模块是Python编程语言中......
  • 最好用的AI换脸软件,rope下载介绍
     随着AI技术的广泛运用,市面上的换脸软件也多了起来,今天给各位介绍其中的王者Rope!先上两个动图,给大伙看看效果  rope是如何实现这种自然的效果呢?这得益于机器学习技术的不断发展,rope经过深度神经网络的无数次迭代优化,最终得出的模型可以自动学习和识别视频中的人脸特征,它......