Blog

ELF段表的结构(Elf32_Shdr)

Content #

“Elf32_Shdr”又被称为段描述符(Section Descriptor)。 Elf32_Shdr被定义在“/usr/include/elf.h”。

typedef struct
{
  Elf32_Word    sh_name;
  Elf32_Word    sh_type;
  Elf32_Word    sh_flags;
  Elf32_Addr    sh_addr;  /* Section virtual addr at execution */
  Elf32_Off     sh_offset; /* Section file offset */
  Elf32_Word    sh_size;  /* Section size in bytes */
  Elf32_Word    sh_link;
  Elf32_Word    sh_info;
  Elf32_Word    sh_addralign;
  Elf32_Word    sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

sh_type #

#define SHT_PROGBITS	  1	/* Program data */
#define SHT_SYMTAB	  2	/* Symbol table */
#define SHT_STRTAB	  3	/* String table */
#define SHT_RELA	  4	/* Relocation entries with addends */
#define SHT_HASH	  5	/* Symbol hash table */
#define SHT_DYNAMIC	  6	/* Dynamic linking information */
#define SHT_NOTE	  7	/* Notes */
#define SHT_NOBITS	  8	/* Program space with no data (bss) */
#define SHT_REL		  9	/* Relocation entries, no addends */
#define SHT_SHLIB	  10	/* Reserved */
#define SHT_DYNSYM	  11	/* Dynamic linker symbol table */
#define SHT_INIT_ARRAY	  14	/* Array of constructors */
#define SHT_FINI_ARRAY	  15	/* Array of destructors */
#define SHT_PREINIT_ARRAY 16	/* Array of pre-constructors */
#define SHT_GROUP	  17	/* Section group */
#define SHT_SYMTAB_SHNDX  18	/* Extended section indices */

sh_flag #

#define SHF_WRITE	     (1 << 0)	/* Writable */
#define SHF_ALLOC	     (1 << 1)	/* Occupies memory during execution */
#define SHF_EXECINSTR	     (1 << 2)	/* Executable */
#define SHF_MERGE	     (1 << 4)	/* Might be merged */
#define SHF_STRINGS	     (1 << 5)	/* Contains nul-terminated strings */
#define SHF_INFO_LINK	     (1 << 6)	/* `sh_info' contains SHT index */
#define SHF_LINK_ORDER	     (1 << 7)	/* Preserve order after combining */
#define SHF_OS_NONCONFORMING (1 << 8)	/* Non-standard OS specific handling required */
#define SHF_GROUP	     (1 << 9)	/* Section is member of a group.  */
#define SHF_TLS		     (1 << 10)	/* Section hold thread-local data.  */
#define SHF_COMPRESSED	     (1 << 11)	/* Section with compressed data. */
...

段的链接信息(sh_link、sh_info) 如果段的类型是与链接相关的(不论是动态链接或静态链接),比如重定位表、符号表等,那么sh_link和sh_info这两个成员所包含的意义如下表所示。对于其他类型的段,这两个成员没有意义。

...

a.out魔数的由来

Content #

a.out格式的魔数为0x01、0x07,为什么会规定这个魔数呢?

UNIX早年是在PDP小型机上诞生的,当时的系统在加载一个可执行文件后直接从文件的第一个字节开始执行,人们一般在文件的最开始放置一条跳转(jump)指令,这条指令负责跳过接下来的7个机器字的文件头到可执行文件的真正入口。而0x01 0x07这两个字节刚好是当时PDP-11的机器的跳转7个机器字的指令。为了跟以前的系统保持兼容性,这条跳转指令被当作魔数一直被保留到了几十年后的今天。

From #

程序员的自我修养

将变量或代码放入指定的段中(GCC)

Content #

正常情况下,GCC编译出来的目标文件中,代码会被放到“.text”段,全局变量和静态变量会被放到“.data”和“.bss”段。但是有时候你可能希望变量或某些部分代码能够放到你所指定的段中去,以实现某些特定的功能。比如为了满足某些硬件的内存和I/O的地址布局,或者是像Linux操作系统内核中用来完成一些初始化和用户空间复制时出现页错误异常等。

GCC提供了一个扩展机制,使得程序员可以指定变量所处的段:

__attribute__((section("FOO"))) int global = 42;

__attribute__((section("BAR"))) void foo()
{
}

我们在全局变量或函数之前加上“_attribute_((section(“name”)))”属性就可以把相应的变量或函数放到以“name”作为段名的段中。

From #

程序员的自我修养

变量存放位置的小测试

Content #

现在让我们来做一个小的测试,请看以下代码:

static int x1 = 0;
static int x2 = 1;

x1和x2会被放在什么段中呢?

x1会被放在.bss中,x2会被放在.data中。

为什么一个在.bss段, 一个在.data段?

因为x1为0,可以认为是未初始化的,因为未初始化的都是0,所以被优化掉了可以放在.bss,这样可以节省磁盘空间,因为.bss不占磁盘空间。另外一个变量x2 初始化值为1,是初始化的,所以放在.data段中。

From #

程序员的自我修养

BSS段不占空间

Content #

一般C语言的编译后执行语句都编译成机器代码,保存在.text段;已初始化的全局变量和局部静态变量都保存在. data段;未初始化的全局变量和局部静态变量一般放在一个叫.“bss”的段里。

我们知道未初始化的全局变量和局部静态变量默认值都为0,本来它们也可以被放在.data段的,但是因为它们都是0,所以为它们在.data段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确是要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记为.bss段。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。

From #

程序员的自我修养

目标文件与可执行文件格式的小历史

Content #

目标文件与可执行文件格式跟操作系统和编译器密切相关,所以不同的系统平台下会有不同的格式,但这些格式又大同小异,目标文件格式与可执行文件格式的历史几乎是操作系统的发展史。

COFF是由Unix System V Release 3首先提出并且使用的格式规范,后来微软公司基于COFF格式,制定了PE格式标准,并将其用于当时的Windows NT系统。 System V Release 4在COFF的基础上引入了ELF格式,目前流行的Linux系统也以 ELF作为基本可执行文件格式。这也就是为什么目前PE和ELF如此相似的主要原因,因为它们都是源于同一种可执行文件格式COFF。

Unix最早的可执行文件格式为a.out格式,它的设计非常地简单,以至于后来共享库这个概念出现的时候,a.out格式就变得捉襟见肘了。于是人们设计了COFF 格式来解决这些问题,这个设计非常通用,以至于COFF的继承者到目前还在被广泛地使用。

COFF的主要贡献是在目标文件里面引入了“段”的机制,不同的目标文件可以拥有不同数量及不同类型的“段”。另外,它还定义了调试数据格式。

From #

程序员的自我修养

程序的指令和数据的分开存放的三个好处

Content #

为什么要那么麻烦,把程序的指令和数据的存放分开?混杂地放在一个段里面不是更加简单?其实数据和指令分段的好处有很多。主要有如下几个方面。

  1. 当程序被装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样可以防止程序的指令被有意或无意地改写。

  2. 对于现代的CPU来说,它们有着极为强大的缓存(Cache)体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局部性。现代CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。

  3. 最重要的原因,就是当系统中运行着多个该程序的副本时,它们的指令都是一样的,所以内存中只须要保存一份该程序的指令部分。对于指令这种只读的区域来说是这样,对于其他的只读数据也一样,比如很多程序里面带有的图标、图片、文本等资源也是属于可以共享的。当然每个副本进程的数据区域是不一样的,它们是进程私有的。不要小看这个共享指令的概念,它在现代的操作系统里面占据了极为重要的地位,特别是在有动态链接的系统中,可以节省大量的内存。

From #

程序员的自我修养

加载根文件系统

Content #

根文件系统挂在哪呢?挂在super_block[8]上。

Linux 0.11操作系统中只有一个super_block[8],每个数组元素是一个超级块,一个超级块管理一个逻辑设备,也就是说操作系统最多只能管理8个逻辑设备,其中只有一个根设备。加载根文件系统最重要的标志就是把根文件系统的根i节点挂在super_block[8]中根设备对应的超级块上。

可以说,加载根文件系统有三个主要步骤: 1)复制根设备的超级块到super_block[8]中,将根设备中的根i节点挂在 super_block[8]中对应根设备的超级块上。 2)将驻留缓冲区中16个缓冲块的根设备逻辑块位图、i节点位图分别挂接在 super_block[8]中根设备超级块的s_zmap[8]、 s_imap[8]上。 3)将当前进程的pwd、root指针指向根设备的根i节点。

From #

Linux内核设计的艺术

get_free_page()

Content #

遍历mem map[],找到主内存中(从高地址开始)第一个空闲页面 //代码路径:mm/memory.c:

#define LOW_MEM 0x100000
#define PAGING_MEMORY (15*1024*1024)
#define PAGING_PAGES (PAGING_MEMORY>>12)

unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

__asm__("std;repne;scasb\n\t"            //反向扫描串(mem map[]),al(0)与di不等则
                                            //重复(找引用对数为0的项)
        "jne 1f\n\t"                    //找不到空闲页,跳转到1
        "movb $1,1(%%edi)\n\t"          //将1赋给edi + 1的位置,在mem map[]中,
                                        //将找到0的项的引用计数置为1
        "sall $12,%%ecx\n\t"            // ecx算术左移12位,页的相对地址
        "addl %2,%%ecx\n\t"             // LOW_MEN + ecx,页的物理地址
        "movl %%ecx,%%edx\n\t"
        "movl $1024,%%ecx\n\t"
        "leal 4092(%%edx),%%edi\n\t"    //将edx + 4 KB的有效地址赋给edi
        "rep;stosl\n\t"             //将eax(即"0"(0))赋给edi指向的地址,目的是页面清零
        "movl %%edx,%%eax\n"
        "1:"
        :"=a" (__res) //%0
        :"0" (0),  //%1:eax初始值0(用于scasb比较)
        "i" (LOW_MEM), //%2:LOW_MEM
        "c" (PAGING_PAGES), //%3:ecx=PAGING_PAGES(总页数)
        "D" (mem_map + PAGING_PAGES-1)  //%4:edi指向mem map[]的最后一个元素
        :"di","cx","dx");                //第三个冒号后是程序中改变过的量
return __res;
}

From #

Linux内核设计的艺术

...

商业模式画布

Content #

问题场景:分析自身商业模式

图形结构:

基本解释及使用:商业模式画布可以非常方便地对公司的商业模式进行一个整体的梳理。它通过 9 个关键的因素来分析一个公司整体的脉络,这 9 个元素分别是 KP( Key Partnerships)关键合作伙伴、 KA(Key Activities)关键活动、 KR(Key Resources )关键资源、VP(Value Propositions)价值主张、 CR( Customer Relationships)客户关系、 CH(Channels) 渠道通路、 CS(Customer Segments)客户人群、CS( Cost Structures)成本结构、 RS(Revenue Streams)收入来源。

这个画布的最底层是公司的整体的收支逻辑,左侧是公司的组织能力,右侧是针对客户的价值主张和如何采取措施。你可以根据你个人、公司、部门的情况通过这个图把整个业务的逻辑梳理出来。

进一步分析:九个因素当中最强和最弱的元素是哪一个?如何发挥优势和补充弱势?

Viewpoints #

From #

26 | 数据分析:15种数据思维图(下)