在阅读glibc源码的时候,遇见了几个没见过的宏,几乎所有的函数都会使用这几个宏:libc_hidden_def、libc_hidden_weak、libc_hidden_proto
因为我比较好奇,所以特地去找了一下有关这些宏的定义(主要也是想多学一点)
Linux下学习源码,最方便的地方就是它的各种命令
使用find ./ | grep -r "define libc_hidden_def"
,发现在include/libc-symbols.h
中定义了
进入该文件
一进去就是一行注释:
/* This file is included implicitly in the compilation of every source file,
using -include. It includes config.h. */
这个文件被隐含地包括在了所有源文件地编译中
下面紧跟着这几个宏:
/* Use `#if IS_IN (module)` to detect what component is being compiled. */
#define PASTE_NAME1(a,b) a##b
#define PASTE_NAME(a,b) PASTE_NAME1 (a,b)
#define IN_MODULE PASTE_NAME (MODULE_, MODULE_NAME)
#define IS_IN(lib) (IN_MODULE == MODULE_##lib)
主要是为了定义IS_IN(lib)
这个宏
看名字就可以看出来,这个宏的意思是判断当前的模块
上面这些倒不是重点,往下面再翻翻,就看到了一段有一点意思的东西
#ifndef __ASSEMBLER__
/* GCC understands weak symbols and aliases; use its interface where
possible, instead of embedded assembly language. */
这部分的描述挺重要的(自己觉得是这样子的)
456行开始有一个长注释,介绍了这几个宏:
/* The following macros are used for PLT bypassing within libc.so
457 (and if needed other libraries similarly).*/
主要内容如下:
首先,你在头文件foo.h
中有一个函数原性,例如
int foo(int __bar);
如果对foo
函数的调用应当始终指向libc.so
(即foo这个函数只能是libc.so
里面的函数),则应当在头文件foo.h
中使用该宏:
libc_hidden_proto (foo)
然后在这个函数的定义,例如foo.c
,后面加入
libc_hidden_def (foo)
或
libc_hidden_weak (foo)
如果foo
只是其他函数的一个别名,则应当在函数的定义后,先使用strong_alias
或者weak_alias
,然后再使用libc_hidden_def
或libc_hidden_weak
如果该函数在多个静态库中出现,则应当这么使用:
# if IS_IN(libc) || IS_IN(xxxx)
hidden_proto(foo)
#endif
下面的宏定义分出了两条分支:汇编和非汇编
一般来说,使用GCC进行编译,能利用GCC的特性做一些事情,因此,这里的注释推荐使用非汇编的路径
#ifndef __ASSEMBLER__
首先看libc_hidden_proto
它在622行:
#define libc_hidden_proto(name, attrs...) hidden_proto (name, ##attrs)
它是定义在hidden_proto
这个宏上的,我们继续看这个宏
#define hidden_proto(name, attrs...) \
__hidden_proto (name, , __GI_##name, ##attrs)
它有两个参数,参数一是name,参数二是attr,定义在__hodden_proto
这个宏上,而这个宏的定义是:
#define __hidden_proto(name, thread, internal, attrs...) \
extern thread __typeof (name) name __asm__ (__hidden_asmname (#internal)) \
__hidden_proto_hiddenattr (attrs);
它的参数有这几个:name、thread、internal、attrs
首先,它声明了一个全局变量或函数,然后,它涉及了这两个东西:
__asm__(__hidden_asmname)
https://blog.csdn.net/xj178926426/article/details/54345449
__thread
变量每一个线程有一份独立实体,各个线程的值互不干扰。
即线程的“私有变量”
显然,hidden_proto
的这一个变量放空了,而下面还有一个hidden_tls_proto
:
#define __hidden_proto(name, thread, internal, attrs...) \
extern thread __typeof (name) name __asm__ (__hidden_asmname (#internal)) \
__hidden_proto_hiddenattr (attrs);
两个的区别就是这个字段是否放空,即是否是线程私有变量
接下来要深挖的是__hidden_asmname
它看起来不像是GCC自己的东西
果然,在下面马上就有定义:
#define __hidden_asmname(name) \
__hidden_asmname1 (__USER_LABEL_PREFIX__, name)
继续追查__hidden_asmname1
:
#define __hidden_asmname1(prefix, name) __hidden_asmname2(prefix, name)
它指向了__hidden_asmname2
:
#define __hidden_asmname1(prefix, name) __hidden_asmname2(prefix, name)
这个宏只是简单地把prefix
粘贴到name
前面
而__hidden_asmname
则是将__USER_LABEL_PREFIX__
粘贴到name
上
这个宏是GNU标准下的默认宏
https://blog.csdn.net/qq_22237829/article/details/73872781
不知道为什么,这个宏的定义是空的,就是什么都没有,我没有找到其他的解释和资料
展开hidden_proto
,它的前半部分大概就是
extern __typeof(name) name \
__asm__(__hidden_asmname(__GI_name))
即
extern __typeof(name) name \
__asm__(__GI_name)
因为__USER_LABEL_PREFIX__
是空的,所以__hidden_asmname
这个宏其实没有起作用
把这一段复制到一个新的文件里面,进行预处理后
hidden_proto(foo)
这一段变成了
extern __typeof (foo) foo __asm__ ("" "__GI_foo") __attribute__ ((visibility ("hidden")));
而后面的__attribute__((visibility("hidden")))
则在strong_alias、weak_alias中说明了:对于链接该库的程序来说,该函数就是不可见的,除非强制声明
即,该接口是不对外暴露的,只是内部使用的
这一段话里面,对我来说,最困惑难懂的就是__asm__
那一段
我不知道內联汇和在编里面放一个名字的意义何在
在下面有一段注释,里面有一句话说:
/*
hidden_proto doesn't make sense for assembly but the equivalent
is to call via the HIDDEN_JUMPTARGET macro instead of JUMPTARGET.
*/
其中,HIDDEN_JUMPTAEGET
的定义是:
#define HIDDEN_JUMPTARGET(name) __GI_##name
就是简单地在名字前面加了一个前缀
因此,大致可以猜测一下__asm__
在这里的用处:起一个别名
在文件中这么写:
int foo() __asm__("__PREFIX_foo");
则使用nm
查看编译出来的ELF文件,发现foo
被替换为了__PREFIX_foo
:
0000000000001129 T __PREFIX_foo
结合下面的注释,则可以明确该宏的作用:给函数加一个__GI_
的前缀。通过这个重命名的把戏,即使别的模块定义了和GLIB中函数重名的函数,GLIB也能保证不访问别人的函数,而是只访问自己的函数
在汇编GCC中,函数由本名进行定义,因此,我们需要给它加一个别名;
在C中,函数使用__GI_
前缀,我们需要将别名添加到实名中
同样,查看libc_hidden_def
#define hidden_def(name) __hidden_ver1(__GI_##name, name, name);
继续追踪
#define __hidden_ver1(local, internal, name) \
__hidden_ver2 (, local, internal, name)
同样,它有三个参数:local、internal、name
#define __hidden_ver2(thread, local, internal, name) \
extern thread __typeof (name) __EI_##name \
__asm__(__hidden_asmname (#internal)); \
extern thread __typeof (name) __EI_##name \
__attribute__((alias (__hidden_asmname (#local)))) \
__attribute_copy__ (name)
因为比较复杂,不好懂,我们像刚刚一样,复制到C文件里,预处理后一窥究竟:
extern __typeof(foo) __EI_foo __asm__("foo");
extern __typeof(foo) __EI_foo __attribute__((alias("__GI_foo"))) __attribute_copy__(foo);
第一句,声明了一个全局的函数__EI_foo
,它的别名是foo
第二句,__EI_foo
变成了__GI_foo
的别名
那么,访问foo
,其实就是在访问__GI_foo
libc_hidden_proto
与libc_hidden_def
合作,到底做了什么事情呢?
如果只使用了libc_hidden_proto
,则
int foo();
extern __typeof (foo) foo __asm__ ("" "__GI_foo") __attribute__ ((visibility ("hidden")));
int foo()
{}
只是声明了一个名为__GI_foo
的全局函数,它的真身是foo
,但是在符号表上却找不到foo
,只有__GI_foo
,还是隐藏的:
$ readelf -s a.out | grep foo
57: 0000000000001129 11 FUNC GLOBAL HIDDEN 14 __GI_foo
同时使用了libc_hidden_def
之后,
int foo();
extern __typeof (foo) foo __asm__ ("" "__GI_foo") __attribute__ ((visibility ("hidden")));
extern __typeof (foo) __EI_foo __asm__("" "foo"); extern __typeof (foo) __EI_foo __attribute__((alias ("" "__GI_foo"))) ;
int foo()
{}
在libc_hidden_proto
的基础上,它声明了一个名字是foo
的全局变量,这个foo
的真身是__EI_foo
下一句马上就说了,__EI_foo
只不过是__GI_foo
的一个别名
那么,所有对foo
的访问,都是在访问__EI_foo
,而对__EI_foo
的访问,都是在访问__GI_foo
,而__GI_foo
就是foo
本身
绕这么大弯子,最终的目的就是,保证所有对foo
的访问最终跑到这个库里
而lib_hidden_weak
,只是在lib_hidden_def
的基础上,增加了一个__attribute__((weak))
即将其做为弱符号处理
弱符号,即用户可以自己定义一个同名的函数