GCC&Gdb基础教程

GCC编译

GCC 介绍

GCC(GNU Compiler Collection)是由GNU开发的编程语言编译器。最初为GNU操作系统开发,现在已被多数类Unix系统(如Linux、BSD、MacOS X等)采用,甚至在Windows上也可以使用。

GCC 编译流程

  1. 预处理:生成 .i 文件

    gcc -o hello.i -E hello.c
  2. 编译:将预处理后的文件转换为汇编语言,生成 .s 文件

    gcc -o hello.s -S hello.i
  3. 汇编:将汇编代码转换为目标代码(机器代码),生成 .o 文件

    gcc -o hello.o -c hello.s
  4. 链接:将目标代码链接生成可执行程序

    gcc -o hello hello.o

GCC 参数

  • -o:指定目标文件名称

    gcc -o output file.c
  • -S:仅生成汇编语言文件

    gcc -S file.c
  • -c:仅编译为目标文件,不进行链接

    gcc -c file.c
  • -E:仅进行预处理

    gcc -E file.c -o file.i
  • -I[dirname]:将 dirname 加入到包含文件的搜索目录列表中

    gcc -I /usr/include file.c
  • -L[dirname]:将 dirname 加入到库文件的搜索目录列表中

    gcc -L /usr/lib file.c
  • -l[library]:链接时使用指定的库文件

    gcc -L /usr/lib -lcurses file.c
  • -O0, -O1, -O2, -O3:设置优化级别,-O0 表示无优化,-O3 表示最高优化级别

    gcc -O3 -o output file.c
  • -w:忽略警告信息

  • -Wall:显示所有警告信息

  • -Werror:将所有警告视为错误

  • -g:生成调试信息

  • -M:生成依赖关系信息

    gcc -M file.c
  • -MM:生成依赖关系,但忽略标准头文件

    gcc -MM file.c
  • -MD-MMD:生成 .d 文件,包含依赖关系

    gcc -MD file.c
    gcc -MMD file.c
  • -MF File:指定依赖文件名称

    gcc -M -MF dependencies.d file.c
  • -D macro:定义宏

    gcc -DYES -o output file.c
  • -U macro:取消宏定义

    gcc -DYES -UYES -o output file.c

静态库与动态库

静态库:以空间换时间,增加代码量,减少运行时间。静态库文件扩展名一般为 .a

动态库:以时间换空间,增加运行时间,减少代码量。动态库文件扩展名一般为 .so

// myalib.h
void test();
// myalib.c
#include<stdio.h>
void test()
{
printf ("test\n");
}
// main
#include "myalib.h"
int main(int argc,char*argu[])
{
test();
return 0;
}

创建静态库

  1. 编译生成目标文件

    gcc -c myalib.c
  2. 使用 ar 命令归档目标文件

    ar -rc libtest.a myalib.o
  3. 使用库文件测试程序main.c

    gcc -I ./ -o main.o -c main.c
    gcc -o main -L ./ main.o -ltest

创建动态库

  1. 编译生成动态库

    gcc -shared -fPIC -o libtest.so mylib.c
  2. 编译并链接使用动态库的程序

    gcc -o main main.c -L ./ -ltest
  3. 使用 ldd 命令检查动态库

    ldd main
  4. 解决动态库问题

    • 将库文件拷贝到系统库目录(如 /lib)。
    • 修改系统库搜索路径,通过配置 /etc/ld.so.conf 或在 /etc/ld.so.conf.d/ 中添加配置文件,运行 ldconfig 更新配置。

Gdb调试

什么是 gdb

gdb 是 GNU 开源组织发布的一个强大的 Unix/Linux 下的程序调试工具。它可以帮助用户调试程序,检查程序运行中的状态,并动态改变程序的执行环境。

gdb 说明文档位置gdb 在线文档

gdb 的作用

  • 启动用户程序:可以按照用户的要求随意运行程序。

  • 设置断点:可让被调试的程序在用户设定的断点处停住。

  • 检查程序状态:程序被停住时,可以检查当前程序中的状态。

  • 动态改变执行环境:可以动态修改程序的执行环境。

gdb调试core

查看程序是否带有调试信息:

readelf -S <executable> | grep debug

生成Core文件方法:

ulimit -c unlimited
echo "/data/coredump/core.%e.%p" > /proc/sys/kernel/core_pattern

编译包含调试信息的可执行文件:使用 -g 选项编译源代码,以确保生成的可执行文件包含调试信息。

gcc -g main.c -o test

调试Core文件:

gdb <executable> <core_file>

gdb 常用命令

启动GDB:

gdb <executable>

载入被调试程序:

file <executable>

查看源码:

  • 列出指定行号的代码:

    list <line_number>,<line_number>
  • 列出指定文件的源码:

    list <filename>:<line_number>
  • 列出指定函数的源码:

    list <function_name>

运行程序:

run [arguments]

设置断点:

  • 通过行号设置断点:

    break <filename>:<line_number>
  • 通过函数名设置断点:

    break <function_name>
  • 设置条件断点:

    break <line_number> if <condition>

查看断点:

info breakpoints

删除断点:

delete <breakpoint_number>

删除所有断点:

delete

禁用/启用所有断点:

disable <breakpoint_number>
enable <breakpoint_number>

单步调试:

  • 单步进入(进入函数内部):

    step
  • 单步执行(不进入函数内部):

    next

继续执行到下一个断点:

continue

查看变量:

print <variable_name>

设置变量:

set variable <variable_name> = <value>

设置观察点:

watch <variable_name>

查看函数调用栈:

backtrace

分屏调试:

gdb -tui <executable>
  • 上半屏显示代码,下半屏显示 GDB 交互界面。

  • 使用方向键调整源代码视图。

  • 使用 fs nfocus next 切换焦点。

Gdb调试死锁

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int g_tickets = 100;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_proc1(void* arg) {
while (1) {
pthread_mutex_lock(&g_mutex);
if (g_tickets > 0)
printf("thread 1 sell tickets:%d\n", g_tickets--);
else {
pthread_mutex_lock(&g_mutex); // 错误地再次加锁
break;
}
pthread_mutex_lock(&g_mutex); // 错误地再次加锁
}
return (void*)1;
}
void* thread_proc2(void* arg) {
while (1) {
pthread_mutex_lock(&g_mutex);
if (g_tickets > 0)
printf("thread 2 sell tickets:%d\n", g_tickets--);
else {
pthread_mutex_unlock(&g_mutex); // 正确的解锁
break;
}
pthread_mutex_unlock(&g_mutex); // 正确的解锁
}
pthread_exit((void*)2);
}
int main(int argc, char*argv[]) {
pthread_t tid1, tid2;
void *ret1, *ret2;
pthread_create(&tid1, NULL, thread_proc1, NULL);
pthread_create(&tid2, NULL, thread_proc2, NULL);
pthread_join(tid1, &ret1);
pthread_join(tid2, &ret2);
printf("ret1:%d\n",(int)ret1);
printf("ret2:%d\n",(int)ret2);
return 0;
}

编译运行

  1. 编译代码:

    gcc -g test.c -lpthread -o test
  2. 运行程序:

    ./test
  3. 输出结果: 程序输出"thread 1 sell tickets:…"直到死锁发生。

调试方法1

  1. 查看test进程号:

    ps aux | grep test
  2. 查看进程中的所有线程:

    pstree -p <process_id>
  3. 使用GDB调试:

    • 启动GDB:

      gdb
    • 附加到进程:

      attach <process_id>
    • 查看所有线程的堆栈跟踪:

      thread apply all bt

调试方法2

  1. 查看test进程号:

    ps -e | grep test
  2. 使用GDB调试:

    • 启动GDB并附加到进程:

      gdb test <process_id>
    • 显示所有线程信息:

      info threads
    • 切换到特定线程(例如第二个线程):

      thread 2
    • 查看该线程的堆栈跟踪,以确定死锁位置:

      bt

Reference

[1] GDB调试指南(入门,看这篇够了): https://blog.csdn.net/chen1415886044/article/details/105094688

[2] GDB:调试死锁:https://blog.csdn.net/guowenyan001/article/details/46238355

------------------------------- 本文结束啦❤感谢您阅读-------------------------------
赞赏一杯咖啡