嵌入式开发必备工具!!!学就完事了!!!
目录
GDB初使用
准备条件:Makefile,section1.cpp
初步使用命令
启动调试
启动调试别传入参数
附加到进程
逐过程执行
逐语句执行
退出函数
调试断点管理
设置断点
查看禁用删除断点
变量查看与修改
查看变量
查看函数参数
设置字符串的显示规则
设置结构体和数组显示规则
使用GDB内嵌函数
变量查看和修改
修改整型
修改字符串
修改结构体
内存查看和修改
从内存地址修改
查看内存
查看整型
查看字符串
寄存器查看和修改
查看编辑
修改
源代码查看搜索
查看
搜索
函数调用栈管理
栈帧
观察点使用
捕获点
编辑 为断点执行命令
窗口管理
查看对象类型
多线程调试管理
线程查找,线程断点
为线程执行命令
线程的日志信息控制
外部命令和保存文件
跳转执行
反向执行undo
多进程
调试子进程
多进程调试
调试时调用内部函数和外部函数
调试时跳过指定函数
制作和调试发行版
修改可执行文件
内存泄漏检测
远程调试
死锁调试
核心存储core-dump
栈溢出的core-dump
无调试符号的core-dump
软件密码“破解”
BUG专区
GDB初使用
准备条件:Makefile,section1.cpp
EXE:= section1
EXECUTABLE:= $(EXE)
LIBDIR:=
LIBS:=
INCLUDES:=.
SRCDIR:=
CC:=g++
CFLAGS:= -g -Wall -O0
CPPFLAGS:= $(CFLAGS)
CPPFLAGS+= $(addprefix -I,$(INCLUDES))
CPPFLAGS+= -I.
CPPFLAGS+= -MMD
RM-F:= rm -f
SRCS:= $(wildcard *.cpp) $(wildcard $(addsuffix /*.cpp, $(SRCDIR)))
OBJS:= $(patsubst %.cpp,%.o,$(SRCS))
DEPS:= $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS:= $(filter-out $(wildcard $(DEPS)),$(DEPS))
.PHONY : all deps objs clean
all:$(EXECUTABLE)
deps:$(DEPS)
objs:$(OBJS)
clean:
@$(RM-F) *.o
@$(RM-F) *.d
@$(RM-F) $(EXE)
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS):
@$(RM-F) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS)
$(EXECUTABLE) : $(OBJS)
$(CC) -o $(EXECUTABLE) $(OBJS) $(addprefix -L,$(LIBDIR)) $(addprefix -l,$(LIBS))
#include <iostream>
void test()
{
int i=100;
std::cout << "i is " << i << std::endl;
}
int main(int argc,char**argv)
{
int itest=100;test();
const char *str="this is a test";
std::cout << "itest is " << itest << ",str is " << str << std::endl;
std::cout << "参数为:" << std::endl;
for(int i=0;i<argc;i++)
{
std::cout << argv[i]<<std::endl;
}
std::cout << "Hello world\n";
return 0;
}
初步使用命令
查看代码:l。默认每次显示10行
设置断点:b
查看断点:i b
运行调试:r
逐行运行:n
断点运行:c
查看指定变量:i itest
查看局部变量:i locales
退出:q
启动调试
启动调试别传入参数
当某些程序需要我们进行传参输入,可以通过下面几种方法
gdb --args 参数
gdb之后,使用:set 参数
gdb之后,使用:r 参数
附加到进程
当一个进程已经运行了,我们对它进行调试,可以如下两种命令
gdb attach pid
gdb --pid pid
其中pid是进程号,通过ps aux | grep sec进行查询
逐过程执行
不会进入函数
n
逐语句执行
会进入函数
s
退出函数
在逐语句中,不想在函数中执行了,可以直接跳出函数
finish
调试断点管理
设置断点
1.b 文件名:行数
2.b 函数名,如果是同名函数则都会标记
3.rb 名字。对于带这个名字的函数都会进行标记,包括虚函数,或者命名不全的函数
4.b 断点 条件 b main.cpp:14 if i==90。也就是执行循环到到i=90才会停止
5.tb 断点。设置临时断点。对于一个循环来说,虽然断点在这,但只会执行一次,就不执行循环了
查看禁用删除断点
i b查看所有断点
i b 1 查看编号1的断点
delete 1删除断点1
delete 删除所有断点
disable 1禁用断点1
enable 1使能断点1
变量查看与修改
查看变量
info args 查看输入的全部信息
查看函数参数
p argc查看输入了几个变量
p argv[0]查看第一个变量是什么
设置字符串的显示规则
set p null-stop//设置字符串不显示后面的空余空间
设置结构体和数组显示规则
set p pretty//结构体和数组显示规则更好看,每行一个
使用GDB内嵌函数
sizeof:显示数据大小
变量查看和修改
在一个循环中,为了加快循环,可以设置循环条件的值,例如下面代码中,for循环10次,在第3次的时候,通过设置i=10,可以提前结束循环
注意事项1:如果按照这种方式,那么后续的值也不会边,相当于for中的语句就不会执行累加
注意事项2:对于输入的参数也可以进行修改,类似代码运行输入1,我们可以在gdb中设置为2
修改整型
p int=10
修改字符串
p str=“str”
设置字符串无效
关于C ++:在gdb中设置字符串 | 码农家园 (codenong.com)
修改结构体
p test.name="str"
p test.agr=10
p strcpy(test.name, "str")
内存查看和修改
从内存地址修改
查看内存
x /d 10进制显示
x /b 二进制显示
x /s显示字符串
查看整型
查看字符串
查看字符串
寄存器查看和修改
如果没有加-g,那么不能直接用 i 和 p取查看信息,这时候只能通过查看寄存器里面的值
查看
对于函数参数少于6个的就在这里,多了的就在栈里面
普通参数:正常显示 i r 寄存器
指针:先找到地址,再利用 x 或者 p
修改
在程序执行中,我可能执行了第一步不想执行第二步,而直接执行第三步,那么我就需要找到第三步的汇编地址,然后执行这个地址。
rip和pc寄存器用来存放下一个需要执行指令的地址,因此我们就是找到需要执行指令的地址,然后设置rip寄存器
注意事项:可以进行代码回退,也就是执行之前的代码
查看某行指令地址:i line 16
修改rip寄存器:p ¥rip=地址
set var $rip=地址
源代码查看搜索
查看
设置显示行数:set listsize 10
显示指定函数代码:l 文件::函数
希纳是指定文件行数:l 文件::行数
搜索
search:往下搜索
forward-search:往下搜索
reverse-search:往上搜索
show directory:显示当前代码搜索路径
directory 路径:设置搜索代码的文件路径。一般有当前路径+工作路径
注意事项:可以通过 l 行数去跳转搜索的起始位置
函数调用栈管理
栈帧
当一个函数执行了还没有返,就称它为一个栈帧,这个栈帧存放在函数在栈中,当函数退出才会消失
查看有哪些栈帧:bt
切换帧: f 帧号
查看栈帧信息
观察点使用
watch 写观察点。当观察点的被写入化时产生中断
rwatch:读观察点。当观察点的值被读取时产生中断
awatch:读写观察点
指定线程的值进行修改:watch 变量 thread 线程号
查看有哪些线程:i thread
指定某个值为多少时:watch 变量1 + 变量2 > 10
捕获点
设置该函数为一个特殊断点,当程序执行到这个函数就会中断
普通函数 :catch 函数名字
系统调用:catch syscall 函数名字
为断点执行命令
1.设置一个断点命令,当断点执行到这里时,可以输出一些元素值
commads
命令1
命令2
end
2.删除断点命令。不需要再打印
commads 断点号
end
保存断点信息
save breakpoints 文件名.txt
还原断点信息
source 文件名.txt
窗口管理
打开一个ui界面,方便使用
查看对象类型
查看变量具体信息,例如属于哪一个类,是基类还是派生类
whatis 名字
set print object on显示new对象真实的类
ptype /r 不用
ptype /o 结构体成员有多大,偏移量是多少
ptype /m 不显示成员函数
ptype /t
i variables 名字:对于全局和静态变量查看,可以再哪个文件下定义
多线程调试管理
info threads:查看所有线程信息
thread num:切换线程
线程查找,线程断点
thread find:查找线程(只能查找地址,id,名字。后面的函数不能)
thread name:设置线程名字(先切换到像改名字的线程,然后执行)
b breakpoint thread id:为线程设置断点(只有设置的线程会在中断处停止,其他线程不会)
为线程执行命令
thread apply 2 3 i args 显示线程2,线程3的参数
thread apply 2-6 i args 显示线程2-6的参数
thread apply all i args显示所有线程的参数
thread apply 2 -s i args对线程2执行 i args命令,如果命令错误就没有提示
thread apply 2 3 -q i args会把线程2,线程3的信息连在一起显示
线程的日志信息控制
线程创建和结束时会在终端打印很多输出,为了不影响观看,于是可以控制
show print thread-events:查看当前线程显示状态
set print thread-events off:关闭当前线程显示状态
外部命令和保存文件
在gdb中可以使用shell命令,也可以使用 ! 代替shell
设置保存debug文件:set logging file 名字.txt
保存文件:set logging on
查看文件:!cat 文件.txt 或者 shell cat 文件.txt
跳转执行
在gdb调试中,可以回溯到之前代码,或者跳过前面的代码直接执行后面代码
1.通过找到line的地址,设置寄存器
2.在line设置断点,然后 jump 行数
3.通过标签,然后jump 标签
反向执行undo
代码执行正常是从上往下,那么现在是从下往上。对于代码而言,就相当于一个变量从赋值状态变为没赋值,把给他的值撤销了
先record,再利用rn回溯
多进程
调试子进程
默认是父进程调试。如果要设置子进程需要进行设置
set follow-fork-mode child
父进程和子进程一起调试
先设置到子进程,然后
set detach-on-fork off:关闭父进程的detach
i inferiors查看当前进程及其子进程信息
inferior 号码:切换进程号码
多进程调试
inferior就是一个调试对象,就是一个调试进程
首先在新建一个gdb,此时i inferior是空的,执行需要调试的进程,首先add-inferior,然后利用attach 进程号进行添加,然后再重复这个过程
在调试过程中,可以只执行一个进程,或者两个都执行。
show schedule-multiple:显示是否所有进程都执行
set schedule-multiple on:开启所有进程一起执行
detach inferiors 号码:关闭号码调试的进程
remove-inferior:删除一个add之后的inferior(不能删除当前的,只能删除之前的)
调试时调用内部函数和外部函数
内部函数
p 函数(参数)
call 函数(参数)
注意事项:如果函数没有返回值,void不会显示
外部函数
调试时跳过指定函数
1.进入函数执行finish或者return,可以结束这个函数
跳过一个文件执行
skip file 文件
跳过一个目录下所有文件
skip -gfi 文件夹/*.*
制作和调试发行版
为了调试一些没有调试信息的程序
制作发行版本
1.不加-g
2.利用strip:strip -g release-section -o release。可以直接利用带调试信息的生成没有调试信息的可执行文件
1.调试发行版的可执行文件
gdb --symbol=release-section -exec=release
直接利用带有调试信息的版本调试没有调试信息的
2.利用待调试信息的可执行文件单独生成调试信息给可执行程序
objcopy --only-keep-debug release-section debug.sym
gdb --symbol=debug.sym -exec=release
修改可执行文件
首先对调试文件利用写的gdb进入:gdb --write 调试文件
然后修改某个函数的值
1.disassemble /mr check_some:查看汇编代码
2. p {unsigned char}地址=0x65:修改某一个字节的值
3.q退出来执行
内存泄漏检测
通过gdb来查看某些函数是否有内存泄漏问题。比较函数进出的内存占用情况来判断
注意事项:在内存中,除了malloc分配地址,本身也还有链表来存储空间也需要内存
call malloc_stats()查看当前的内存分布
call malloc_info(0,stdout)把内存分布情况按照xml格式输出
加入 -fsanitize=address选项,在发生内存泄漏,堆栈溢出,野指针,全局内存会直接定位到具体代码
远程调试
远程调试
利用客户端调试远程服务端。服务端需要按照gdbserver
服务端:gdbserver ip:9988 ./调试文件
客户端:1: gdb
2:target remote ip:9988
死锁调试
利用attach把程序加进来,然后查看线程,以及分析线程的bt来确定是否有死锁
核心存储core-dump
为活着的进程生成core-dump文件
1.利用gdb attach 进程号添加进程
2.利用gcore test.core生成文件
当程序发送段错误时生成core-dump文件
ulimit -c 查看是否允许保留core文件。0为不保存
ulimit -c unlimited 允许生成core文件
修改生成dump文件的名字:sudo echo -e "%e-%p-%t" > /proc/sys/kernel/core_pattern
利用dump文件进行调试
软件密码“破解”
通过 ri 汇编执行到出错位置,查看输入寄存器rsi和rdi,对比结果找到答案
BUG专区
1.gab attach出错:ptrace: 不允许的操作.attach: 没有那个文件或目录
解决办法:
编辑/etc/sysctl.d/10-ptrace.conf这个文件,若没有,创建之。设置(默认是1)
kernel.yama.ptrace_scope = 0
在后续使用中,开启sudo执行命令,例如sudo gdb attach pid即可解决
2./sysdeps/unix/sysv/linux/read.c不存在
解决办法:
在你确少信息的目录添加这个文件,文件内容如下
/* Linux read syscall implementation.
Copyright (C) 2017-2022 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <unistd.h>
#include <sysdep-cancel.h>
/* Read NBYTES into BUF from FD. Return the number read or -1. */
ssize_t
__libc_read (int fd, void *buf, size_t nbytes)
{
return SYSCALL_CANCEL (read, fd, buf, nbytes);
}
libc_hidden_def (__libc_read)
libc_hidden_def (__read)
weak_alias (__libc_read, __read)
libc_hidden_def (read)
weak_alias (__libc_read, read)