浅谈GNU LIBC的版本间的变化

多线程调试的意外发现

昨天笔者在协助朋友调试一个多线程文件传输的应用时(传输代码不依赖开源库),发现会多次打开同一个文件。这样产生的一个结果是文件描述符泄露,应用运行一段时间后,就不能再创建新的文件描述符了。因不了解代码,笔者在目标设备上安装了eBPF调试工具bpftrace,之后使用以下脚本调试该应用,查看该应用打开文件的调用栈:

#!/usr/bin/bpftraceuprobe:/lib/x86_64-linux-gnu/libc-2.27.so:openat,
uprobe:/lib/x86_64-linux-gnu/libc-2.27.so:open / pid == $1 / {printf("[GLIBC6] PID: %d, comm: %s, open(%s)", pid, comm, str(arg0));print(ustack);
}

注意,目标设备使用的glibc动态库为ARM64平台的libc-2.27.so,这里笔者把问题简化,在Ubuntu-18.04系统上复现了该问题。因目标设备上的glibc动态库不带有调试信息,于是笔者没有使用bpftrace的Syscall-Tracepoint调试方法,因为这样可能不能获得正确的应用调用栈回溯。笔者编写了一个简单的演示代码,稍后会贴出相应代码。bpftrace期望的结果为:

root@vmware:~/trace# bpftrace ./glibc-open.bt 5534
Attaching 2 probes...
[GLIBC6] PID: 5534, comm: test-open, open(/proc/uptime)__open64+0open_test_l1+9open_test_l2+9open_test_l3+10main+187__libc_start_main+2310x2ee258d4c544155

但始终没得到以上结果;bpftrace输出的结果为空。以上为笔者把演示应用中的多线程功能禁用后得到的调试结果;这说明多线程干扰了我们的调试过程。

带有重复符号表的libpthread.so动态库

在笔者之前动态链接器的分析文章中提到,动态链接器/lib64/ld-linux-x86-64.so.2也提供了malloc/free等标准函数的符号:

root@vmware:~/trace# nm -D --defined-only /lib64/ld-linux-x86-64.so.2 | grep -e malloc -e free
0000000000017df0 T _dl_exception_free
000000000001b800 W free
000000000001b690 W malloc
root@vmware:~/trace# nm -D --defined-only /lib/x86_64-linux-gnu/libc.so.6 | grep -e malloc -e free
0000000000097910 T cfree
0000000000097910 T free
0000000000108820 T freeaddrinfo
...
0000000000097020 T malloc

于是笔者很容易地联想到,是不是多线程库libpthread也提供了一些open之类系统调用?以下结果可以确认:

root@vmware:~/trace# nm -D --defined-only /lib/x86_64-linux-gnu/libpthread.so.0 | grep -e open -e write
0000000000011dd0 W open
0000000000011dd0 T __open
0000000000011dd0 W open64
0000000000011dd0 T __open64
00000000000120f0 W pwrite
00000000000120f0 W pwrite64

由此可以确认,文件传输应用因存在多线程库的依赖,调用到了libpthread.so动态库中的open系统调用函数。那么笔者改进的bpftrace脚本可以检测到文件的打开:

#!/usr/bin/bpftraceuprobe:/lib/x86_64-linux-gnu/libc-2.27.so:openat,
uprobe:/lib/x86_64-linux-gnu/libc-2.27.so:open / pid == $1 / {printf("[GLIBC6] PID: %d, comm: %s, open(%s)", pid, comm, str(arg0));print(ustack);
}uprobe:/lib/x86_64-linux-gnu/libpthread-2.27.so:open / pid == $1 / {printf("[THREAD] PID: %d, comm: %s, open(%s)", pid, comm, str(arg0));print(ustack);
}

调试结果如下:

root@vmware:~/trace# bpftrace open.bt 5591
Attaching 3 probes...
[THREAD] PID: 5591, comm: test-open.threa, open(/proc/uptime)open+0open_test_l1+9open_test_l2+9open_test_l3+10main+220__libc_start_main+2310x1226258d4c544155

新版本glibc库的变化

以上调试结果笔者使用的系统为Ubuntu-22.04,始终未能复现该问题。最终确认新系统的libpthread.so库不存在重复的符号导出:

yejq@ubuntu:~$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.8) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
yejq@ubuntu:~$ nm -D --defined-only /lib/x86_64-linux-gnu/libpthread.so.0 | grep -e open -e write
yejq@ubuntu:~$

从此可以看出不同版本的glibc确实出现了一些变化;实际上,新版本glibc中的libpthread.so库仅是一个空壳,相关的线程操作相关函数已由libc.so库提供:

root@ubuntu:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.4 LTS
Release:        22.04
Codename:       jammy
root@ubuntu:~# nm -D --defined-only /lib/x86_64-linux-gnu/libpthread.so.0 | grep -e pthread_create
root@ubuntu:~# nm -D --defined-only /lib/x86_64-linux-gnu/libc.so.6 | grep -e pthread_create
0000000000094c40 T pthread_create@GLIBC_2.2.5
0000000000094c40 T pthread_create@@GLIBC_2.34

此外,笔者还注意到,动态链接库libdl中的函数也同样由libc.so.6提供了:

root@ubuntu:~# nm -D --defined-only /lib/x86_64-linux-gnu/libdl.so.2
0000000000000000 A GLIBC_2.2.5
0000000000000000 A GLIBC_2.3.3
0000000000000000 A GLIBC_2.3.4
0000000000001100 T __libdl_version_placeholder@GLIBC_2.2.5
0000000000001100 T __libdl_version_placeholder@GLIBC_2.3.4
0000000000001100 T __libdl_version_placeholder@GLIBC_2.3.3
root@ubuntu:~# nm -D --defined-only /lib/x86_64-linux-gnu/libc.so.6 | grep -e dlopen -e dlclose
000000000008fe30 T dlclose@GLIBC_2.2.5
000000000008fe30 T dlclose@@GLIBC_2.34
0000000000090680 T dlopen@GLIBC_2.2.5
0000000000090680 T dlopen@@GLIBC_2.34

GLIBC的线程库变化

笔者通过查看glibc的代码仓库历史发现,libpthread库提供的API的大量移动自2021年1月份开始,后续开发人员几个月的陆续修改渐渐把libpthread库提供的函数移动到libc.so动态库中。存在变动的glibc版本从2.34开始:

commit 7384193b71a1720a381b7150ed44e07b13af45d5
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date:   Tue Jan 19 09:18:46 2021 -0300nptl: Move fork into libcThis is part of the libpthread removal project:<https://sourceware.org/ml/libc-alpha/2019-10/msg00080.html>Checked on x86_64-linux-gnu.

上面的链接有详细的讨论;在2019年研发人员的在邮件中有这么一句话:

We only need one implementation.  The indirection from libc to libpthread is completely unnecessary.

了解Linux内核及对线程的支持的人可能知道,早期的多线程支持是由LinuxThread实现的,后来POSIX实义了通了的多线程调用接口POSIX Thread,于是就存在两个不同的动态库提供线程的接口API:

       Over time, two threading implementations have been provided bythe GNU C library on Linux:LinuxThreadsThis is the original Pthreads implementation.  Since glibc2.4, this implementation is no longer supported.NPTL (Native POSIX Threads Library)This is the modern Pthreads implementation.  By comparisonwith LinuxThreads, NPTL provides closer conformance to therequirements of the POSIX.1 specification and betterperformance when creating large numbers of threads.  NPTLis available since glibc 2.3.2, and requires features thatare present in the Linux 2.6 kernel。

但随着Posix Thread的标准化及广泛应用,glibc库中逐渐去掉了LinuxThread的相关代码;但NPTL的实现仍保留在单独的动态库libpthread中。而现在最新的glibc库提供的libpthread库仅是一个placeholder。其一个好处是,简化C/C++代码的链接,可以不再加入-lpthread链接选项,从而可避免很多的链接报错。

笔者在系统开发过程中也注意到,使用clock_gettime系统调用,早期的glibc要求可执行文件链接到librt.so库,而最近几年遇到的开发环境已不再有这个链接选项的要求。这些确实都是开源社区缓慢而又可见的改进。

相关调试代码

笔者为了追踪这个问题,编写了一个简单的演示代码open-test.c。通过一个宏可以禁用是否使用libpthread提供的函数;两次分别的编译操作如下:

gcc -Wall -fPIC -DHAVE_PTHREAD_H=0 -O1 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -o test-open open-test.c
gcc -Wall -fPIC -DHAVE_PTHREAD_H=1 -O1 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -o test-open.thread open-test.c -lpthread

最后,笔者编写的演示代码open-test.c内容如下:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>#ifndef HAVE_PTHREAD_H
#define HAVE_PTHREAD_H 0
#endif#if HAVE_PTHREAD_H
#include <pthread.h>
#endif#define _NO_INL_ __attribute__((__noinline__))
static int open_test_l0(const char * arg) _NO_INL_;
static int open_test_l1(const char * arg) _NO_INL_;
static int open_test_l2(const char * arg) _NO_INL_;
static int open_test_l3(const char * arg) _NO_INL_;int main(int argc, char *argv[])
{int i, ret;fprintf(stdout, "PID: %ld\n", (long) getpid());
#if HAVE_PTHREAD_Hfprintf(stdout, "Current pthread ID: %ld\n", (long) pthread_self());
#endiffprintf(stdout, "Press any key to continue...\n");fflush(stdout);(void) getchar();for (i = 1; i < argc; ++i) {const char *argp;argp = argv[i];if (argp == NULL || argp[0] == '\0') {fprintf(stderr, "Error, invalid argument at %d\n", i);fflush(stderr);}ret = open_test_l3(argp);fprintf(stderr, "open(%s) has returned: %d\n", argp, ret);fflush(stderr);}fprintf(stdout, "Press any key to exit...\n");fflush(stdout);(void) getchar();return 0;
}int open_test_l0(const char * arg)
{int fd;fd = open(arg, O_RDONLY);if (fd >= 0)close(fd);return fd;
}int open_test_l1(const char * arg)
{int ret;ret = open_test_l0(arg);return ret;
}int open_test_l2(const char * arg)
{return open_test_l1(arg);
}int open_test_l3(const char * arg)
{return open_test_l2(arg);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/25898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis映射器:实现动态SQL语句

大家好&#xff0c;我是王有志&#xff0c;一个分享硬核 Java 技术的金融摸鱼侠&#xff0c;欢迎大家加入 Java 人自己的交流群“共同富裕的 Java 人”。 上一篇文章中&#xff0c;我们已经学习了如何在 MyBatis 的映射器中通过简单的 SQL 语句实现增删改查&#xff0c;今天我…

vue组件之间的通信方式有哪些

在开发过程中&#xff0c;数据传输是一个核心的知识点&#xff0c;掌握了数据传输&#xff0c;相当于掌握了80%的内容。 Vue.js 提供了多种组件间的通信方式&#xff0c;这些方式适应不同的场景和需求。下面是4种常见的通信方式&#xff1a; 1. Props & Events (父子组件通…

Alsa UCM

Alsa Use Case Manager&#xff08;用例管理器&#xff09;描述如何为某些用例&#xff08;如 “播放音频”、“通话”&#xff09;设置 mixer 混频器。它还描述如何修改 mixer 混频器状态以将音频路由到某些输出和输入&#xff0c;以及如何控制这些设备。 这基本上涵盖了 Pul…

1688商品库存查询

目录 下载安装与运行 功能简介 快速入门&#xff08;视频&#xff09; 当前支持的导出项 常用功能 历史商品是什么意思 粘贴商品有什么要求 导入商品需要什么样的模板 单个商品的查看 查看单个商品详情 下载安装与运行 下载、安装与运行 语雀 功能简介 最近一次测…

自下而上语法分析、自上而下语法分析和递归下降法、预测分析法、LL(1)和LR是什么关系

自下而上语法分析、自上而下语法分析、递归下降法、预测分析法、LL(1)和LR都是与语法分析&#xff08;语法解析&#xff09;相关的概念和技术。它们在编译原理中扮演着重要的角色&#xff0c;用于将源代码的字符流转换为语法树&#xff08;或抽象语法树&#xff0c;AST&#xf…

逆序队专题

逆序对的定义是&#xff0c;在一个数组中&#xff0c;对于下标 ( i ) 和 ( j )&#xff08;其中 ( i < j )&#xff09;&#xff0c;如果 ( a[i] > a[j] )&#xff0c;则称 ((a[i], a[j])) 为数组的一个逆序对。 换句话说&#xff0c;逆序对就是在数组中前面的元素大于后…

C++使用Sanp7 实现西门子s7通信

使用Snap7库可以实现与西门子S7系列PLC的通信。以下是一个基本的C示例&#xff0c;演示如何使用Snap7库来连接西门子S7 PLC并读取和写入数据。 环境准备 安装Snap7&#xff1a;从&#xff08;https://sourceforge.net/projects/snap7/files/1.4.2/&#xff09;Snap7官网下载并…

C++算法——埃氏筛

C判断素数&#xff1a;埃氏筛 思路 这个算法是利用打表的方法来计算的&#xff1a; 首先&#xff0c;我们要知道一个特性 就是一个质数的倍数&#xff0c;一定是一个合数 利用这个特性 我们可以写出以下代码 for (int i 2; i * i < n; i) {if (!prime[i] true){for (i…

每日两题7

文章目录 买卖股票的最佳时机含冷冻期买卖股票的最佳时机含手续费 买卖股票的最佳时机含冷冻期 分析&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int n prices.size();vector<vector<int>> dp(n, vector<int>(3…

Python爬取城市空气质量数据并写入mysql数据库

Python爬取城市空气质量数据并写入mysql数据库 这篇文章介绍了如何爬取城市空气质量数据,下面的代码添加了数据库操作,包括使用pymysql创建数据库和数据表,并向数据库中写入数据,完整代码如下: import csv import time import requests # 导入网络请求库requests from bs4…

python为什么要字符串格式化

Python2.6 开始&#xff0c;新增了一种格式化字符串的函数 str.format()&#xff0c;它增强了字符串格式化的功能。相对于老版的%格式方法&#xff0c;它有很多优点。 1.在%方法中%s只能替代字符串类型&#xff0c;而在format中不需要理会数据类型&#xff1b; 2.单个参数可以…

Qt 简易Word

Ui界面如下&#xff1a; 查找和替换界面&#xff1a; 具体代码&#xff1a; GitHub : 简易Word Gitee : 简易Word

【java11】java11新特性之Predicate接口API改进

Java 11向Predicate接口引入了新方法not()来否定类似于negate方法的现有谓词&#xff0c;进一步增强了其功能&#xff0c;使得条件判断和组合更加灵活和方便。 Predicate.not() Predicate.not方法用于创建一个取反的谓词&#xff08;predicate&#xff09;&#xff0c;即返回…

FedAvg论文

论文&#xff1a;Communication-Efficient Learning of Deep Networks from Decentralized Data 原code Reproducing 通过阅读帖子进行的了解。 联邦平均算法就是最典型的平均算法之一。将每个客户端上的本地随机梯度下降和执行模型的平均服务器结合在一起。 联邦优化问题 数…

文案策划背后的秘密 | 职场高手养成记

要想在文案策划这个行当里混&#xff0c;首先得对自己的文字功底有足够的信心&#xff0c;那种“文章独步天下”的气势不可或缺。 要是没有这份自信&#xff0c;我建议你还是另寻他路。 要想跨入文案策划的大门&#xff0c;可以从以下几个方面入手&#xff1a; 1. 学习文案基…

6.7.13 MV-Swin-T:使用多视图 SWIN 变压器进行乳房 X 光检查分类

传统的乳腺癌分类深度学习方法主要集中在单视图分析上。然而,在临床实践中,放射科医生会同时检查乳房 X 线摄影检查中的所有视图,利用这些视图中固有的相关性来有效检测肿瘤。 在本文中,我们提出了一种完全基于 Transformer 的创新多视图网络,以解决乳房 X 线摄影图像分类…

使用docker-compose搭建达梦数据库主备集群

目录 1. Docker集群的搭建 2. 检查主备数据库 3. 主备集群的JDBC连接设置 1. Docker集群的搭建 达梦的镜像文件都是tar文件&#xff0c;通过docker load命令导入&#xff1a; docker load -i dm8_20240422_x86_rh6_64_rq_ent_8.1.3.140.tar 成功导入后&#xff0c;可看到…

Flutter娱乐与休闲类APP常用的第三方库总汇

Flutter娱乐与休闲类APP常用的第三方库总汇 娱乐与休闲类APP为用户提供了丰富的放松和娱乐方式&#xff0c;包括游戏、音乐、视频、阅读等多种形式。Flutter作为一个高效的跨平台移动应用开发框架&#xff0c;为这类应用的开发提供了强大的支持。本文将汇总Flutter娱乐与休闲类…

机器学习笔记——支持向量机

支持向量机 参数模型对分布需要假设&#xff08;这也是与非参数模型的区别之一&#xff09;间隔最大化&#xff0c;形式转化为凸二次规划问题 最大化间隔 间隔最大化是意思&#xff1a;对训练集有着充分大的确信度来分类训练数据&#xff0c;最难以分的点也有足够大的信度将…

68. UE5 RPG 处理多个角色后续bug

我们现在已经有了四个敌人角色&#xff0c;接下来&#xff0c;处理一下在战斗中遇到的问题。 处理角色死亡后还会攻击的问题 因为我们有角色溶解的效果&#xff0c;角色在死亡以后的5秒钟才会被销毁掉。所以在这五秒钟之内&#xff0c;角色其实还是会攻击。主要时因为AI行为树…