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

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

中大概梳理了整个流程,还有_dl_map_object_from_fd(),以及link_map结构没有进行分析,在这里对这两部分进行分析

由于_dl_map_object_from_fd()比较长,整个函数的代码就放到最后作为附录,前面部分来一点点进行梳理。

一、_dl_map_object_from_fd函数内容梳理

首先在_dl_map_object_from_fd中,定义了link_map以及与elf格式相关的内容

  struct link_map *l = NULL;
  const ElfW(Ehdr) *header;
  const ElfW(Phdr) *phdr;
  const ElfW(Phdr) *ph;
  size_t maplength;

上面的Ehdr对应于elf的文件头,Phdr对应于elf格式的程序头,其结构如下

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half	e_type;			/* Object file type */
  Elf64_Half	e_machine;		/* Architecture */
  Elf64_Word	e_version;		/* Object file version */
  Elf64_Addr	e_entry;		/* Entry point virtual address */
  Elf64_Off	e_phoff;		/* Program header table file offset */
  Elf64_Off	e_shoff;		/* Section header table file offset */
  Elf64_Word	e_flags;		/* Processor-specific flags */
  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
  Elf64_Half	e_phentsize;		/* Program header table entry size */
  Elf64_Half	e_phnum;		/* Program header table entry count */
  Elf64_Half	e_shentsize;		/* Section header table entry size */
  Elf64_Half	e_shnum;		/* Section header table entry count */
  Elf64_Half	e_shstrndx;		/* Section header string table index */
} Elf64_Ehdr;

typedef struct
{
  Elf64_Word	p_type;			/* Segment type */
  Elf64_Word	p_flags;		/* Segment flags */
  Elf64_Off	p_offset;		/* Segment file offset */
  Elf64_Addr	p_vaddr;		/* Segment virtual address */
  Elf64_Addr	p_paddr;		/* Segment physical address */
  Elf64_Xword	p_filesz;		/* Segment size in file */
  Elf64_Xword	p_memsz;		/* Segment size in memory */
  Elf64_Xword	p_align;		/* Segment alignment */
} Elf64_Phdr;

(关于elf格式有大量的博客进行介绍,可以参考其他博文,这里暂时没对elf格式进行介绍)

然后使用如下代码进行检查,是否已经存在一个link_map形容了我这个so文件

  /* Look again to see if the real name matched another already loaded.  */
  for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
    if (l->l_removed == 0 && l->l_ino == st.st_ino && l->l_dev == st.st_dev)
      {
	/* The object is already loaded.
	   Just bump its reference count and return it.  */
	__close (fd);

	/* If the name is not in the list of names for this object add
	   it.  */
	free (realname);
	add_name_to_object (l, name);

	return l;
      }

如果找到了,就关闭文件,返回这个link_map

可以看到在其中有这样一个if语句

if (l->l_removed == 0 && l->l_ino == st.st_ino && l->l_dev == st.st_dev)

其中,st_ino,这是物理文件在内存中编号,且文件的设备号st_dev相同,从底层来比较文件,如果相同就证明已经形成了一个link_map,直接返回就可以了。

继续往下看,发现了在函数中对最开始的Ehdr进行赋值的情况,即对header进行了赋值,从注释中发现是将在open_verify中得到的fbp->buf中的内容赋值给header。

  /* This is the ELF header.  We read it in `open_verify'.  */
  header = (void *) fbp->buf;

继续往下

  /* Extract the remaining details we need from the ELF header
     and then read in the program header table.  */
  //程序入口的虚拟地址。如果目标文件无程序入口,可为0
  l->l_entry = header->e_entry;
  //目标文件标识(2-》可执行文件,3-》共享目标文件)
  type = header->e_type;
  //程序头部表项目
  l->l_phnum = header->e_phnum;

  //maplength等于程序头部表项目数量乘以程序头结构体大小
  maplength = header->e_phnum * sizeof (ElfW(Phdr));
  //如果程序头表格的偏移量加上maplength的长度还没达到读取上来的fbp->len的长度,就不需要继续读
  if (header->e_phoff + maplength <= (size_t) fbp->len)
    phdr = (void *) (fbp->buf + header->e_phoff);
  //如果不足就需要从文件中读取出来
  else
    {
      phdr = alloca (maplength);
      __lseek (fd, header->e_phoff, SEEK_SET);
      if ((size_t) __libc_read (fd, (void *) phdr, maplength) != maplength)
	{
	  errstring = N_("cannot read file data");
	  goto call_lose_errno;
	}
    }

这里对link_map的相关字段进行了赋值,同时对maplength、phdr都进行了赋值,这是在为elf格式的分节映射进行了准备。继续往下分析,在下面定义了这样一个结构体,通过注释可知这是在收集加载命令。

    /* Scan the program header table, collecting its load commands.  */
    struct loadcmd
      {
	ElfW(Addr) mapstart, mapend, dataend, allocend;
	off_t mapoff;
	int prot;
      } loadcmds[l->l_phnum], *c;
    size_t nloadcmds = 0;

接着对phdr(程序头表)进行遍历,根据每个程序头的type的类型做具体的操作。

    for (ph = phdr; ph < &phdr[l->l_phnum]; ++ph)
      switch (ph->p_type)
	{
	case PT_DYNAMIC:
	  l->l_ld = (void *) ph->p_vaddr;
	  l->l_ldnum = ph->p_memsz / sizeof (ElfW(Dyn));
	  break;

	case PT_PHDR:
	  l->l_phdr = (void *) ph->p_vaddr;
	  break;

	case PT_LOAD:
	  /* A load command tells us to map in part of the file.
	     We record the load commands and process them all later.  */

	  c = &loadcmds[nloadcmds++];
	  c->mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
	  c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
		       & ~(GLRO(dl_pagesize) - 1));
	  c->dataend = ph->p_vaddr + ph->p_filesz;
	  c->allocend = ph->p_vaddr + ph->p_memsz;
	  c->mapoff = ph->p_offset & ~(GLRO(dl_pagesize) - 1);

	  /* Determine whether there is a gap between the last segment
	     and this one.  */
	  if (nloadcmds > 1 && c[-1].mapend != c->mapstart)
	    has_holes = true;

	  /* Optimize a common case.  */
#if (PF_R | PF_W | PF_X) == 7 && (PROT_READ | PROT_WRITE | PROT_EXEC) == 7
	  c->prot = (PF_TO_PROT
		     >> ((ph->p_flags & (PF_R | PF_W | PF_X)) * 4)) & 0xf;
#else
	  c->prot = 0;
	  if (ph->p_flags & PF_R)
	    c->prot |= PROT_READ;
	  if (ph->p_flags & PF_W)
	    c->prot |= PROT_WRITE;
	  if (ph->p_flags & PF_X)
	    c->prot |= PROT_EXEC;
#endif
	  break;

	case PT_TLS:
	  if (ph->p_memsz == 0)
	    /* Nothing to do for an empty segment.  */
	    break;

	  l->l_tls_blocksize = ph->p_memsz;
	  l->l_tls_align = ph->p_align;
	  if (ph->p_align == 0)
	    l->l_tls_firstbyte_offset = 0;
	  else
	    l->l_tls_firstbyte_offset = ph->p_vaddr & (ph->p_align - 1);
	  l->l_tls_initimage_size = ph->p_filesz;
	  /* Since we don't know the load address yet only store the
	     offset.  We will adjust it later.  */
	  l->l_tls_initimage = (void *) ph->p_vaddr;

	  /* If not loading the initial set of shared libraries,
	     check whether we should permit loading a TLS segment.  */
	  if (__builtin_expect (l->l_type == lt_library, 1)
	      /* If GL(dl_tls_dtv_slotinfo_list) == NULL, then rtld.c did
		 not set up TLS data structures, so don't use them now.  */
	      || __builtin_expect (GL(dl_tls_dtv_slotinfo_list) != NULL, 1))
	    {
	      /* Assign the next available module ID.  */
	      l->l_tls_modid = _dl_next_tls_modid ();
	      break;
	    }

	  errval = 0;
	  errstring = N_("cannot handle TLS data");
	  goto call_lose;
	  break;

	case PT_GNU_STACK:
	  stack_flags = ph->p_flags;
	  break;

	case PT_GNU_RELRO:
	  l->l_relro_addr = ph->p_vaddr;
	  l->l_relro_size = ph->p_memsz;
	  break;
	}

在elf文件的规范中,根据不同的program header 不同,要实现不同的功能,采用不同的处理策略。

在case PT_LOAD中

把所有的可以加载的节都在加载的数据结构中loadcmds中构建完成,然后继续往下看

    /* Now process the load commands and map segments into memory.  */
    c = loadcmds;

    /* Length of the sections to be loaded.  */
    maplength = loadcmds[nloadcmds - 1].allocend - c->mapstart;

存在如上两个赋值,继续往下就开始了映射

	ElfW(Addr) mappref;
	mappref = (ELF_PREFERRED_ADDRESS (loader, maplength,
					  c->mapstart & GLRO(dl_use_load_bias))
		   - MAP_BASE_ADDR (l));

	/* Remember which part of the address space this object uses.  */
	l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
					      c->prot,
					      MAP_COPY|MAP_FILE,
					      fd, c->mapoff);

	l->l_map_end = l->l_map_start + maplength;
	l->l_addr = l->l_map_start - c->mapstart;

把整个文件都进行映射,并对link_map部分内容进行赋值

	if (has_holes)
	  /* Change protection on the excess portion to disallow all access;
	     the portions we do not remap later will be inaccessible as if
	     unallocated.  Then jump into the normal segment-mapping loop to
	     handle the portion of the segment past the end of the file
	     mapping.  */
	  __mprotect ((caddr_t) (l->l_addr + c->mapend),
		      loadcmds[nloadcmds - 1].mapstart - c->mapend,
		      PROT_NONE);

上述代码为对高地址进行保护,继续往下看

    /* Remember which part of the address space this object uses.  */
    l->l_map_start = c->mapstart + l->l_addr;
    l->l_map_end = l->l_map_start + maplength;
    l->l_contiguous = !has_holes;

    while (c < &loadcmds[nloadcmds])
      {
	if (c->mapend > c->mapstart
	    /* Map the segment contents from the file.  */
	    && (__mmap ((void *) (l->l_addr + c->mapstart),
			c->mapend - c->mapstart, c->prot,
			MAP_FIXED|MAP_COPY|MAP_FILE,
			fd, c->mapoff)
		== MAP_FAILED))
	  goto map_error;

      postmap:
	if (c->prot & PROT_EXEC)
	  l->l_text_end = l->l_addr + c->mapend;

	if (l->l_phdr == 0
	    && (ElfW(Off)) c->mapoff <= header->e_phoff
	    && ((size_t) (c->mapend - c->mapstart + c->mapoff)
		>= header->e_phoff + header->e_phnum * sizeof (ElfW(Phdr))))
	  /* Found the program header in this segment.  */
	  l->l_phdr = (void *) (c->mapstart + header->e_phoff - c->mapoff);

	if (c->allocend > c->dataend)
	  {
	    /* Extra zero pages should appear at the end of this segment,
	       after the data mapped from the file.   */
	    ElfW(Addr) zero, zeroend, zeropage;

	    zero = l->l_addr + c->dataend;
	    zeroend = l->l_addr + c->allocend;
	    zeropage = ((zero + GLRO(dl_pagesize) - 1)
			& ~(GLRO(dl_pagesize) - 1));

	    if (zeroend < zeropage)
	      /* All the extra data is in the last page of the segment.
		 We can just zero it.  */
	      zeropage = zeroend;

	    if (zeropage > zero)
	      {
		/* Zero the final part of the last page of the segment.  */
		if (__builtin_expect ((c->prot & PROT_WRITE) == 0, 0))
		  {
		    /* Dag nab it.  */
		    if (__mprotect ((caddr_t) (zero
					       & ~(GLRO(dl_pagesize) - 1)),
				    GLRO(dl_pagesize), c->prot|PROT_WRITE) < 0)
		      {
			errstring = N_("cannot change memory protections");
			goto call_lose_errno;
		      }
		  }
		memset ((void *) zero, '\0', zeropage - zero);
		if (__builtin_expect ((c->prot & PROT_WRITE) == 0, 0))
		  __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值