引言
cerrno是C++对errno.h头文件的封装,里面实现了一个errno宏,返回上一次的错误码。我们来看看这个宏的具体实现以及其背后的原理。
cerrno 头文件
代码位置: www.aospxref.com/android-12.…
52 int* __errno(void) __attribute_const__;
53
54 /**
55 * [errno(3)](http://man7.org/linux/man-pages/man3/errno.3.html) is the last error on the calling
56 * thread.
57 */
58 #define errno (*__errno())
可见这个宏的具体实现其实是调用了int* __errno(void)
函数,我们继续追踪
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/__errno.cpp
32 #include "pthread_internal.h"
33
34 int* __errno() {
35 return &__get_thread()->errno_value;
36 }
这里调用了__get_thread()
函数,获取其对应的errno_value
值,然后取地址返回指针,我们看看这个函数的作用:
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/pthread_internal.h
197 // Make __get_thread() inlined for performance reason. See http://b/19825434.
198 static inline __always_inline pthread_internal_t* __get_thread() {
199 return static_cast<pthread_internal_t*>(__get_tls()[TLS_SLOT_THREAD_ID]);
200 }
这里实际是通过__get_tls()获取之后,取index为TLS_SLOT_THREAD_ID的元素,格式转换为pthread_internal_t*得到的这个信息,我们先来看看pthread_internal_t的结构定义:
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/pthread_internal.h
65 class pthread_internal_t {
66 public:
67 class pthread_internal_t* next;
68 class pthread_internal_t* prev;
69
70 pid_t tid;
...
160 bionic_tls* bionic_tls;
161
162 int errno_value;//这个就是我们需要的errno
163 };
对应TLS_SLOT_THREAD_ID的定义,是一个与计算机体系结构相关的量:
72 #if defined(__arm__) || defined(__aarch64__)
...
86 #define TLS_SLOT_THREAD_ID 1
...
97 #elif defined(__i386__) || defined(__x86_64__)
98
99 // x86 uses variant 2 ELF TLS layout, which places the executable's TLS segment
100 // immediately before the thread pointer. New slots are allocated at positive
101 // offsets from the thread pointer.
...
106 #define TLS_SLOT_THREAD_ID 1
继续追踪__get_tls()函数
、、http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/ndk_cruft.cpp
75 void** __get_tls() {
76 #include "platform/bionic/tls.h"
77 return __get_tls();
78 }
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/platform/bionic/tls.h
31 #if defined(__aarch64__)
32 # define __get_tls() ({ void** __val; __asm__("mrs %0, tpidr_el0" : "=r"(__val)); __val; })
33 #elif defined(__arm__)
34 # define __get_tls() ({ void** __val; __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(__val)); __val; })
35 #elif defined(__i386__)
36 # define __get_tls() ({ void** __val; __asm__("movl %%gs:0, %0" : "=r"(__val)); __val; })
37 #elif defined(__x86_64__)
38 # define __get_tls() ({ void** __val; __asm__("mov %%fs:0, %0" : "=r"(__val)); __val; })
39 #else
40 #error unsupported architecture
41 #endif
到这里其实就已经很明显了,对应我们分析__x86_64__架构的逻辑,定义void**指针__val,通过__asm__汇编语法,__asm__("mov %%fs:0, %0" : "=r"(__val));
这里是将__val作为返回参数,填充到%0的位置,即mov %%fs:0, __val
,即将fs段寄存器偏移为0的地址拷贝给到__val,这里保存了TLS(Thread Local Storage)的信息,当前线程的相关信息都保存在此处,通过后续的转换,即可以得到对应的错误信息。
为什么fs段寄存器会保存TLS的信息?有哪些段寄存器可以使用呢?------这些都是与对应的操作系统架构相关的,通过上面的宏我们可以看到至少有四种不同的获取方式,针对aarch64/arm/i386/x86_64都是使用不同的汇编语句返回其对应的TLS信息。 x86_64架构下面还有如下的段寄存器:
- cs(Code Segment): 代码段
- ds(Data Segment): 数据段
- ss(Stack Segment): 栈段
- es(Extra Segment): 扩展段
- fs: 数据段,通常保存动态创建的数据结构
- gs: 数据段,保存另一个程序共享出来的数据