StephenChan's Tech Space 潜心修炼

6Jun/100

ELF文件格式说明

ELF文件类型

ELF文件主要分为三种类型:

  • 可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
  • 可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
  • 共享目标文件(Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。

ELF文件的数据表示

ELF文件头结构及相关常数被定义在"/usr/include/elf.h"里。ELF目标文件中的所有数据结构都遵从自然大小和对齐规则。如果必要,数据结构可以包含显式的补齐,例如为了确保4字节对象按4字节边界对齐。数据对齐同样适用于文件内部。下面为ELF中常用的数据格式:

名称 大小 对齐 描述
Elf32_Addr 4 4 无符号程序地址
Elf32_Half 2 2 无符号短整型
Elf32_Off 4 4 无符号偏移地址
Elf32_Sword 4 4 有符号整型
Elf32_Word 4 4 有符号整型

ELF除了32位版还有64位版本,数据类型的名称和大小也相应地变化(Elf64_Addr...)。

5Jun/100

可执行文件的装载

对于32位平台下的4G的虚拟空间,在默认情况下,Linux操作系统将进程的虚拟地址空间划分为两部分,其中操作系统本身用去了一部分:从地址0xC0000000到0xFFFFFFFF,共1GB。剩下的从0x00000000地址开始到0xBFFFFFFF共3GB的空间都是进程使用的。而对于Windows操作系统来说,它的进程虚拟地址空间划分是操作系统占用2GB。

覆盖装入(Overlay)和页映射(Paging)是两种很类型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中。

覆盖载入在没有发明虚拟存储之前使用比较广泛,现在已经几乎被淘汰了。覆盖载入的方法是把挖掘内存潜力的任务交给了程序员,即是程序员在编写代码的同时要自己手动对程序模块进行内存和磁盘的切换,使不需要的代码切换出磁盘,腾出空间给需要执行的代码块。

与覆盖载入的原理相似,页映射也不是一下子就把程序的所有数据和指令都载入内存,而是将内存和所有磁盘中的数据和指令按照“页(Page)”为单位划分分成若干个页,以后所有的装载和操作的单位就是页。以目前的情况,硬件规定的页大小有4096字节、8192字节、2MB、4MB等。其实这个是属于操作系统存储管理的一部分,内存页的切换会使用先进先出、最少使用等等算法。

可执行文件的装载

可执行文件的装载和进程的建立大概经历3个步骤:

  • 首先是创建虚拟地址空间。一个虚拟空间是由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录(Page Directory)就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页错误的时候再进行设置。
  • 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。上面一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该“缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪个位置。
  • 将CPU指令寄存器设置成可执行文件入口,启动运行。这一步涉及了内核堆栈和用户堆栈的切换、CPU运行权限的切换等等。跳转到入口指令其实就是在ELF文件头中e_entry记录的可执行文件入口地址。

4Jun/100

URL映射

url映射是目前流行的框架都提供的基本功能,参考了django的url映射的源码,用python实现url映射的主要逻辑还是比较简单的。主要是由两个部分组成:

  • 将处理函数的命名空间解析成模块,使用__import__,导入模块,获取处理函数的引用(见get_callable());
  • 匹配访问路径,提取参数,使用正则表达式进行匹配。
# add url_base as namespace to handlers in url_patterns
def include(url_base, url_patterns):
    for index, src_pattern in enumerate(url_patterns):
        pattern = list(src_pattern)
        pattern[1] = "%s.%s" % (url_base, pattern[1])
        url_patterns[index] = tuple(pattern)
    return url_patterns

# get module name and func name
def get_mod_func(handler):
    try:
        dot = handler.rindex(".")
    except ValueError:
        return handler, ''
    return handler[:dot], handler[dot+1:]
Filed under: Python Continue reading
3Jun/100

Python的logging模块

初次使用logging模块觉得有点诡异,涉及到Logger、Handler、Level等概念,看代码最实际了:

import logging
import sys 

logger = logging.getLogger("endlesscode")
formatter = logging.Formatter('%(name)-12s %(asctime)s %(levelname)-8s %(message)s', '%a, %d %b %Y %H:%M:%S',)
file_handler = logging.FileHandler("test.log")
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stderr)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
#logger.setLevel(logging.ERROR)

logger.error("fuckgfw")

logger.removeHandler(stream_handler)
logger.error("fuckgov")

文档上已经说明得很清楚了,有几个比较特别的地方:

Filed under: Python Continue reading
3Jun/100

静态链接

链接有哪些过程?

假如有2个源文件如下,这两个源文件生成的目标文件链接成可执行文件后,涉及到空间和地址的分配、符号解析和重地位等等过程。

/* a.c */
extern int shared;

int main()
{
    int a = 100;
    swap( &a, &shared);
}
/* b.c */
int shared = 1;

void swap( int* a, int* b)
{
    *a ^= *b ^= *a ^= *b;
}

在a.c中引用到了b.c中的变量"shared"和函数"swap"。使用gcc编译成目标文件a.o和b.o之后,接着就需要将两个目标文件链接成一个可执行文件。

gcc -c a.c b.c

一般来说,链接过程分为两步:

Page 3 of 1312345678910...Last »