当前位置: 首页 > 工具软件 > libc++ > 使用案例 >

libc_hidden_def、libc_hidden_weak、libc_hidden_proto

路伟
2023-12-01

libc_hidden_def、libc_hidden_weak、libc_hidden_proto

在阅读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_deflibc_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

首先,它声明了一个全局变量或函数,然后,它涉及了这两个东西:

  1. thread
  2. __asm__(__hidden_asmname)
  3. internal

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_protolibc_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))
即将其做为弱符号处理

弱符号,即用户可以自己定义一个同名的函数

 类似资料:

相关阅读

相关文章

相关问答