既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
命令 | 含义 |
---|---|
catch fork | 程序调用fork时中断 |
tcatch fork | 设置的断点只触发一次,之后被自动删除 |
catch syscall ptrace | 为ptrace系统调用设置断点 |
在
command
命令后加断点编号,可以定义断点触发后想要执行的操作。在一些高级的自动化调试场景中可能会用到。
命令行
命令 | 作用 |
---|---|
run arglist | 以arglist为参数列表运行程序 |
set args arglist | 指定启动命令行参数 |
set args | 指定空的参数列表 |
show args | 打印命令行列表 |
程序栈
命令 | 作用 |
---|---|
backtrace [n] | 打印栈帧 |
frame [n] | 选择第n个栈帧,如果不存在,则打印当前栈帧 |
up n | 选择当前栈帧编号+n的栈帧 |
down n | 选择当前栈帧编号-n的栈帧 |
info frame [addr] | 描述当前选择的栈帧 |
info args | 当前栈帧的参数列表 |
info locals | 当前栈帧的局部变量 |
多进程、多线程
多进程
GDB在调试多进程程序(程序含fork
调用)时,默认只追踪父进程。可以通过命令设置,实现只追踪父进程或子进程,或者同时调试父进程和子进程。
命令 | 作用 |
---|---|
info inferiors | 查看进程列表 |
attach pid | 绑定进程id |
inferior num | 切换到指定进程上进行调试 |
print $_exitcode | 显示程序退出时的返回值 |
set follow-fork-mode child | 追踪子进程 |
set follow-fork-mode parent | 追踪父进程 |
set detach-on-fork on | fork调用时只追踪其中一个进程 |
set detach-on-fork off | fork调用时会同时追踪父子进程 |
在调试多进程程序时候,默认情况下,除了当前调试的进程,其他进程都处于挂起状态,所以,如果需要在调试当前进程的时候,其他进程也能正常执行,那么通过设置set schedule-multiple on
即可。
多线程
多线程开发在日常开发工作中很常见,所以多线程的调试技巧非常有必要掌握。
默认调试多线程时,一旦程序中断,所有线程都将暂停。如果此时再继续执行当前线程,其他线程也会同时执行。
命令 | 作用 |
---|---|
info threads | 查看线程列表 |
print $_thread | 显示当前正在调试的线程编号 |
set scheduler-locking on | 调试一个线程时,其他线程暂停执行 |
set scheduler-locking off | 调试一个线程时,其他线程同步执行 |
set scheduler-locking step | 仅用step调试线程时其他线程不执行,用其他命令如next调试时仍执行 |
如果只关心当前线程,建议临时设置 scheduler-locking
为 on
,避免其他线程同时运行,导致命中其他断点分散注意力。
打印输出
通常情况下,在调试的过程中,我们需要查看某个变量的值,以分析其是否符合预期,这个时候就需要打印输出变量值。
命令 | 作用 |
---|---|
whatis variable | 查看变量的类型 |
ptype variable | 查看变量详细的类型信息 |
info variables var | 查看定义该变量的文件,不支持局部变量 |
打印字符串
使用x/s
命令打印ASCII
字符串,如果是宽字符字符串,需要先看宽字符的长度 print sizeof(str)
。
如果长度为2
,则使用x/hs
打印;如果长度为4
,则使用x/ws
打印。
命令 | 作用 |
---|---|
x/s str | 打印字符串 |
set print elements 0 | 打印不限制字符串长度/或不限制数组长度 |
call printf(“%s\n”,xxx) | 这时打印出的字符串不会含有多余的转义符 |
printf “%s\n”,xxx | 同上 |
打印数组
命令 | 作用 |
---|---|
print *array@10 | 打印从数组开头连续10个元素的值 |
print array[60]@10 | 打印array数组下标从60开始的10个元素,即第60~69个元素 |
set print array-indexes on | 打印数组元素时,同时打印数组的下标 |
打印指针
命令 | 作用 |
---|---|
print ptr | 查看该指针指向的类型及指针地址 |
print *(struct xxx *)ptr | 查看指向的结构体的内容 |
打印指定内存地址的值
使用x
命令来打印内存的值,格式为x/nfu addr
,以f
格式打印从addr
开始的n
个长度单元为u
的内存值。
n
:输出单元的个数f
:输出格式,如x
表示以16
进制输出,o
表示以8
进制输出,默认为x
u
:一个单元的长度,b
表示1
个byte
,h
表示2
个byte
(half word
),w
表示4
个byte
,g
表示8
个byte
(giant word
)
命令 | 作用 |
---|---|
x/8xb array | 以16进制打印数组array的前8个byte的值 |
x/8xw array | 以16进制打印数组array的前16个word的值 |
打印局部变量
命令 | 作用 |
---|---|
info locals | 打印当前函数局部变量的值 |
backtrace full | 打印当前栈帧各个函数的局部变量值,命令可缩写为bt |
bt full n | 从内到外显示n个栈帧及其局部变量 |
bt full -n | 从外向内显示n个栈帧及其局部变量 |
打印结构体
命令 | 作用 |
---|---|
set print pretty on | 每行只显示结构体的一名成员 |
set print null-stop | 不显示’\000’这种 |
函数跳转
命令 | 作用 |
---|---|
set step-mode on | 不跳过不含调试信息的函数,可以显示和调试汇编代码 |
finish | 执行完当前函数并打印返回值,然后触发中断 |
return 0 | 不再执行后面的指令,直接返回,可以指定返回值 |
call printf(“%s\n”, str) | 调用printf函数,打印字符串(可以使用call或者print调用函数) |
print func() | 调用func函数(可以使用call或者print调用函数) |
set var variable=xxx | 设置变量variable的值为xxx |
set {type}address = xxx | 给存储地址为address,类型为type的变量赋值 |
info frame | 显示函数堆栈的信息(堆栈帧地址、指令寄存器的值等) |
其它
图形化
tui为terminal user interface
的缩写,在启动时候指定-tui
参数,或者调试时使用ctrl+x+a
组合键,可进入或退出图形化界面。
命令 | 含义 |
---|---|
layout src | 显示源码窗口 |
layout asm | 显示汇编窗口 |
layout split | 显示源码 + 汇编窗口 |
layout regs | 显示寄存器 + 源码或汇编窗口 |
winheight src +5 | 源码窗口高度增加5行 |
winheight asm -5 | 汇编窗口高度减小5行 |
winheight cmd +5 | 控制台窗口高度增加5行 |
winheight regs -5 | 寄存器窗口高度减小5行 |
汇编
命令 | 含义 |
---|---|
disassemble function | 查看函数的汇编代码 |
disassemble /mr function | 同时比较函数源代码和汇编代码 |
调试和保存core文件
命令 | 含义 |
---|---|
file exec_file *# * | 加载可执行文件的符号表信息 |
core core_file | 加载core-dump文件 |
gcore core_file | 生成core-dump文件,记录当前进程的状态 |
启动方式
使用gdb调试,一般有以下几种启动方式:
- gdb filename: 调试可执行程序
- gdb attach pid: 通过”绑定“进程ID来调试正在运行的进程
- gdb filename -c coredump_file: 调试可执行文件
在下面的几节中,将分别对上述几种调试方式进行讲解,从例子的角度出发,使得大家能够更好的掌握调试技巧。
调试
可执行文件
单线程
首先,我们先看一段代码:
#include<stdio.h>
void print(int xx, int \*xxptr) {
printf("In print():\n");
printf(" xx is %d and is stored at %p.\n", xx, &xx);
printf(" ptr points to %p which holds %d.\n", xxptr, \*xxptr);
}
int main(void) {
int x = 10;
int \*ptr = &x;
printf("In main():\n");
printf(" x is %d and is stored at %p.\n", x, &x);
printf(" ptr points to %p which holds %d.\n", ptr, \*ptr);
print(x, ptr);
return 0;
}
这个代码比较简单,下面我们开始进入调试:
gdb ./test_main
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86\_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/test_main...done.
(gdb) r
Starting program: /root/./test_main
In main():
x is 10 and is stored at 0x7fffffffe424.
ptr points to 0x7fffffffe424 which holds 10.
In print():
xx is 10 and is stored at 0x7fffffffe40c.
xxptr points to 0x7fffffffe424 which holds 10.
[Inferior 1 (process 31518) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64
在上述命令中,我们通过gdb test命令启动调试,然后通过执行r(run命令的缩写)执行程序,直至退出,换句话说,上述命令是一个完整的使用gdb运行可执行程序的完整过程(只使用了r命令),接下来,我们将以此为例子,介绍几种比较常见的命令。
断点
(gdb) b 15
Breakpoint 1 at 0x400601: file test_main.cc, line 15.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400601 in main() at test_main.cc:15
(gdb) r
Starting program: /root/./test_main
In main():
x is 10 and is stored at 0x7fffffffe424.
ptr points to 0x7fffffffe424 which holds 10.
Breakpoint 1, main () at test_main.cc:15
15 print(xx, xxptr);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86\_64
(gdb)
backtrace
(gdb) backtrace
#0 main () at test_main.cc:15
(gdb)
backtrace命令是列出当前堆栈中的所有帧。在上面的例子中,栈上只有一帧,编号为0,属于main函数。
(gdb) step
print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
4 printf("In print():\n");
(gdb)
接着,我们执行了step命令,即进入函数内。下面我们继续通过backtrace命令来查看栈帧信息。
(gdb) backtrace
#0 print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
#1 0x0000000000400612 in main () at test_main.cc:15
(gdb)
从上面输出结果,我们能够看出,有两个栈帧,第1帧属于main函数,第0帧属于print函数。
每个栈帧都列出了该函数的参数列表。从上面我们可以看出,main函数没有参数,而print函数有参数,并且显示了其参数的值。
有一点我们可能比较迷惑,在第一次执行backtrace的时候,main函数所在的栈帧编号为0,而第二次执行的时候,main函数的栈帧为1,而print函数的栈帧为0,这是因为_与栈的向下增长_规律一致,我们只需要记住_编号最小帧号就是最近一次调用的函数_。
frame
栈帧用来存储函数的变量值等信息,默认情况下,GDB总是位于当前正在执行函数对应栈帧的上下文中。
在前面的例子中,由于当前正在print()函数中执行,GDB位于第0帧的上下文中。可以通过frame命令来获取当前正在执行的上下文所在的帧。
(gdb) frame
#0 print (xx=10, xxptr=0x7fffffffe424) at test_main.cc:4
4 printf("In print():\n");
(gdb)
下面,我们尝试使用print命令打印下当前栈帧的值,如下:
(gdb) print xx
$1 = 10
(gdb) print xxptr
$2 = (int *) 0x7fffffffe424
(gdb)
如果我们想看其他栈帧的内容呢?比如main函数中x和ptr的信息呢?假如直接打印这俩值的话,那么就会得到如下:
(gdb) print x
No symbol "x" in current context.
(gdb) print xxptr
No symbol "ptr" in current context.
(gdb)
在此,我们可以通过_frame num_来切换栈帧,如下:
(gdb) frame 1
#1 0x0000000000400612 in main () at test_main.cc:15
15 print(x, ptr);
(gdb) print x
$3 = 10
(gdb) print ptr
$4 = (int *) 0x7fffffffe424
(gdb)
多线程
为了方便进行演示,我们创建一个简单的例子,代码如下:
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
int fun_int(int n) {
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "in fun_int n = " << n << std::endl;
return 0;
}
int fun_string(const std::string &s) {
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "in fun_string s = " << s << std::endl;
return 0;
}
int main() {
std::vector<int> v;
v.emplace_back(1);
v.emplace_back(2);
v.emplace_back(3);
std::cout << v.size() << std::endl;
std::thread t1(fun_int, 1);
std::thread t2(fun_string, "test");
std::cout << "after thread create" << std::endl;
t1.join();
t2.join();
return 0;
}
上述代码比较简单:
- 函数fun_int的功能是休眠10s,然后打印其参数
- 函数fun_string功能是休眠10s,然后打印其参数
- main函数中,创建两个线程,分别执行上述两个函数
下面是一个完整的调试过程:
(gdb) b 27
Breakpoint 1 at 0x4013d5: file test.cc, line 27.
(gdb) b test.cc:32
Breakpoint 2 at 0x40142d: file test.cc, line 32.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004013d5 in main() at test.cc:27
2 breakpoint keep y 0x000000000040142d in main() at test.cc:32
(gdb) r
Starting program: /root/test
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main () at test.cc:27
(gdb) c
Continuing.
3
[New Thread 0x7ffff6fd2700 (LWP 44996)]
in fun_int n = 1
[New Thread 0x7ffff67d1700 (LWP 44997)]
Breakpoint 2, main () at test.cc:32
32 std::cout << "after thread create" << std::endl;
(gdb) info threads
Id Target Id Frame
3 Thread 0x7ffff67d1700 (LWP 44997) "test" 0x00007ffff7051fc3 in new_heap () from /lib64/libc.so.6
2 Thread 0x7ffff6fd2700 (LWP 44996) "test" 0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
* 1 Thread 0x7ffff7fe7740 (LWP 44987) "test" main () at test.cc:32
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff6fd2700 (LWP 44996))]
#0 0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
(gdb) bt
#0 0x00007ffff7097e2d in nanosleep () from /lib64/libc.so.6
#1 0x00007ffff7097cc4 in sleep () from /lib64/libc.so.6
#2 0x00007ffff796ceb9 in std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) () from /lib64/libstdc++.so.6
#3 0x00000000004018cc in std::this_thread::sleep_for<long, std::ratio<1l, 1l> > (__rtime=...) at /usr/include/c++/4.8.2/thread:281
#4 0x0000000000401307 in fun_int (n=1) at test.cc:9
#5 0x0000000000404696 in std::_Bind_simple<int (*(int))(int)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x609080)
at /usr/include/c++/4.8.2/functional:1732
#6 0x000000000040443d in std::_Bind_simple<int (*(int))(int)>::operator()() (this=0x609080) at /usr/include/c++/4.8.2/functional:1720
#7 0x000000000040436e in std::thread::_Impl<std::_Bind_simple<int (*(int))(int)> >::_M_run() (this=0x609068) at /usr/include/c++/4.8.2/thread:115
#8 0x00007ffff796d070 in ?? () from /lib64/libstdc++.so.6
#9 0x00007ffff7bc6dd5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007ffff70d0ead in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
after thread create
in fun_int n = 1
[Thread 0x7ffff6fd2700 (LWP 45234) exited]
in fun_string s = test
[Thread 0x7ffff67d1700 (LWP 45235) exited]
[Inferior 1 (process 45230) exited normally]
(gdb) q
在上述调试过程中:
- b 27 在第27行加上断点
- b test.cc:32 在第32行加上断点(效果与b 32一致)
- info b 输出所有的断点信息
- r 程序开始运行,并在第一个断点处暂停
- c 执行c命令,在第二个断点处暂停,在第一个断点和第二个断点之间,创建了两个线程t1和t2
- info threads 输出所有的线程信息,从输出上可以看出,总共有3个线程,分别为main线程、t1和t2
- thread 2 切换至线程2
- bt 输出线程2的堆栈信息
- c 直至程序结束
- q 退出gdb
多进程
同上面一样,我们仍然以一个例子进行模拟多进程调试,代码如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid\_t pid = fork();
if (pid == -1) {
perror("fork error\n");
return -1;
}
if(pid == 0) { // 子进程
int num = 1;
while(num == 1){
sleep(10);
}
printf("this is child,pid = %d\n", getpid());
} else { // 父进程
printf("this is parent,pid = %d\n", getpid());
wait(NULL); // 等待子进程退出
}
return 0;
}
在上面代码中,包含两个进程,一个是父进程(也就是main进程),另外一个是由fork()函数创建的子进程。
在默认情况下,在多进程程序中,GDB只调试main进程,也就是说无论程序调用了多少次fork()函数创建了多少个子进程,GDB在默认情况下,只调试父进程。为了支持多进程调试,从GDB版本7.0开始支持单独调试(调试父进程或者子进程)和同时调试多个进程。
那么,我们该如何调试子进程呢?我们可以使用如下几种方式进行子进程调试。
attach
首先,无论是父进程还是子进程,都可以通过attach命令启动gdb进行调试。我们都知道,对于每个正在运行的程序,操作系统都会为其分配一个唯一ID号,也就是进程ID。如果我们知道了进程ID,就可以使用attach命令对其进行调试了。
在上面代码中,fork()函数创建的子进程内部,首先会进入while循环sleep,然后在while循环之后调用printf函数。这样做的目的有如下:
- 帮助attach捕获要调试的进程id
- 在使用gdb进行调试的时候,真正的代码(即print函数)没有被执行,这样就可以从头开始对子进程进行调试
可能会有疑惑,上面代码以及进入while循环,无论如何是不会执行到下面printf函数。其实,这就是gdb的厉害之处,可以通过gdb命令修改num的值,以便其跳出while循环
使用如下命令编译生成可执行文件test_process
g++ -g test_process.cc -o test_process
现在,我们开始尝试启动调试。
gdb -q ./test_process
Reading symbols from /root/test_process...done.
(gdb)
这里需要说明下,之所以加-q选项,是想去掉其他不必要的输出,q为quite的缩写。
(gdb) r
Starting program: /root/./test_process
Detaching after fork from child process 37482.
this is parent,pid = 37478
[Inferior 1 (process 37478) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) attach 37482
//符号类输出,此处略去
(gdb) n
Single stepping until exit from function __nanosleep_nocancel,
which has no line number information.
0x00007ffff72b3cc4 in sleep () from /lib64/libc.so.6
(gdb)
Single stepping until exit from function sleep,
which has no line number information.
main () at test_process.cc:8
8 while(num==10){
(gdb)
在上述命令中,我们执行了n(next的缩写),使其重新对while循环的判断体进行判断。
(gdb) set num = 1
(gdb) n
12 printf("this is child,pid = %d\n",getpid());
(gdb) c
Continuing.
this is child,pid = 37482
[Inferior 1 (process 37482) exited normally]
(gdb)
为了退出while循环,我们使用set命令设置了num的值为1,这样条件就会失效退出while循环,进而执行下面的printf()函数;在最后我们执行了c(continue的缩写)命令,支持程序退出。
如果程序正在正常运行,出现了死锁等现象,则可以通过ps获取进程ID,然后根据gdb attach pid进行绑定,进而查看堆栈信息
指定进程
默认情况下,GDB调试多进程程序时候,只调试父进程。GDB提供了两个命令,可以通过follow-fork-mode和detach-on-fork来指定调试父进程还是子进程。
follow-fork-mode
该命令的使用方式为:
(gdb) set follow-fork-mode mode
其中,mode有以下两个选项:
- parent:父进程,mode的默认选项
- child:子进程,其目的是告诉 gdb 在目标应用调用fork之后接着调试子进程而不是父进程,因为在Linux系统中fork()系统调用成功会返回两次,一次在父进程,一次在子进程
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork-mode child
(gdb) r
Starting program: /root/./test_process
[New process 37830]
this is parent,pid = 37826
^C
Program received signal SIGINT, Interrupt.
[Switching to process 37830]
0x00007ffff72b3e10 in __nanosleep_nocancel () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) n
Single stepping until exit from function __nanosleep_nocancel,
which has no line number information.
0x00007ffff72b3cc4 in sleep () from /lib64/libc.so.6
(gdb) n
Single stepping until exit from function sleep,
which has no line number information.
main () at test_process.cc:8
8 while(num==10){
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
(gdb)
在上述命令中,我们做了如下操作:
- show follow-fork-mode:通过该命令来查看当前处于什么模式下,通过输出可以看出,处于parent即父进程模式
- set follow-fork-mode child:指定调试子进程模式
- r:运行程序,直接运行程序,此时会进入子进程,然后执行while循环
- ctrl + c:通过该命令,可以使得GDB收到SIGINT命令,从而暂停执行while循环
- n(next):继续执行,进而进入到while循环的条件判断处
- show follow-fork-mode:再次执行该命令,通过输出可以看出,当前处于child模式下
detach-on-fork
如果一开始指定要调试子进程还是父进程,那么使用follow-fork-mode命令完全可以满足需求;但是如果想在调试过程中,想根据实际情况在父进程和子进程之间来回切换调试呢?
GDB提供了另外一个命令:
(gdb) set detach-on-fork mode
其中mode有如下两个值:
on:默认值,即表明只调试一个进程,可以是子进程,也可以是父进程
off:程序中的每个进程都会被记录,进而我们可以对所有的进程进行调试
如果选择关闭detach-on-fork
模式(mode为off),那么GDB将保留对所有被fork出来的进程控制,即可用调试所有被fork出来的进程。可用 使用info forks
命令列出所有的可被GDB调试的fork进程,并可用使用fork命令从一个fork进程切换到另一个fork进程。
- info forks: 打印DGB控制下的所有被fork出来的进程列表。该列表包括fork id、进程id和当前进程的位置
- fork fork-id: 参数fork-id是GDB分配的内部fork编号,该编号可用通过上面的命令
info forks
获取
coredump
当我们开发或者使用一个程序时候,最怕的莫过于程序莫名其妙崩溃。为了分析崩溃产生的原因,操作系统的内存内容(包括程序崩溃时候的堆栈等信息)会在程序崩溃的时候dump出来(默认情况下,这个文件名为core.pid,其中pid为进程id),这个dump操作叫做coredump(核心转储),然后我们可以用调试器调试此文件,以还原程序崩溃时候的场景。
在我们分析如果用gdb调试coredump文件之前,先需要生成一个coredump,为了简单起见,我们就用如下例子来生成:
#include <stdio.h>
void print(int \*v, int size) {
for (int i = 0; i < size; ++i) {
printf("elem[%d] = %d\n", i, v[i]);
}
}
int main() {
int v[] = {0, 1, 2, 3, 4};
print(v, 1000);
return 0;
}
编译并运行该程序:
g++ -g test_core.cc -o test_core
./test_core
输出如下:
elem[775] = 1702113070
elem[776] = 1667200115
elem[777] = 6648431
elem[778] = 0
elem[779] = 0
段错误(吐核)
如我们预期,程序产生了异常,但是却没有生成coredump文件,这是因为在系统默认情况下,coredump生成是关闭的,所以需要设置对应的选项以打开coredump生成。
针对多线程程序产生的coredump,有时候其堆栈信息并不能完整的去分析原因,这就使得我们得有其他方式。
18年有一次线上故障,在测试环境一切正常,但是在线上的时候,就会coredump,根据gdb调试coredump,只能定位到了libcurl里面,但却定位不出原因,用了大概两天的时间,发现只有在超时的时候,才会coredump,而测试环境因为配置比较差超时设置的是20ms,而线上是5ms,知道coredump原因后,采用逐步定位缩小范围法
,逐步缩小代码范围,最终定位到是libcurl一个bug导致。所以,很多时候,定位线上问题需要结合实际情况,采取合适的方法来定位问题。
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
st_core
输出如下:
elem[775] = 1702113070
elem[776] = 1667200115
elem[777] = 6648431
elem[778] = 0
elem[779] = 0
段错误(吐核)
如我们预期,程序产生了异常,但是却没有生成coredump文件,这是因为在系统默认情况下,coredump生成是关闭的,所以需要设置对应的选项以打开coredump生成。
针对多线程程序产生的coredump,有时候其堆栈信息并不能完整的去分析原因,这就使得我们得有其他方式。
18年有一次线上故障,在测试环境一切正常,但是在线上的时候,就会coredump,根据gdb调试coredump,只能定位到了libcurl里面,但却定位不出原因,用了大概两天的时间,发现只有在超时的时候,才会coredump,而测试环境因为配置比较差超时设置的是20ms,而线上是5ms,知道coredump原因后,采用`逐步定位缩小范围法`,逐步缩小代码范围,最终定位到是libcurl一个bug导致。所以,很多时候,定位线上问题需要结合实际情况,采取合适的方法来定位问题。
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-bgzNHnJR-1715876212240)]
[外链图片转存中...(img-QoKyUAG0-1715876212241)]
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**