glibc ld对DT_NEEDED的装载以及dl_open的装载(2)

linux的进程启动经过fork-execv之后,会将加载共享对象的工作交给ld去处理,ld会先进行自身的bootstrap,之后处理块。前面的就先跳过不看了,后面的以glibc-2.27的代码为准。

首先是DT_NEEDED的装载,进入的地方在elf/rtld.c的dl_main,不过暂时没必要细说这个,懒得打字了,直接向后定位,分别经过如下几层:

dl_main
|_ ( _dl_map_object_deps )  dl-deps.c
..|_ ( openaux ) dl-deps.c
....|_ ( _dl_map_object )  dl-load.c
......|_ ( _dl_map_object_from_fd ) dl-load.c
........|_ ( _dl_map_segments) dl-map-segments.h

而使用_dl_open的也是一样,会从_dl_open走到dl_open_worker(dl-open.c),然后到_dl_map_object_deps,后面都一样。

对_dl_map_segments的代码进行查看可以发现原因(注释机器翻译+人工润色,可能有不通的地方):

/* 这个实现假定(dl-unmap-secments.h中的_dl_unmap_段的对应实现也是这样)共享对象总是用所有连续的段(或者它们之间的间隙足够小,这样会优先用PROT_NONE映射将所有整页保留在gaps中,而不是允许使用地址空间的这些部分的其他部分)来布局共享对象。  */

static __always_inline const char *
_dl_map_segments (struct link_map *l, int fd,
                  const ElfW(Ehdr) *header, int type,
                  const struct loadcmd loadcmds[], size_t nloadcmds,
                  const size_t maplength, bool has_holes,
                  struct link_map *loader)
{
  const struct loadcmd *c = loadcmds;

  if (__glibc_likely (type == ET_DYN))
    {
      /* 这是一个位置无关的共享对象。
         我们可以让内核将它映射到它喜欢的任何地方,但是我们必须保证所有内容相对于第一个对象有固定的空间。
         因此,我们映射了第一段,但没有设置MAP_FIXED,随着范围的增加,它会覆盖到所有的段。
         然后,我们从多余的部分删除访问,并且已知有足够的空间从后面的段重新映射。
         还有一种完善措施,即:有时我们会有一个地址,我们希望将这些对象映射到这里;
         但这只是我们的一种想法,操作系统可以做任何它喜欢的事情。
      */
      ElfW(Addr) 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);
      if (__glibc_unlikely ((void *) l->l_map_start == MAP_FAILED))
        return DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT;

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

      if (has_holes)
        {
          /* 更改多余部分的保护以禁止所有访问;以后不重新映射的部分将无法访问,就好像它们从未分配一样。
             然后跳到正常的段映射循环中,处理文件映射结束后的段部分。  */
          if (__glibc_unlikely
              (__mprotect ((caddr_t) (l->l_addr + c->mapend),
                           loadcmds[nloadcmds - 1].mapstart - c->mapend,
                           PROT_NONE) < 0))
            return DL_MAP_SEGMENTS_ERROR_MPROTECT;
        }

      l->l_contiguous = 1;

      goto postmap;
    }

  /* 记住此对象使用的地址空间的哪个部分。  */
  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))
        return DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT;

    postmap:
      _dl_postprocess_loadcmd (l, header, c);

      if (c->allocend > c->dataend)
        {
          /* 额外的零页应该出现在这个段的末尾,在文件映射的数据之后。 */
          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)
            /* 所有额外的数据都在段的最后一页。我们可以把它设置为0。*/
            zeropage = zeroend;

          if (zeropage > zero)
            {
              /* 使段最后一页的最后部分为零。  */
              if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
                {
                  /* Dag nab it.  */
                  if (__mprotect ((caddr_t) (zero
                                             & ~(GLRO(dl_pagesize) - 1)),
                                  GLRO(dl_pagesize), c->prot|PROT_WRITE) < 0)
                    return DL_MAP_SEGMENTS_ERROR_MPROTECT;
                }
              memset ((void *) zero, '\0', zeropage - zero);
              if (__glibc_unlikely ((c->prot & PROT_WRITE) == 0))
                __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize) - 1)),
                            GLRO(dl_pagesize), c->prot);
            }

          if (zeroend > zeropage)
            {
              /*从零填充FD处映射其余的零页。 */
              caddr_t mapat;
              mapat = __mmap ((caddr_t) zeropage, zeroend - zeropage,
                              c->prot, MAP_ANON|MAP_PRIVATE|MAP_FIXED,
                              -1, 0);
              if (__glibc_unlikely (mapat == MAP_FAILED))
                return DL_MAP_SEGMENTS_ERROR_MAP_ZERO_FILL;
            }
        }

      ++c;
    }

  /* 通知ELF_PREFERRED_ADDRESS,我们必须加载这个固定地址。 */
  ELF_FIXED_ADDRESS (loader, c->mapstart);

  return NULL;
}

相比于质量糟糕的openssl之流的代码,glibc的代码写得真的很好,注释十分完备,其实也没必要多打了,注释已经解释了第一部分的问题。

标签:none

添加新评论

captcha
请输入验证码