引言
cerrno 是 C++对 errno.h 头文件的封装,里面实现了一个 errno 宏,返回上一次的错误码。我们来看看这个宏的具体实现以及其背后的原理。
cerrno 头文件
代码位置: http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/errno.h
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
复制代码
继续追踪__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: 数据段,保存另一个程序共享出来的数据
评论