该版本所使用的动态链接库技术是基于ELF可执行文件格式的,该格式在之前的 zenglOX v0.0.11 ELF format(ELF可执行文件格式)与execve系统调用 文章中做过介绍,不过那时只对ELF结构做了一个基本的介绍,ELF中与动态链接相关的部分并没有进行详细介绍,下面就先对这部分内容进行介绍...

    v1.3.0的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)

    Dropbox地址:点此进入Dropbox网盘  该版本位于zenglOX_v1.3.0的文件夹,该文件夹里的zenglOX_v1.3.0.zip的压缩包为源代码,readme.txt为当前版本的简单说明。

    Google Drive地址:点此进入Google Drive云端硬盘 该版本位于zenglOX_v1.3.0的文件夹,里面包含了和上面Dropbox里一样的2个文件。

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  该版本位于zenglOX_v1.3.0的文件夹,里面包含了和上面Dropbox里一样的2个文件。

    Dropbox与Google Drive如果正常途径访问不了,则需要使用代理访问。

    另外再附加一个英特尔英文手册的共享链接地址:
    点此进入原百度盘点此进入Dropbox网盘点此进入Google Drive  (下面介绍task gate时会用到,Dropbox与Google Drive里是intel_manual.pdf文档)

    该版本所使用的动态链接库技术是基于ELF可执行文件格式的,该格式在之前的"zenglOX v0.0.11 ELF format(ELF可执行文件格式)与execve系统调用"文章中做过介绍,不过那时只对ELF结构做了一个基本的介绍,ELF中与动态链接相关的部分并没有进行详细介绍,下面就先对这部分内容进行介绍(如果对ELF的基本格式还不是很清楚的话,则请先参考上面提到的v0.0.11版本里的文章)。

    之前的v0.0.11版本中只用到了section header table,而当前版本则将只使用program header table,因为动态链接库在被加载到内存后,还需要ELF头部结构里的各种信息,才能在运行时解析出函数的地址来(由于动态链接库可以被加载到虚拟内存的任意位置,因此动态链接库里的函数地址只有在运行时才能确定下来)。

    在Linux系统中,我们可以使用 readelf -a 命令来查看ELF格式的可执行文件与动态链接库的program header table信息:

zenglOX_v1.3.0/build_initrd_img$ readelf -a shell
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
............................................
  Type:                              EXEC (Executable file)
............................................
  Start of program headers:          52 (bytes into file)
............................................

Section Headers:
............................................

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000a0 0x000a0 R E 0x4
  INTERP         0x0000d4 0x080480d4 0x080480d4 0x00006 0x00006 R   0x1
      [Requesting program interpreter: ld.so]
  LOAD           0x000000 0x08048000 0x08048000 0x00628 0x00628 R E 0x1000
  LOAD           0x000628 0x08049628 0x08049628 0x000b8 0x000b8 RW  0x1000
  DYNAMIC        0x000628 0x08049628 0x08049628 0x00088 0x00088 RW  0x4

............................................

zenglOX_v1.3.0/build_initrd_img$ readelf -a ld.so
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
............................................
  Type:                              DYN (Shared object file)
............................................
  Start of program headers:          52 (bytes into file)
............................................

Section Headers:
............................................

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x00a90 0x00a90 R E 0x1000
  LOAD           0x000a90 0x00001a90 0x00001a90 0x000d8 0x000dc RW  0x1000
  DYNAMIC        0x000a90 0x00001a90 0x00001a90 0x000a0 0x000a0 RW  0x4

............................................

zenglOX_v1.3.0/build_initrd_img$ 


    上面输出中,shell与ld.so都是v1.3.0版本下make命令生成的,从输出的Type信息可以看到,shell是EXEC类型的可执行文件,ld.so则是DYN类型的动态链接库文件。

    其中,shell文件对应的Program Headers输出里显示了该文件的program header table数组中一共包含了5个program header结构,第一个PHDR类型的program header结构里包含了program header table在文件里的偏移值信息,这里为0x34(对应十进制为52),与ELF Header头部结构里显示的Start of program headers: 52 (bytes into file)信息一致。

    第二个INTERP类型的program header结构里包含了一个0xd4的偏移值,表示在shell文件的0xd4偏移位置处有一个"ld.so"的字符串,系统在将shell加载到内存里后,会将ld.so也加载到内存里,最后还会将控制流程也转到ld.so中,INTERP结构指向的字符串是在编译链接时通过特殊的选项参数来指定的。在build_initrd_img目录内的makefile文件里可以看到:

#makefile for ram disk

.............................................

define C_make_template
# translation 
$1: $1.o $(C_DEP_LIB)
	@echo "building $$@"
	$(CROSS_CC) -Wl,-emain -Wl,-dynamic-linker,ld.so -o $$@ $$< $(CROSS_CLINK_FLAGS) -L. -lcommon
endef

.............................................


    上面的 -Wl,-dynamic-linker,ld.so 通过 -Wl 参数将紧随其后的 -dynamic-linker,ld.so 参数传递给链接器,这样编译结束后,链接器就会将ELF可执行文件的INTERP结构指向的字符串设置为 ld.so

    在INTERP结构后面是两个LOAD类型的program header结构,该结构表示需要将文件中的哪部分数据给加载到虚拟内存中,例如:LOAD  0x000628 0x08049628 0x08049628 0x000b8 0x000b8 就表示需要将文件中的0x628偏移位置处的0xb8大小的数据给加载到0x08049628的虚拟内存位置。

    LOAD结构后面是DYNAMIC类型的program header结构,该结构里包含了Dynamic节在文件及虚拟内存里的偏移位置信息,和动态链接相关的结构的位置和尺寸信息都包含在该节里。

    从前面 readelf -a ld.so 命令的输出可以看到,和shell可执行文件相比,ld.so之类的动态链接库文件则只是没有PHDR与INTERP类型的program header结构,但是仍然包含LOAD和DYNAMIC类型的program header结构。

    zenglOX里执行shell之类的依赖动态链接库的可执行文件时,第一步就是将shell和ld.so的LOAD类型的program header结构所指定的数据给加载到虚拟内存里,可以在zlox_elf.c文件中看到和加载过程相关的代码:

// 通过zlox_shlib_loadToVAddr函数将buf文件缓冲里需要加载的数据
// 给加载到vaddr指定的起始虚拟内存位置
ZLOX_UINT32 zlox_shlib_loadToVAddr(ZLOX_ELF32_EHDR *hdr, ZLOX_UINT8 * buf, ZLOX_UINT32 vaddr)
{
	ZLOX_ELF32_PHDR * phdr = (ZLOX_ELF32_PHDR *)((ZLOX_UINT32)hdr + hdr->e_phoff);
	ZLOX_UINT32 i, first_load = 0, last_load = 0;
	ZLOX_BOOL isFirst = ZLOX_TRUE;
        // 循环遍历动态链接库的每个program header结构
	for(i = 0; i < hdr->e_phnum; i++)
	{
		switch(phdr[i].p_type)
		{
		case ZLOX_PT_LOAD:
                // 将动态链接库LOAD类型指定的p_offset偏移处的p_filesz大小的数据
                // 加载到p_vaddr + vaddr对应的虚拟内存位置,
                // 该虚拟内存的尺寸为p_memsz字节大小
			zlox_pages_alloc((phdr[i].p_vaddr + vaddr), phdr[i].p_memsz);
			zlox_memcpy((ZLOX_UINT8 *)(phdr[i].p_vaddr + vaddr),
					(ZLOX_UINT8 *)(buf + phdr[i].p_offset),phdr[i].p_filesz);
                // 如果虚拟内存的大小p_memsz大于文件里的数据大小p_filesz时,
                // 则将虚拟内存里多出来的空间给清空为0
                // (当包含bss段时,p_memsz就会大于p_filesz)
			if(phdr[i].p_memsz > phdr[i].p_filesz)
			{
				zlox_memset((ZLOX_UINT8 *)(phdr[i].p_vaddr + vaddr + phdr[i].p_filesz), 0 , 
						(phdr[i].p_memsz - phdr[i].p_filesz));
			}
			if(isFirst)
			{
				first_load = i;
				isFirst = ZLOX_FALSE;
			}
			last_load = i;
			break;
		}
	}
        // 计算出加载到虚拟内存里的数据的总字节数
	ZLOX_UINT32 msize = (phdr[last_load].p_vaddr + phdr[last_load].p_memsz) - phdr[first_load].p_vaddr;
	return msize;
}

// 将soname对应的动态链接库加载到vaddr对应的起始虚拟内存位置处
ZLOX_UINT32 zlox_load_shared_library(ZLOX_CHAR * soname, ZLOX_UINT32 vaddr)
{
...........................................
        // 如果是系统里第一次加载该动态链接库的话,
        // 就通过上面定义的zlox_shlib_loadToVAddr函数来完成加载工作
        ZLOX_UINT32 msize = zlox_shlib_loadToVAddr((ZLOX_ELF32_EHDR *)buf, (ZLOX_UINT8 *)buf, vaddr);
...........................................
}

// 通过zlox_load_elf函数将ELF可执行文件及其依赖的
// 动态链接库文件给加载到虚拟内存空间里
ZLOX_UINT32 zlox_load_elf(ZLOX_ELF32_EHDR *hdr,ZLOX_UINT8 * buf)
{
	ZLOX_ELF32_PHDR * phdr = (ZLOX_ELF32_PHDR *)((ZLOX_UINT32)hdr + hdr->e_phoff);
	ZLOX_CHAR * interp = ZLOX_NULL;
	ZLOX_UINT32 i,vaddr = 0,last_load,ret;
        // 循环遍历动态链接库的每个program header结构
	for(i = 0; i < hdr->e_phnum; i++)
	{
		switch(phdr[i].p_type)
		{
		case ZLOX_PT_LOAD:
                // 将ELF可执行文件中LOAD类型指定的p_offset偏移处的p_filesz大小的数据
                // 加载到p_vaddr对应的虚拟内存位置,该虚拟内存的
                // 尺寸为p_memsz字节大小
			zlox_pages_alloc(phdr[i].p_vaddr, phdr[i].p_memsz);
			zlox_memcpy((ZLOX_UINT8 *)phdr[i].p_vaddr,
					(ZLOX_UINT8 *)(buf + phdr[i].p_offset),phdr[i].p_filesz);
			if(phdr[i].p_memsz > phdr[i].p_filesz)
			{
				zlox_memset((ZLOX_UINT8 *)(phdr[i].p_vaddr + phdr[i].p_filesz), 0 , 
						(phdr[i].p_memsz - phdr[i].p_filesz));
			}
			if(vaddr == 0)
				vaddr = phdr[i].p_vaddr;
			last_load = i;
			break;
                // 得到动态链接库解释器的字符串名称,如:ld.so
		case ZLOX_PT_INTERP:
			interp = (ZLOX_CHAR *)(buf + phdr[i].p_offset);
			break;
		}
	}

...........................................

	ret = hdr->e_entry;

	if(interp != ZLOX_NULL)
	{
                // 通过上面定义的zlox_load_shared_library函数将ld.so之类的
                // 动态库解释器加载到ZLOX_ELF_LD_SO_VADDR对应的
                // 虚拟内存位置
		ret = zlox_load_shared_library(interp, ZLOX_ELF_LD_SO_VADDR);
...........................................
	}

...........................................
}

ZLOX_SINT32 zlox_execve(const ZLOX_CHAR * filename)
{
...........................................
        // zlox_execve会通过zlox_load_elf函数将ELF格式
        // 的可执行文件及其依赖的动态链接库给加载到内存中
	if((addr = zlox_load_elf((ZLOX_ELF32_EHDR *)buf,(ZLOX_UINT8 *)buf)) > 0)
	{
...........................................
	}
...........................................
}


    上面的代码片段里,棕色的注释是此处额外添加的,源代码中暂时没有,从上面的代码可以看出来,ELF可执行文件被加载到的目标虚拟内存位置是一个固定的值,例如,之前的 readelf -a shell 命令输出的 0x08048000 等,而动态链接库的目标虚拟内存位置则不是一个固定的值,上面的zlox_load_elf函数里,就将ld.so加载到ZLOX_ELF_LD_SO_VADDR指定的内存位置处,ZLOX_ELF_LD_SO_VADDR是zlox_elf.h头文件里定义的宏:

#define ZLOX_ELF_LD_SO_VADDR    0x80000000

    因此,ld.so的第一个LOAD部分会被加载到0x80000000的虚拟内存位置处,而第二个LOAD部分则会被加载到 0x80000000 + 0x00001a90 = 0x80001a90 的虚拟内存位置处。

    加载到内存里的ELF可执行文件与加载到内存中的动态链接库,都有一个对应的ZLOX_ELF_LINK_MAP结构,该结构定义在zlox_elf.h头文件中:

typedef struct _ZLOX_ELF_LINK_MAP_LIST ZLOX_ELF_LINK_MAP_LIST;

typedef struct _ZLOX_ELF_LINK_MAP{
	ZLOX_ELF_LINK_MAP_LIST * maplst;
	ZLOX_UINT32 kmap_index;
	ZLOX_UINT32 index;
	ZLOX_CHAR * soname;
	ZLOX_UINT32 entry;
	ZLOX_UINT32 vaddr;
	ZLOX_UINT32 msize;
	ZLOX_ELF_DYN_MAP dyn;
} ZLOX_ELF_LINK_MAP;

struct _ZLOX_ELF_LINK_MAP_LIST{
    ZLOX_BOOL isInit;
    ZLOX_SINT32 count;
    ZLOX_SINT32 size;
    ZLOX_ELF_LINK_MAP * ptr;
};

    为了方便访问和管理,这些ZLOX_ELF_LINK_MAP结构都会被存放在一个可以动态扩容的数组里,这个动态数组则由ZLOX_ELF_LINK_MAP_LIST结构体进行管理,ZLOX_ELF_LINK_MAP_LIST结构中存储了动态数组在堆里所分配的指针信息,以及动态数组里存储的元素的个数信息等,为了方便循环操作,每个ZLOX_ELF_LINK_MAP结构的第一个字段就存储了ZLOX_ELF_LINK_MAP_LIST结构体的指针信息。

    每个ZLOX_ELF_LINK_MAP结构都存储了对应的可执行文件或动态链接库文件在内存中的虚拟内存地址,虚拟内存大小,动态链接库的文件名等信息,在该结构体里,还有一个最重要的字段就是dyn,它是一个ZLOX_ELF_DYN_MAP结构体,该结构体也定义在zlox_elf.h头文件中:

typedef struct _ZLOX_ELF_DYN_MAP{
	ZLOX_ELF32_DYN * Dynamic;
	ZLOX_VOID * dl_runtime_resolve;
	ZLOX_UINT32 hash;
	ZLOX_UINT32 strtab;
	ZLOX_UINT32 symtab;
	ZLOX_UINT32 plt_got;
	ZLOX_UINT32 jmprel_type;
	ZLOX_UINT32 jmprel;
	ZLOX_UINT32 jmprelsz;
	ZLOX_UINT32 rel;
	ZLOX_UINT32 relsz;
} ZLOX_ELF_DYN_MAP;


    上面的ZLOX_ELF_DYN_MAP结构体中缓存了可执行文件或动态链接库文件的Dynamic节里的信息,同样可以通过 readelf -a 命令来查看该节里的信息:

zenglOX_v1.3.0/build_initrd_img$ readelf -a ld.so
...............................................
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x00a90 0x00a90 R E 0x1000
  LOAD           0x000a90 0x00001a90 0x00001a90 0x000d8 0x000dc RW  0x1000
  DYNAMIC        0x000a90 0x00001a90 0x00001a90 0x000a0 0x000a0 RW  0x4

...............................................

Dynamic section at offset 0xa90 contains 15 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libcommon.so]
 0x00000004 (HASH)                       0x94
 0x00000005 (STRTAB)                     0x248
 0x00000006 (SYMTAB)                     0x128
 0x0000000a (STRSZ)                      221 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000003 (PLTGOT)                     0x1b38
 0x00000002 (PLTRELSZ)                   72 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x340
 0x00000011 (REL)                        0x328
 0x00000012 (RELSZ)                      24 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x00000016 (TEXTREL)                    0x0
 0x00000000 (NULL)                       0x0
...............................................
zenglOX_v1.3.0/build_initrd_img$ 


    可以看出来在ld.so文件的0xa90偏移位置处,存储了与该文件相关的Dynamic节,ld.so文件的该节里一共存储了15个项目,每个项目都是一个ZLOX_ELF32_DYN的结构体,该结构体定义在zlox_elf.h里:

typedef struct _ZLOX_ELF32_DYN{
	ZLOX_ELF32_SWORD    d_tag;
	union {
		ZLOX_ELF32_WORD d_val;
		ZLOX_ELF32_ADDR d_ptr;
	} d_un;
} ZLOX_ELF32_DYN;


    上面的结构体是参考 /usr/include/elf.h 头文件里的信息来写的:

/* Dynamic section entry.  */

typedef struct
{
  Elf32_Sword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf32_Word d_val;			/* Integer value */
      Elf32_Addr d_ptr;			/* Address value */
    } d_un;
} Elf32_Dyn;


    linux系统下,在man elf命令的输出信息里,也可以看到上面这个结构体的定义,结构体中的d_tag字段表示项目的类型,从前面的 readelf -a ld.so 命令的输出可以看到,有NEED,HASH,STRTAB,SYMTAB,STRSZ等类型,结构体中的d_un字段是一个联合体,你既可以使用d_un.d_val来引用该联合体的值,也可以使用d_un.d_ptr来引用该联合体的值,d_un.d_val侧重于STRSZ这类的尺寸大小信息,d_un.d_ptr则侧重于HASH,STRTAB这类的指针信息。

    Dynamic节里的这些信息,主要用于完成动态链接库的Relocation(重定位)工作,以及ld.so在运行时解析出函数地址的工作。

    在zlox_elf.c文件里定义了一个zlox_elf_make_link_map函数,通过该函数来完成可执行文件或动态链接库的ZLOX_ELF_LINK_MAP结构的初始化,以及对应的Dynamic节信息的缓存工作:

ZLOX_ELF_LINK_MAP * zlox_elf_make_link_map(ZLOX_UINT32 kmap_index, ZLOX_CHAR * soname, ZLOX_UINT32 vaddr, ZLOX_UINT32 msize)
{
	ZLOX_ELF32_EHDR * hdr = (ZLOX_ELF32_EHDR *)vaddr;
	ZLOX_ELF32_PHDR * phdr = (ZLOX_ELF32_PHDR *)((ZLOX_UINT32)hdr + hdr->e_phoff);
	ZLOX_ELF32_DYN * Dynamic = ZLOX_NULL;
	ZLOX_ELF_LINK_MAP * tmp_map;
        // 通过zlox_push_elf_link_map函数向动态数组中添加一个
        // ZLOX_ELF_LINK_MAP结构
	tmp_map = zlox_push_elf_link_map(vaddr, msize);
	tmp_map->maplst = &current_task->link_maps;
	tmp_map->kmap_index = kmap_index;
	tmp_map->soname = soname;
	tmp_map->entry = hdr->e_entry + (tmp_map->index != 0 ? vaddr : 0);
        // 循环遍历program header table,找到Dynamic节的实际虚拟内存地址
    	for(ZLOX_UINT32 i = 0; i < hdr->e_phnum; i++)
	{
		if(phdr[i].p_type == ZLOX_PT_DYNAMIC)
		{
			tmp_map->dyn.Dynamic = Dynamic = (ZLOX_ELF32_DYN *)((tmp_map->index != 0 ? vaddr : 0) + phdr[i].p_vaddr);
			break;
		}
	}
	if(Dynamic == ZLOX_NULL)
		return tmp_map;
        // 循环遍历Dynamic节,将节里所需项目的值缓存起来
	while(Dynamic->d_tag != 0)
	{
		switch(Dynamic->d_tag)
		{
		case ZLOX_DT_HASH:
                // tmp_map->index == 0时,表示是ELF可执行文件,
                // ELF可执行文件的虚拟内存地址都是固定的值。
                // tmp_map->index != 0则表示是动态链接库,
                // 动态链接库里的地址信息都是非固定的值,需要
                // 加上vaddr(即动态链接库被加载的起始虚拟地址)
			tmp_map->dyn.hash = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_STRTAB:
			tmp_map->dyn.strtab = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_SYMTAB:
			tmp_map->dyn.symtab = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_PLTGOT:
			tmp_map->dyn.plt_got = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_PLTREL:
			tmp_map->dyn.jmprel_type = Dynamic->d_un.d_val;
			break;
		case ZLOX_DT_JMPREL:
			tmp_map->dyn.jmprel = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_PLTRELSZ:
			tmp_map->dyn.jmprelsz = Dynamic->d_un.d_val;
			break;
		case ZLOX_DT_REL:
			tmp_map->dyn.rel = Dynamic->d_un.d_ptr + (tmp_map->index != 0 ? vaddr : 0);
			break;
		case ZLOX_DT_RELSZ:
			tmp_map->dyn.relsz = Dynamic->d_un.d_val;
			break;
		}
		Dynamic++;
	}
..................................................
..................................................
	return tmp_map;
}


    同样的,上面代码里棕色的注释只是此处额外添加的,源代码中暂时没有。

    上面缓存的Dynamic节信息里,并没有包含之前的 readelf -a ld.so 命令所输出显示的 NEEDED 类型,因为该类型只会在加载时使用一次,通过 NEEDED 类型,就可以知道可执行文件与动态链接库所依赖的其他的动态链接库的文件名,然后就可以根据这些文件名来将依赖的动态库给加载到虚拟内存里。在zlox_elf.c文件的zlox_load_elf与zlox_load_shared_library函数里,当加载完当前的可执行文件后,还会循环将其依赖的动态库也加载进来:

ZLOX_UINT32 zlox_load_shared_library(ZLOX_CHAR * soname, ZLOX_UINT32 vaddr)
{
............................................

	map = zlox_elf_make_link_map(kmap->index, kmap->soname, vaddr, kmap->msize);
	if(map == ZLOX_NULL)
		return 0;
	
	ZLOX_ELF32_DYN * Dynamic = map->dyn.Dynamic;
        // 循环遍历Dynamic节,将NEEDED类型指定的动态库
        // 给加载到虚拟内存中
	while(Dynamic->d_tag != 0)
	{
		if(Dynamic->d_tag == ZLOX_DT_NEEDED)
		{
                // NEEDED类型的d_un.d_val值是strtab(字符串表)里的                
                // 偏移值,通过Dynamic->d_un.d_val + map->dyn.strtab
                // 就可以得到动态库的名称字符串
    			ZLOX_CHAR * tmp_soname = (ZLOX_CHAR *)(Dynamic->d_un.d_val + map->dyn.strtab);
                // tmp_vaddr里存储着动态库需要被加载到的虚拟内存地址
                // 新的动态库需要被放置在之前加载的最后一个动态库的结束
                // 地址的下一页
			ZLOX_UINT32 tmp_vaddr = map->maplst->ptr[map->maplst->count - 1].vaddr;
			tmp_vaddr += map->maplst->ptr[map->maplst->count - 1].msize;
			tmp_vaddr = ZLOX_NEXT_PAGE_START(tmp_vaddr);
                // 通过递归调用zlox_load_shared_library函数来将
                // 依赖的动态链接库给加载到虚拟内存中
			if(zlox_load_shared_library(tmp_soname, tmp_vaddr) == 0)
			{
				zlox_monitor_write("shared library Error:load \"");
				zlox_monitor_write(tmp_soname);
				zlox_monitor_write("\" failed \n");
				return 0;
			}
		}
		Dynamic++;
	}
	return map->entry;
}

ZLOX_UINT32 zlox_load_elf(ZLOX_ELF32_EHDR *hdr,ZLOX_UINT8 * buf)
{
............................................

	ZLOX_UINT32 msize = (phdr[last_load].p_vaddr + phdr[last_load].p_memsz) - vaddr;
	ZLOX_ELF_LINK_MAP * map = zlox_elf_make_link_map(0, ZLOX_NULL, vaddr, msize);

	ret = hdr->e_entry;

	if(interp != ZLOX_NULL)
	{
		ret = zlox_load_shared_library(interp, ZLOX_ELF_LD_SO_VADDR);
		if(ret == 0)
			return 0;
		ZLOX_ELF32_DYN * Dynamic = map->dyn.Dynamic;
		if(Dynamic != 0)
                // 循环遍历Dynamic节,将NEEDED类型指定的动态库
                // 给加载到虚拟内存中
			while(Dynamic->d_tag != 0)
			{
				if(Dynamic->d_tag == ZLOX_DT_NEEDED)
				{
					ZLOX_CHAR * tmp_soname = (ZLOX_CHAR *)(Dynamic->d_un.d_val + map->dyn.strtab);
					ZLOX_UINT32 tmp_vaddr = map->maplst->ptr[map->maplst->count - 1].vaddr;
					tmp_vaddr += map->maplst->ptr[map->maplst->count - 1].msize;
					tmp_vaddr = ZLOX_NEXT_PAGE_START(tmp_vaddr);
					if(zlox_load_shared_library(tmp_soname, tmp_vaddr) == 0)
					{
						zlox_monitor_write("shared library Error:load \"");
						zlox_monitor_write(tmp_soname);
						zlox_monitor_write("\" failed \n");
						return 0;
					}
				}
				Dynamic++;
			}
		map->dyn.dl_runtime_resolve = map->maplst->ptr[1].dyn.dl_runtime_resolve;
	}

............................................
}


    有了Dynamic节的缓存信息,接下来,就可以根据该节的信息来进行相关的重定位工作了。

    在介绍zenglOX里重定位相关的代码之前,有必要先了解下动态链接库重定位的原理。

    ELF可执行文件与动态链接库文件都有两种代码模式,一种是常规的与虚拟内存位置相关的代码,还有一种是PIC(Position Independent Code 与虚拟内存位置无关的代码)。

    先通过下面的dyntest.c程式,来看下常规的与虚拟内存位置相关的代码:

// dyntest.c

int gltest = 0;

int dyntest()
{
	gltest = 2;
	return 2;
}

int dynfunc(int arg)
{
	gltest = arg;
	dyntest();
	return 0;
}


    我们可以通过下面的命令来生成libdyntest.so的动态库文件:

$ gcc -shared -o libdyntest.so dyntest.c -gstabs
$


    上面通过 -shared 选项就可以生成动态库文件,在没有使用 -fpic 选项时,所生成的是和虚拟内存位置相关的代码。

    我们可以通过objdump -S命令来查看该文件的反汇编代码(由于之前的gcc命令使用了-gstabs选项来加入调试信息,因此这里就可以使用objdump -S命令来查看源代码与反汇编的混合输出):

$ objdump -S libdyntest.so 
libdyntest.so:     file format elf32-i386

Disassembly of section .init:

............................................

000003dc :
// dyntest.c

int gltest = 0;

int dyntest()
{
 3dc:	55                   	push   %ebp
 3dd:	89 e5                	mov    %esp,%ebp
	gltest = 2;
 3df:	c7 05 00 00 00 00 02 	movl   $0x2,0x0
 3e6:	00 00 00 
	return 2;
 3e9:	b8 02 00 00 00       	mov    $0x2,%eax
}
 3ee:	5d                   	pop    %ebp
 3ef:	c3                   	ret    

000003f0 :

int dynfunc(int arg)
{
 3f0:	55                   	push   %ebp
 3f1:	89 e5                	mov    %esp,%ebp
	gltest = arg;
 3f3:	8b 45 08             	mov    0x8(%ebp),%eax
 3f6:	a3 00 00 00 00       	mov    %eax,0x0
	dyntest();
 3fb:	e8 fc ff ff ff       	call   3fc 
	return 0;
 400:	b8 00 00 00 00       	mov    $0x0,%eax
}
 405:	5d                   	pop    %ebp
 406:	c3                   	ret    
 407:	90                   	nop
 408:	90                   	nop
 409:	90                   	nop
 40a:	90                   	nop
 40b:	90                   	nop
 40c:	90                   	nop
 40d:	90                   	nop
 40e:	90                   	nop
 40f:	90                   	nop

............................................
$ 


    上面的输出显示,如果有全局变量,则在编译链接时,会将全局变量的内存地址统一使用0x0来代替,例如上面的 movl $0x2,0x0mov %eax,0x0 ,如果有全局函数,则全局函数在被调用时的内存偏移值,会使用call之类的指令的opcode的下一个字节的偏移值来代替,例如上面的call 3fc指令对应的十六进制为 e8 fc ff ff ff ,其中,e8是call指令的opcode(操作码),fc ff ff ff 则是0x3fc(opcode的下一个字节)与0x400(call指令的返回地址)的差值,call指令在调用函数时都是使用的目标位置与返回地址的差值。

    可以看出来,无论是全局变量还是全局函数,编译器和链接器都无法直接给出准确的内存地址,都只是使用一个具有象征意义的值来代替,这是因为,动态链接库可以被加载到虚拟内存的任意位置,它们无法在编译链接时就知道运行时的具体位置。

    因此,当libdyntest.so被加载到虚拟内存中后,要让这段指令能够被正常执行的话,就必须在加载后,对全局变量和全局函数的地址进行重定位操作,将上面提到的0x0之类的伪地址替换为当前任务中的真实地址。

    要完成这个替换工作的话,内核还必须要知道哪些地方需要进行替换,因为我们不可能对所有的指令都做一次扫描,编译链接器在编译链接时,会在动态链接库里生成一个重定位表格,同样可以通过 readelf -a 命令来查看到该表:

$ readelf -a libdyntest.so 
...................................................

Dynamic section at offset 0x47c contains 21 entries:
  Tag        Type                         Name/Value
...................................................
 0x00000011 (REL)                        0x274
 0x00000012 (RELSZ)                      56 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
...................................................

Relocation section '.rel.dyn' at offset 0x274 contains 7 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
...................................................  
000003e1  00000901 R_386_32          00001570   gltest
000003f7  00000901 R_386_32          00001570   gltest
000003fc  00000602 R_386_PC32        000003dc   dyntest
...................................................

$ 


    从上面的输出可以看到,在文件的0x274的偏移位置处有一个REL重定位表(也就是上面的.rel.dyn节),该表格显示出,在0x3e1与0x3f7偏移位置处,有两个R_386_32类型的需要被重定位的全局变量,我们只需要将Sym.Value里的0x00001570加上动态库的起始虚拟内存地址,得到的值写入到0x3e1与0x3f7处,替换掉原来的0x0即可。这里需要注意的是,这里涉及到的地址和偏移值都是相对于动态库的起始虚拟地址而言的,例如,假设libdyntest.so被加载到0x80000000的虚拟内存位置的话,那么这个REL表就会位于0x80000274的位置,同样的,0x00001570 需要加上 0x80000000 得到 0x80001570,然后将0x80001570写入到0x800003e1与0x800003f7位置处。

    如果是全局函数,如上面的dyntest,则REL表里的Type类型会显示R_386_PC32,对于全局函数的话,Sym.Value里的值只是个目标函数的地址,而call指令需要的是目标地址与返回地址之间的偏移值,因此,我们需要先将offset处的值加4得到返回地址,然后使用Sym.Value里的值减去返回地址,得到的偏移值再写入到offset处,例如,上面显示dyntest的Sym.Value的值为0x000003dc,offset为0x000003fc,因此,偏移值就是 0x3dc - (0x3fc + 4) ,然后将计算得到的偏移值写入到0x3fc处,以替换掉原来的链接器设置的伪偏移值。

    另外,REL重定位表里的每一项,本身只包含offset和Info字段,而Sym.Value与Sym.Name字段的值,则是通过Info字段里的值,再结合SYMTAB符号表与STRTAB字符串表里的信息得来的(在之前Dynamic节的缓存信息里就包含了这两个表的偏移地址)。在zenglOX中,REL表的每一项都是一个ZLOX_ELF32_REL结构体,该结构体也定义在zlox_elf.h头文件里:

typedef struct _ZLOX_ELF32_REL{
	ZLOX_ELF32_ADDR r_offset;
	ZLOX_UINT32   r_info;
} ZLOX_ELF32_REL;


    info字段的值又由两部分组成:低8位表示Type类型,其他位则用于表示SYMTAB符号表里的索引值,在zlox_elf.h头文件中就定义了两个宏以方便获取到这两个值:

#define ZLOX_ELF32_R_SYM(val)		((val) >> 8)
#define ZLOX_ELF32_R_TYPE(val)		((val) & 0xff)


    在zlox_elf.c文件的zlox_elf_reloc函数里包含了对R_386_32和R_386_PC32类型的重定位操作:

ZLOX_BOOL zlox_elf_reloc(ZLOX_ELF32_REL * rel, ZLOX_UINT32 relsz, ZLOX_ELF32_SYM * symtab, ZLOX_ELF_LINK_MAP * map)
{
.................................................
	for(ZLOX_UINT32 i = 0;i < relsz;i++)
	{
		ZLOX_UINT32 symindex = ZLOX_ELF32_R_SYM(rel[i].r_info);
		ZLOX_UINT8 type = ZLOX_ELF32_R_TYPE(rel[i].r_info);
		switch(type)
		{
		case ZLOX_R_386_32:
		case ZLOX_R_386_PC32:
.................................................
			{
				ZLOX_UINT32 * offset = (ZLOX_UINT32 *)(rel[i].r_offset + (map->index != 0 ? map->vaddr : 0));
				ZLOX_UINT32 value;
				if(symtab[symindex].st_value != 0)
				{
.................................................
					if(type == ZLOX_R_386_PC32)
						value = (value - ((ZLOX_UINT32)offset + 4)) & 0xFFFFFFFF;
					(*offset) = value;
				}
.................................................
}


    此外,如果全局变量或全局函数不在当前动态库里的话,REL表里的Sym.Value的值就会是0,此时就需要到其他的动态库模块里去搜索出这些全局变量或函数的虚拟内存地址来,然后将地址写入到当前模块的offset偏移位置处,上面的zlox_elf_reloc函数里也包含了这种情况:

ZLOX_BOOL zlox_elf_reloc(ZLOX_ELF32_REL * rel, ZLOX_UINT32 relsz, ZLOX_ELF32_SYM * symtab, ZLOX_ELF_LINK_MAP * map)
{
.................................................
	switch(type)
	{
	case ZLOX_R_386_32:
	case ZLOX_R_386_PC32:
.................................................
		if(symtab[symindex].st_value != 0)
		{
.................................................
			if(type == ZLOX_R_386_PC32)
				value = (value - ((ZLOX_UINT32)offset + 4)) & 0xFFFFFFFF;
			(*offset) = value;
		}
		else
		{
			ZLOX_CHAR * name = (ZLOX_CHAR *)(symtab[symindex].st_name + map->dyn.strtab);
			ZLOX_ELF32_SYM * retsym;
			ZLOX_ELF_LINK_MAP * retmap;
			if(zlox_elf_findsym_fromlst(name, map, &retsym, &retmap) == ZLOX_FALSE)
			{
				zlox_monitor_write("shared library Error:can't find symbol \"");
				zlox_monitor_write(name);
				zlox_monitor_write("\" \n");
				return ZLOX_FALSE;
			}
			if(retsym->st_value > ZLOX_EXECVE_ADDR && 
				retsym->st_value < ZLOX_ELF_LD_SO_VADDR)
				value = retsym->st_value;
			else
				value = retsym->st_value + (retmap->index != 0 ? retmap->vaddr : 0);
			zlox_page_copy((ZLOX_UINT32)offset);
			if(type == ZLOX_R_386_PC32)
				value = (value - ((ZLOX_UINT32)offset + 4)) & 0xFFFFFFFF;
			(*offset) = value;
		}
.................................................
}


    上面代码显示,当symtab[symindex].st_value值为0时,会调用zlox_elf_findsym_fromlst函数在其他的动态库里搜索name(全局变量或全局函数的名称字符串),返回的retsym符号信息里就包含了变量或函数的虚拟内存地址。

    上面介绍的与位置相关的代码会让动态库的共享属性大打折扣,因为,每个加载动态库的任务(或者叫进程),都需要对text代码段进行写入操作以完成重定位,这种写入操作会触发写时复制,动态库原本的一个目的是为了能让多个任务可以访问和执行同一个物理代码段,如果代码段被写时复制的话,那么每个任务的这段代码都会有自己的物理内存副本,就像静态库那样,从而失去了共享同一段物理内存里的代码的意义。

    所以,现在一般提倡使用PIC(Position Independent Code 与虚拟内存位置无关的代码),要使用这种方式,只需在编译时加入-fpic的选项即可:

$ gcc -fpic -shared -o libdyntest.so dyntest.c -gstabs
$


    下面我们先通过 readelf -a 命令来查看下libdyntest.so中gltest全局变量在REL重定位表里的情况:

$ readelf -a libdyntest.so 
.......................................................

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
.......................................................
  [17] .got              PROGBITS        0000156c 00056c 000010 04  WA  0   0  4
  [18] .got.plt          PROGBITS        0000157c 00057c 000018 04  WA  0   0  4
.......................................................

Relocation section '.rel.dyn' at offset 0x274 contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
.......................................................
00001574  00000906 R_386_GLOB_DAT    000015a0   gltest
.......................................................

$


    这一次REL表里gltest全局变量的需要写入的offset偏移位置0x1574位于section headers显示的.got表里,该表位于动态库的数据段,对数据段进行写入操作就不会破坏代码段的共享属性了,动态链接库的数据段在每个任务里本身就是各不相同的,因为每个任务都可以有自己的私有数据,因此我们可以放心的将Sym.Value里的0x15a0的值加上动态库的起始地址,得到的值再写入到0x1574加动态库的起始地址处,例如,假设动态库被加载到0x80000000的虚拟内存位置的话,那么,我们只需将0x15a0 + 0x80000000 = 0x800015a0的值写入到0x80001574的位置处即可。

    代码段里的指令也对应做了调整,从而可以通过.got表来访问全局变量,同样可以通过objdump -S命令来进行反汇编查看:

$ objdump -S libdyntest.so 

libdyntest.so:     file format elf32-i386

............................................

000003ec <dyntest>:
// dyntest.c

int gltest = 0;

int dyntest()
{
 3ec:	55                   	push   %ebp
 3ed:	89 e5                	mov    %esp,%ebp
 3ef:	e8 00 00 00 00       	call   3f4 <dyntest+0x8>
 3f4:	59                   	pop    %ecx
 3f5:	81 c1 88 11 00 00    	add    $0x1188,%ecx
	gltest = 2;
 3fb:	8b 81 f8 ff ff ff    	mov    -0x8(%ecx),%eax
 401:	c7 00 02 00 00 00    	movl   $0x2,(%eax)
	return 2;
 407:	b8 02 00 00 00       	mov    $0x2,%eax
}
 40c:	5d                   	pop    %ebp
 40d:	c3                   	ret    

............................................
$ 


    上面的dyntest函数里,先通过call 3f4 <dyntest+0x8>指令,跳转到下一条pop %ecx指令位置处,这样栈里就存储了pop %ecx处的实际的虚拟内存地址了,然后pop %ecx就可以将这个实际的虚拟内存地址存储到ECX寄存器中,接着再通过add $0x1188,%ecx指令,将ECX里的值加上0x1188,例如,假设libdyntest.so的动态库被加载到了0x80000000的虚拟内存位置处,那么 call 与 pop 指令执行完后,ECX里的值就会是0x800003f4,这样,接下来的add $0x1188,%ecx指令得到的ECX里的值就会是0x8000157c,这个值就是.got表的结束位置的值,再往后,mov -0x8(%ecx), %eax指令执行后,EAX寄存器中就存储了gltest在REL表里的offset值即0x80001574,最后,就可以通过movl $0x2,(%eax)指令将2设置到gltest全局变量里了。

    对位置无关的全局变量进行的重定位操作与之前的位置相关的重定位操作都是相同的,只不过前者是对数据段进行写入操作,而后者则是直接对代码段进行写入操作,在zlox_elf.c文件的zlox_elf_reloc函数里对这两种重定位操作都放在了一起:

ZLOX_BOOL zlox_elf_reloc(ZLOX_ELF32_REL * rel, ZLOX_UINT32 relsz, ZLOX_ELF32_SYM * symtab, ZLOX_ELF_LINK_MAP * map)
{
.........................................
	switch(type)
	{
	case ZLOX_R_386_32:
	case ZLOX_R_386_PC32:
	case ZLOX_R_386_GLOB_DAT:
.........................................
}


    上面的ZLOX_R_386_32与ZLOX_R_386_PC32类型是直接对代码段进行写入操作,ZLOX_R_386_GLOB_DAT类型则是对数据段的.got表进行写入操作。

    下面我们再来看下PIC的代码中,REL表里的全局函数的情况,同样的,我们可以先通过 readelf -a 命令来查看这些重定位表:

$ readelf -a libdyntest.so 
.............................................

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
.................................................
  [17] .got              PROGBITS        0000156c 00056c 000010 04  WA  0   0  4
  [18] .got.plt          PROGBITS        0000157c 00057c 000018 04  WA  0   0  4
.................................................

Dynamic section at offset 0x4ac contains 20 entries:
.................................................
  Tag        Type                         Name/Value
 0x00000003 (PLTGOT)                     0x157c
 0x00000002 (PLTRELSZ)                   24 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x29c

Relocation section '.rel.dyn' at offset 0x274 contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
.................................................
00001574  00000906 R_386_GLOB_DAT    000015a0   gltest
.................................................

Relocation section '.rel.plt' at offset 0x29c contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00001588  00000107 R_386_JUMP_SLOT   00000000   __gmon_start__
0000158c  00000607 R_386_JUMP_SLOT   000003ec   dyntest
00001590  00000a07 R_386_JUMP_SLOT   00000000   __cxa_finalize
$


    与之前介绍的位置相关的代码所不同的地方在于,dyntest不再位于'.rel.dyn'的REL表里了,而是位于'.rel.plt'的JMPREL表里了,从Dynamic节缓存的JMPREL的值可以定位到.rel.plt表,在该表里,dyntest的offset值为0x158c,Sym.Value的值为0x3ec,这样当libdyntest.so被加载到0x80000000位置处时,就需要将0x800003ec写入到0x8000158c的位置处,而0x8000158c是位于.got.plt表里的,该表与上面介绍的.got表一样,也是位于动态库的数据段部分,因此对该位置进行写入操作就不会影响到动态库的代码段的共享属性,.got.plt表可以通过Dynamic节里缓存的PLTGOT的值来定位到。

    如果全局函数位于其他的动态库模块里,则Sym.Value的值会是0x0,在对这种全局函数进行写入操作之前,我们必须先了解PIC模式的动态库是如何调用外部的全局函数的,同样,我们先通过objdump -S命令来查看下libdyntest.so的反汇编指令:

$ objdump -S libdyntest.so 

libdyntest.so:     file format elf32-i386

............................................

Disassembly of section .plt:

000002e4 <__gmon_start__@plt-0x10>:
 2e4:	ff b3 04 00 00 00    	pushl  0x4(%ebx)
 2ea:	ff a3 08 00 00 00    	jmp    *0x8(%ebx)
 2f0:	00 00                	add    %al,(%eax)
	...

000002f4 <__gmon_start__@plt>:
 2f4:	ff a3 0c 00 00 00    	jmp    *0xc(%ebx)
 2fa:	68 00 00 00 00       	push   $0x0
 2ff:	e9 e0 ff ff ff       	jmp    2e4 <_init+0x30>

00000304 <dyntest@plt>:
 304:	ff a3 10 00 00 00    	jmp    *0x10(%ebx)
 30a:	68 08 00 00 00       	push   $0x8
 30f:	e9 d0 ff ff ff       	jmp    2e4 <_init+0x30>

00000314 <__cxa_finalize@plt>:
 314:	ff a3 14 00 00 00    	jmp    *0x14(%ebx)
 31a:	68 10 00 00 00       	push   $0x10
 31f:	e9 c0 ff ff ff       	jmp    2e4 <_init+0x30>

Disassembly of section .text:

............................................

int dynfunc(int arg)
{
 40e:	55                   	push   %ebp
 40f:	89 e5                	mov    %esp,%ebp
 411:	53                   	push   %ebx
 412:	83 ec 04             	sub    $0x4,%esp
 415:	e8 00 00 00 00       	call   41a <dynfunc+0xc>
 41a:	5b                   	pop    %ebx
 41b:	81 c3 62 11 00 00    	add    $0x1162,%ebx
	gltest = arg;
 421:	8b 83 f8 ff ff ff    	mov    -0x8(%ebx),%eax
 427:	8b 55 08             	mov    0x8(%ebp),%edx
 42a:	89 10                	mov    %edx,(%eax)
	dyntest();
 42c:	e8 d3 fe ff ff       	call   304 <dyntest@plt>
	return 0;
 431:	b8 00 00 00 00       	mov    $0x0,%eax
}
 436:	83 c4 04             	add    $0x4,%esp
 439:	5b                   	pop    %ebx
 43a:	5d                   	pop    %ebp
 43b:	c3                   	ret    
 43c:	90                   	nop
 43d:	90                   	nop
 43e:	90                   	nop
 43f:	90                   	nop   

............................................
$ 


    无论是本模块里的全局函数还是其他动态库里的全局函数,其调用过程都是一样的,上面的输出显示,当调用dyntest全局函数时,会先通过call 304 <dyntest@plt>指令,进入.plt节里定义的dyntest@plt函数处,该函数的第一条jmp *0x10(%ebx)指令中,EBX寄存器里存储了.got.plt表的内存位置,如果dyntest位于当前的动态库时,则0x10(%ebx)存储的就是dyntest全局函数的地址,jmp *0x10(%ebx)就可以直接跳转到目标函数去执行。

    如果dyntest位于其他的动态库里的话(即.rel.plt表里对应的Sym.Value值为0x0时),我们就必须将.rel.plt表对应的offset处的值设置为jmp *0x10(%ebx)的下一条指令的内存地址,这样,jmp *0x10(%ebx)执行后,就会跳转到下一条push $0x8的指令处,push $0x8指令里的0x8是dyntest在.rel.plt表里的偏移值,接着再通过jmp 2e4 <_init+0x30>跳转到0x2e4的偏移位置处,所有外部模块的全局函数在没经过ld.so的解析之前,最后都会统一跳转到这里。

    在0x2e4位置处,会先通过pushl 0x4(%ebx)指令将.rel.plt表的第2项内容压入栈(该项里存储了加载动态库时写入的link map指针值,我们之前介绍过,在zenglOX里每个动态库都对应有一个ZLOX_ELF_LINK_MAP结构),最后的jmp *0x8(%ebx)指令中,0x8(%ebx)对应为.rel.plt表的第3项内容(该项里存储了ld.so的_dl_runtime_resolve函数地址,该地址也是我们在加载动态库时写入的),jmp指令可以根据该地址跳转到ld.so里的_dl_runtime_resolve函数去解析出实际的函数地址来,ld.so的_dl_runtime_resolve函数在解析完后,会将函数的实际地址写入到.rel.plt的offset处,这样下一次再调用该全局函数时,就可以直接跳转到目标函数去执行了。

    我们可以通过下面两幅图来加深理解:


图1

    上图显示的是当外部的全局函数没有被解析之前的跳转过程(这里假设dyntest是外部的全局函数)。
 
   
图2

    上图显示的是当外部的全局函数经过ld.so解析后,并将解析的地址写入到了.rel.plt表对应的offset位置处后,call指令跳转到dyntest@plt时,会直接jmp跳转到目标函数去执行。

   在zlox_elf.c文件的zlox_elf_reloc函数里,也包含了对这种.rel.plt表里的全局函数进行重定位的代码:

ZLOX_BOOL zlox_elf_reloc(ZLOX_ELF32_REL * rel, ZLOX_UINT32 relsz, ZLOX_ELF32_SYM * symtab, ZLOX_ELF_LINK_MAP * map)
{
	relsz = relsz / sizeof(ZLOX_ELF32_REL);
	for(ZLOX_UINT32 i = 0;i < relsz;i++)
	{
		ZLOX_UINT32 symindex = ZLOX_ELF32_R_SYM(rel[i].r_info);
		ZLOX_UINT8 type = ZLOX_ELF32_R_TYPE(rel[i].r_info);
		switch(type)
		{
.......................................................
		case ZLOX_R_386_JMP_SLOT:
			{
				ZLOX_UINT32 * offset = (ZLOX_UINT32 *)(rel[i].r_offset + (map->index != 0 ? map->vaddr : 0));
				ZLOX_UINT32 value;
				if(symtab[symindex].st_value != 0)
				{
					if(symtab[symindex].st_value > ZLOX_EXECVE_ADDR && 
						symtab[symindex].st_value < ZLOX_ELF_LD_SO_VADDR)
						value = symtab[symindex].st_value;
					else
						value = symtab[symindex].st_value + (map->index != 0 ? map->vaddr : 0);
					zlox_page_copy((ZLOX_UINT32)offset);
					(*offset) = value;
				}
				else
				{
					value = (*offset) + (map->index != 0 ? map->vaddr : 0);
					zlox_page_copy((ZLOX_UINT32)offset);
					(*offset) = value;
				}
			}
			break;
		}
	}
	return ZLOX_TRUE;
}


    上面代码里的ZLOX_R_386_JMP_SLOT类型就是针对.rel.plt表中的全局函数的,如果symtab[symindex].st_value对应的符号项里的值不为0的话,就将该值写入到offset指定的偏移位置处(如果是动态库的话,符号项里的值还需要加上动态库的起始虚拟地址,而ELF可执行文件的该符号值是固定的,就无需添加起始地址)。

    如果symtab[symindex].st_value对应的符号项里的值等于0的话,就说明该全局函数是其他依赖的动态库里的,那么当map->index != 0时,即该全局函数是在某个动态库里被调用时,就将offset的值加上map->vaddr(即当前动态库的起始虚拟地址),得到的value值再写回offset偏移位置处。

    在上面的zlox_elf_reloc函数中,还有一个ZLOX_R_386_COPY类型的重定位操作,该重定位类型主要用于当ELF可执行文件里引用外部动态库里的全局变量时才用的,而且ELF可执行文件需要是非PIC的代码,才会出现该类型,例如下面的test.c程式:

// test.c

int dynfunc(int arg);

extern int gltest;

int main()
{
	gltest = 8;
	dynfunc(3);
	return 0;
}


    test.c程式里会通过extern关键字来引用libdyntest.so动态库里的gltest全局变量,我们可以使用如下命令来编译test.c程式:

$ gcc -o test test.c -L. -ldyntest -gstabs
$


    上面的gcc命令并没有使用-fpic选项,因此,生成的是非PIC的代码,在这种情况下,生成的test程式的REL重定位表里,就会出现ZLOX_R_386_COPY类型,我们可以使用 readelf -a 命令来查看:

$ readelf -a test
...........................................

Relocation section '.rel.dyn' at offset 0x320 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
...........................................
0804967c  00000a05 R_386_COPY        0804967c   gltest

...........................................
$ objdump -S test

...........................................

int main()
{
...........................................
	gltest = 8;
 8048485:	c7 05 7c 96 04 08 08 	movl   $0x8,0x804967c
...........................................
}
...........................................

$


    由于生成的是非PIC的指令代码,因此上面使用objdump -S命令输出显示的gltest = 8;语句对应的 movl   $0x8,0x804967c 的汇编指令里,就直接使用的是0x804967c的虚拟地址,而且REL表里offset的值并非movl $0x8, 0x804967c处的偏移位置,因此,我们不能对该ELF进行重定位操作,我们所能做的就是,在依赖的动态链接库里查找gltest全局变量,然后将找到的动态库的SYMTAB符号表里的gltest所对应的符号项里的值,给修改成ELF里指定的0x804967c,这样,所有的依赖动态库里,都将直接使用0x804967c来访问gltest全局变量,相当于将动态库里的gltest全局变量给重定位到ELF可执行文件里了。

    在zlox_elf.c文件的zlox_elf_reloc函数中就有对ZLOX_R_386_COPY类型的重定位操作:

ZLOX_BOOL zlox_elf_reloc(ZLOX_ELF32_REL * rel, ZLOX_UINT32 relsz, ZLOX_ELF32_SYM * symtab, ZLOX_ELF_LINK_MAP * map)
{
	relsz = relsz / sizeof(ZLOX_ELF32_REL);
	for(ZLOX_UINT32 i = 0;i < relsz;i++)
	{
		ZLOX_UINT32 symindex = ZLOX_ELF32_R_SYM(rel[i].r_info);
		ZLOX_UINT8 type = ZLOX_ELF32_R_TYPE(rel[i].r_info);
		switch(type)
		{
................................................
		case ZLOX_R_386_COPY:
			{
				ZLOX_UINT32 value = symtab[symindex].st_value + (map->index != 0 ? map->vaddr : 0);
				ZLOX_CHAR * name = (ZLOX_CHAR *)(symtab[symindex].st_name + map->dyn.strtab);
				ZLOX_ELF32_SYM * retsym;
				ZLOX_ELF_LINK_MAP * retmap;
				if(zlox_elf_findsym_fromlst(name, map, &retsym, &retmap) == ZLOX_FALSE)
				{
					zlox_monitor_write("shared library Error:can't find symbol \"");
					zlox_monitor_write(name);
					zlox_monitor_write("\" \n");
					return ZLOX_FALSE;
				}
				ZLOX_ELF32_ADDR * destaddr =  &retsym->st_value;
				zlox_page_copy((ZLOX_UINT32)destaddr);
				(*destaddr) = value;
			}
			break;
................................................
		}
	}
	return ZLOX_TRUE;
}


    上面的zlox_elf_reloc函数中,当遇到ZLOX_R_386_COPY类型时,会先根据symtab符号表与strtab字符串表找到REL表项所指定的全局变量名,例如上面提到的gltest等,然后再通过zlox_elf_findsym_fromlst函数在依赖的动态库里搜索name(全局变量名),返回的retsym符号结构里的st_value就是目标动态库里需要重定位的地方,最后将ELF里指定的虚拟地址(即上面函数里的value值)写入到st_value中即可,这样动态库在通过st_value进行重定位操作时,就会自动重定位到ELF可执行文件里了。

    当我们使用-fpic选项将test程式生成为PIC(与位置无关)的指令代码时,就不会看到ZLOX_R_386_COPY类型,取而代之的是R_386_GLOB_DAT类型:

$ gcc -fpic -o test test.c -L. -ldyntest -gstabs
$ readelf -a test
................................................

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
................................................
  [20] .got              PROGBITS        08049678 000678 000008 04  WA  0   0  4
  [21] .got.plt          PROGBITS        08049680 000680 000018 04  WA  0   0  4
................................................

Relocation section '.rel.dyn' at offset 0x320 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
................................................
0804967c  00000a06 R_386_GLOB_DAT    00000000   gltest
................................................
$


    从 readelf -a 命令的输出可以看到,gltest变为了R_386_GLOB_DAT类型,该类型上面介绍过,对应的offset位于数据段的.got表里,此时的Sym.Value值为0,这样在重定位时,就会从依赖的动态库里搜索出gltest的虚拟地址来,然后将动态库里的虚拟地址给写入到offset偏移位置处,从而让ELF可执行文件里的gltest全局变量给重定位到动态库里,这样也符合extern关键字向外引用的含义,在PIC的test程式里,所有访问gltest的操作都会根据.got表来重定位到动态库中。

    在动态库和可执行文件的strtab字符串表里,搜索字符串时,会用到hash(哈希表),哈希表可以加快字符串的搜索,哈希表的算法位于zlox_elf.c文件的zlox_elf_hash函数中:

ZLOX_UINT32 zlox_elf_hash(const ZLOX_CHAR *name)
{
	ZLOX_UINT32 h = 0, g;
	while (*name)
	{
		h = (h << 4) + *name++;
		if ((g = h & 0xf0000000))
			h ^= g >> 24;
		h &= ~g;
	}
	return h;
}


    上面的函数来源自 http://www.sco.com/developers/gabi/latest/ch5.dynamic.html#hash 链接,该链接里对动态库的结构,以及hash表的结构都做了详细的介绍。

    在zlox_elf.h头文件里,还有一个ZLOX_ELF_KERNEL_MAP结构体:

typedef struct _ZLOX_ELF_KERNEL_MAP{
	ZLOX_BOOL isValid;
	ZLOX_UINT32 index;
	ZLOX_CHAR soname[128];
	ZLOX_VOID * heap;
	ZLOX_UINT32 npage;
	ZLOX_UINT32 msize;
	ZLOX_UINT32 ref_count;
} ZLOX_ELF_KERNEL_MAP;

typedef struct _ZLOX_ELF_KERNEL_MAP_LIST{
    ZLOX_BOOL isInit;
    ZLOX_SINT32 count;
    ZLOX_SINT32 size;
    ZLOX_ELF_KERNEL_MAP * ptr;
} ZLOX_ELF_KERNEL_MAP_LIST;


    每个动态链接库在内核中都有一个唯一的ZLOX_ELF_KERNEL_MAP结构体与之相对应,这些ZLOX_ELF_KERNEL_MAP结构体都放置在一个动态数组中,该动态数组则由上面的ZLOX_ELF_KERNEL_MAP_LIST结构体进行管理和维护。

    zlox_elf.c文件里的zlox_add_elf_kernel_map函数负责为动态库创建一个ZLOX_ELF_KERNEL_MAP结构体,然后将该结构体放置到动态数组中:

ZLOX_ELF_KERNEL_MAP * zlox_add_elf_kernel_map(ZLOX_CHAR * soname, ZLOX_VOID * task, ZLOX_UINT32 vaddr, ZLOX_UINT32 msize)
{
	ZLOX_ELF_KERNEL_MAP_LIST * maps = &elf_kmaplst;
	if(!maps->isInit) // 如果没进行过初始化,则初始化kernel map list
	{
		maps->size = ZLOX_ELF_KERNEL_MAP_LIST_SIZE;
		maps->ptr = (ZLOX_ELF_KERNEL_MAP *)zlox_kmalloc(maps->size * sizeof(ZLOX_ELF_KERNEL_MAP));
		zlox_memset((ZLOX_UINT8 *)maps->ptr,0,maps->size * sizeof(ZLOX_ELF_KERNEL_MAP));	
		maps->count = 0;
		maps->isInit = ZLOX_TRUE;
	}
        // 当动态数组里的元素个数等于可用容量时,
        // 就对数组进行动态扩容
	else if(maps->count == maps->size)
	{
		ZLOX_ELF_KERNEL_MAP * tmp_ptr = maps->ptr;
		maps->size += ZLOX_ELF_KERNEL_MAP_LIST_SIZE;
		maps->ptr = (ZLOX_ELF_KERNEL_MAP *)zlox_kmalloc(maps->size * sizeof(ZLOX_ELF_KERNEL_MAP));
		zlox_memcpy((ZLOX_UINT8 *)maps->ptr,(ZLOX_UINT8 *)tmp_ptr, maps->count * sizeof(ZLOX_ELF_KERNEL_MAP));
		zlox_memset((ZLOX_UINT8 *)(maps->ptr + maps->count), 0,
				ZLOX_ELF_KERNEL_MAP_LIST_SIZE * sizeof(ZLOX_ELF_KERNEL_MAP));
	}
	
	ZLOX_SINT32 i;
	for(i = 0; i < maps->size ;i++)
	{
		if(maps->ptr[i].isValid == ZLOX_FALSE)
		{
			zlox_strcpy(maps->ptr[i].soname, soname);
                // 通过zlox_pages_map_to_heap函数,将动态链接库
                // 的虚拟内存页表映射到heap堆里,这样,所有依赖该
                // 动态链接库的任务都可以直接从该heap堆里将动态库的页表项
                // 映射到自己的页表项中,除了heap堆里的页表项的读写位为1外,
                // 其他所有依赖该动态链接库的任务的页表项的读写位都为0,这样,
                // 当对该动态库进行写入操作时,将会触发写时复制。
			maps->ptr[i].heap = zlox_pages_map_to_heap(task, vaddr, msize, ZLOX_TRUE, &maps->ptr[i].npage);
			maps->ptr[i].msize = msize;
			maps->ptr[i].index = i;
			maps->ptr[i].ref_count = 1;
			maps->ptr[i].isValid = ZLOX_TRUE;
			maps->count++;
			return &maps->ptr[i];
		}
	}
	
	zlox_monitor_write("shared library Error: when load \"");
	zlox_monitor_write(soname);
	zlox_monitor_write("\" can't find empty kernel map!\n");
	return ZLOX_NULL;
}


    同样的,棕色注释也是这里额外添加的。上面的zlox_add_elf_kernel_map函数里用到了一个zlox_pages_map_to_heap函数,该函数定义在zlox_paging.c文件中:

ZLOX_VOID * zlox_pages_map_to_heap(ZLOX_TASK * task, ZLOX_UINT32 svaddr, ZLOX_UINT32 size, ZLOX_BOOL clear_me_rw,
					ZLOX_UINT32 * ret_npage)
{
	ZLOX_PAGE_DIRECTORY * src_dir = task->page_directory;
	ZLOX_UINT32 s, stable_idx, spage_idx;
	ZLOX_UINT32 npage = 0,i;
	for(s = svaddr ; s < (svaddr + size); (s = ZLOX_NEXT_PAGE_START(s)) )
	{
		npage++;
	}
	ZLOX_PAGE * heap = (ZLOX_PAGE *)zlox_kmalloc(npage * sizeof(ZLOX_PAGE));
	for(i=0, s = svaddr; i < npage ; i++, (s = ZLOX_NEXT_PAGE_START(s)))
	{
		stable_idx = (s / 0x1000) / 1024;
		spage_idx = (s / 0x1000) % 1024;
		heap[i] = src_dir->tables[stable_idx]->pages[spage_idx];
		if(clear_me_rw)
			src_dir->tables[stable_idx]->pages[spage_idx].rw = 0;
	}
	if(clear_me_rw && (current_task->page_directory == src_dir))
		zlox_page_Flush_TLB();
	(*ret_npage) = npage;
	return (ZLOX_VOID *)heap;
}


    上面函数里的task参数为加载某动态库的任务,svaddr为动态库在task任务中的起始虚拟地址,size为动态库在虚拟内存空间中所占的尺寸大小,clear_me_rw参数表示在将动态库的页表映射到heap堆后,是否将task任务对应的页表的读写位清空为0,ret_npage参数用于存储动态库的页面数目。

    当某个任务加载了动态链接库后,其他任务也需要加载该动态库时,就可以直接通过zlox_paging.c文件里的zlox_pages_map函数来将堆中的页表项映射到自己的页表中:

ZLOX_SINT32 zlox_pages_map(ZLOX_UINT32 dvaddr, ZLOX_PAGE * heap, ZLOX_UINT32 npage)
{
	ZLOX_BOOL need_FlushTLB = ZLOX_FALSE;
	ZLOX_UINT32 d, dtable_idx, dpage_idx, i;
	for( d = dvaddr, i = 0 ; i < npage; (d = ZLOX_NEXT_PAGE_START(d)) , i++)
	{
		dtable_idx = (d / 0x1000) / 1024;
		if((current_directory->tables[dtable_idx] == 0) || 
			((current_directory->tablesPhysical[dtable_idx] & 0x2) == 0))
		{
			zlox_page_alloc(d, ZLOX_FALSE);
		}
		dpage_idx = (d / 0x1000) % 1024;
		current_directory->tables[dtable_idx]->pages[dpage_idx] = heap[i];
		current_directory->tables[dtable_idx]->pages[dpage_idx].rw = 0;
		need_FlushTLB = ZLOX_TRUE;
	}
	if(need_FlushTLB == ZLOX_TRUE && (current_task->page_directory == current_directory))
		zlox_page_Flush_TLB();
	return 0;
}


    上面的zlox_pages_map函数的第一个参数dvaddr表示需要将动态库映射到当前任务的dvaddr对应的虚拟地址处,第二个参数heap堆里存储了已加载的动态库的页表项,npage参数表示页表项的数目。

    函数里会根据dvaddr计算出该虚拟地址在当前页目录中的页表索引dtable_idex与页表项索引dpage_idx,然后直接将heap[i]页表项赋值过去,就可以将当前任务的虚拟内存地址映射到动态库的物理内存地址了,最后再将当前任务对应页表项的rw读写位清0,这样,当前任务在对动态库进行写入操作时就可以触发写时复制。

    当任务结束时,会通过zlox_elf.c文件的zlox_elf_free_lnk_maplst函数将可以释放的动态库的物理内存给释放掉:

ZLOX_SINT32 zlox_elf_free_lnk_maplst(ZLOX_ELF_LINK_MAP_LIST * maplst)
{
	ZLOX_ELF_LINK_MAP * tmp_map;
	for(ZLOX_SINT32 i = 1; i < maplst->count; i++)
	{
		tmp_map = maplst->ptr + i;
		ZLOX_UINT32 kmap_index = tmp_map->kmap_index;
		elf_kmaplst.ptr[kmap_index].ref_count = (elf_kmaplst.ptr[kmap_index].ref_count > 0 ? 
						(elf_kmaplst.ptr[kmap_index].ref_count - 1) : 0);
		if(elf_kmaplst.ptr[kmap_index].ref_count == 0)
		{
			ZLOX_PAGE * page = (ZLOX_PAGE *)elf_kmaplst.ptr[kmap_index].heap;
			ZLOX_UINT32 npage = elf_kmaplst.ptr[kmap_index].npage;
			for(ZLOX_UINT32 j=0; j < npage ;j++)
			{
				zlox_free_frame(page);
				page++;
			}
			zlox_kfree(elf_kmaplst.ptr[kmap_index].heap);
			zlox_memset((ZLOX_UINT8 *)&elf_kmaplst.ptr[kmap_index], 0, sizeof(ZLOX_ELF_KERNEL_MAP));
		}
	}
	return 0;
}


    每个动态库的ZLOX_ELF_KERNEL_MAP结构体中都有一个ref_count字段,用于表示该动态库的任务引用数,当ref_count减为0时,则表示没有任务需要该动态库了,就可以通过zlox_free_frame函数将heap堆里的页表项中的物理内存给释放掉。

    在当前版本里,还将任务的内核栈从堆里移动到了固定的0xF0000000的虚拟内存地址处,这样可以方便维护和管理,用户栈的起始栈顶位置还是0xE0000000,如果用户栈溢出的话,可以直接从分页错误中捕获到:

/* zlox_paging.c Defines the interface for and structures relating to paging.*/

ZLOX_VOID zlox_page_fault(ZLOX_ISR_REGISTERS * regs)
{
...................................................

	if (present)  
	{
		zlox_monitor_write("present ");
		if(faulting_address > 0xc0000000 && faulting_address < 0xE0000000)
		{
			zlox_monitor_write("and user stack overflow... ");
		}
	}
...................................................

	// 如果只是用户态程式出错,则只需结束掉当前任务,而无需 ZLOX_PANIC 挂掉整个系统
	if(regs->eip >= 0x8048000 && regs->eip < 0xc0000000)
	{
		zlox_exit(-1);
	}

	ZLOX_PANIC("Page fault");
}


    但是,当内核栈发生溢出时,由于分页错误异常也是使用的当前任务的内核栈,因此,分页错误会执行失败,从而触发double fault(双误异常),如果双误异常也得不到正确的处理的话,就会触发Triple Fault(三重错误),三重错误会直接导致重启。

    因此,为了能让double fault被正常处理,我们需要使用task gate,让该错误被触发时能够自动转到一个新的任务的内核栈中,task gate的触发和zenglOX普通任务的切换方式不同,普通的任务是通过软件方式来切换的,而task gate的任务切换则是处理器从硬件层面触发的切换。

    在英特尔手册的第2077页到第2078页,有task gate的详细介绍,我们可以参考下图来进行相关的设置:


图3

    上图位于英特尔手册的第2078页,我们可以参考该图,先创建一个TSS结构体的变量,然后在GDT里创建一个TSS Descriptor描述符,并将TSS结构体的指针写入到TSS Descriptor中,最后在double fault所在的IDT项目内设置一个Task Gate,并让该Task Gate指向GDT的TSS Descriptor描述符即可,IDT中的Task Gate描述符的结构如下图所示(该图位于手册的第2077页):
 

图4

    上图的Reserved区域是保留的,直接用0填充即可,GDT的TSS Descriptor的选择子需要写入到Tss Segment Selector中。

    和task gate相关的代码主要位于zlox_descriptor_tables.c文件里:

/*zlox_descriptor_tables.c Initialises the GDT and IDT, 
  and defines the default ISR and IRQ handler.*/

...............................................

ZLOX_TSS_ENTRY tss_entry;
ZLOX_TSS_ENTRY tss_entry_double_fault;

...............................................

// Initialises the GDT (global descriptor table)
static ZLOX_VOID zlox_init_gdt()
{
...............................................
	zlox_write_tss_db_fault(6, 0x10, 0x0);
...............................................
}

...............................................

// Initialise our task state segment structure for double fault.
static ZLOX_VOID zlox_write_tss_db_fault(ZLOX_SINT32 num, ZLOX_UINT16 ss0, ZLOX_UINT32 esp0)
{
    // Firstly, let's compute the base and limit of our entry into the GDT.
    ZLOX_UINT32 base = (ZLOX_UINT32) &tss_entry_double_fault;
    ZLOX_UINT32 limit = sizeof(tss_entry_double_fault) - 1;
    
    // Now, add our TSS descriptor's address to the GDT.
    zlox_gdt_set_gate(num, base, limit, 0xE9, 0x00);

    // Ensure the descriptor is initially zero.
    zlox_memset((ZLOX_UINT8 *)&tss_entry_double_fault, 0, sizeof(tss_entry_double_fault));

    tss_entry_double_fault.ss0  = ss0;  // Set the kernel stack segment.
    tss_entry_double_fault.esp0 = esp0; // Set the kernel stack pointer.
    
    tss_entry_double_fault.cs   = 0x08;
    tss_entry_double_fault.ss = tss_entry_double_fault.ds = tss_entry_double_fault.es = tss_entry_double_fault.fs
	 = tss_entry_double_fault.gs = 0x10;
    tss_entry_double_fault.eip = (ZLOX_UINT32)_zlox_isr_8;
}

...............................................

// Initialise the interrupt descriptor table.
static ZLOX_VOID zlox_init_idt()
{
...............................................
	zlox_write_task_gate_db_fault(8, 0xE5);
...............................................
}

...............................................

static ZLOX_VOID zlox_write_task_gate_db_fault(ZLOX_UINT8 num, ZLOX_UINT8 flags)
{
	ZLOX_UINT32 base = 0;
	ZLOX_UINT16 sel = 0x33;
	zlox_idt_set_gate(num, base, sel, flags);
}


    在代码的开头定义了一个新的TSS即tss_entry_double_fault变量,该变量专用于double fault,里面有与常规任务的TSS不同的内核栈(当前版本直接使用的初始化任务的内核栈),接着通过zlox_write_tss_db_fault函数将新的TSS描述符写入到GDT表的最后一项中,最后再通过zlox_write_task_gate_db_fault(8, 0xE5);函数在IDT里设置好double fault的task gate描述符,并让该描述符通过0x33的选择子指向GDT的新TSS描述符。

    double fault对应的异常中断号为8,当发生double fault时,处理器会以硬件方式切换到新的TSS的环境下,并跳转到_zlox_isr_8函数去执行,该函数执行到最后,会调用zlox_paging.c文件的zlox_double_fault函数,来将出错时的详细信息给输出显示出来:

ZLOX_VOID zlox_double_fault(ZLOX_ISR_REGISTERS * regs)
{
	ZLOX_UNUSED(regs);

	zlox_monitor_write("double fault! following is some info dumps: \n");
	if(tss_entry.esp > 0xE0000000 && tss_entry.esp < tss_entry.esp0)
	{
		zlox_monitor_write("*** kernel stack overflow! *** \n");
	}
	zlox_monitor_write("cs : ");
	zlox_monitor_write_hex(tss_entry.cs);
	zlox_monitor_write("  eip: ");
	zlox_monitor_write_hex(tss_entry.eip);
	zlox_monitor_write("\nss : ");
	zlox_monitor_write_hex(tss_entry.ss);
	zlox_monitor_write("  esp: ");
	zlox_monitor_write_hex(tss_entry.esp);
	zlox_monitor_write("  ebp: ");
	zlox_monitor_write_hex(tss_entry.ebp);
	zlox_monitor_write("\nss0: ");
	zlox_monitor_write_hex(tss_entry.ss0);
	zlox_monitor_write("  esp0: ");
	zlox_monitor_write_hex(tss_entry.esp0);
	zlox_monitor_write("\nds : ");
	zlox_monitor_write_hex(tss_entry.ds);
	zlox_monitor_write("  fs : ");
	zlox_monitor_write_hex(tss_entry.fs);
	zlox_monitor_write("\ngs : ");
	zlox_monitor_write_hex(tss_entry.gs);
	zlox_monitor_write("  es : ");
	zlox_monitor_write_hex(tss_entry.es);
	zlox_monitor_write("\neflags: ");
	zlox_monitor_write_hex(tss_entry.eflags);
	zlox_monitor_write("  cr3: ");
	zlox_monitor_write_hex(tss_entry.cr3);
	zlox_monitor_write("\neax: ");
	zlox_monitor_write_hex(tss_entry.eax);
	zlox_monitor_write("  ebx: ");
	zlox_monitor_write_hex(tss_entry.ebx);
	zlox_monitor_write("\necx: ");
	zlox_monitor_write_hex(tss_entry.ecx);
	zlox_monitor_write("  edx: ");
	zlox_monitor_write_hex(tss_entry.edx);
	zlox_monitor_write("\nesi: ");
	zlox_monitor_write_hex(tss_entry.esi);
	zlox_monitor_write("  edi: ");
	zlox_monitor_write_hex(tss_entry.edi);
	zlox_monitor_write("\n\n");
	ZLOX_PANIC("system halt");
}


    可以看到,该函数会将保存在tss_entry里的寄存器值都显示出来(当处理器硬件切换到tss_entry_double_fault时,原任务的环境信息会被自动保存在tss_entry里)。

    在当前版本里,增加了一个syscall_overflow_test的专门测试内核栈溢出的系统调用,可以在zenglOX的命令行下,通过testoverflow程式来进行测试:


图5

    如果给testoverflow添加-u参数的话,就可以测试用户栈溢出,用户栈溢出时只会结束掉当前的任务,不会像内核栈溢出那样直接当掉整个内核:
 

图6

    最后,原来的ps程式,新增了一个-d参数,通过该参数就可以显示出每个任务所加载的动态链接库的情况:
 

图7

    以上就是当前版本的相关内容。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
 
上下篇

下一篇: zenglOX v1.4.0与v1.4.1 通过ATA驱动读写硬盘里的数据, BUG修复, VirtualBox与VMware的调试功能

上一篇: zenglOX v1.2.0 ISO 9660文件系统

相关文章

zenglOX v3.2.0 USB v1.1, UHCI, USB KeyBoard, USB Mouse

zenglOX v1.4.0与v1.4.1 通过ATA驱动读写硬盘里的数据, BUG修复, VirtualBox与VMware的调试功能

zenglOX v2.4.0 DMA(Direct Memory Access)

zenglOX v1.1.0 通过ATAPI读取光盘里的数据

zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

zenglOX v2.3.0 移植zengl嵌入式脚本语言