龙芯Mips平台vmlinux文件分析

  • 一、文件格式
    • 1. 文件类型
    • 2. 使用场景
  • 二、结构内容描述
    • 1. ELF header(ELF 头)
    • 2. 字符串表表项 Entry
    • 3. 读取字符串表 Section 的内容
    • 4. 读取代码段的内容
    • 5. Program header
  • 三、.head.text 文本段
  • 参考链接

一、文件格式

首先,vmlinux 属于 ELF(Executable and Linkable Format) ⽂件,要想了解如何启动 vmlinux,⾸先需要知道 ELF 的格式。

  • text段
    代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定。

  • data段
    数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

  • bss段
    通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态内存分配。

  • init段
    linux定义的一种初始化过程中才会用到的段,一旦初始化完成,那么这些段所占用的内存会被释放掉,后续会继续说明。

1. 文件类型

在 Linux 系统中,一个 ELF 文件主要用来表示 3 种类型的文件:

  1. 可执行文件(EXEC)
  2. 目标文件(.o)
  3. 共享文件(.so)

vmlinux文件头部内容中,就存在一个字段,用来表示:当前这个 ELF 文件,它到底是一个什么类型的文件,可以通过readelf -h vmlinux命令查看到mips平台上的这个vmlinux文件是一个可执行文件(EXEC)

user$ readelf -h vmlinux
ELF 头:Magic:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类别:                              ELF64数据:                              2 补码,小端序 (little endian)版本:                              1 (current)OS/ABI:                            UNIX - System VABI 版本:                          0类型:                              EXEC (可执行文件)系统架构:                          MIPS R3000版本:                              0x1入口点地址:              0xffffffff81b9b740程序头起点:              64 (bytes into file)Start of section headers:          258099400 (bytes into file)标志:             0x80000001, noreorder, mips64r2本头的大小:       64 (字节)程序头大小:       56 (字节)Number of program headers:         2节头大小:         64 (字节)节头数量:         34字符串表索引节头: 33

2. 使用场景

既然vmlinux文件有3 种类型,那么肯定是在 3 种不同的场合下被使用:

  1. 可执行文件:被操作系统中的加载器从硬盘上读取,载入到内存中去执行;
  2. 目标文件:被链接器读取,用来产生一个可执行文件或者共享库文件;
  3. 共享库文件:在动态链接的时候,由 ld-linux.so 来读取;

对于链接器而言,关心 ELF header, Sections 以及 Section header table 这 3 部分内容:

对于加载器只关心 ELF header, Program header tableSegment 这 3 部分内容。

对于中间部分的 Sections, 它改了个名字,叫做 Segments(段)。换汤不换药,本质上都是一样的。
可以理解为:一个 Segment 可能包含一个或者多个 Sections,就像下面这样:

这就好比超市里的货架上摆放的商品:有矿泉水、可乐、啤酒,巧克力,牛肉干,薯片。

从理货员的角度看:它们属于 6 种不同的商品;但是从超市经理的角度看,它们只属于 2 类商品:饮料和零食。

二、结构内容描述

在 Linux 系统中,会有不同的数据结构来描述上面所说的每部分内容,该文件为:usr/include/elf.h

  • 描述 ELF header 的结构体:

    #define EI_NIDENT (16)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;
    
  • 描述 Program header table 的结构体:

    /* Program segment header.  */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;
    
  • 描述 Section header table 的结构体:

    /* Section header.  */typedef struct
    {Elf64_Word sh_name;        /* Section name (string tbl index) */Elf64_Word sh_type;        /* Section type */Elf64_Xword   sh_flags;       /* Section flags */Elf64_Addr   sh_addr;        /* Section virtual addr at execution */Elf64_Off    sh_offset;      /* Section file offset */Elf64_Xword    sh_size;        /* Section size in bytes */Elf64_Word   sh_link;        /* Link to another section */Elf64_Word sh_info;        /* Additional section information */Elf64_Xword sh_addralign;       /* Section alignment */Elf64_Xword  sh_entsize;     /* Entry size if section holds table */
    } Elf64_Shdr;
    

1. ELF header(ELF 头)

头部内容,就相当于是一个总管,它决定了这个完整的 ELF 文件内部的所有信息,比如:

这是一个 ELF 文件;
一些基本信息:版本,文件类型,机器类型;
Program header table(程序头表)的开始地址,在整个文件的什么地方;
Section header table(节头表)的开始地址,在整个文件的什么地方;

可以通过readelf -h vmlinux命令查看到mips平台上的vmlinux的header信息

user$ readelf -h vmlinux
ELF 头:Magic:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类别:                              ELF64数据:                              2 补码,小端序 (little endian)版本:                              1 (current)OS/ABI:                            UNIX - System VABI 版本:                          0类型:                              EXEC (可执行文件)系统架构:                          MIPS R3000版本:                              0x1入口点地址:              0xffffffff81b9b740程序头起点:              64 (bytes into file)Start of section headers:          258099400 (bytes into file)标志:             0x80000001, noreorder, mips64r2本头的大小:       64 (字节)程序头大小:       56 (字节)Number of program headers:         2节头大小:         64 (字节)节头数量:         34字符串表索引节头: 33

在一个 ELF 文件中,存在很多个 Sections/Segments,这些 Sections 的具体信息,是在 Program header table 或者 Section head table 中进行描述的。

例如 Section head table

假如一个 ELF 文件中一共存在 4 个 Section: .text.rodata.data.bss,那么在 Section head table 中,将会有 4 个 Entry(条目)来分别描述这 4 个 Section 的具体信息(严格来说,不止 4 个 Entry,因为还存在一些其他辅助的 Sections),就像下面这样:

前面看到的vmlinux文件ELF header 部分的内容,一共是 64 个字节,查看开头的这 64 个字节码。
用命令:od -Ax -t x1 -N 64 vmlinux来读取vmlinux中的字节码。

-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
-N 52:只需要读取 52 个字节;

user$ od -Ax -t x1 -N 64 vmlinux
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 02 00 08 00 01 00 00 00 40 b7 b9 81 ff ff ff ff
000020 40 00 00 00 00 00 00 00 c8 48 62 0f 00 00 00 00
000030 01 00 00 80 40 00 38 00 02 00 40 00 22 00 21 00
000040

这 64 个字节的内容,你可以对照上面的结构体中每个字段来解释了。
首先看一下前 16 个字节。
在结构体中的第一个成员是 unsigned char e_ident[EI_NIDENT]EI_NIDENT 的长度是 16,代表了 EL header 中的开始 16 个字节,具体含义如下(数据内容可能不对,主要看含义):

官方文档对于这部分的解释是:

关于大端、小端格式,这个vmlinux 文件中显示的是 1,代表mips平台是小端格式。
关于大小端模式可以看下面的图片

  • 大端模式

  • 小端模式

    剩下的 48 个字节(64 - 16 = 48),的字节码含义:

  • 16 - 31 字节:

  • 32 - 47 字节:

  • 48 - 63 个字节:

2. 字符串表表项 Entry

在一个 ELF 文件中,存在很多字符串,例如:变量名、Section名称、链接器加入的符号等等,这些字符串的长度都是不固定的,因此用一个固定的结构来表示这些字符串,肯定是不现实的。

于是:把这些字符串集中起来,统一放在一起,作为一个独立的 Section 来进行管理。

在文件中的其他地方呢,如果想表示一个字符串,就在这个地方写一个数字索引:表示这个字符串位于字符串统一存储地方的某个偏移位置,经过这样的按图索骥,就可以找到这个具体的字符串了。

比如说啊,下面这个空间中存储了所有的字符串:

在程序的其他地方,如果想引用字符串 “hello,world!”,那么就只需要在那个地方标明数字 13 就可以了,表示:这个字符串从偏移 13 个字节处开始。

在 ELF header 的最后 2 个字节是 0x21 0x00,它对应结构体中的成员 e_shstrndx,意思是这个 ELF 文件中,字符串表是一个普通的 Section,在这个 Section 中,存储了 ELF 文件中使用到的所有的字符串。

既然是一个 Section,那么在 Section header table 中,就一定有一个表项 Entry 来描述它,那么是哪一个表项呢?

这就是 0x21 0x00 这个表项,也就是第 33 个表项。
这里,我们还可以用指令 readelf -S vmlinux 来看一下这个 ELF 文件中所有的 Section 信息:

uos$ readelf -S vmlinux
There are 34 section headers, starting at offset 0xf6248c8:节头:[号] 名称              类型             地址              偏移量大小              全体大小          旗标   链接   信息   对齐[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .text             PROGBITS         ffffffff80200000  0000400000000000019b8524  0000000000000000  AX       0     0     64[ 2] __ex_table        PROGBITS         ffffffff81bb8530  019bc5300000000000006400  0000000000000000   A       0     0     8[ 3] .notes            NOTE             ffffffff81bbe930  019c29300000000000000054  0000000000000000   A       0     0     4[ 4] .rodata           PROGBITS         ffffffff81bbf000  019c3000000000000032ec50  0000000000000000  WA       0     0     256[ 5] .pci_fixup        PROGBITS         ffffffff81eedc50  01cf1c5000000000000031c8  0000000000000000   A       0     0     8[ 6] __ksymtab         PROGBITS         ffffffff81ef0e18  01cf4e1800000000000161b0  0000000000000000   A       0     0     8[ 7] __ksymtab_gpl     PROGBITS         ffffffff81f06fc8  01d0afc80000000000012250  0000000000000000   A       0     0     8[ 8] __kcrctab         PROGBITS         ffffffff81f19218  01d1d218000000000000586c  0000000000000000   A       0     0     4[ 9] __kcrctab_gpl     PROGBITS         ffffffff81f1ea84  01d22a840000000000004894  0000000000000000   A       0     0     4[10] __ksymtab_strings PROGBITS         ffffffff81f23318  01d2731800000000000318ab  0000000000000000   A       0     0     1[11] __param           PROGBITS         ffffffff81f54bc8  01d58bc800000000000046c8  0000000000000000   A       0     0     8[12] __modver          PROGBITS         ffffffff81f59290  01d5d2900000000000000d70  0000000000000000   A       0     0     8[13] .data             PROGBITS         ffffffff81f5a000  01d5e00000000000000a7160  0000000000000000  WA       0     0     64[14] .data..page_align PROGBITS         ffffffff82004000  01e080000000000000010000  0000000000000000  WA       0     0     16384[15] .init.text        PROGBITS         ffffffff82014000  01e180000000000000081f40  0000000000000000  AX       0     0     64[16] .init.data        PROGBITS         ffffffff82096000  01e9a000000000000001e880  0000000000000000  WA       0     0     256[17] .exit.text        PROGBITS         ffffffff820b4880  01eb88800000000000003e40  0000000000000000  AX       0     0     64[18] .data..percpu     PROGBITS         ffffffff820bc000  01ec0000000000000000b560  0000000000000000  WA       0     0     64[19] .bss              NOBITS           ffffffff820d0000  01ecb560000000000114d5e0  0000000000000000  WA       0     0     16384[20] .mdebug.abi64     PROGBITS         ffffffff8321d5e0  01ecb5600000000000000000  0000000000000000           0     0     1[21] .comment          PROGBITS         0000000000000000  01ecb560000000000000002f  0000000000000001  MS       0     0     1[22] .gnu.attributes   GNU_ATTRIBUTES   0000000000000000  01ecb58f0000000000000010  0000000000000000           0     0     1[23] .debug_aranges    MIPS_DWARF       0000000000000000  01ecb5a000000000000241f0  0000000000000000           0     0     16[24] .debug_info       MIPS_DWARF       0000000000000000  01eef790000000000abb1ae8  0000000000000000           0     0     1[25] .debug_abbrev     MIPS_DWARF       0000000000000000  0caa127800000000004a58ca  0000000000000000           0     0     1[26] .debug_line       MIPS_DWARF       0000000000000000  0cf46b420000000000ae322e  0000000000000000           0     0     1[27] .debug_frame      MIPS_DWARF       0000000000000000  0da29d700000000000228770  0000000000000000           0     0     8[28] .debug_str        MIPS_DWARF       0000000000000000  0dc524e000000000002dfb06  0000000000000001  MS       0     0     1[29] .debug_loc        MIPS_DWARF       0000000000000000  0df31fe60000000000d34671  0000000000000000           0     0     1[30] .debug_ranges     MIPS_DWARF       0000000000000000  0ec6666000000000005ad8b0  0000000000000000           0     0     16[31] .symtab           SYMTAB           0000000000000000  0f213f10000000000023aab0  0000000000000018          32   61785     8[32] .strtab           STRTAB           0000000000000000  0f44e9c000000000001d5d9a  0000000000000000           0     0     1[33] .shstrtab         STRTAB           0000000000000000  0f62475a0000000000000168  0000000000000000           0     0     1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),p (processor specific)

其中的第 33 个 Section,描述的正是字符串表 Section:

  [33] .shstrtab         STRTAB           0000000000000000  0f62475a0000000000000168  0000000000000000           0     0     1

可以看出来:这个 Section 在 ELF 文件中的偏移地址是 0x0f62475a,长度是 0x0000000000000168个字节。

3. 读取字符串表 Section 的内容

下面,我们从 ELF header 的二进制数据中,来推断这信息。

要想打印字符串表 Section 的内容,就必须知道这个 Section 在 ELF 文件中的偏移地址。

要想知道偏移地址,只能从 Section head table 中第 33 个表项描述信息中获取。

要想知道第 33 个表项的地址,就必须知道 Section head table 在 ELF 文件中的开始地址,以及每一个表项的大小。

正好最后这 2 个需求信息,在 ELF header 中都告诉我们了。

ELF header 中的第 40 到 47 字节内容是:c8 48 62 0f 00 00 00 00(注意这里的字节序,低位在前),表示的就是 Section head table 在 ELF 文件中的开始地址(e_shoff)。

0x0f6248c8 = 258099400,也就是说 Section head table 的开始地址位于 ELF 文件的第 258099400 个字节处。

知道了开始地址,再来算一下第 33 个表项 Entry 的地址。

ELF header 中的第 52、53 字节内容是:40 00,表示每个表项的长度是 0x0040 = 64 个字节。

注意这里的计算都是从 0 开始的,因此第 33 个表项的开始地址就是:258099400 + 33 * 64 = 258101512,也就是说用来描述字符串表这个 Section 的表项,位于 ELF 文件的 258101512 字节的位置。

既然知道了这个表项 Entry 的地址,那么就扒开来看一下其中的二进制内容:

执行指令:od -Ad -t x1 -j 258101512 -N 64 vmlinux

其中的 -j 258101512 选项,表示跳过前面的 258101512 个字节,也就是我们从 vmlinux 这个 ELF 文件的 258101512 字节处开始读取,一共读 64 个字节。

uos$ od -Ad -t x1 -j 258101512 -N 64 vmlinux
258101512 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00
258101528 00 00 00 00 00 00 00 00 5a 47 62 0f 00 00 00 00
258101544 68 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
258101560 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
258101576

这 64 个字节的内容,就对应了 Elf32_Shdr 结构体中的每个成员变量:

/* Section header.  */typedef struct
{Elf64_Word sh_name;        /* Section name (string tbl index) */Elf64_Word sh_type;        /* Section type */Elf64_Xword   sh_flags;       /* Section flags */Elf64_Addr   sh_addr;        /* Section virtual addr at execution */Elf64_Off    sh_offset;      /* Section file offset */Elf64_Xword    sh_size;        /* Section size in bytes */Elf64_Word   sh_link;        /* Link to another section */Elf64_Word sh_info;        /* Additional section information */Elf64_Xword sh_addralign;       /* Section alignment */Elf64_Xword  sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;

这里主要关注一下以下 4 个字段:

sh_name: ;
sh_type:表示这个 Section 的类型,3 表示这是一个 string table;

sh_offset: 表示这个 Section,在 ELF 文件中的偏移量。0x0f62475a = 258099034,意思是字符串表这个 Section 的内容,从 ELF 文件的 258099034 个字节处开始;

sh_size:表示这个 Section 的长度。0x00000168 = 360 个字节,意思是字符串表这个 Section 的内容,一共有 360 个字节。

与之前使用 readelf 工具,读取到字符串表 Section 在 ELF 文件中的偏移地址是 0x0f62475a,长度是 0x000168 个字节吗

  [33] .shstrtab         STRTAB           0000000000000000  0f62475a0000000000000168  0000000000000000           0     0     1

既然知道了字符串表这个 Section 在 ELF 文件中的偏移量以及长度,那么执行指令: od -Ad -t c -j 258099034 -N 360 vmlinux,就可以把它的字节码内容读取出来。

uos@uos-PC:/media/uos/data/project/mips-kernel/loongson-kernel$ od -Ad -t c -j 258099034 -N 360 vmlinux
258099034  \0   .   s   y   m   t   a   b  \0   .   s   t   r   t   a   b
258099050  \0   .   s   h   s   t   r   t   a   b  \0   _   _   e   x   _
258099066   t   a   b   l   e  \0   .   n   o   t   e   s  \0   .   r   o
258099082   d   a   t   a  \0   .   p   c   i   _   f   i   x   u   p  \0
258099098   _   _   k   s   y   m   t   a   b  \0   _   _   k   s   y   m
258099114   t   a   b   _   g   p   l  \0   _   _   k   c   r   c   t   a
258099130   b  \0   _   _   k   c   r   c   t   a   b   _   g   p   l  \0
258099146   _   _   k   s   y   m   t   a   b   _   s   t   r   i   n   g
258099162   s  \0   _   _   p   a   r   a   m  \0   _   _   m   o   d   v
258099178   e   r  \0   .   d   a   t   a   .   .   p   a   g   e   _   a
258099194   l   i   g   n   e   d  \0   .   i   n   i   t   .   t   e   x
258099210   t  \0   .   i   n   i   t   .   d   a   t   a  \0   .   e   x
258099226   i   t   .   t   e   x   t  \0   .   d   a   t   a   .   .   p
258099242   e   r   c   p   u  \0   .   b   s   s  \0   .   m   d   e   b
258099258   u   g   .   a   b   i   6   4  \0   .   c   o   m   m   e   n
258099274   t  \0   .   g   n   u   .   a   t   t   r   i   b   u   t   e
258099290   s  \0   .   d   e   b   u   g   _   a   r   a   n   g   e   s
258099306  \0   .   d   e   b   u   g   _   i   n   f   o  \0   .   d   e
258099322   b   u   g   _   a   b   b   r   e   v  \0   .   d   e   b   u
258099338   g   _   l   i   n   e  \0   .   d   e   b   u   g   _   f   r
258099354   a   m   e  \0   .   d   e   b   u   g   _   s   t   r  \0   .
258099370   d   e   b   u   g   _   l   o   c  \0   .   d   e   b   u   g
258099386   _   r   a   n   g   e   s  \0
258099394

这个 Section 中存储的全部是字符串

刚才没有解释 sh_name 这个字段,它表示字符串表这个 Section 本身的名字,既然是名字,那一定是个字符串。

但是这个字符串不是直接存储在这里的,而是存储了一个索引,索引值是 0x00000011,也就是十进制数值 17。

现在我们来数一下字符串表 Section 内容中,第 17 个字节开始的地方,存储的正是:“.shstrtab” 这个字符串(\0是字符串的分隔符)。

4. 读取代码段的内容

从下面的代码(指令:readelf -S vmlinux):

user@$ readelf -S vmlinux
There are 34 section headers, starting at offset 0xf6248c8:节头:[号] 名称              类型             地址              偏移量大小              全体大小          旗标   链接   信息   对齐[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .text             PROGBITS         ffffffff80200000  0000400000000000019b8524  0000000000000000  AX       0     0     64[ 2] __ex_table        PROGBITS         ffffffff81bb8530  019bc5300000000000006400  0000000000000000   A       0     0     8[ 3] .notes            NOTE             ffffffff81bbe930  019c29300000000000000054  0000000000000000   A       0     0     4[ 4] .rodata           PROGBITS         ffffffff81bbf000  019c3000000000000032ec50  0000000000000000  WA       0     0     256[ 5] .pci_fixup        PROGBITS         ffffffff81eedc50  01cf1c5000000000000031c8  0000000000000000   A       0     0     8[ 6] __ksymtab         PROGBITS         ffffffff81ef0e18  01cf4e1800000000000161b0  0000000000000000   A       0     0     8[ 7] __ksymtab_gpl     PROGBITS         ffffffff81f06fc8  01d0afc80000000000012250  0000000000000000   A       0     0     8[ 8] __kcrctab         PROGBITS         ffffffff81f19218  01d1d218000000000000586c  0000000000000000   A       0     0     4[ 9] __kcrctab_gpl     PROGBITS         ffffffff81f1ea84  01d22a840000000000004894  0000000000000000   A       0     0     4[10] __ksymtab_strings PROGBITS         ffffffff81f23318  01d2731800000000000318ab  0000000000000000   A       0     0     1[11] __param           PROGBITS         ffffffff81f54bc8  01d58bc800000000000046c8  0000000000000000   A       0     0     8[12] __modver          PROGBITS         ffffffff81f59290  01d5d2900000000000000d70  0000000000000000   A       0     0     8[13] .data             PROGBITS         ffffffff81f5a000  01d5e00000000000000a7160  0000000000000000  WA       0     0     64[14] .data..page_align PROGBITS         ffffffff82004000  01e080000000000000010000  0000000000000000  WA       0     0     16384[15] .init.text        PROGBITS         ffffffff82014000  01e180000000000000081f40  0000000000000000  AX       0     0     64[16] .init.data        PROGBITS         ffffffff82096000  01e9a000000000000001e880  0000000000000000  WA       0     0     256[17] .exit.text        PROGBITS         ffffffff820b4880  01eb88800000000000003e40  0000000000000000  AX       0     0     64[18] .data..percpu     PROGBITS         ffffffff820bc000  01ec0000000000000000b560  0000000000000000  WA       0     0     64[19] .bss              NOBITS           ffffffff820d0000  01ecb560000000000114d5e0  0000000000000000  WA       0     0     16384[20] .mdebug.abi64     PROGBITS         ffffffff8321d5e0  01ecb5600000000000000000  0000000000000000           0     0     1[21] .comment          PROGBITS         0000000000000000  01ecb560000000000000002f  0000000000000001  MS       0     0     1[22] .gnu.attributes   GNU_ATTRIBUTES   0000000000000000  01ecb58f0000000000000010  0000000000000000           0     0     1[23] .debug_aranges    MIPS_DWARF       0000000000000000  01ecb5a000000000000241f0  0000000000000000           0     0     16[24] .debug_info       MIPS_DWARF       0000000000000000  01eef790000000000abb1ae8  0000000000000000           0     0     1[25] .debug_abbrev     MIPS_DWARF       0000000000000000  0caa127800000000004a58ca  0000000000000000           0     0     1[26] .debug_line       MIPS_DWARF       0000000000000000  0cf46b420000000000ae322e  0000000000000000           0     0     1[27] .debug_frame      MIPS_DWARF       0000000000000000  0da29d700000000000228770  0000000000000000           0     0     8[28] .debug_str        MIPS_DWARF       0000000000000000  0dc524e000000000002dfb06  0000000000000001  MS       0     0     1[29] .debug_loc        MIPS_DWARF       0000000000000000  0df31fe60000000000d34671  0000000000000000           0     0     1[30] .debug_ranges     MIPS_DWARF       0000000000000000  0ec6666000000000005ad8b0  0000000000000000           0     0     16[31] .symtab           SYMTAB           0000000000000000  0f213f10000000000023aab0  0000000000000018          32   61785     8[32] .strtab           STRTAB           0000000000000000  0f44e9c000000000001d5d9a  0000000000000000           0     0     1[33] .shstrtab         STRTAB           0000000000000000  0f62475a0000000000000168  0000000000000000           0     0     1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),p (processor specific)

可以看到代码段是位于第 1 个表项中,加载(虚拟)地址是 ffffffff80200000,它位于 ELF 文件中的偏移量是 00004000,长度是 00000000019b8524 个字节。

首先计算这个表项 Entry 的地址:258099400 + 1 * 64 = 258099464

然后读取这个表项 Entry,读取指令是 od -Ad -t x1 -j 258099464 -N 64 vmlinux:

user@$ od -Ad -t x1 -j 258099464 -N 64 vmlinux
258099464 ac 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00
258099480 00 00 20 80 ff ff ff ff 00 40 00 00 00 00 00 00
258099496 24 85 9b 01 00 00 00 00 00 00 00 00 00 00 00 00
258099512 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
258099528
typedef struct
{Elf64_Word sh_name;        /* Section name (string tbl index) */Elf64_Word sh_type;        /* Section type */Elf64_Xword   sh_flags;       /* Section flags */Elf64_Addr   sh_addr;        /* Section virtual addr at execution */Elf64_Off    sh_offset;      /* Section file offset */Elf64_Xword    sh_size;        /* Section size in bytes */Elf64_Word   sh_link;        /* Link to another section */Elf64_Word sh_info;        /* Additional section information */Elf64_Xword sh_addralign;       /* Section alignment */Elf64_Xword  sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;

同样的,我们也只关心下面这 5 个字段内容:

sh_name: 这回应该清楚了,表示代码段的名称在字符串表 Section 中的偏移位置。0xac = 172 字节,也就是在字符串表 Section 的第 172 字节处,存储的就是代码段的名字。回过头去找一下,看一下是不是字符串 “.text”;

sh_type:表示这个 Section 的类型,1(SHT_PROGBITS) 表示这是代码;

sh_addr:表示这个 Section 加载的虚拟地址是 0xffffffff80200000,这个值与 ELF header 中的 e_entry 字段的值是相同的;

sh_offset: 表示这个 Section,在 ELF 文件中的偏移量。0x00000040 = 64,意思是这个 Section 的内容,从 ELF 文件的 64 个字节处开始;

sh_size:表示这个 Section 的长度。0x00000000019b8524 = 26969380 个字节,意思是代码段一共有 26969380 个字节。

以上这些分析结构,与指令 readelf -S main 读取出来的完全一样!

PS: 在查看字符串表 Section 中的字符串时,可以计算一下:字符串表的开始地址是 258099034(十进制),加上 172,结果就是 258099206,所以从 258099206 开始的地方,就是代码段的名称,也就是 “.text”。

知道了以上这些信息,我们就可以读取代码段的字节码了.使用指令:od -Ad -t x1 -j 64 -N 26969380 vmlinux 即可。

5. Program header

文章的开头,链接器和加载器读取vmlinx的结果是不同的。

先用 readelf 这个工具来从总体上看一下 vmlinux 文件中的所有段信息。

执行指令:readelf -l vmlinux,得到以下代码:

user$ readelf -l vmlinuxElf 文件类型为 EXEC (可执行文件)
Entry point 0xffffffff81b9b740
There are 2 program headers, starting at offset 64程序头:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignLOAD           0x0000000000004000 0xffffffff80200000 0xffffffff802000000x0000000001ec7560 0x000000000301d5e0  RWE    0x4000NOTE           0x00000000019c2930 0xffffffff81bbe930 0xffffffff81bbe9300x0000000000000054 0x0000000000000054  R      0x4Section to Segment mapping:段节...00     .text __ex_table .notes .rodata .pci_fixup __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl __ksymtab_strings __param __modver .data .data..page_aligned .init.text .init.data .exit.text .data..percpu .bss 01     .notes

显示的信息已经很明白了:

  1. 这是一个可执行程序;
  2. 入口地址是 0xffffffff81b9b740;
  3. 一共有 2 个 Program header,是从 ELF 文件的 64 个偏移地址开始的;
  4. 此段对应的 section 为.head.text .text .got.plt…,所以 vmlinux 的入口在 .head.text 文本段。

三、.head.text 文本段

通过 vmlinux.lds.S 找到 vmlinux 的入口函数。具体分析如下:

/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/asm-offsets.h>
#include <asm/thread_info.h>#define PAGE_SIZE _PAGE_SIZE/** Put .bss..swapper_pg_dir as the first thing in .bss. This will* ensure that it has .bss alignment (64K).*/
#define BSS_FIRST_SECTIONS *(.bss..swapper_pg_dir)#include <asm-generic/vmlinux.lds.h>#undef mips
#define mips mips
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)

根据链接脚本语法,可以知道 OUTPUT_ARCH 关键字指定了链接之后的输出文件的体系结构是 mips。ENTRY 关键字指定了输出文件 vmlinux 的入口 地址是 kernel_entry, 因此只需找到 kernel_entry 的定义就可以知道 vmlinux 的入口函数。
参考我之前写过的基于Mips平台的Linux Kernel启动过程可知

内核的初始入口位于 arch/mips/kernel/head.S 中的 kernel_entry 。严格来说,kernel_entry 只是非压缩版原始内核的执行入口点(编译内核产生的 ELF可执行内核文件叫 vmlinux ,即非压缩版的原始内核;将 vmlinux 压缩以后再加上一个新的 ELF 头就得到压缩版内核 vmlinuz ;BIOS 既可以启动压缩版内核,也可以启动原始内核,龙芯平台启动的是压缩版内核)。

之后便是内核启动过程了。

参考链接

Linux 系统中编译、链接的基石 - ELF 文件:扒开它的层层外衣,从字节码的粒度来探索
一文搞懂 | 内核的启动

龙芯Mips平台vmlinux文件分析相关推荐

  1. 麒麟 mips mysql_中标麒麟OS+龙芯MIPS适配开源中间件

    大纲 一 背景 二 目标 三 服务器 四 中间件适配 五 应用 六 部署 七 风险 八总结 九 最后rpm 制作 一 背景 19年国家开始筹备和实施安可工程.我司有幸参与其中. 二 目标 适配安装OS ...

  2. 基于龙芯2K1000平台CL1606AD760-8驱动调试总结

    历经两周终于将设计的AD采集板驱动起来,并且读取电流值成功,唯不是一个突破!由硬件设计再到驱动,一步步设计改变:到设备树添加驱动并加载,总结主要分为两方面,一个驱动调试和硬件调试.细节如下: 驱动调试 ...

  3. 龙芯Fedora21平台制作docker镜像,并且解决vi乱码问题

    http://ask.loongnix.org/?/article/81 实验环境   本文的实验都是在龙芯3A3000机器上. 操作系统是loongnix(Fedora21)20170726版本, ...

  4. 龙芯Fedora21平台制作feodra21-tools docker镜像

    http://ask.loongnix.org/?/article/82 在按照本文的方式制作镜像之前,首先要先创建基础镜像fedora21-base,可以参考: http://ask.loongni ...

  5. 龙芯Fedora21平台制作feodra21-loongson-app docker镜像

    http://ask.loongnix.org/?/article/86 在按照本文的方式制作镜像之前,首先要先创建基础镜像feodra21-apache-php-mysql,可以参考: http:/ ...

  6. 龙芯电脑平台kubernetes集群编译及部署方案

    http://ask.loongnix.org/?/article/105  一.环境 操作系统: loongnix 内核: 3.10.84 go版本: go1.9.2 linux/mips64le ...

  7. 龙芯(mips)+麒麟V10 electron适配

    此适配基于龙芯+uos环境,可见我上一篇文章https://blog.csdn.net/qq_33174891/article/details/120722310 说明:龙芯麒麟v10 node可安装 ...

  8. 奔跑吧!中国芯,北京迅为龙芯处理器平台

    龙芯是中国科学院计算所自主研发的通用CPU,采用简单指令集,类似于MIPS指令集.龙芯1号的频率为266MHz,最早在2002年开始使用.龙芯2号的频率最高为1GHz.龙芯3A是首款国产商用4核处理器 ...

  9. 龙芯3B1500平台,源码安装nodejs过程中失败问题

    http://ask.loongnix.org/?/question/91 你好,我在龙芯3B1500,安装已经移植成功的Nodejs-4.3.1 ,完全按照社区步骤走的. 机器上gcc版本4.8.3 ...

最新文章

  1. 浅析网站优化知识自学从哪些方面开始起步
  2. jvm七:数组创建本质
  3. mysql数据库设计的原则_MySQL数据库设计原则
  4. 腾讯数据解读:95后基本不爱用微信,98%都是成年人,社交应用存在巨大机会...
  5. RPOPLPUSH用法(转)
  6. 计算机模拟技术在教学上的应用,计算机模拟技术在水利工程学科试验教学中的应用...
  7. uboot源码——gd_t和bd_t数据结构
  8. drawer的用法_MMDrawerController抽屉侧边栏的简单使用
  9. html5操作类名API——classlist
  10. 如何在java中实现线程_用代码说话:如何在Java中实现线程
  11. mysql中exeits用法_MySQL 8 在 Windows 下安装及使用
  12. 用Java实现HTTP文件队列下载
  13. Oracle→表、表字段数据类型、表DDL语句、数据DML语句、约束、case...when、decode
  14. postman导出请求url_postman接口测试之复制多个接口或collections到某个子文件夹或collections下...
  15. IOS检查更新的方法
  16. Swift App启动干了什么事情, 删掉UIApplicationMain,自定义实现main类, Main Runloop
  17. Latex中导入VISIO图片
  18. 塑胶模具注塑常用哪些材质
  19. 滴滴开源基于金融场景的Vuejs组件库Mand Mobile
  20. c语言递推兔子繁殖问题,兔子繁殖问题(斐波那契数列)

热门文章

  1. php crypt mysql password_php crypt函数加密和解密的实例分享
  2. 如何设计数据库(数据库设计原则)
  3. "北京成功故事"系列报道之四:巴伐利亚啤酒酿造师在北京
  4. SM3加密与解密校验
  5. UWB测距原理及实现
  6. Vue实现登录功能全套详解(含封装axios)
  7. uniapp 自定义启动图
  8. 树莓派初始设置无法连接WiFi,设置路由器信道可解决
  9. 中国军人本色!(感动)
  10. Ubuntu菜鸟入门—— 有道词典安装