Linux 从core信息中找到TLS信息
来源:互联网 发布:皇马2016欧冠夺冠数据 编辑:程序博客网 时间:2024/05/18 03:54
原文出处:http://blog.csdn.net/hnwyllmm/article/details/52164245
- 背景
- 依据
- 测试
- 总结
背景
我们在查core问题时,有时候需要查看某个TLS变量的值,但是GDB没有提供直接的命令,或者我不知道。这篇文字的目的,就是想办法从core文件中找出某个线程存放TLS变量的内容。
依据
Linux的glibc库创建线程时,使用mmap
创建一块内存空间,作为此线程的栈空间。并将一个叫做struct pthread
的数据结构放在栈的顶端(参考glibc代码allocate_stack@allocatestack.c
),而TLS的数据结构就在struct pthread
中:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
其中specific_1stblock
数组是第一层的TLS变量,PTHREAD_KEY_2NDLEVEL_SIZE
是一个宏定义,在glib2.20中的大小是32。如果TLS变量超过了这个值,就会使用specific
来存储。从这里可以看出来,只要我们找到了specific_1stblock
的位置,就能找到TLS变量的位置了。
根据上面的分析,我们需要先找到struct pthread
的位置。先看一下struct pthread
在栈中的位置:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
pd
的定义是struct pthread *pd;
。代码中的mem
是使用mmap
创建的内存首地址。coloring
根据宏定义COLORING_INCREMENT
来决定是否是一个变化的值。在我看的代码版本和使用的操作系统(Redhat 6.5)安装的glibc中,都是0,也就是说coloring
是一个常量0。这里还有两个宏定义条件,TLS_TCB_AT_TP
和TLS_DTV_AT_TP
,在glibc2.20,x86_64上使用的是TLS_TCB_AT_TP
,因此pd
相对于mem
的偏移就是固定的大小sizeof(struct pthread)
。
通过上面的描述,如果我们可以知道某个线程所在内存段,那么找到这个内存段的尾部,然后向前偏移sizeof(struct pthread)
就可以找到struct pthread *
的地址,进而找到specific_1stblock
和specific
的位置。
然而还有一个问题,就是怎么确定sizeof(struct pthread)
的值?
虽然一个结构体在编译后的大小已经固定下来,但是看到glibc中复杂的定义,还有那么多宏定义限制,我就只能呵呵了。不过,我还有一招,就是直接从当前运行的一些程序中,确定sizeof(struct pthread)
的大小。
glibc提供的很多函数中都会获取TLS信息,比如pthread_self
。这个函数很短:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
代码中THREAD_SELF
的定义是
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
这个代码只是拿到fs
段寄存器加上固定的偏移量的值。其实我本来想过直接用fs
寄存器的值,可惜这个值不管在正在运行的程序中还是在core
文件中,gdb都是看不到的。好吧,做了这么多白搭了。
不过幸运的是,gdb在调试正在执行的程序的时候,是可以直接执行函数的,我把pthread_self()
函数的返回值拿出来,然后跟这个线程所在段的内存做对比,就可以知道struct pthread *
相对于栈底的偏移量了。
费了九牛二虎之力拿到了sizeof(struct pthread)
,回头看一看,才完成了任务的一半。还得知道specific_1stblock
相对于struct pthread *
的偏移量。不过还好,这个是比较容易做的,看看pthread_getspecific
的汇编代码就一目了然了:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
对比一下glibc中的代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
THREAD_SELF
就是当前线程的struct pthread *
。C代码跟汇编代码对比着看,就很容易找到specific_1stblock
的偏移量。汇编中的edi
寄存器就是传入的参数pthread_key_t key
。 mov %fs:0x10,%rdx
这一行代码使用了fs
寄存器,跟上面看到的pthread_self
函数的方法一样,这就可以确定是获取struct pthread *
的地址。
那么接下来的一行lea 0x310(%rdx,%rax,1),%rdx
自然就是获取specific_1stblock
的值了。这一行中rdx
寄存器存放struct pthread*
,rax
存放key * sizeof(struct pthread_key_data)
,最后把rdx + (rax * 1) + 0x310
的值放入了rdx
中,很明显,0x310就是specific_1stblock
的偏移量(0x310)。
到目前为止,已经准备好了所有获取TLS变量的条件,sizeof(struct pthread)
和specific_1stblock
的偏移量。下面就开始动手测试验证。
测试
写一个使用TLS的测试代码
这个代码创建了一个线程变量和一个线程,创建出来的线程设置了线程变量的值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
编译
- 1
- 1
默认生成a.out
。直接执行,会在sleep
中暂停一段时间,用gdb attach上去。
执行info thread
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
我们来看Thread 2,就是创建出来的线程。
执行thread 2
切换到线程2。
执行call pthread_self()
,结果却得到
- 1
- 2
- 1
- 2
改成十六进制打印
- 1
- 2
- 1
- 2
明显还是不对,相当无语,gdb的call指令只打印了4个字节。不过稍微注意一下就发现了info thread
输出的结果,有一个数据和这里一样:
- 1
- 1
Thread后面的数字,就是pthread
的地址,不过这个数据在调试core文件时并没有打印:
- 1
- 2
- 3
- 1
- 2
- 3
虽然执行的结果与预期不符,但是还好拿到了pthread的地址。接下来找到这个线程所在的内存段,就是栈区间。进程的数据段信息可以从/proc/pid
/maps文件中看到,其中pid
是进程号。
这是我测试出来的进程中的内存信息:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
很明显,0x7f6cc2d15710属于这一段:
- 1
- 1
这就是线程2的栈空间,由于栈是从上往下增长的,那么栈底就是7f6cc2d1d000。它与0x7f6cc2d15710的距离是0x78f0。
在gdb中用gcore
命令生成一个core文件,用gdb打开core文件验证测试,并找出TLS的值。
- 1
- 1
打印出core文件记录的程序内存段
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
一大堆内存段,哪个才是自己要找的线程呢?
线程所处的空间是一个栈空间,那只要找到某个线程的栈上的变量或者其它信息,再根据这个信息就可以找到对应的内存段。有一个很容易查看的栈信息就是栈寄存器rsp
。
看下线程的栈寄存器:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
这样就找到了这个段:
- 1
- 1
这一段也是刚才看到的线程栈空间。拿栈底的地址就是 0x00007f6cc2d1d000,减去pthread偏移0x78f0就是 0x7F6CC2D15710,再加上specific_1stblock
的偏移量0x310,得到0x7F6CC2D15A20。
最后一个,验证拿到地址正确性:
- 1
- 2
- 1
- 2
大功告成,上面的结果,第一个数字是seq
,第二个是data
(这两个是struct pthread_key_data
的成员)。
虽然验证的core文件正好是拿执行程序生成的,不过就是再运行一次生成一个新的core文件,这个方法一样适用。
不过这也有受限的地方,最重要的原因是认为线程数据struct pthread
就位于栈底,而栈在进程空间中是单独的一个内存段。如果这个栈空间是由用户创建线程时提供的,这个方法就可能不会适用。希望后面能找到更通用的方法,或许GDB会直接提供命令访问线程变量。
总结
- 先找到
struct pthread
地址。可以通过gdb跟踪正在执行的程序,查找进程栈内存空间,找到距离栈底的距离; - 通过反汇编
pthread_getspecific
,找到specific_1stblock
相对于struct pthread *
的偏移量; - 在core文件中,通过栈寄存器rsp的地址,找到该线程所处内存段,根据上两步的信息,计算出
specific_1stblock
的地址,进而打印出TLS变量的值。
NOTE: 此方法受限于GLIBC自己创建的内存栈空间和Linux X86_64环境。
- Linux 从core信息中找到TLS信息
- Linux 从core信息中找到TLS信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息
- Linux中gdb 查看core堆栈信息 gdb core 调试
- uiwdt,从LOG中找到相应的信息
- 从文件名找到文件信息(namei)
- 解决“从用户数据存储中检索信息时出错。未找到平台。”问题。
- 解决“从用户数据存储中检索信息时出错。未找到平台。”问题
- 从用户数据存储中检索信息时出错。未找到平台。
- 从用户数据存储中检索信息时出错。未找到平台。”
- “从用户数据存储中检索信息时出错,未找到平台。”出错时的解决办法
- VS2005 解决“从用户数据存储中检索信息时出错。未找到平台"
- “从用户数据存储中检索信息时出错,未找到平台。”出错时的解决办法
- 凭兴趣求职80%会失败,为什么
- 固定区域内鼠标跟随简单例子
- ubuntu 16.04 启动进入命令行的解决方案 ubuntu 16.04 boot into text mode
- 第六十二天学习笔记
- [编程题]句子逆序
- Linux 从core信息中找到TLS信息
- 在maven中新建的jsp报错的解决方法
- JSP请求转发
- Ubuntu下的超级用户root密码设置 & 用户切换
- 文章标题
- .Net Micro Framework 嵌入式开发
- Asp.Net WebForm 母版页+用户控件的使用
- hbuilder(js+html+css)开发的APP效果实例
- 通过JavaScript提交form(jquery框架下),注意是$("form")其中的form不是form的id和名字,而是固定就要这么写