记一次pthread_key_create导致的__nptl_deallocate_tsd段错误

2023-10-14 06:48

本文主要是介绍记一次pthread_key_create导致的__nptl_deallocate_tsd段错误,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

__nptl_deallocate_tsd

rtoax
2021年5月25日

记一次由于pthread_key_create导致的__nptl_deallocate_tsd

  • 版本:glibc-2.17
  • 完整示例代码

1. 简介

#include <pthread.h>int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

其中destructor为析构函数,它将在__nptl_deallocate_tsd中被调用。

2. Coredump:__nptl_deallocate_tsd

Thread 2 "a.out" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff77f0700 (LWP 140173)]
__GI___libc_free (mem=0x1) at malloc.c:2941
2941	  if (chunk_is_mmapped(p))                       /* release mmapped memory. */
(gdb) 
(gdb) bt
#0  __GI___libc_free (mem=0x1) at malloc.c:2941
#1  0x00007ffff7bc6c62 in __nptl_deallocate_tsd () at pthread_create.c:155
#2  0x00007ffff7bc6e73 in start_thread (arg=0x7ffff77f0700) at pthread_create.c:314
#3  0x00007ffff78ef88d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

为什么会这样?为什么会执行析构函数。一步一步分析。

首先创建key,这里我们挂入malloc对应的free,因为我们将为每个线程的key使用malloc分配内存:

pthread_key_create(&key, free);

然后创建线程:

pthread_create(&thid1, NULL, thread1, NULL);

在thread1中申请内存并将其设置为该线程的TLS的key值:

int *key_va = malloc(sizeof(int)) ;
*key_va = 2;
pthread_setspecific(key, (void*)key_va);

当线程thread1执行结束后,主线程调用pthread_join回收线程,这时候析构函数将被执行:

pthread_join(thid1, NULL);

这里我在调研过程中,有些文章也讲到,由于key的malloc对应的析构函数被设置为NULL,导致内存泄漏。

当key使用完毕后进行删除:

pthread_key_delete(key);

示例代码见完整示例代码或者文末章节。

那么段错误如何产生呢?

我们还是使用free填充析构函数:

pthread_key_create(&key, free);

但是我将pthread_setspecific入参的value填写问静态变量值:

int key_va = 2;
pthread_setspecific(key, (void*)key_va);

没错,这时候在运行程序并用gdb调试,就会产生文章开头描述的段错误。

3. 不显示调用pthread_key_create的段错误

不显示调用pthread_key_create的程序也可能出错,比如说文末章节的实例sane.c.

不能对开源软件有过高的要求,有bug大家一起解决。

void* scan_thread(void *arg) {SANE_Status status;status = sane_init(NULL, NULL);assert(status == SANE_STATUS_GOOD);const SANE_Device** device_list = NULL;status = sane_get_devices(&device_list, false);assert(status == SANE_STATUS_GOOD);int i;for(i = 0; device_list[i] != NULL; ++i){printf("%s\n", device_list[i]->name);}sane_exit();
}

gdb运行程序,并设置断点:

(gdb) b pthread_key_create
Function "pthread_key_create" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (pthread_key_create) pending.

到达断点:

Thread 2 "test.c.out" hit Breakpoint 1, __GI___pthread_key_create (key=key@entry=0x7ffff75c20c0 <key>, destr=destr@entry=0x7ffff73c01f0 <free_key_mem>) at pthread_key_create.c:26
26	{

继续执行,产生段错误:

(gdb) c
Continuing.Thread 2 "test.c.out" hit Breakpoint 1, __GI___pthread_key_create (key=0x7fffe4eb6dc8, destr=0x7fffe4c77600 <cups_globals_free>) at pthread_key_create.c:26
26	{
(gdb) c
Continuing.Thread 2 "test.c.out" received signal SIGSEGV, Segmentation fault.
0x00007fffe4c77600 in ?? ()
(gdb) bt
#0  0x00007fffe4c77600 in ?? ()
#1  0x00007ffff7998c62 in __nptl_deallocate_tsd () at pthread_create.c:155
#2  0x00007ffff7998e73 in start_thread (arg=0x7ffff4168700) at pthread_create.c:314
#3  0x00007ffff76c188d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111
(gdb) 

这种问题如何解决呢?

  • 去除sane_exit();,但是可能面临内存泄漏的危险;
  • TODO:走读sane-backends源码,提交patch;

4. 示例代码

4.1. main.c

#include <pthread.h>
#include <stdio.h>
#include <malloc.h>pthread_key_t key;
pthread_t thid1;
pthread_t thid2;#ifndef MALLOC_KEY
#define MALLOC_KEY  0
#endifvoid* thread2(void* arg)
{printf("thread:%lu is running\n", pthread_self());
#if MALLOC_KEY    int *key_va = malloc(sizeof(int)) ;*key_va = 2;
#elseint key_va = 2;
#endifpthread_setspecific(key, (void*)key_va);printf("thread:%lu return %d\n", pthread_self(), (int)pthread_getspecific(key));
}void* thread1(void* arg)
{printf("thread:%lu is running\n", pthread_self());#if MALLOC_KEY    int *key_va = malloc(sizeof(int)) ;*key_va = 1;
#elseint key_va = 1;
#endifpthread_setspecific(key, (void*)key_va);pthread_create(&thid2, NULL, thread2, NULL);printf("thread:%lu return %d\n", pthread_self(), (int)pthread_getspecific(key));
}int main()
{printf("main thread:%lu is running\n", pthread_self());//如果 pthread_setspecific 传入的是局部变量,//并且 pthread_key_create 传入了析构函数,//那么将产生如下段错误//#0  __GI___libc_free (mem=0x5) at malloc.c:2941//#1  0x00007feb4c550c62 in __nptl_deallocate_tsd () at pthread_create.c:155//#2  0x00007feb4c550e73 in start_thread (arg=0x7feb4c17a700) at pthread_create.c:314//#3  0x00007feb4c27988d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111pthread_key_create(&key, free); //这里会段错误 pthread_create(&thid1, NULL, thread1, NULL);pthread_join(thid1, NULL);pthread_join(thid2, NULL);#if MALLOC_KEY    int *key_va = malloc(sizeof(int)) ;*key_va = 2;
#elseint key_va = 2;
#endifpthread_setspecific(key, (void*)key_va);printf("thread:%lu return %d\n", pthread_self(), (int)pthread_getspecific(key));pthread_key_delete(key);printf("main thread exit\n");return 0;
}

4.2. sane.c

此示例参见https://bugzilla.redhat.com/show_bug.cgi?id=1065695。

#include <assert.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <malloc.h>
#include <sane/sane.h>#define PTHREAD_STACK_MIN	16384void* scan_thread(void *arg) {SANE_Status status;status = sane_init(NULL, NULL);assert(status == SANE_STATUS_GOOD);const SANE_Device** device_list = NULL;status = sane_get_devices(&device_list, false);assert(status == SANE_STATUS_GOOD);int i;for(i = 0; device_list[i] != NULL; ++i){printf("%s\n", device_list[i]->name);}sane_exit();
}int main()
{    pthread_t t;pthread_attr_t attr;void *stackAddr = NULL;int paseSize = getpagesize();size_t stacksize = paseSize*4;pthread_attr_init(&attr);posix_memalign(&stackAddr, paseSize, stacksize);pthread_attr_setstack(&attr, stackAddr, stacksize);pthread_create(&t, NULL, scan_thread, NULL);pthread_join(t, NULL);return 0;
}

这篇关于记一次pthread_key_create导致的__nptl_deallocate_tsd段错误的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/208831

相关文章

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

SpringBoot3匹配Mybatis3的错误与解决方案

《SpringBoot3匹配Mybatis3的错误与解决方案》文章指出SpringBoot3与MyBatis3兼容性问题,因未更新MyBatis-Plus依赖至SpringBoot3专用坐标,导致类冲... 目录SpringBoot3匹配MyBATis3的错误与解决mybatis在SpringBoot3如果

nginx配置错误日志的实现步骤

《nginx配置错误日志的实现步骤》配置nginx代理过程中,如果出现错误,需要看日志,可以把nginx日志配置出来,以便快速定位日志问题,下面就来介绍一下nginx配置错误日志的实现步骤,感兴趣的可... 目录前言nginx配置错误日志总结前言在配置nginx代理过程中,如果出现错误,需要看日志,可以把

shell脚本批量导出redis key-value方式

《shell脚本批量导出rediskey-value方式》为避免keys全量扫描导致Redis卡顿,可先通过dump.rdb备份文件在本地恢复,再使用scan命令渐进导出key-value,通过CN... 目录1 背景2 详细步骤2.1 本地docker启动Redis2.2 shell批量导出脚本3 附录总

Python错误AttributeError: 'NoneType' object has no attribute问题的彻底解决方法

《Python错误AttributeError:NoneTypeobjecthasnoattribute问题的彻底解决方法》在Python项目开发和调试过程中,经常会碰到这样一个异常信息... 目录问题背景与概述错误解读:AttributeError: 'NoneType' object has no at

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp