Linux动态库加载函数dlopen源码梳理(一)

下载了libc的源码,现在就开始libc源码的学习,最近了解到了linux动态库的相关知识,那么就从linux动态库加载函数dlopen进行梳理学习吧。如果还没下载libc源码,可通过https://blog.csdn.net/SweeNeil/article/details/83744069来查看自己需要的libc版本并进行下载。在这里我使用的是glibc-2.15一、glibc...
摘要由CSDN通过智能技术生成

下载了libc的源码,现在就开始libc源码的学习,最近了解到了linux动态库的相关知识,那么就从linux动态库加载函数dlopen进行梳理学习吧。

如果还没下载libc源码,可通过

https://blog.csdn.net/SweeNeil/article/details/83744069

来查看自己需要的libc版本并进行下载。在这里我使用的是glibc-2.15

一、glibc/dlfcn/dlopen.c

解压源代码,首先进入glibc/dlfcn目录下,我们看到在该目录下有很多的dlxxx文件

其中dlopen.c就是我们需要分析的dlopen函数的定义地址,进入到dlopen.c中

dlopen函数如下:

void *
dlopen (const char *file, int mode)
{
  return __dlopen (file, mode, RETURN_ADDRESS (0));
}

它实际上是调用了__dlopen函数,我们在进入到__dlopen函数中

void *
__dlopen (const char *file, int mode DL_CALLER_DECL)
{
# ifdef SHARED
  if (__builtin_expect (_dlfcn_hook != NULL, 0))
    return _dlfcn_hook->dlopen (file, mode, DL_CALLER);
# endif

  struct dlopen_args args;
  args.file = file;
  args.mode = mode;
  args.caller = DL_CALLER;

# ifdef SHARED
  return _dlerror_run (dlopen_doit, &args) ? NULL : args.new;
# else
  if (_dlerror_run (dlopen_doit, &args))
    return NULL;

  __libc_register_dl_open_hook ((struct link_map *) args.new);
  __libc_register_dlfcn_hook ((struct link_map *) args.new);

  return args.new;
# endif
}

该函数主要对args进行了赋值,比较关键的就是args.new,它作为了返回值被返回,而这个args.new是通过dlopen_doit函数进行赋值的。

先来了解一个这个args到底是什么

struct dlopen_args
{
  /* The arguments for dlopen_doit.  */
  const char *file;
  int mode;
  /* The return value of dlopen_doit.  */
  void *new;
  /* Address of the caller.  */
  const void *caller;
};

它由上述所示的四个字段组成,这个void *new就是最终的返回值,它在dlopen_doit中被返回到__dlopen。进入到dlopen_doit函数中

static void
dlopen_doit (void *a)
{
  struct dlopen_args *args = (struct dlopen_args *) a;

  if (args->mode & ~(RTLD_BINDING_MASK | RTLD_NOLOAD | RTLD_DEEPBIND
		     | RTLD_GLOBAL | RTLD_LOCAL | RTLD_NODELETE
		     | __RTLD_SPROF))
    GLRO(dl_signal_error) (0, NULL, NULL, _("invalid mode parameter"));

  args->new = GLRO(dl_open) (args->file ?: "", args->mode | __RTLD_DLOPEN,
			     args->caller,
			     args->file == NULL ? LM_ID_BASE : NS,
			     __dlfcn_argc, __dlfcn_argv, __environ);
}

上述代码主要是对args->new进行赋值,不过是通过_dl_open函数来进行的,这个GLRO(dl_open)实际上就是_dl_open函数,因此下一步又需要去查看_dl_open函数,此时_dl_open函数就已经不再是定义在dlopen.c中了,而是dl-open.c中。

暂且将dlopen.c文件中的调用关系称为第一层调用吧,其具体图示如下所示:

二、glibc/elf/dl-open.c

在dl-open.c下也有着大量的关于dl-xxx的具体实现

我们需要了解的是其中的dl-open,打开dl-open文件,里面有_dl_open函数的具体实现,省略号部分表示省略了部分代码。

void *
_dl_open (const char *file, int mode, const void *caller_dlopen, Lmid_t nsid,
	  int argc, char *argv[], char *env[])
{
    ……
    ……

  /* Never allow loading a DSO in a namespace which is empty.  Such
     direct placements is only causing problems.  Also don't allow
     loading into a namespace used for auditing.  */
  else if (__builtin_expect (nsid != LM_ID_BASE && nsid != __LM_ID_CALLER, 0)
	   && (GL(dl_ns)[nsid]._ns_nloaded == 0
	       || GL(dl_ns)[nsid]._ns_loaded->l_auditing))
    _dl_signal_error (EINVAL, file, NULL,
		      N_("invalid target namespace in dlmopen()"));
#ifndef SHARED
  else if ((nsid == LM_ID_BASE || nsid == __LM_ID_CALLER)
	   && GL(dl_ns)[LM_ID_BASE]._ns_loaded == NULL
	   && GL(dl_nns) == 0)
    GL(dl_nns) = 1;
#endif

  struct dl_open_args args;
  args.file = file;
  args.mode = mode;
  args.caller_dlopen = caller_dlopen;
  args.caller_dl_open = RETURN_ADDRESS (0);
  args.map = NULL;
  args.nsid = nsid;
  args.argc = argc;
  args.argv = argv;
  args.env = env;

  const char *objname;
  const char *errstring;
  bool malloced;
  int errcode = _dl_catch_error (&objname, &errstring, &malloced,
				 dl_open_worker, &args);

#ifndef MAP_COPY
  /* We must munmap() the cache file.  */
  _dl_unload_cache ();
#endif

  /* See if an error occurred during loading.  */
  if (__builtin_expect (errstring != NULL, 0))
    {
      /* Remove the object from memory.  It may be in an inconsistent
	 state if relocation failed, for example.  */
      if (args.map)
	{
	  /* Maybe some of the modules which were loaded use TLS.
	     Since it will be removed in the following _dl_close call
	     we have to mark the dtv array as having gaps to fill the
	     holes.  This is a pessimistic assumption which won't hurt
	     if not true.  There is no need to do this when we are
	     loading the auditing DSOs since TLS has not yet been set
	     up.  */
	  if ((mode & __RTLD_AUDIT) == 0)
	    GL(dl_tls_dtv_gaps) = true;

	  _dl_close_worker (args.map);
	}

      assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);

      /* Release the lock.  */
      __rtld_lock_unlock_recursive (GL(dl_load_lock));

      /* Make a local copy of the error string so that we can release the
	 memory allocated for it.  */
      size_t len_errstring = strlen (errstring) + 1;
      char *local_errstring;
      if (objname == errstring + len_errstring)
	{
	  size_t total_len = len_errstring + strlen (objname) + 1;
	  local_errstring = alloca (total_len);
	  memcpy (local_errstring, errstring, total_len);
	  objname = local_errstring + len_errstring;
	}
      else
	{
	  local_errstring = alloca (len_errstring);
	  memcpy (local_errstring, errstring, len_errstring);
	}

      if (malloced)
	free ((char *) errstring);

      /* Reraise the error.  */
      _dl_signal_error (errcode, objname, NULL, local_errstring);
    }

  assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);

  /* Release the lock.  */
  __rtld_lock_unlock_recursive (GL(dl_load_lock));

#ifndef SHARED
  DL_STATIC_INIT (args.map);
#endif

  return args.map;
}

在_dl_open函数中定义了一个新的结构体dl_open_args,并且_dl_open中实际上是调用

  • 14
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux 环境下动态加载库,可以使用动态链接库(Dynamic Linking)实现。动态链接库是一种可执行文件,它在运行时才会被加载到内存,并且可以在多个程序之间共享。这样,可以减小每个程序的内存占用,同时也方便了代码的维护和更新。 Linux 系统提供了一些函数来实现动态加载库,包括 `dlopen()`、`dlsym()`、`dlclose()` 等。其中,`dlopen()` 函数用于打开一个动态链接库,`dlsym()` 函数用于获取动态链接库中的符号,`dlclose()` 函数用于关闭动态链接库。 下面是一个简单的动态加载库的示例: ```c #include <stdio.h> #include <dlfcn.h> int main() { void *handle; double (*cosine)(double); char *error; handle = dlopen("libm.so", RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } cosine = dlsym(handle, "cos"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); return 1; } printf("%f\n", (*cosine)(2.0)); dlclose(handle); return 0; } ``` 上面的代码中,首先使用 `dlopen()` 函数打开了名为 `libm.so` 的动态链接库,并将其句柄保存在 `handle` 变量中。然后使用 `dlsym()` 函数获取了名为 `cos` 的符号,并将其保存在 `cosine` 变量中。最后,调用了 `cosine` 函数,并将结果打印出来。最后,使用 `dlclose()` 函数关闭了动态链接库。 需要注意的是,在使用动态链接库时,需要确保库文件的路径正确,并且需要保证库文件与应用程序的架构一致。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值