在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