CLL-目标文件的三魂七魄

本文基于一个简单的目标文件例子,来窥探目标文件的三魂七魄。


首先,展示一下例子的源码,了解一下这段代码做了些什么。代码中的注释部分进行了详细说明。
在这里插入图片描述

然后,通过gcc将main.c编译成main.o
在这里插入图片描述

注意:绿色部分的main.o文件大小是1896(0x768)。

由于目标文件涉及的知识点非常的多,本文不可能讲解所有细节,主要讲解目标文件的主干部分---“三魂七魄”。"三魂七魄"到底是什么鬼!!!

目标文件的整体轮廓

通过readelf命令先来感受一下main.o里面到底长啥样!
在这里插入图片描述

在这里插入图片描述

上面两张图中的ELF Header、Section Headers、Symbol table就是目标文件的"三魂"

ELF Header

ELF文件类型

ELF(Executable Linkable Format)是LINUX系统中的一种文件格式。通常我们见到的目标文件(.o)、可执行文件、动态库(.so)都是ELF文件类型。

可以通过file命令查看文件类型
在这里插入图片描述

从上图可以看出,main.o 是ELF relocatable文件,可重定位文件能被链接成可执行文件或动态库;main.c是c源文件;/bin/ls是ELF executable文件,可执行文件;/usr/lib/libcpupower.so.4.4.0-154是ELF shared object文件,即动态库。从这里其实可以看出(如果你对gcc编译的流程熟悉的话),relocatable file通常被链接成 executable file或 shared object file。

了解了什么是ELF,接下来再看看什么是ELF Header。

ELF Header描述

为什么我将elf header看作是三魂之一,说明了它非常重要。elf header描述了整个目标文件的基础属性,如:
在这里插入图片描述

ELF Magic

​ ELF Magic由16个字节组成,前四个字节(第0到第3个字节)是所有ELF文件都相同的标识码0x7f、0x45、0x4c、0x46,如果你对ASCII码有所了解的话,你会发现,0x7f刚好对应DEL字符,0x45、0x4c、0x46分别对应大写的’E’ ‘L’ ‘F’。第4个字节(0x02)表示文件CLASS,也即文件位数,因为我的机器是64位的,所以0x02表示64位,如果是32位的机器,第4个字节的值应该是0x01。第5个字节(0x01)表示字节序,0x01表示小端字节序,0x02表示大端字节序。第6个字节表示ELF的主版本号,ELF标准从1.2版本后就再没有更新了,所以一般是1。剩下的9个字节通常都为0。

​ 其实可以发现,ELF Header的16字节的魔数包含了Class(机器位数)、Data(字节序)、Version(ELF版本)信息。

OS/ABI 表示运行的平台为unix-system v。

ABI Version 表示ABI版本。

ELF Type

​ REL 表示relocatable file,说明该文件是目标文件(可重定位文件),即.o文件
​ EXEC 表示executable file,说明该文件是可执行文件
​ DYN 表示shared object file,说明该文件是动态库文件,即.so文件
​ 可分别执行readelf -h /bin/ls 和 readelf -h /usr/lib/libcpupower.so.4.4.0-154 查看ELF Type。

Machine

​ 表示该ELF文件的可运行的平台属性。

Entry point address

​ 入口地址,也就是ELF程序的虚拟入口地址,OS在加载完程序后从这个地址开始执行。relocatable文件因为还没有链接,所以一般没有入口地址,其值为0。

Start of program headers

​ program header描述了ELF文件是怎样被操作系统加载到进程的虚拟地址空间的。由于目标文件(relocatable file)没有program header(可通过readelf -l main.o查看program header),所以这里不展开讲解program header。

Start of section headers

​ 段表在文件中的偏移。main.o的start of section headers值为1064(0x428),表示段表从文件的1065个字节开始。section headers将在后面详细讲解。

Flags

​ 描述平台架构相关的属性。可以查看/usr/include/elf.h 中以EF_开头的宏。

Size of this header

​ 表示elf文件头本身的大小。main.o的文件头大小为64字节。

Size of program headers

​ 由于目标文件没有program header,所以这里暂时不管。

Number of program headers

​ 由于目标文件没有program header,所以这里暂时不管。

Size of section headers

​ 表示段表描述符的大小。一般等于sizeof(Elf64_Shdr)。64字节。

Number of section headers

​ 表示段表中段的个数。main.o中段的个数为13,说明有13个段

Section header string table index

​ 表示段表字符串表 所在段的下标。

说明:本文中,用中文表达的段都是指section,因为还有一个概念叫segment,但在中文表达上不好区分,所以这里特别说明一下,以免产生混淆。

我个人觉得,可以将section翻译为"节",将segment翻译为"段"。但是由于书籍或已经约定俗成的叫法中已经将section表达为段了,例如,代码段、数据段等,这里的段是指section。

为了在中文上尽量表达清晰而不产生混淆,我后面的系列文章会将section表达成"节",例如,代码节、数据节;而将segment表达成"段"。"段"是比"节"更大一点的概念。当然,如果你习惯用英文表达,就无所谓了。

section headers(节表头)

首先,我们来看看main.o的节表信息
在这里插入图片描述

可以看到 section(节)的个数是13个,和我们前面分析elf header的number of section headers的值一致。starting at offset 0x428 也就是1064,也就是前面分析elf header的start of section headers。在代码层面,每个section是通过Elf64_Shdr来描述的,所以Elf64_Shdr也叫做节描述符(section descriptor)

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;

可以发现,Elf64_Shdr的10个成员刚好和readelf命令显示的每个section的属性字段相对应。接下来,详细分析每个section及其相应字段的含义。

Name

节名,通常 节名的字符串是存放在.shstrtab中的,sh_name是节名字符串在.shstrtab中的偏移。

Type

NULL 表示 无效节
PROGBITS 表示程序节,代码节、数据节都是这种类型
RELA 表示重定位表
NOBITS 表示该节在目标文件中没有内容,如 .bss节
STRTAB 表示该节的内容为字符串表
SYMTAB 表示该节的内容为符号表

Address

表示节被加载后的进程虚拟地址。这里暂不做过多说明。

Off

表示节偏移,即节在文件中的偏移,如果节的内容不在文件中,那么off就没有意义

Size

表示节的大小

Es(section entry size)

有些节包含了一些固定大小的项(entry),而Es就表示这种项的大小。如果Es为0表示该节不包含固定大小的项。例如,符号表,它包含的每个符号所占的大小都是一样的。

Flg

节的标志位 表示 节在进程虚拟地址空间中的属性。

A 表示该节在进程空间中需要分配相应的空间。

E 表示该节在进程空间中可以被执行,一般都是指代码节。

W 表示该节在进程空间中可写。

Lk Inf

表示节的链接信息。一般节的类型与链接相关,这两个成员才有意义。例如节的类型是重定位表、符号表等。

Al

表示节地址对齐。

.strtab 字符串表,用来保存普通字符串

.shstrtab 段表字符串表,用来保存节表中用到的字符串,最常见的就是节名(section name)

Symbol(符号表)

在目标文件的链接过程中,本质上是对“地址”的引用,而“地址”即是函数或变量的地址。同时,为了方便引用,函数或变量都有自己的名字,这个名字就是所以为的符号(symbol),函数名或变量名就是符号名(symbol name)。

我们可以将符号看作是链接过程的粘合剂,整个链接过程正是基于符号才能够完成。链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应符号表(symbol table),这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(symbol value),对于变量和函数来说,符号值就是他们地址。

符号的分类:

  • 定义在本目标文件中的全局符号,可以被其他目标文件引用;如main.c中的g_var, func, main;
  • 在目标文件中引用的全局符号,却没有定义在本目标文件中,一般叫做外部符号(external symbol);如,printf
  • 节名,这种符号通常由编译器产生,它的值就是该段的起始地址;如,.data,.text;
  • 局部符号,这类符号只在编译单元内可见,这种符号通常对于链接过程没有作用,链接器往往会忽略他们;如,l_static_var,l_static_var2;
  • 行号信息,目标文件指令与源代码中代码行的对应关系,可选;

在链接过程中,"可见的"还是全局符号和外部符号。

接下来,先展示一下main.o里面的符号表到底长得是什么样子,然后再详细解释里面的内容。
在这里插入图片描述
符号表通过readelf -s main.o命令查看,符号表的section name 一般叫做’.symtab’。接下来依次解释符号表资格字段的含义。

Num

表示符号表数组的索引。

Size

表示相应符号的内容的大小

Type

NOTYPE 表示未知类型符号

FILE 该符号为文件名

SECTION 该符号表示一个节,这种符号的Bind一定是LOCAL,他们的Name没有显示,起始默认就是指相应的section name,所以没有必要显示出来。

OBJECT 表示一个数据对象,如,变量、数组等

FUNC 一般表示函数名

Bind

LOCAL 表示局部符号,目标文件外部不可见

GLOBAL 表示全局符号,目标文件外部可见

Ndx

一般表示该符号所在节的节索引(查看节索引命令:readelf -S -W main.o),如,g_static_val,g_var在.data节。

ABS 表示该符号包含了一个绝对值。文件名的符号就是这一类

COM “COMMON块”,一般未初始化的全局变量就是这种

UND 表示该符号未定义,说明该符号不在本目标文件中定义,但在其他文件中定义

Value

  • 如果符号在目标文件内定义,则value表示该符号所在节的偏移值。
  • 如果符号是COM,value表示对齐属性
  • 在可执行文件中,value表示符号的虚拟地址

了解了前面的5个字段的含义,再来解读符号表的内容就显得更加的从容啦!

main和func都是函数名,显然属于.text,所以Ndx为1,类型为FUNC;Bind为GLOBAL表示全局可见;value表示相对于.text起始位置的偏移量

g_static_val、l_static_val.2813、g_var,初始化的静态存储的外部或内部链接变量 都属于.data。

g_static_val2、l_static_val2.2814 未初始化的静态存储的内部或空链接的变量 都属于.bss。

g_var2 Ndx为COM,它的value为对齐属性,外部可见。

从这里例子,可以发现,我们平常所说的未初始化的全局变量(g_var2)放在.bss中是不准确的。

抱歉,我无法提供完整的 MATLAB 程序。但是,我可以给你一些关于 FSVM-cll(Feature Selection based on Convex Optimization for Linear Learning)的基本信息和示例代码。 FSVM-cll 是一种基于凸优化的线性学习的特征选择方法。它通过最小化特征权重的 L1 范数来实现特征选择,从而提高分类器的性能和泛化能力。 下面是一个使用 FSVM-cll 进行特征选择的 MATLAB 示例代码: ```matlab % 加载数据集 load('data.mat'); % 将数据集文件命名为 data.mat,包括训练数据 X 和标签 y % 数据预处理 X = normalize(X); % 对特征进行归一化处理 % 设置 FSVM 参数 lambda = 0.1; % 正则化参数 kfold = 5; % 交叉验证折数 % 特征选择 [selected_features, selected_indices] = FSVM_cll(X, y, lambda, kfold); % 输出选择的特征及其索引 disp('Selected features:'); disp(selected_features); disp('Indices of selected features:'); disp(selected_indices); ``` 以上代码假设你已经准备好了数据集,并将其保存为名为 `data.mat` 的文件,其中包括训练数据矩阵 `X` 和标签向量 `y`。你可以根据自己的数据集进行相应的修改。 请注意,示例代码中的 `normalize` 函数用于对特征进行归一化处理,你可以根据需要进行调整或添加其他数据预处理步骤。 希望这可以帮助你开始使用 FSVM-cll 进行特征选择。如果你需要更详细的说明或其他帮助,请提供更多具体问题的细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值