0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何调试glibc?

Linux阅码场 来源:础光操作系统 2023-05-11 09:03 次阅读

背景

国科础石操作系统团队在开发础光智能操作系统的过程中,需要分析glibc启动过程中的异常信息,在此过程中探索出一条快速调试glibc流程的方法。

由于glibc启动代码复杂,printf、ptrace等辅助调试手段还不能正常使用,给分析过程带来困难。本文探索的方法避免了对printf、ptrace的依赖。

glibc 简介

glibc是Linux系统中常用的C运行时库,它是GNU项目的一部分,是一组函数和子例程的集合,为Linux操作系统上的C程序提供了基本的运行时支持。

glibc提供了Linux系统所需的底层功能和工具,包括内存管理、线程支持、网络编程、文件系统访问、数学计算、时间和日期处理、本地化支持等等。它还提供了标准的C库函数,如字符串操作、输入输出、数据结构操作等等。

glibc还提供了一些高级功能,例如动态内存管理、线程安全、多语言支持、安全性等等。它提供了一些重要的头文件和宏定义,例如stdio.h、stdlib.h、string.h、time.h等等。

glibc还提供了一些调试和性能分析工具,例如gdb调试器和strace系统调用跟踪器等。

总之,glibc是Linux系统中最重要的C运行时库之一,提供了许多基本和高级功能,为开发人员提供了强大的工具和支持,使得他们能够更加轻松地编写高质量、高效、可靠的C程序。

glibc是什么?

举个简单的例子来解释glibc大概做了什么 :

#include 


int sum (int a, int b) {
    return a + b;
}


int main (void) {
    int a = 35;
    int b = 24;
    printf("%d + %d = %d
", a, b, sum(a, b));
    return 0;
}

当我们编写一个c程序时,在 glibc 的帮助下会给我们一种错觉 : 当我们运行编译出来的二进制文件,操作系统直接运行到 main 函数,然后执行由提供的函数或我们自己编写的逻辑代码,在上述例子中,我们使用了libc提供的 "printf" 打印函数。我们自己编写了一个求和的逻辑代码。那么glibc真的就是提供一些函数接口的库么?

其实对于操作系统而言,它会都不"认识"main函数。而一个进程的执行也并非由 main 函数开始的。在链接时,链接器会设置函数入口,而该可执行程序入口不是 main。

[vizdl@localhost glibc_debug]# readelf -h build/crt.elf  
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0x400580
  Start of program headers:          64 (bytes into file)
  Start of section headers:          634584 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         28
  Section header string table index: 27

在这里我将上述代码编译链接后,使用 readelf -h 读取该可执行文件的头部信息,可以看到 "Entry point address: 0x400580",表明可执行程序的入口地址是 0x400580。

[vizdl@localhost glibc_debug]# readelf -s  build/crt.elf  | grep 400580
    29: 0000000000400580     0 NOTYPE  LOCAL  DEFAULT    6 $x
  2471: 0000000000400580    60 FUNC    GLOBAL HIDDEN     6 _start

我们通过 readelf -s 指令查看该二进制的符号表,可以看到, elf 执行的第一个"函数"是 _start,而不是 main。可执行文件执行到main函数之前,其实 glibc 偷偷加了一些代码。这部分代码笼统地讲其实就是做了一些进程环境设置的工作,让编写c代码的程序员可以避免每次都要编写重复的进程的环境设置!glibc真切地做到了做好事不留名:)但是今天我们提供一种方式,让大家都能看到glibc做的好事~

glibc 开发者如何调试 glibc?

在 glibc 中,一些地方调用c库函数会出现问题,特别是 _start -> main 这段代码,由于进程环境未初始化,导致大多数的 glibc 的函数运行的前提无法保证,于是绝大多数 glibc 的函数无法在这段代码内运行,这导致对glibc的观察可谓是困难重重,如何提供一种简单通用且可靠的调试方法一直是业界的难题。

我们在 glibc 入口函数找到了一些代码,并调用自定义函数dl_debug_printf来进行调试输出:

LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
         int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
         ElfW(auxv_t) *auxvec,
#endif
         __typeof (main) init,
         void (*fini) (void),
         void (*rtld_fini) (void), void *stack_end)
{
    ...
    if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
        GLRO(dl_debug_printf) ("
initialize program: %s

", argv[0]);
    ...
}

但是 dl_debug_printf 应该怎么用?它依赖什么?有什么限制?要深入分析会很麻烦,而且在使用中很大概率会因为不够了解其原理而导致遇到各种坑。我们何不另辟蹊径,自己制造出一种可靠的调试方式?

上述问题都能得以解决!

另辟蹊径

在 glibc 中添加一个调试函数 dbg_printf, 该调试函数依赖我们"新增"的系统调用,并且该系统调用仅仅通过 printk 打印的方式将传入的参数打印到 printk 环形缓冲区中。再通过 dmesg 来取数据。

如果真正地新增系统调用,则会导致需要重新编译内核,不够通用。我们采用了 tracepoint hook 点,依赖寄存器读取修改的方式,支持以驱动的方法实现一个系统调用。

本方法的要点在于:

(1) 新添加的dbg_printf不依赖于标准C库的任何系统调用,实现了一份完全干净的字符串格式化方法。

(2) 实现一个内核模块,在内核模块中 实现一个tracepoint hook,该 tracepoint hook会监控sys_enter事件,这样就可以拦截系统调用,而不必通过修改Linux源代码的方式,来扩展新的系统调用。

我们做了什么

该项目一共包含三个主体 : glibc, debug_printf 驱动, 一个简单的测试程序 test.c。

glibc

我们对glibc添加了一个补丁,该补丁在 make devel 时打到 glibc 源码中。

这个补丁添加了 dbg_printf 调试函数的实现

int
__dbg_printf (const char *fmt, ...)
{
    int ret = 0;
    int len = 0;
    char buf[buffsize];
    va_list ap;


    memset(buf, 0, buffsize);
    va_start(ap, fmt);
    len = dbg_vsnprintf(buf, buffsize, fmt, ap);
    buf[len] = 0;
    va_end(ap);
    ret = syscall_intface2(__NR_dbg, (long)buf, len + 1);


    return ret;
}


#undef _IO_printf
ldbl_strong_alias (__dbg_printf, dbg_printf)
ldbl_strong_alias (__dbg_printf, _IO_dbg_printf)

这个补丁调用 dbg_printf 调试函数,打印该进程收到的参数。

void print_args (int argc, char **argv) {
  int i;
  dbg_printf("argc : %d
", argc);
  for (i = 0; i < argc; i++) {
    dbg_printf("argv[%d] : %s
", i, argv[i]);
  }
}


LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
         int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
         ElfW(auxv_t) *auxvec,
#endif
         __typeof (main) init,
         void (*fini) (void),
         void (*rtld_fini) (void), void *stack_end)
{
  ...
  /* Perform IREL{,A} relocations.  */
  ARCH_SETUP_IREL ();


  /* print argc and argv */
  print_args(argc, argv);


  /* The stack guard goes into the TCB, so initialize it early.  */
  ARCH_SETUP_TLS ();
  ...
}

debug_printf 驱动

利用 tracepoint sys_enter hook 点,伪造一个不存在的系统调用。

test.c

一个普通的c程序,该程序会被链接到我们编译的glibc上,因此我们在 glibc 上的改动(打印参数),会在运行该程序时执行。

#include 


int main (void) {
    printf("Hello, glibcdbg
");
    return 0;
}

遇到的问题

我们在 glibc 中使用 dbg_printf 时调用 vsnprintf 与 syscall 函数时,居然出现了堆栈错误,后续将其换成了自己实现的 dbg_vsnprintf 和 syscall_intface2。

实验环境

glibc的编译与链接存在着许多坑,为避免读者再次趟坑,我们提供了docker编译环境,避免环境问题导致实验失败。

推荐实验环境

推荐使用 ubuntu 18.04 x86_64 架构环境。

vizdl@ubuntu:~/glibcdbg$ uname -a
Linux ubuntu 5.4.0-146-generic #163~18.04.1-Ubuntu SMP Mon Mar 20 1559 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

准备环境依赖

该项目需要依赖基本的编译工具

sudo apt install gcc make git -y

该项目依赖docker,所以第一步需要先安装docker(docker需要内核版本较高,最低内核版本 linux 3.10),如若已安装可跳过。

sudo curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

拉取项目

gitclonegit@gitee.com:kernelsoft/glibcdbg.git

构建编译环境 : 这步骤主要是下载glibc代码,打上我们的补丁以及构建 docker image。

make devel

编译 : 这步骤主要是编译驱动模块/测试小程序/glibc

make build

安装驱动 : 该步骤仅安装驱动模块

make install

运行测试案例并输出 : 运行测试小程序然后使用 dmesg 获取我们使用 printk 输出在内核的信息

make run

卸载驱动 : 该步骤仅卸载驱动模块

make uninstall

清理环境 : 恢复到初始项目状态。

make distclean





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux系统
    +关注

    关注

    4

    文章

    591

    浏览量

    27346
  • 调试器
    +关注

    关注

    1

    文章

    300

    浏览量

    23685
  • GNU
    GNU
    +关注

    关注

    0

    文章

    143

    浏览量

    17475
  • GDB调试
    +关注

    关注

    0

    文章

    24

    浏览量

    1437

原文标题:硬核:如何调试glibc

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    交叉编译glibc时候出错,寻求帮助

    各位好:自己在建立arm交叉編譯工具鏈的過程中,交叉編譯glibc的時候出現了如下錯誤:/opt/gcc-arm/lib/gcc/arm-linux-gnueabi/4.6.4
    发表于 07-18 12:09

    影响 Linux 系统安全基石的 glibc 严重漏洞

    编者按:这个消息是几个月前曝出的,也许我们该对基础软件的安全问题更加重视。谷歌披露的一个严重漏洞影响到了主流的 Linux 发行版。glibc 的漏洞可能导致远程代码执行。几个月前,Linux 用户
    发表于 06-25 10:01

    导入tensorflow时未找到“GLIBC_2.23”错误

    导入tensorflow时,它给我一个错误。附上错误的屏幕截图。请帮忙。GLIBC_Error.PNG 40.9 K.以上来自于谷歌翻译以下为原文Hi, I created a new conda
    发表于 11-14 09:59

    libc、glibc和glib有什么关系

    libc、glibc和glib的关系
    发表于 04-14 11:44

    【源码】arm-linux-gcc-3.4.5-glibc-2.3.6.tar

    arm-linux-gcc-3.4.5-glibc-2.3.6.tar程序源码回复帖子查看资料下载链接:[hide][/hide]
    发表于 08-18 10:33

    0K335XD的GLIBC建议升级一下

    0K335XD的GLIBC2.12.2的,但QT5要求的版本较高,建议官方升级一下
    发表于 01-12 06:13

    Yocto Bitbake Glibc构建失败了怎么解决?

    我正在尝试构建 Yocto 映像,但在编译 glibc 时构建失败。我运行了这个命令:bitbake 精简版图像我收到如下编译错误: | /media/ubu/LocalDisk
    发表于 03-24 08:01

    全志Tina Linux下如何编译glibc

    / make工具 注意由于AW服务器make版本为3.8.1,在编译glibc高版本时候不兼容,所以需要更新make工具。假如服务器make版本较高,可以不用更新make工具。 网址 http
    发表于 06-02 10:00

    使用全志方案遇到glibc库版本低以及编译报错的解决方法

    Glibc 包含了linux一些主要的C库,用于分配内存、搜索目录、打开关闭文件、读写文件、字串处理、模式匹配、数学计算等,在遇到glibc库版本低编译还报错的情况时,遵循以下
    发表于 06-25 09:48

    在soc平台按照bmnnsdk2开发手册设置环境后出现glibc版本问题如何解决?

    python报出下列错误: vi: /system/usr/lib/aarch64-linux-gnu/libm.so.6: version `GLIBC_2.29\' not found
    发表于 09-18 06:29

    glibc内存管理存在的共性问题及解决方法

    引言 对于嵌入式设备来说,用户态内存管理是一项基础功能,目前主流的用户态内存管理库有glibc、uclibc、tcmalloc、jemalloc等。 本文基于glibc2.17版本进行分析,围绕
    的头像 发表于 06-18 14:50 3148次阅读

    Glibc内存管理之Ptmalloc2源代码分析

    Glibc内存管理之Ptmalloc2源代码分析
    发表于 07-29 09:20 24次下载

    glibc导致的堆外内存泄露的排查过程

    本文记录一次glibc导致的堆外内存泄露的排查过程。
    的头像 发表于 09-01 09:43 676次阅读
    <b class='flag-5'>glibc</b>导致的堆外内存泄露的排查过程

    如何使用tcmalloc来替换glibc的malloc

    代码中使用tcmalloc替换malloc 我们如何使用tcmalloc来替换glibc的malloc呢? 在链接tcmalloc的时候我们可以使用以下任意一种方式: 1.启动程序之前,预先加载
    的头像 发表于 11-11 16:52 2024次阅读
    如何使用tcmalloc来替换<b class='flag-5'>glibc</b>的malloc

    glibc的内存分配回收策略

    。malloc、free均发生在这个区域。本文将简单介绍下glibc在动态内存管理方面的机制,抛砖引玉,希望能和大家
    的头像 发表于 11-13 11:16 634次阅读
    <b class='flag-5'>glibc</b>的内存分配回收策略