main
tink 1 year ago
parent 2ce33e057d
commit e3c4aca36c

@ -21,6 +21,7 @@ plugin:
pip install mkdocs-jupyter # jupyter
pip install mkdocs-glightbox # image lightbox
pip install mkdocs-git-committers-plugin-2
pip install mkdocs-rss-plugin
push:
git push origin

@ -1,3 +1,3 @@
# dev-wiki
网站地址: https://doc.cyub.vip/dev-wiki/
网站地址: https://wiki.cyub.vip

@ -118,6 +118,17 @@
![](./images/cache_binlog.jpg)
## 缓存模式
### 旁路(cache-aside)模式
![](./images/cache-aside.png)
## 资料
- [链表如何实现LRU缓存淘汰算法?](https://jingbin.me/2019/04/13/list/)
- [链表如何实现LRU缓存淘汰算法?](https://jingbin.me/2019/04/13/list/)
- [Caching Strategy: Cache-Aside Pattern](https://www.enjoyalgorithms.com/blog/cache-aside-caching-strategy)
- [What is Read-Through vs Write-Through Cache?](https://www.designgurus.io/answers/detail/what-is-read-through-vs-write-through-cache)
- [Caching patterns](https://docs.aws.amazon.com/whitepapers/latest/database-caching-strategies-using-redis/caching-patterns.html)
- [A Hitchhikers Guide to Caching Patterns](https://hazelcast.com/blog/a-hitchhikers-guide-to-caching-patterns/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,763 @@
GDB 调试:让程序运行中的错误无所遁形
====================
来自 [Linux二进制](https://mp.weixin.qq.com/s/UBxW5G7VKvT85LKk1oJNJw)
引言
---
作为`C/C++`开发人员,确保程序正常运行是根本且首要的目标。而要达成这一目标,调试是最为基础的手段。熟悉各类调试方式,能够助力我们更迅速地定位程序问题,提升开发效率。在开发进程中,倘若程序的运行结果未达预期,首要之举便是启用`GDB`进行调试,在相应位置"设置断点",进而剖析缘由;当线上服务出现问题时,首先查看进程是否存在。若进程不存在,需查看是否生成了`coredump`文件。若有,可借助`GDB`调试该文件;若没有,则通过`dmesg`来分析内核日志以探寻原因。
概念
---
`GDB``GNU Debugger`)是一个由`GNU`开源组织发布的、`UNIX/LINUX`操作系统下的、功能强大的程序调试工具。
它允许开发者在程序运行时查看变量的值、设置断点、单步执行代码、查看调用栈等,从而帮助开发者找出程序中的错误和优化程序的性能。
`GDB`可以调试多种编程语言编写的程序,如`C`、`C++`、`Objective-C`等。它支持在本地和远程系统上进行调试,并且可以处理多线程和多进程的程序。
总的来说,`GDB`是软件开发过程中非常重要的工具,对于提高程序的质量和稳定性起着关键作用。
常用调试指令
------
### 1、断点
断点属于我们在调试过程中频繁运用的一项功能。当我们于特定位置设定断点以后,程序运行至该位置就会暂时停止,此时我们能够针对程序实施更多的操作,例如查看变量的内容、堆栈的状况等等,从而辅助我们对程序进行调试。
断点的命令经过归纳整理可大概分为以下三类:
*
breakpoint
*
watchpoint
*
catchpoint
这些分类主要基于断点的不同类型和功能,让我们一起了解一下这些命令。
#### breakpoint
可以根据行号、函数、条件生成断点,下面是相关命令以及对应的作用列表:
| 命令 | 作用 |
|-----------------------------|-------------------------------|
| **break** | 在下一个指令处设置断点。 |
| **break \[file\]:function** | 在文件`file`的`function`函数入口设置断点。 |
| **break \[file\]:line** | 在文件`file`的第`line`行设置断点。 |
| **info breakpoints** | 查看断点列表。 |
| **break \[+-\]offset** | 在当前位置偏移量为`[+-]offset`处设置断点。 |
| **break \*addr** | 在地址`addr`处设置断点。 |
| **break ... if expr** | 设置条件断点,仅仅在条件满足时。 |
| **ignore n count** | "忽略断点`n`次"或者"跳过`n`次达到断点的执行"。 |
| **clear** | 删除当前行的断点。 |
| **clear \[file:\]line** | 删除第`line`行的断点。 |
| **clear function** | 删除所有位于`function`内的断点。 |
| **delete n** | 删除指定编号的断点。 |
| **enable n** | 启用指定编号的断点。 |
| **disable n** | 禁用指定编号的断点。 |
| **save breakpoints file** | 保存断点信息到指定文件。 |
#### watchpoint
`watchpoint`是一种与常规断点相似但具有独特特性的调试工具,它并非固定于某一行源代码之上,而是让`GDB`在监控的表达式值发生变化时自动暂停程序的执行。这种机制允许开发者在无需预知代码执行路径的情况下,对特定变量或表达式的变化保持高度关注,从而更有效地诊断程序中的问题。
`watchpoint`的实现方式主要分为硬件辅助和软件模拟两种。硬件实现的`watchpoint`依赖于底层硬件系统的支持,能够更高效地检测变量的值变化。而软件实现的`watchpoint`则通过模拟方式工作,即在程序的每一步执行后都检查被监控的变量值是否发生了改变。`GDB`在创建新的数据断点时,会首先尝试采用硬件辅助的`watchpoint`以提高效率,如果由于硬件限制或配置问题无法成功创建,则会回退到软件模拟的方式来实现。
| 命令 | 作用 |
|----------------------------------|-----------------|
| **watch variable** | 设置变量数据断点。 |
| **watch var1 + var2** | 设置表达式数据断点。 |
| **rwatch variable** | 设置读断点,仅支持硬件实现。 |
| **awatch variable** | 设置读写断点,仅支持硬件实现。 |
| **info watchpoints** | 查看数据断点列表。 |
| **set can-use-hw-watchpoints 0** | 强制基于软件方式实现。 |
> **注意**
>
> *
> 当监控变量为局部变量时,一旦局部变量失效,数据断点也会失效
>
> *
> 如果监控的是指针变量`p`,则`watch *p`监控的是`p`所指内存数据的变化情况,而`watch p`监控的是`p`指针本身有没有改变指向
>
最常见的数据断点应用场景:**「定位堆上的结构体内部成员何时被修改」**。由于指针一般为局部变量,为了解决断点失效,一般有两种方法。
| 命令 | 作用 |
|-----------------------------|---------------------------|
| **print \&variable** | 查看变量的内存地址。 |
| **watch(type)address** | 通过内存地址间接设置断点。 |
| **watch -l variable** | 指定`location`参数。 |
| **watch variable thread 1** | 仅编号为`1`的线程修改变量`var`值时会中断。 |
#### catchpoint
`catchpoint`命令是`GDBGNU Debugger`中用于设置捕获点(`catchpoint`)的命令,它允许程序在发生特定事件时停止执行,这些事件包括异常抛出、库加载、系统调用等。以下是一些常用的`catchpoint`命令及其说明:
| 命令 | 作用 |
|----------------------------|---------------------------------------------------------------------------------------------------|
| **catch throw** | 捕获`C++`程序中的异常抛出事件。当程序抛出异常时,将停止执行。该命令是`GDB`为`C++`程序中的异常抛出事件提供的专门调试工具,而`C`程序则需要通过其他方式来检测和调试错误或异常情况。 |
| **catch catch** | 捕获`C++`程序中的异常捕获事件。当程序捕获到异常时,将停止执行。 |
| **catch load** | 捕获动态链接库(共享库)的加载事件。当程序加载新的动态链接库时,将停止执行。 |
| **catch unload** | 捕获动态链接库的卸载事件。当程序卸载动态链接库时,将停止执行。 |
| **catch fork** | 捕获`fork`系统调用。在`Unix-like`系统中,当程序调用`fork`时,将停止执行。 |
| **catch vfork** | 捕获`vfork`系统调用。类似于`fork`,但在某些情况下,`vfork`的行为略有不同。 |
| **catch exec** | 捕获`exec`系统调用。当程序通过`exec`系列函数(如`execl、execp、execvp`等)执行新程序时,将停止执行。 |
| **catch syscall系统调用名称或编号** | 捕获特定的系统调用。可以通过系统调用的名称、组或编号来指定要捕获的系统调用。 |
> 【**拓展1** 】查看和管理`catchpoint`可通过如下方式:
>
> *
> `info break`:显示所有已设置的断点(包括`catchpoints`)。虽然命令是`info break`,但它也会列出`catchpoints`。
> *
> `delete`、`disable`、`enable`等命令同样适用于`catchpoint`,用于删除、禁用或启用已设置的捕获点。
> 【**拓展2** 】在`GDB(GNU Debugger)`中,`commands`命令是一个非常强大的功能,它允许你为特定的断点、观察点或捕获点指定一系列`GDB`命令,这些命令将在断点被触发时自动执行。以下是使用`commands`命令的标准方法:
>
> (gdb) commands [breakpoint-number]
> > [command1]
> > [command2]
> > ...
> > end
>
> *
> `[breakpoint-number]`:是可选的,表示你想要附加命令的断点的编号。如果省略,则默认应用于最近设置的断点。
> *
> `[command1]`,`[command2]`, ...:是在断点触发时你希望自动执行的`GDB`命令,每行一个命令,以`end`作为结束标记。
### 2、启动与退出
使用`GDB`调试,一般有以下几种启动与退出方式:
| 命令 | 作用 |
|-------------------------------------------------|--------------------------------------------------------------------|
| **gdb program** | 最常用的启动`GDB`调试程序的方式。 |
| **gdb --args program arg1 arg2 ...** | 带参数启动程序。 |
| **gdb -x /path/to/gdbinit --args program args** | 带自定义`gdbinit`脚本启动。 |
| **gdb program -c coredump_file** | 用`GDB`查看`core dump`文件,跟踪程序崩溃的原因。 |
| **gdb attach pid** | 附加(`attach`)到一个已经在运行的进程号为`pid`的进程上,以便进行调试。 |
| **gdb -p pid** | 调试一个已经在运行的进程,其中`pid`是你想要调试的进程的进程`ID`;与`gdb attach pid`效果相同,但语法更简洁。 |
| **quit或q** | 退出`GDB`调试器。 |
| **Ctrl+D** | 退出`GDB`调试器,同`quit`。 |
`GDB`进入进程进行调试的方式主要包括:调试可执行程序、调试正在运行的进程、调试`core dump`文件以及带参数调试。这些方式涵盖了`GDB`调试的主要应用场景,能够满足大多数开发者的需求。
> 【**拓展** 】`Linux`平台如何生成`core dump`文件
>
> 在`Linux`平台上生成`core dump`文件通常是在程序崩溃时自动发生的,但这依赖于系统的几个配置选项。下面是一些步骤来确保在程序崩溃时能够生成`core dump`文件:
>
> 1.
> **设置`ulimit`参数**
>
> `ulimit`命令用于限制用户在`Shell`中的资源使用。要允许生成`core dump`文件,你需要设置`core`文件大小限制。打开终端并执行以下命令:
>
> ulimit-cunlimited
>
> 这将允许生成任意大小的`core dump`文件。如果你不想设置无限制大小,可以指定一个具体的大小,例如:
>
> ulimit-c1024
>
> 这里的`1024`代表`1024KB`,即`1MB`。
>
> 2.
> **配置`core_pattern`**
>
> `core_pattern`决定了`core dump`文件的命名和保存位置。默认情况下,`core dump`文件可能会被保存在程序崩溃的位置,并命名为`core`。为了更精确地控制这些文件的生成,你可以编辑`/etc/sysctl.conf`文件,并加入以下行:
>
> kernel.core_pattern = /var/crash/%e.%p.%h.%t.coredump
>
> 这里的`%e`是崩溃程序的名字,`%p`是进程`ID``%h`是主机名,`%t`是时间戳。你可以根据需要调整这些参数。之后,运行以下命令使设置生效:
>
> sudosysctl-p
>
> 3.
> **确保目标目录可写**
>
> 确保`core_pattern`中指定的目录对生成`core dump`文件的用户是可写的。例如,你可以为`/var/crash`目录设置权限:
>
> sudomkdir-p/var/crash
> #root是用户名,指的是系统管理员账户;
> # adm 是组名,它通常包含一些可以访问系统日志和其它管理信息的用户。
> sudochownroot:adm/var/crash
> sudochmod2775/var/crash
>
> 4.
> **确认信号处理**
>
> 默认情况下,当程序接收到`SIGSEGV`(段错误)、`SIGFPE`(浮点异常)、`SIGILL`(非法指令)等信号时,系统会生成`core dump`文件。你也可以通过程序代码显式地请求生成`core dump`文件,例如使用`raise(SIGQUIT)`。
>
> 5.
> **重新启动系统或应用**
>
> 为了确保所有的设置生效,你可能需要重启你的应用程序或者整个系统。
>
> 6.
> **检查和分析core dump文件**
>
> 一旦程序崩溃并生成了`core dump`文件,你可以使用调试器如`gdb`来加载和分析这个文件,帮助你确定崩溃的原因。例如:
>
> gdb/path/to/your/executable/path/to/corefile
>
> 请确保你有正确的权限去读取`core dump`文件和执行可执行文件。
>
> 遵循以上步骤,你应该能够在`Linux`平台上成功地生成和分析`core dump`文件。如果遇到问题,检查系统日志(如`/var/log/syslog`)可能有助于诊断原因。
### 3、命令行
在`GDB(GNU Debugger)`中,`run`、`set args`、`show args`等命令与程序的命令行参数处理紧密相关。以下是对`GDB`命令行命令的总结:
| 命令 | 作用 |
|----------------------|------------------------------------------------------------------------|
| **start** | 开始执行被调试的程序,并在程序的入口点(通常是`main`函数的第一条语句之前)暂停,方便您查看程序初始状态和设置断点等,以便后续进行调试。 |
| **run arglist** | 以`arglist`为参数列表运行程序,即`run arg1 arg2 arg3 ...`。 |
| **set args arglist** | 设置程序启动时接收的命令行参数,每次你使用`run`命令启动程序时,这些参数都会被传入程序。 |
| **set args** | 设置程序启动时接收的命令行参数,这里表示设置空的参数列表。 |
| **show args** | 显示当前设置的命令行参数。 |
使用`set args`来预设参数,然后使用`run`命令启动程序时,这些参数会被自动传递给程序。在调试多参数或需要特定参数的程序时,`set args`和`run`的组合使用非常有用。`show args`命令可以随时用来检查当前的参数设置,确保在运行程序前参数正确无误。
这些命令允许你在`GDB`环境中灵活地控制程序的启动条件,特别是对于需要命令行参数的程序来说,这能提供很大的便利。通过这些命令,你可以模拟不同的运行环境,测试程序在不同输入下的行为,这对于调试和验证程序逻辑至关重要。
### 4、程序栈
在`GDB(GNU Debugger)`中,有几个命令行工具可以用来检查和操作程序的调用栈。这些命令帮助你理解程序的执行流程,定位问题所在,以及查看函数调用的历史。以下是几个常用的与程序栈相关的`GDB`命令:
| 命令 | 作用 |
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **backtrace \[n\]** | 显示程序崩溃或停止执行时的函数调用历史;`backtrace`会列出一系列函数调用,每个调用都对应着程序中的一个位置。列表的顶部通常是导致程序停止执行的函数,而底部则是更早的调用,直到程序的入口点。`n`表示来查看调用堆栈的前 n 个帧。 |
| **frame \[n\]** | 选择第`n`个栈帧,如果不存在,则查看当前栈帧;切换到调用栈中的不同帧(`frame`),以便检查函数的局部变量、参数和返回地址等信息。 |
| **up \[n\]** | `up`表示回到编号更大的上一帧,即选择当前栈帧的调用者;`n`表示选择当前栈帧编号`+n`的栈帧。 |
| **down \[n\]** | `down`表示移到编号更小的下一帧,即选择被当前栈帧调用的下一个函数;`n`表示选择当前栈帧编号`-n`的栈帧。 |
| **info frame n** | `n`表示你想要查看的帧的编号。这会显示该函数调用的详细信息,包括参数和局部变量的值。 |
| **info frame \[addr\]** | 显示当前选定的栈帧(`stack frame`)的信息,或者如果提供了地址参数 \[`addr`\],则会显示指定地址处的栈帧信息。栈帧是在函数调用时创建的,它包含了函数的局部变量、参数以及返回地址等信息。通过`info frame`命令,你可以检查函数调用的上下文,这对于调试程序非常有用。 |
| **info args** | 显示当前选中栈帧的所有函数参数的值;当你在一个函数调用点设定了断点并停止程序执行时,可以通过输入`info args`来查看该函数的参数。`GDB`将列出每个参数的名字(如果源代码可用并且编译时包含了调试信息),以及它们的当前值。 |
| **info locals** | 显示当前栈帧中所有局部变量的值。当程序在一个函数的断点处暂停时,使用`info locals`命令可以帮助你检查这些局部变量的状态,它会列出当前函数内所有的局部变量,包括它们的名称、类型和当前值。如果函数中有多个嵌套的代码块,那么每个代码块的局部变量都会被显示出来。`info locals`命令还可以接受一个可选的变量名作为参数,以便只显示特定变量的信息。例如,`info locals local_var1`将只显示`local_var1`的信息。 |
| **finish** | 继续执行直到当前栈帧结束并返回到调用者。这可以让你查看一个函数的完整执行流程而不必逐行跟踪。 |
> 【**拓展** 】`info frame`命令可能显示的信息类型:
>
> *
> **栈级别Stack Level**:显示当前帧相对于栈顶部的级别。
> *
> **帧地址Frame Address**:显示当前帧的内存地址。
> *
> **指令指针Instruction Pointer** :对于`x86`架构,这可能是`EIP`或`RIP`寄存器的值,显示了当前正在执行的指令地址。
> *
> **源文件和行号Source File and Line Number**:显示了当前指令所在的源代码文件和行号。
> *
> **保存的指令指针Saved Instruction Pointer**:这是调用当前函数的上一层函数的返回地址。
> *
> **调用者帧地址Caller's Frame Address**:显示了调用当前函数的上一层函数的帧地址。
> *
> **源语言Source Language** :显示了源代码的语言,如`C`或`C++`。
> *
> **参数列表Argument List**:显示了传递给当前函数的参数。
>
> 例如,以下是一个`info frame`命令的输出示例:
>
> 1(gdb) info frame
> 2Stack level 0, frame at 0xbffd0cd0:
> 3 eip = 0x80483ca in show3 (main.c:4);
> 4 saved eip = 0x80483ef
> 5 called by frame at 0xbffd0ce0
> 6 source language c.
> 7 Arglist at 0xbffd0cc8, args: ...
>
> 在这个例子中,我们可以看到当前的栈帧位于地址`0xbffd0cd0`,当前正在执行的指令是在`main.c`文件的第`4`行中的`show3`函数,而上一个函数的返回地址是`0x80483ef`。调用者帧地址是`0xbffd0ce0`。这个地址指向调用当前函数的那个栈帧的基址,可以用来查看调用者函数的上下文信息。
在`GDB``GNU Debugger`)中,程序栈(`call stack`)是追踪程序执行流程的关键工具之一。程序栈记录了程序执行过程中的函数调用历史,每个函数调用都会在栈上创建一个新的栈帧(`stack frame`)。栈帧包含了函数的局部变量、函数参数、返回地址和其他相关信息。
### 5、多进程
`GDB`在调试多进程程序(程序含`fork`调用)时,默认只追踪父进程。可以通过命令设置,实现只追踪父进程或子进程,或者同时调试父进程和子进程。
| 命令 | 作用 |
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| **info inferiors** | 显示当前被调试的进程列表,包括进程`ID`和一些基本信息。 |
| **inferior inferior-num** | 切换到指定编号的进程进行调试。 |
| **attach pid** | 附加到一个已经运行的进程,其中`pid`是进程`ID`。 |
| **print $_exitcode** | `$_exitcode`是一个特殊的变量,它包含了程序退出时的退出代码。但是,`$_exitcode`只在程序正常退出后才可用。如果你试图在程序仍在运行或未正常退出时使用`print $_exitcode``GDB`将无法提供退出代码,因为此时`_exitcode`尚未确定。 |
| **set follow-fork-mode child** | 设置`GDB`追踪子进程。 |
| **set follow-fork-mode parent** | 设置`GDB`追踪父进程。 |
| **set detach-on-fork on** | 设置了这个选项,当你的程序调用`fork()`创建子进程时,`GDB`将不会自动追踪子进程,这可以减少`GDB`追踪的进程数量。 |
| **set detach-on-fork off** | 在`fork()`后同时追踪父进程和子进程。 |
在调试多进程程序时候,默认情况下,除了当前调试的进程,其他进程都处于挂起状态,所以,如果需要在调试当前进程的时候,其他进程也能正常执行,那么通过设置`set schedule-multiple on`即可。
> 【**拓展** 】在`GDB`中,`set schedule-multiple on`命令开启了一个特性,允许`GDB`在多线程或多进程调试环境中同时调度多个线程或进程。默认情况下,`GDB`在每次执行`continue`,`step`,`next`, 或其他类似的命令时,只会调度一个线程或进程。但是,当`schedule-multiple`被设置为`on`时,`GDB`将尝试同时推进所有活动线程或进程的执行,直到它们都遇到断点、系统调用或其他暂停点。
>
> 这个特性在调试高度并发的程序时尤其有用,因为你可以观察到所有线程或进程的动态行为,而不仅仅局限于某一个线程或进程的视角。例如,在多线程程序中,你可以看到不同线程之间的交互和同步点。
>
> `set schedule-multiple on`的使用格式如下:
>
> (gdb) set schedule-multiple on
>
> 一旦启用,`GDB`将在执行命令时尽可能地并行推进所有线程或进程。但是,需要注意的是,这可能会导致调试会话变得更为复杂,因为你需要同时关注多个执行流。
>
> 在多进程调试中,`schedule-multiple`的效果可能受限于`GDB`当前控制的进程数以及操作系统对进程调度的限制。此外,不是所有的`GDB`版本都完全支持`schedule-multiple`,尤其是在处理多进程环境时。
### 6、多线程
多线程编程在日常开发中广泛使用,掌握其调试技巧对软件工程师至关重要。
使用`GNU Debugger (GDB)`调试多线程程序时,有一些关键的指令可以帮助你更有效地管理线程和定位问题。以下是一些常用的`GDB`命令,它们特别适用于多线程环境:
| 命令 | 作用 |
|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **info threads** | 列出所有线程的`ID`和状态。 |
| **thread num** | 切换到编号为`num`的线程。 |
| **thread apply thread-id-list command** | 对特定线程列表执行一个给定的命令;`thread-id-list`可以是一个单一的线程`ID`、一个线程`ID`的范围,或者多个线程`ID`的列表。线程`ID`可以通过`info threads`命令获得,它会列出所有当前活动线程的信息,包括它们的`ID`。假设你有两个活动线程,`ID`分别是`1`和`2`,你想要查看线程`1`和线程`2`上变量`x`的值,可以这样操作:`thread apply 1,2 print x`。 |
| **thread apply all command** | 在所有线程上执行`command`。其中`command`是你希望在所有线程上执行的`GDB`命令。例如,如果你想要查看所有线程中某个变量`x`的值,`thread apply all print x`。 |
| **break functionthread n** | 在特定线程`n`的特定函数`function`上设置断点。 |
| **set scheduler-locking on** | 调试多线程程序时,设置仅当前选中的线程会执行,即锁定当前选中的线程,直到你显式地解除锁定或切换到另一个线程。 |
| **set scheduler-locking off** | 调试多线程程序时,设置所有线程都可以执行,即意味着所有线程都可以被调度,没有任何锁定,这是默认值。 |
| **set scheduler-locking step** | 当您在当前选定的线程上使用`next`、`step`或`finish`命令进行单步执行时,`GDB`会暂时锁定当前线程,防止其他线程被调度,**注意** **只在单步执行时会锁定当前线程** 。这意味着在你单步执行的过程中,其他线程不会抢占`CPU`时间,从而避免了它们可能引发的意外行为或数据竞争。一旦单步执行命令完成,`GDB`将解除锁定,允许其他线程再次被调度。这有助于你专注于单个线程的行为,而不必担心其他线程的干扰。 |
如果只关心当前线程,建议临时设置`scheduler-locking`为`on`,避免其他线程同时运行,导致命中其他断点分散注意力。
### 7、打印输出
通常情况下,在调试的过程中,我们需要查看某个变量的值,以分析其是否符合预期,这个时候就需要打印输出变量值。以下是一些常用的打印变量的`GDB`命令:
| 命令 | 作用 |
|-----------------------------|---------------------------------------------------------------------------------------------------|
| **whatis variable** | 显示变量的类型。 |
| **ptype variable** | 查看变量详细的类型信息。 |
| **info variables variable** | 查看定义变量`variable`的文件,不支持局部变量。 |
| **display variable** | 用于在每次程序暂停时自动显示一个变量或表达式的值。可以使用`undisplay display-number`来取消显示,其中`display-number`是`display`命令返回的编号。 |
#### 1打印字符串
使用`x/s`命令打印`ASCII`字符串,如果是宽字符字符串,需要先看宽字符的长度 `print sizeof(str)`
如果长度为`2`,则使用`x/hs`打印;如果长度为`4`,则使用`x/ws`打印。
| 命令 | 作用 |
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **x/s str** | 以字符串(`s`)格式显示位于`str`所指内存地址处的数据,即打印字符串。 |
| **set print elements 0** | 控制当你使用`print`命令打印数组或集合时,`GDB`显示的元素数量。默认情况下,`GDB`会限制显示的元素数量,以避免输出过长或不必要的信息。当你设置`set print elements 0`时,实际上是在告诉`GDB`不要限制输出的元素数量。也就是说,`GDB`将尽可能打印出整个数组或集合的所有元素,而不是只显示一部分。 |
| **call printf("%s\\n",xxx)** | 使用`printf`函数来打印一个字符串变量`xxx`。要注意的是,`xxx`必须是一个有效的指针,指向一个有效的字符串。虽然`printf`的标准返回类型是`int`,表示输出的字符数量,但在某些情况下,`GDB`可能无法识别这一点。为了解决这个问题,你需要显式地指定`printf`的返回类型。正确的命令格式如下:`call (int)printf("Your format string here\n", argument1, argument2, ...)`。 |
| **printf "%s\\n",xxx** | 同上。 |
> 【**拓展** 】在`GDB`中,`x`命令是一个用于检查内存内容的强大工具,全称是`examine`。它允许你以不同的格式查看内存中的数据,如字节、半字、字、双字或字符串。`x`命令的语法是:
>
> x/<count><format><address>
>
> 其中:
>
> *
> `<count>`是你想要查看的数据项的数量,默认为`1`。
> *
> `<format>`是你希望用来显示数据的格式,比如`x`表示十六进制,`d`表示有符号十进制,`u`表示无符号十进制,`t`表示二进制,`f`表示浮点数,`a`表示`ASCII``s`表示字符串。
> *
> `<address>`是你想要检查的内存起始地址,可以是一个变量名(如果是指针)、一个表达式的结果或一个直接的十六进制地址。
>
> 例如,`x/s str_ptr`命令是`examine string at str_ptr`的简写,意味着检查并以字符串格式显示`str_ptr`所指向的内存区域的内容。
>
> `x`命令的灵活性使其成为调试时检查变量状态、跟踪内存泄漏或验证数据结构完整性的重要工具。通过选择不同的格式和数量,你可以深入了解程序的内存布局和数据状态。
#### 2打印数组
在`GDB`中打印数组可以通过几种不同的方式来实现。以下是一些常用的方法:
| 命令 | 作用 |
|--------------------------------|--------------------------------------------------------------------------------------------------------------------|
| **print arr** | 打印名为`arr`的数组,默认情况下,`GDB`会打印数组的一部分。 |
| **print \*arr@num** | 打印从数组开头连续`num`个元素的值。 |
| **x/10wx arr** | 打印数组的前`10`个元素,在这里,`/10w`指定要打印`10`个字(`word`)的数据,`x`表示以十六进制格式显示数据,`arr`是数组的起始地址。`w`表示以整数(`word`)的格式打印数据。 |
| **print arr\[index\]@num** | 打印`arr`数组下标从`index`开始的`num`个元素。 |
| **set print array-indexes on** | 打印数组时同时显示数组元素的索引,使得输出更加易读,特别是在处理多维数组或大型一维数组时。如`print arr`会输出:`$1 = {[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5}`。 |
#### 3打印指针
在`GDB`中打印指针有两种主要方式:一种是打印指针本身的值,另一种是打印指针指向的内存内容。
| 命令 | 作用 |
|-----------------------------------|----------------------------------------------------------------------------|
| **print ptr** | 查看一个指针变量`ptr`的值(即它指向的内存地址),通常是一个十六进制的内存地址。 |
| **print \*ptr** | 查看指针`ptr`指向的内存内容,而不是指针本身,即指针指向的地址上存储的值。 |
| **print (\*ptr).field_name** | 使用解引用指针`ptr`操作来查看结构体或类的字段。 |
| **print ptr-\>field_name** | 如果结构体的字段支持直接访问,也可以这样打印结构体字段。 |
| **print \*(struct xxx \*)ptr** | 查看指针`ptr`指向的结构体的内容。 |
| **print \*my_ptr_array\[index\]** | 查看`my_ptr_array`数组中位于`index`位置的指针所指向的值。`*`操作符的作用,它用于解引用指针,即访问指针所指向的内存位置的值。 |
| **print \*my_array_ptr** | 如果`my_array_ptr`是一个指向数组的指针,你可以通过解引用这个指针来查看数组的第一个元素。 |
#### 4打印指定内存地址的值
在`GDB`中,`x`命令是用于检查内存区域内容的强大工具。`x`是`examine`的缩写,它允许你查看程序运行时的内存状态。使用`x`命令的基本语法如下:
x/<count><format><address>
三个参数含义如下:
*
`<count>`是要检查的元素数量。
*
`<format>`是数据的显示格式。
*
`<address>`是要检查的内存起始地址。
其中,`<format>`参数可以是以下几种之一:
*
`x`:以十六进制显示字节。
*
`bx`:以十六进制显示字节,更专注于字节级别数据的显示。
*
`hx`:以十六进制显示半字(`half word`,通常是`2`字节)。
*
`wx`:以十六进制显示字(`word`,通常是`4`字节)。
*
`gx`:以十六进制显示双字(`double word`,通常是`8`字节)。
*
`z`:以十六进制显示四字(`quad word`,通常是`16`字节)。
*
`c`:以`ASCII`字符显示字节。
*
`b`:以有符号的八进制显示字节。
*
`h`:以有符号的十六进制显示半字。
*
`w`:以有符号的十进制显示字。
*
`g`:以有符号的十进制显示双字。
*
`q`:以有符号的十进制显示四字。
*
`s`:以字符串形式显示数据。
*
`a`以ASCII字符显示字节。
使用`x`命令是调试时检查内存内容和变量状态的重要手段,特别是在追踪内存相关的问题时。
| 命令 | 作用 |
|---------------|-------------------------------|
| **x/8bx arr** | 以十六进制打印数组`arr`的前`8`个`byte`的值。 |
| **x/8wx arr** | 以十六进制打印数组`arr`的前`8`个`word`的值。 |
#### 5打印局部变量
在`GDB`中打印局部变量的值可以通过几种不同的方式来实现,以下是其中的一些方法:
| 命令 | 作用 |
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **info locals** | 显示当前栈帧中所有局部变量的值。当程序在一个函数的断点处暂停时,使用`info locals`命令可以帮助你检查这些局部变量的状态,它会列出当前函数内所有的局部变量,包括它们的名称、类型和当前值。如果函数中有多个嵌套的代码块,那么每个代码块的局部变量都会被显示出来。`info locals`命令还可以接受一个可选的变量名作为参数,以便只显示特定变量的信息。例如,`info locals local_var1`将只显示`local_var1`的信息。 |
| **backtrace full** | 是`backtrace`命令的一个增强版本,它不仅显示函数调用的堆栈,还提供更详细的信息,如每个调用帧中的局部变量和参数的值。这对于深入分析程序状态和找出问题所在尤其有用。 |
| **bt full n** | 从栈顶开始向栈底方向显示`n`个栈帧及其局部变量和参数的值。 |
| **bt full -n** | 从栈底开始向栈顶方向显示`n`个栈帧及其局部变量和参数的值。 |
需要注意的是,由于`backtrace full`提供了大量的细节,它可能会产生相当长的输出,特别是对于复杂的调用堆栈。因此,在使用这个命令时,你应该确保有足够的耐心去阅读和理解输出结果,同时也考虑到性能和效率方面的影响。
#### 6打印结构体
在`GDB`中打印结构体可以采用几种不同的方法,这取决于结构体的复杂程度以及你想要查看的具体信息。以下是一些基本的步骤和命令,可以帮助你打印和理解结构体的内容:
| 命令 | 作用 |
|-----------------------------|----------------------------------------------------------------------------------------------------------------|
| **print myStruct** | 直接打印结构体,将显示结构体的所有成员和它们的值。`GDB`默认会尝试以一种可读的格式显示结构体,包括递归地显示嵌套的结构体成员。 |
| **print myStruct.memberX** | 打印结构体的特定成员,如果你想只查看结构体中的特定成员,可以使用`myStruct.memberX`这种格式,其中,`memberX`是结构体中特定成员。 |
| **set print pretty on** | 设置每行只显示结构体的一名成员,先使用该命令进行设置,再使用`print`打印结构体。 |
| **set print null-stop on** | `GDB`会在遇到第一个空字符(`null character`,即`\0`)时停止打印字符串,这是大多数编程语言中字符串结束的标志。这是默认行为,因为在标准`C/C++`中,字符串通常是由以空字符结尾的字符数组表示的。 |
| **set print null-stop off** | 当`null-stop`被设为`off``GDB`不会在遇到第一个空字符(`null character`,即`\0`)时停止打印字符串。将会继续打印字符串直到达到预设的最大字符数限制,或者直到达到内存区域的末尾。 |
如果你想要在每次断点触发时自动显示结构体或其成员的值,可以使用`display`命令。结合`info locals`命令,你还可以查看当前作用域内所有局部变量,包括结构体变量。通过组合使用上述命令,你可以有效地在`GDB`中调试和理解结构体。根据你的具体需求,选择最合适的方法来查看和分析结构体数据。
### 8、函数跳转
在`GDB`中进行函数跳转,主要是指在调试过程中控制程序流,使其跳转到特定的函数或代码段执行。这可以通过几种不同的方式实现:
| 命令 | 作用 |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **set step-mode on** | 在`GDB`中,`set step-mode on`命令用于改变单步调试的行为。默认情况下,当你使用`step`命令时,`GDB`会单步执行到下一个源代码行,但是如果遇到的函数没有调试信息(例如,系统库函数或优化掉的代码),`GDB`通常不会进入这些函数的内部,而是直接跨越过去。当你设置了`set step-mode on``GDB`的行为会发生变化,它会尝试进入那些没有调试信息的函数,继续进行单步执行,直到遇到有调试信息的代码或到达下一个断点。这对于查看低级代码的执行流程或检查没有调试信息的函数内部行为非常有用。 |
| **finish** | 当你在函数内部设置了断点并开始调试时,使用`finish`命令可以让函数自然执行到底,直到它返回给调用者。这在你想要跟踪一个函数的全部执行流程,但又不想逐行执行的情况下特别有用。一旦发出`finish`命令,`GDB`将继续执行,直到当前函数返回。此时,`GDB`将在函数返回点停下来,你可以查看函数返回后的状态,包括任何可能的返回值或函数调用栈的变化。 |
| **return expression** | `return`命令可以让你从当前正在调试的函数中提前返回,并且可以选择性地指定返回值。当你在函数内部设置了断点,并且想要模拟函数提前返回的情况,`return`命令就非常有用。这对于测试函数的不同退出路径或调试函数的返回值逻辑很有帮助。其中`expression`是你想要作为函数返回值的表达式。如果省略`expression`,则函数将返回默认值,这通常是零或空值,具体取决于函数的返回类型。 |
| **call printf("%s\\n",xxx)** | 使用`printf`函数来打印一个字符串变量`xxx`。要注意的是,`xxx`必须是一个有效的指针,指向一个有效的字符串。虽然`printf`的标准返回类型是`int`,表示输出的字符数量,但在某些情况下,`GDB`可能无法识别这一点。为了解决这个问题,你需要显式地指定`printf`的返回类型。正确的命令格式如下:`call (int)printf("Your format string here\n", argument1, argument2, ...)`。 |
| **set {type} address = value** | 使用`set`命令结合类型和内存地址可以让你直接在内存中修改数据。这里的`type`是你想要存储的值的数据类型,`address`是要修改的内存地址,而`value`是你想要写入的新值。`set`命令的修改只在当前的调试会话中有效,一旦你退出`GDB`或重启程序,这些修改将不会被保留。 |
### 9、其它
#### 1图形化
`GDB`的`TUI``Terminal User Interface`)模式提供了一个图形化的界面,允许你在终端窗口中以更直观的方式进行调试。`TUI`模式提供了比纯文本模式更丰富的界面,包括源代码高亮、堆栈轨迹的可视化、以及各种调试信息的面板展示等。
要在启动`GDB`时直接进入`TUI`模式,你可以在命令行中加入`-tui`参数,如下所示:
gdb -tui your_program
或者,如果你已经在`GDB`中,可以通过使用组合键`Ctrl+X`然后按`A`键,切换到`TUI`模式。在`TUI`模式下,你可以使用方向键和其它键盘快捷键来浏览和操作界面。
在`TUI`模式下,你还可以使用以下命令来控制窗口的布局和焦点:
*
**切换到下一个窗口** :`Ctrl+X`然后按`O`或者使用`Focus Next`(`fs n`)
*
**切换到源码窗口** :`Focus Source`(`fs s`)
*
**切换到命令窗口** :`Focus Command`(`fs c`)
*
**切换到汇编窗口** :`Focus Asm`(`fs a`)
*
**切换到寄存器窗口** :`Focus Regs`(`fs r`)
*
**切换到上一个窗口** :`Focus Prev`(`fs p`)
*
**切换到双窗口模式** `Ctrl+X`然后按`2`
*
**切换到单窗口模式** `Ctrl+X`然后按`1`
*
**切换到传统的命令行界面或返回TUI模式** `Ctrl+X`然后按`A`
另外,`TUI`模式下还有一些命令可以操作界面,如下:
| 命令 | 作用 |
|-----------------------|------------------|
| **layout src** | 显示源码窗口。 |
| **layout asm** | 显示汇编窗口。 |
| **layout split** | 同时显示源代码和汇编代码窗口。 |
| **layout regs** | 显示寄存器 + 源码或汇编窗口。 |
| **winheight src +5** | 源码窗口高度增加`5`行。 |
| **winheight asm -5** | 汇编窗口高度减小`5`行。 |
| **winheight cmd +5** | 命令行窗口高度增加`5`行。 |
| **winheight regs -5** | 寄存器窗口高度减小`5`行。 |
#### 2汇编
在`GDB`中查看汇编代码可以通过几种不同的方式完成,具体取决于你想要查看的代码范围和上下文。以下是几种常用的方法:
| 命令 | 作用 |
|----------------------------------|----------------------------------------------------------------------------------------------------------------|
| **disassemblefunction_name** | 查看某个特定函数的汇编代码,可以使用`disassemble`命令,简称`disas`。例如,如果你想查看名为`my_function`的函数的汇编代码,可以这样操作:`disassemble my_function`。 |
| **disassemble** | 如果你想查看当前程序计数器(`PC`)周围的汇编代码,可以省略参数,这将显示当前执行点周围的一段汇编代码。 |
| **disassemble /mrfunction_name** | 同时比较函数源代码和汇编代码。 |
#### 3调试和保存core文件
在`GDB`中调试`core dump`文件是一种非常有用的技巧,特别是当程序崩溃并且生成了`core dump`文件时。`core dump`文件包含了程序崩溃瞬间的内存快照和进程状态,这对于诊断问题原因至关重要。
| 命令 | 作用 |
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| **file exec_file** | 当你调试未带调试信息的可执行文件时,使用`fileexec_file`命令可在`GDB`命令行中加载带调试信息的新编译的可执行文件,以便加载后,提供新编译的可执行文件的符号表信息给未带调试信息的可执行文件使用。**注意** :二者的源码需一致,这里新编译可执行文件时仅增加`-g`调试命令。 |
| **core core_dump_file** | 加载一个特定的核心转储文件(`core-dump`)来调试。 |
| **gcore core_file** | 使用`gcore`命令实际上并不用于加载核心转储文件。`gcore`是一个`GDB`内部的命令,它的用途是在程序正在运行时创建一个核心转储文件(`core dump`),记录当前进程的状态。这通常在你怀疑程序可能即将崩溃,但还没有实际崩溃的情况下使用,以便保存此时的程序状态供后续分析。 |
GDB调试案例
-------
以下是一个可能会引发`core dump`的`C`程序示例,包含了多线程、结构体、指针操作等复杂情况:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
//定义结构体
typedefstruct{
int\*data;
}DataStruct;
//线程函数
void*threadFunction(void*arg){
DataStruct\*ds=(DataStruct\*)arg;
//错误的指针操作可能导致coredump
\*ds-\>data=100;
returnNULL;
}
intmain(){
pthread_tthread;
DataStructds;
ds.data=NULL;
//创建线程
if(pthread_create(\&thread,NULL,threadFunction,\&ds)!=0){
perror("pthread_createfailed");
return1;
}
//等待线程结束
if(pthread_join(thread,NULL)!=0){
perror("pthread_joinfailed");
return1;
}
return0;
}
下面是使用`GDB`调试这段代码的详细流程:
*
**第一步:编译代码**
使用`-g`标志编译代码以包含调试信息。
gcc -g crash_program.c -o crash_program -lpthread
*
**第二步:运行可执行程序**
[root@localhost tmp]# ./crash_program
Segmentation fault (core dumped)
*
**第三步:查看`coredump`文件**
[root@localhost tmp]# gdb ./crash_program -c crash_program.892409.1721113020.coredump
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-20.el8
Copyright (C) 2018 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".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./crash_program...done.
\[New LWP 892410\]
\[New LWP 892409\]
\[Thread debugging using libthread_db enabled\]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by \`./crash_program'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 threadFunction (arg=0x7ffefa858550) at crash_program.c:14
14 \*ds-\>data = 100;
\[Current thread is 1 (Thread 0x7fb85b7c1700 (LWP 892410))\]
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64
(gdb)
通过查看`coredump`文件,可以看出程序在线程函数`threadFunction`中发生段错误。
也可以通过`backtrace`验证。
*
**第四步:查看错误发生时的堆栈跟踪**
(gdb) backtrace
#0 threadFunction (arg=0x7fffffffe1a0) at crash_program.c:12
#1 0x00007ffff7bb51ca in start_thread () from /lib64/libpthread.so.0
#2 0x00007ffff78108d3 in clone () from /lib64/libc.so.6
通过`backtrace`可知,栈顶是`threadFunction`函数,因此,可断点错误发生在`threadFunction`函数中。
*
**第五步:查看源代码**
(gdb) list
9
10 // 线程函数
11 void *threadFunction(void *arg) {
12 DataStruct *ds = (DataStruct *)arg;
13 // 错误的指针操作,可能导致 core dump
14 *ds->data = 100;
15 return NULL;
16 }
17
18 int main() {
(gdb)
19 pthread_t thread;
20 DataStruct ds;
21
22 ds.data = NULL;
23
24 // 创建线程
25 if (pthread_create(&thread, NULL, threadFunction, &ds)!= 0) {
26 perror("pthread_create failed");
27 return 1;
28 }
(gdb)
29
30 // 等待线程结束
31 if (pthread_join(thread, NULL)!= 0) {
32 perror("pthread_join failed");
33 return 1;
34 }
35
36 return 0;
37 }
(gdb)
*
**第六步:设置断点**
在`threadFunction`函数中`DataStruct *ds = (DataStruct *)arg;`和`*ds->data = 100;`这二行设置断点。
(gdb) b 12
Breakpoint 1 at 0x40066e: file crash_program.c, line 12.
(gdb) b 14
Breakpoint 2 at 0x400676: file crash_program.c, line 14.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040066e in threadFunction at crash_program.c:12
2 breakpoint keep y 0x0000000000400676 in threadFunction at crash_program.c:14
*
**第七步:运行程序直到断点**
(gdb) run
Starting program: /tmp/test/gdb/tmp/crash_program
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff77d6700 (LWP 902893)]
[Switching to Thread 0x7ffff77d6700 (LWP 902893)]
Thread 2 "crash_program" hit Breakpoint 1, threadFunction (arg=0x7fffffffe1a0) at crash_program.c:12
12 DataStruct \*ds = (DataStruct \*)arg;
*
**第八步:切换到线程**
当程序因断点而停止时,`GDB`可能不会自动切换到线程。使用`info threads`命令查看所有线程,然后使用`thread [thread number]`命令切换到你想要调试的线程。如果线程尚未开始运行,你可能需要先`continue`让线程启动。根据`*`可知这里已切换到线程`2`,因此,无需再使用`thread [thread number]`命令切换到线程`2`。
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7fe8740 (LWP 902889) "crash_program" 0x00007ffff7bb66cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
* 2 Thread 0x7ffff77d6700 (LWP 902893) "crash_program" threadFunction (arg=0x7fffffffe1a0) at crash_program.c:12
*
**第九步:检查变量**
使用`print`命令观察`ds`和`ds->data`的值。
(gdb) print ds
$1 = (DataStruct *) 0x0
(gdb) print ds->data
Cannot access memory at address 0x0
(gdb)
从`print ds->data`输出中,`ds->data`指针的地址为`0x0`,因此接下来的解引用这个指针是非法的,即`*ds->data = 100;`操作会导致`core dump`错误。
*
**第十步:退出`GDB`**
使用`quit`命令退出`GDB`。
(gdb) q
A debugging session is active.
Inferior 1 \[process 902889\] will be killed.
Quit anyway? (y or n) y
通过上述步骤,你将能够定位和修复由于错误的指针操作而导致的`core dump`问题。在本例中,关键在于在使用指针之前确保它指向有效的内存区域。
结语
---
`GDB`作为`Linux`平台上不可或缺的调试利器,其重要性在软件开发领域中不言而喻。无论是面对单线程的简单调试,还是处理多线程及多进程的复杂场景,乃至分析突如其来的`core dump`文件,`GDB`都能以其丰富的功能助开发者一臂之力。本文通过一系列命令以及实例,不仅展示了`GDB`在不同场景下的应用,更旨在引导读者掌握其基础操作,从而在日常开发中实现效率的显著提升。
然而,`GDB`的功能远不止于此。从基础的断点设置、变量查看到高级的反汇编代码分析、线程切换乃至信号量跟踪,`GDB`提供了一个全面的调试框架,足以满足各种调试需求。掌握`GDB`,意味着掌握了驾驭代码的钥匙,使得开发者能够在复杂多变的项目中游刃有余,让编程之路更加顺畅。因此,无论你是初出茅庐的新手还是久经沙场的老将,持续探索和学习`GDB`的奥秘,都将是你职业生涯中一笔宝贵的财富。
[跳转到 Cubox 查看](https://cubox.pro/my/card?id=7223784296694156027)

@ -0,0 +1,274 @@
# Makefile
原始内容来自 [rstacruz/cheatsheets](https://github.com/rstacruz/cheatsheets/blob/master/makefile.md) 有改动。
## 变量赋值
```makefile
foo = "bar" # foo 当前值为 bar允许后面进行会修改
bar = $(foo) foo # Makefile 展开,确定 foo 值之后才能决定最后 bar 的值
dum := $(foo) foo # dum值为 bar foo
foo := "boo" # 此时 foo 值被修改为 boo
foo ?= /usr/local # 如果 foo 没有赋值过,才会进行赋值
bar += world # 追加
foo != echo fooo # 执行shell 命令后foo 值为 fooo
# 因为 foo 最终值确定为 fooo所以最终 bar 值为fooo foo world。
```
`=` 用于创建一个可变的变量,后面可以重新赋值这个变量,最终值是整个 Makefile 最后指定的值。`:=`是赋予当前位置的值。`?=`是如果该变量没有被赋值,才被赋值为等号后面的值。
```makefile
x = foo
y = $(x) bar
z := $(x) bar
x = xyz
```
上面例子中x,y,z最终值分别为`xyz`,`xyz bar`, `foo bar`
## 魔术变量
```makefile
out.o: src.c src.h src.c
$@ # "out.o" (目标对象)
$< # "src.c" (第一个前置依赖)
$^ # "src.c src.h" (所有的前置依赖)
$+ # "src.c src.h src.c" (类似$^,不同于$^地方是它不会去重)
$? # 所有比目标对象新的依赖的集合
$(@D) # "." (目标对象中的目录部分,没有目录部分那么对应是.)
```
## 命令前缀
| 前缀 | 描述 |
| --- | --- |
| `-` | 忽略错误 |
| `@` | 不打印命令输出 |
| `+` | 忽略错误,但会打印错误信息 |
```makefile
build:
@echo "compiling"
-gcc $< $@
-include .depend
```
## 查找文件
```makefile
js_files := $(wildcard test/*.js) # 匹配所有test目录下的js文件
all_files := $(shell find images -name "*") # 查找images目录下所有文件
```
## 函数
```makefile
# 替换相关
file = $(SOURCE:.cpp=.o) # 替换 .cpp 为 .o
outputs = $(files:src/%.coffee=lib/%.js) # 替换 .coffee 为 .js
outputs = $(patsubst %.c, %.o, $(wildcard *.c)) # 替换 *.c 为 *.o
assets = $(patsubst images/%, assets/%, $(wildcard images/*)) # 替换 images/ 为 assets/
# 其他函数
$(strip $(string_var)) # 移除字符串两端的空格
$(filter %.less, $(files)) # 匹配所有以 .less 结尾的文件
$(filter-out %.less, $(files)) # 匹配所有不是以 .less 结尾的文件
$(subst ee,EE,feet on the street) # 替换 ee 为 EE
```
`patsubst <pattern>,<replacement>,<text>` 是将 text 中的所有匹配 pattern 的部分替换成 replacement。
## 多目标规则
Makefile的规则中的目标可以不止一个其支持多目标有可能我们的多个目标同时依赖于一个文件并且其生成的命令大体类似。于是我们就能把其合并起来。
```makefile
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
```
其中, `-$(subst output,,$@)` 中的 $ 表示执行一个Makefile的函数函数名为subst后面的为参数。上述规则等价于
```makefile
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
```
### 静态模式
静态模式可以更加容易地定义多目标的规则。
```makefile
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
```
- targets定义了一系列的目标文件可以有通配符。是目标的一个集合。
- target-pattern是指明了targets的模式也就是的目标集模式。
- prereq-patterns是目标的依赖模式它对target-pattern形成的模式再进行一次依赖目标的定义。
```makefile
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
```
上面的例子中,指明了我们的目标从$object中获取 %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集也就是“foo.o bar.o”。于是上面的规则展开后等价于下面的规则
```makefile
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
```
再看一个例子:
```makefile
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
```
`$(filter %.o,$(files))`表示调用Makefile的filter函数过滤“$files”集只要其中模式为“%.o”的内容。
## 隐含规则与模式规则
### 隐含规则
```makefile
foo : foo.o bar.o
cc o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
```
这个Makefile中并没有写下如何生成 foo.o 和 bar.o 这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。在上面的那个例子中make调用的隐含规则是把 .o 的目标的依赖文件置成 .c 并使用C的编译命令 `cc c $(CFLAGS) foo.c` 来生成 foo.o 的目标。也就是说,它等同于下面的两条规则:
```makefile
foo.o : foo.c
cc c foo.c $(CFLAGS)
bar.o : bar.c
cc c bar.c $(CFLAGS)
```
另外对于没有 Makefile 的时候,对于一个 foo.c 文件,我们可以 make foo 时,会自动运行`cc foo.c -o foo`。
### 老式风格的“后缀规则”
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。为了和老版本的Makefile兼容GNU make同样兼容于这些东西。后缀规则有两种方式“双后缀”和“单后缀”。
双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如 .c.o 相当于 %o : %c 。单后缀规则只定义一个后缀,也就是源文件的后缀。如 .c 相当于 % : %.c 。后缀规则不允许任何的依赖文件,如果有依赖文件的话,那就不是后缀规则,那些后缀统统被认为是文件名。
```makefile
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
```
### 模式规则
我们可以使用模式规则来定义一个隐含规则,模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配。
```makefile
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ # .c .o
```
## 条件判断
```makefile
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
```
## 自动生成依赖性
在Makefile中我们的依赖关系可能会需要包含一系列的头文件我们可以借助编译器来生成依赖关系。
```bash
cc -M main.c # -M选项输出依赖关系
gcc -MM main.c # -MM 选项输出依赖关系,输出的依赖关系中会去掉标准库中的头文件
cc -MM src/chap8/udpserv01.c -I./src/include # 使用 -I 参数指定自定义头文件位置
```
## 包含其他 Makefile
```makefile
-include foo.make
```
make会在当前目录下首先寻找如果当前目录下没有找到那么make还会在下面的几个目录下找
1. 如果make执行时有 -I 或 --include-dir 参数那么make就会在这个参数所指定的目录下去寻找。
2. 接下来按顺序寻找目录 <prefix>/include (一般是 /usr/local/bin )、 /usr/gnu/include 、 /usr/local/include 、 /usr/include 。
环境变量 .INCLUDE_DIRS 包含当前 make 会寻找的目录列表。你应当避免使用命令行参数 -I 来寻找以上这些默认目录,否则会使得 make “忘掉”所有已经设定的包含目录,包括默认目录。
## 命令行选项
```sh
make
-e, --environment-overrides # 允许环境变量覆盖 Makefile 的变量
-B, --always-make # 强制重新编译所有目标
-s, --silent # 不打印任何信息
-j, --jobs=N # 并行编译N 表示同时编译的线程数
-f, --file=FILE # 指定 Makefile 文件
```
## 参考示例
```makefile
CC=cc
CFLAGS+=-Wall -Werror -Wformat=2 -g
LDFLAGS=-I./src/include -L./src/lib
COMPILER_VERSION=$(shell $(CC) --version)
ifneq '' '$(findstring clang, $(COMPILER_VERSION))'
CFLAGS += -Qunused-arguments
endif
lib := src/lib
bin := udpserv01 udpcli01
all: $(bin)
lib_objects :=$(patsubst %.c, %.o, $(wildcard $(lib)/*.c))
$(lib_objects): %.o : %.c
$(CC) -c $(CFLAGS) $(LDFLAGS) $< -o $@
.PHONY: udpserv01
udpserv01: src/chap8/udpserv01.c $(lib_objects)
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
.PHONY: udpcli01
udpcli01: $(lib_objects) src/chap8/udpcli01.c dg_cli.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
```
## 进一步阅读
- [isaacs's Makefile](https://gist.github.com/isaacs/62a2d1825d04437c6f08)
- [Your Makefiles are wrong](https://tech.davis-hansson.com/p/make/)
- [Manual](https://www.gnu.org/software/make/manual/html_node/index.html)
- [跟我一起写Makefile](https://seisman.github.io/how-to-write-makefile/index.html)

@ -4,4 +4,12 @@
Start learning with basic concepts like Key problems with networking, Network interface card (NIC) device drivers, System initialization, Layer 2 (link-layer) tasks implementation and problems based on these data asked in product based tier etc.
<iframe width="760" height="315" src="https://www.youtube.com/embed/aq0TLYXGQCA?si=kxvvfvgnRlzJ2OKk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<iframe width="760" height="315" src="https://www.youtube.com/embed/aq0TLYXGQCA?si=kxvvfvgnRlzJ2OKk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## 斯坦福大学CS144计算机网络
这门课的主讲人之一是网络领域的巨擘 Nick McKeown 教授。这位拥有自己创业公司的学界业界双巨佬会在他慕课每一章节的最后采访一位业界的高管或者学界的高人,非常开阔眼界。
在这门课的 Project 中,你将用 C++ 循序渐进地搭建出整个 TCP/IP 协议栈,实现 IP 路由以及 ARP 协议,最后利用你自己的协议栈代替 Linux Kernel 的网络协议栈和其他学生的计算机进行通信,非常 amazing
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=460131849&bvid=BV1e5411c7aY&cid=325860309&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

@ -35,3 +35,11 @@ Rob teaches CS310, Data Structures in Java at San Diego State University. These
<iframe width="760" height="315" src="https://www.youtube.com/embed/videoseries?si=3cIw5iM67-rXJWij&amp;list=PLpPXw4zFa0uKKhaSz87IowJnOTzh9tiBk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## 普林斯顿大学: 算法
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=1102309518&bvid=BV1WA4m1N7v2&cid=1486798617&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
## LeetCode算法题解动画片
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=112609630096277&bvid=BV1GbGSenEDq&cid=500001580876679&p=3&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

@ -0,0 +1,9 @@
# 数据库
## Writing My Own Database From Scratch(Rust语言实现)
In this video I write my own SQL database from scratch without using any libraries or third party dependencies. Only the standard library provided by the chosen programming language is allowed (Rust in this case). The end result is a basic ACID compliant database that can process only one transaction at a time, something similar to the first versions of SQLite from the early 2000s, but with less features (and probably much worse performance).
The goal of these videos is to provide a high level overview of how such complicated systems work internally, not explaining all the code in detail as that would require dozens of hours of content.
<iframe width="760" height="315" src="https://www.youtube.com/embed/5Pc18ge9ohI?si=uw8LROzF8_xH-C8g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

@ -24,4 +24,10 @@ Udemy: https://www.udemy.com/course/multithreading-in-go-lang/
Udemy: https://www.udemy.com/course/working-with-concurrency-in-go-golang/
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=471642000&bvid=BV1TT411L7mt&cid=794192308&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=471642000&bvid=BV1TT411L7mt&cid=794192308&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
## Distributed File Storage In Go Full Course
Learn how to build a decentralized, fully distributed content-addressable file storage system using Go that can handle and stream very large files. This course covers system design, low-level programming, and network protocols, all while building a highly practical and scalable application.
<iframe width="760" height="315" src="https://www.youtube.com/embed/IoY6bE--A54?si=CyVdHaAGzWcKYyjp" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

@ -21,3 +21,7 @@ Pointers in C and C++ are often challenging to understand. In this course, they
## 【编程抽象方法 cs106x 2017】斯坦福—中英字幕
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=1955276817&bvid=BV1By411h75g&cid=1567773882&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
## C++ Development In Visual Studio Code - The perfect setup.
<iframe width="760" height="315" src="https://www.youtube.com/embed/videoseries?si=vbRbZemjrkNDmDUQ&amp;list=PLQMs5svASiXPpae_tifdn7rElAAJotCwm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

@ -69,4 +69,8 @@ https://www.youtube.com/playlist?list=PLbtzT1TYeoMhTPzyTZboW_j7TPAnjv9XB
## x86 Assembly with NASM
<iframe width="760" height="315" src="https://www.youtube.com/embed/videoseries?si=LQk0Cp8OZPVIfvka&amp;list=PL2EF13wm-hWCoj6tUBGUmrkJmH1972dBB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<iframe width="760" height="315" src="https://www.youtube.com/embed/videoseries?si=LQk0Cp8OZPVIfvka&amp;list=PL2EF13wm-hWCoj6tUBGUmrkJmH1972dBB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## UC伯克利CS162: 操作系统与系统编程
<iframe width="760" height="315" src="//player.bilibili.com/player.html?isOutside=true&aid=1901607247&bvid=BV18m411d7uz&cid=1466926268&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

@ -76,9 +76,14 @@ nav:
- Go:
- 视频: video/Go.md
- Rust: course/rust.md
- 数据库:
- 视频: video/Database.md
- 更多: course/more.md
- PPT:
- Go语言简介: assets/ppt/Golang简介.pdf
- CheatSheet:
- Makefile: cheatsheet/makefile.md
- GDB: cheatsheet/gdb.md
- QA:
- redis: qa/redis.md
- mysql: qa/mysql.md
@ -167,6 +172,14 @@ plugins:
- git-committers:
repository: cyub/dev-wiki
branch: main
- rss:
length: 500
use_git: true
feeds_filenames:
json_created: feed.json
json_updated: feed-updated.json
rss_created: feed.xml
rss_updated: feed-updated.xml
# Additional configuration
extra:
@ -179,10 +192,16 @@ extra:
social:
- icon: fontawesome/solid/blog
link: https://www.cyub.vip
name: 个人博客
- icon: fontawesome/brands/golang
link: https://go.cyub.vip
name: 深入Go语言之旅
- icon: fontawesome/brands/github
link: https://github.com/cyub
name: Github
- icon: material/rss
link: /feed.xml
name: Subscribe to our RSS Feed
extra_css:
- 'css/extra.css'

Loading…
Cancel
Save