linux下可执行文件分析
- 格式:doc
- 大小:123.50 KB
- 文档页数:14
浅析Linux下core文件当我们的程序崩溃时,内核有可能把该程序当前内存映射到core文件里,方便程序员找到程序出现问题的地方。
最常出现的,几乎所有C程序员都出现过的错误就是“段错误”了。
也是最难查出问题原因的一个错误。
下面我们就针对“段错误”来分析core文件的产生、以及我们如何利用core文件找到出现崩溃的地方。
何谓core文件当一个程序崩溃时,在进程当前工作目录的core文件中复制了该进程的存储图像。
core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的。
当程序接收到以下UNIX信号会产生core文件:在系统默认动作列,“终止w/core”表示在进程当前工作目录的core文件中复制了该进程的存储图像(该文件名为core,由此可以看出这种功能很久之前就是UNIX功能的一部分)。
大多数UNIX调试程序都使用core文件以检查进程在终止时的状态。
core文件的产生不是POSIX.1所属部分,而是很多UNIX版本的实现特征。
UNIX第6版没有检查条件(a)和(b),并且其源代码中包含如下说明:“如果你正在找寻保护信号,那么当设置-用户-ID命令执行时,将可能产生大量的这种信号”。
4.3 + BSD 产生名为core.prog的文件,其中prog是被执行的程序名的前1 6个字符。
它对core文件给予了某种标识,所以是一种改进特征。
表中“硬件故障”对应于实现定义的硬件故障。
这些名字中有很多取自UNIX早先在DP-11上的实现。
请查看你所使用的系统的手册,以确切地确定这些信号对应于哪些错误类型。
下面比较详细地说明这些信号。
SIGABRT 调用abort函数时产生此信号。
进程异常终止。
SIGBUS 指示一个实现定义的硬件故障。
SIGEMT 指示一个实现定义的硬件故障。
EMT这一名字来自PDP-11的emulator trap 指令。
SIGFPE 此信号表示一个算术运算异常,例如除以0,浮点溢出等。
Linux下如何查找可执⾏⽂件Linux下的可执⾏⽂件Linux下如何查找可执⾏⽂件,作为⼀个Linux⼩菜刚刚有了这个问题,在windows中,可以通过后缀名判断是否是可执⾏⽂件,⽐如.exe,.bat等是可执⾏⽂件,但是在Linux下呢?Linux下不能简单根据⽂件后缀判断是否可执⾏。
linux下判断⼀个⽂件是否可执⾏,关键看是否有可执⾏权限,⽐如:在终端中输⼊:,会列出类似于下的列表:-rwxrwxr-x 1 bingyue bingyue 48141 Jul 17 02:50 redis-trib.rb*-rw-rw-r-- 1 bingyue bingyue 2163 Aug 5 23:34 release.cdrwxrwxr-x 2 bingyue bingyue 4096 Jul 22 20:03 Documents以下⾯输出为例,下划线区域说明了⽂件的权限,包括可读、可写、可执⾏等等。
10个字符确定不同⽤户能对⽂件⼲什么:第⼀个字符代表⽂件(-)、⽬录(d),链接(l)其余字符每3个⼀组(rwx),读(r)、写(w)、执⾏(x),分别说明⽂件所有者(User)、⽂件所有者所在的⽤户组其他⽤户(Group)、其他组⽤户(Others)对该⽂件拥有的权限。
第⼀组rwx:⽂件所有者的权限是读、写和执⾏第⼆组rwx:与⽂件所有者同⼀组的⽤户的权限是读、写和执⾏第三组r-x:不与⽂件所有者同组的其他⽤户的权限是读和执⾏,不能写另外有部分⽂件也可以通过后缀名判断,⽐如redhat中,凡是.rpm格式的都能在redhat中执⾏,debian中.deb格式的在debianlinux中能直接执⾏。
如何查找可执⾏⽂件使⽤ls -F|grep "*"Ubuntu下也可以使⽤ll | grep "*"ll不是linux下⼀个基本的命令,可以认为是ls -l的⼀个别名。
操作系统第⼆次实验报告——Linux创建进程及可执⾏⽂件结构分析0 个⼈信息张樱姿201821121038计算18121 实验⽬的熟练Linux创建进程fork操作。
2 实验内容在服务器上⽤VIM编写⼀个程序:⼀个进程创建两个⼦进程。
查看进程树查看进程相关信息3 实验报告 3.1编写程序创建两个⼦进程1 #include<sys/types.h>2 #include<stdio.h>3 #include<unistd.h>45int main(){6 pid_t cpid1 = fork(); //创建⼦进程178if(cpid1<0){9 printf("fork cd1 failed\n");10 }11else if(cpid1==0){12 printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid());13 }14else{15 pid_t cpid2 = fork(); //创建⼦进程216if(cpid2<0){17 printf("fork cd2 failed\n");18 }19else if(cpid2==0){20 printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid());21 }22else{23 printf("Parent: pid :%d\n",getpid());24 }25 }26 }编译运⾏后的结果:3.2打印进程树 添加sleep函数以挂起进程,⽅便打印进程树:1 #include<sys/types.h>2 #include<stdio.h>3 #include<unistd.h>45int main(){6 pid_t cpid1 = fork();78if(cpid1<0){9 printf("fork cd1 failed\n");10 }11else if(cpid1==0){12 printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid());13 sleep(30); //挂起30秒14 }15else{16 pid_t cpid2 = fork();17if(cpid2<0){18 printf("fork cd2 failed\n");19 }20else if(cpid2==0){21 printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid());22 sleep(30); //挂起30秒23 }24else{25 printf("Parent: pid :%d\n",getpid());26 sleep(60); //挂起60秒27 }28 }29 }pstree -p pid #打印进程树 3.3 解读进程相关信息 3.3.1 解释执⾏ps -ef后返回结果中每个字段的含义 ps -ef输出格式 :UID PID PPID C STIME TTY TIME CMDUID: User ID,⽤户ID。
linux下的三种可执⾏⽂件格式的⽐较linux下的三种可执⾏⽂件格式的⽐较本⽂讨论了 UNIX/LINUX 平台下三种主要的可执⾏⽂件格式:a.out(assembler and link editor output 汇编器和链接编辑器的输出)、COFF(Common Object File Format 通⽤对象⽂件格式)、ELF(Executable and Linking Format 可执⾏和链接格式)。
⾸先是对可执⾏⽂件格式的⼀个综述,并通过描述 ELF ⽂件加载过程以揭⽰可执⾏⽂件内容与加载运⾏操作之间的关系。
随后依此讨论了此三种⽂件格式,并着重讨论 ELF ⽂件的动态连接机制,其间也穿插了对各种⽂件格式优缺点的评价。
最后对三种可执⾏⽂件格式有⼀个简单总结,并提出作者对可⽂件格式评价的⼀些感想。
可执⾏⽂件格式综述相对于其它⽂件类型,可执⾏⽂件可能是⼀个操作系统中最重要的⽂件类型,因为它们是完成操作的真正执⾏者。
可执⾏⽂件的⼤⼩、运⾏速度、资源占⽤情况以及可扩展性、可移植性等与⽂件格式的定义和⽂件加载过程紧密相关。
研究可执⾏⽂件的格式对编写⾼性能程序和⼀些⿊客技术的运⽤都是⾮常有意义的。
不管何种可执⾏⽂件格式,⼀些基本的要素是必须的,显⽽易见的,⽂件中应包含代码和数据。
因为⽂件可能引⽤外部⽂件定义的符号(变量和函数),因此重定位信息和符号信息也是需要的。
⼀些辅助信息是可选的,如调试信息、硬件信息等。
基本上任意⼀种可执⾏⽂件格式都是按区间保存上述信息,称为段(Segment)或节(Section)。
不同的⽂件格式中段和节的含义可能有细微区别,但根据上下⽂关系可以很清楚的理解,这不是关键问题。
最后,可执⾏⽂件通常都有⼀个⽂件头部以描述本⽂件的总体结构。
相对可执⾏⽂件有三个重要的概念:编译(compile)、连接(link,也可称为链接、联接)、加载(load)。
源程序⽂件被编译成⽬标⽂件,多个⽬标⽂件被连接成⼀个最终的可执⾏⽂件,可执⾏⽂件被加载到内存中运⾏。
Linux系统下的ELF文件分析摘要:随着linux系统的发展,elf成了十分重要的可执行文件格式。
本文介绍了eIf文件的格式,并在此基础上分析出eIf文件的特性。
关键词:elf文件:平台相关PIC1.引言ELF(Executable and Linkable Format)IN可执行连接文件格式.是LinuxSVR4和Solaris2,0默认的目标文件格式,目前标准接口委员会TIS已将ELF标准化为一种可移植的目标文件格式,运行于32一bitIntel体系微机上,可与多种操作系统兼容。
分析elf文件有助于理解一些重要的系统概念,例如程序的编译和链接,程序的加载和运行等2.ELF文件格式2.1 ELF文件的类型ELF文件主要有三种类型(1)可重定位文件包含了代码和数据.可与其它ELF文件建立一个可执行或共享的文件:(2)可执行文件时可直接执行的程序:(3)共享目标文件包括代码和数据,可以在两个地方链接。
第一,连接器可以把它和其它可重定位文件和共享文件一起处理以建立另一个ELF文件;第二,动态链接器把它和一个可执行文件和其它共享文件结合在一起建立一个进程映像。
2.2 ELF文件的组织ELF文件参与程序的连接(建立一个程序)和程序的执行(运行一个程序),编译器和链接器将其视为节头表(section headertable)描述的一些节(section)的集合,而加载器则将其视为程序头表(program header table)描述的段(segment)的集合,通常一个段可以包含多个节。
可重定位文件都包含一个节头表.可执行文件都包含一个程序头表。
共享文件两者都包含有。
为此,ELF文件格式同时提供了两种看待文件内容的方式,反映了不同行为的不同要求。
2.3文件头EIF头在在程序的开始部位,作为引路表描述整个ELF的文件结构,其信息大致分为四部分:一是系统相关信息,二是目标文件类型,三是加载相关信息,四是链接相关信息其中系统相关信息包括elf文件魔数(标识elf文件),平台位数,数据编码方式,elf头部版本,硬件平台e machine,目标文件版本e_version,处理器特定标志e ftags:这些信息的引入极大增强了elf文件的可移植性使交叉编译成为可能。
Linux命令行中的文件比较和合并技巧在Linux命令行中,文件比较和合并是常见的操作。
通过使用一些特定的命令和技巧,可以方便地进行文件比较和合并,以满足不同的需求。
本文将介绍一些常用的Linux命令行中的文件比较和合并技巧。
1. 文件比较技巧:1.1 文件内容比较:使用diff命令可以比较两个文件的内容差异。
通过在命令行中输入以下命令,可以列出两个文件之间的差异:```bashdiff file1.txt file2.txt```1.2 忽略空格和空行:有时候文件中的空格和空行对比较结果没有太大意义,可以使用-d参数来忽略它们,如下所示:```bashdiff -d file1.txt file2.txt```1.3 比较目录:如果需要比较两个目录下的文件差异,可以使用-d 参数结合-r参数来递归比较两个目录的文件:```bashdiff -dr dir1 dir2```2. 文件合并技巧:2.1 合并文件内容:使用cat命令可以合并多个文件的内容。
通过在命令行中输入以下命令,可以将多个文件的内容合并到一个新文件中:```bashcat file1.txt file2.txt > merged.txt```2.2 合并目录下的文件:如果需要合并一个目录下的所有文件,可以使用find命令结合cat命令来实现。
以下命令会将目录dir下所有的txt文件内容合并到一个新文件中:```bashfind dir -name "*.txt" -exec cat {} + > merged.txt```2.3 合并有序文件:如果需要将多个有序的文件按照顺序合并,可以使用sort命令结合cat命令来实现。
以下命令会按照文件名的顺序将文件内容合并到一个新文件中:```bashcat $(ls -v file*.txt) > merged.txt```3. 文件对比和合并工具:除了基本的命令行工具外,还有一些强大的文件对比和合并工具可供使用:3.1 Meld:Meld是一款简单易用的图形化文件比较和合并工具。
linux obj dump 用法Linux objdump 用法详解Linux objdump 是一个非常强大的命令行工具,用于分析目标文件或可执行文件,可以显示文件的各个节(section)的详细信息,如代码段、数据段等。
本篇文章将详细介绍objdump 命令的使用方法,从基础到高级逐步回答。
第一步:安装objdump在Linux 系统上,objdump 命令通常属于binutils 软件包的一部分。
大多数常见的Linux 发行版都默认安装了binutils,所以无需额外安装。
如果你的系统中没有安装binutils,则可以使用以下命令来安装:bashsudo apt-get install binutils根据你使用的Linux 发行版可能不同,安装命令也会有所不同。
请根据你的系统选择正确的安装命令。
第二步:基本语法和用法objdump 命令的基本语法如下:bashobjdump [options] objfile...其中objfile 是指定要分析的目标文件或可执行文件的文件名。
options 表示objdump 命令的选项参数,我们将在后面的内容中详细介绍。
以下是一些常用的选项参数:- `-d` 或`disassemble`:显示反汇编代码。
- `-s` 或`full-contents`:显示所有节的内容。
- `-t` 或`syms`:显示符号表信息。
- `-h` 或`section-headers`:显示所有节的头信息。
- `-r` 或`reloc`:显示重定位表。
- `-x` 或`all-headers`:显示所有头信息。
- `-l` 或`line-numbers`:显示源代码行号信息。
下面我们将逐个选项参数进行详细讲解。
- 反汇编代码(-d 或disassemble)使用`-d` 或`disassemble` 选项参数可以将目标文件或可执行文件中的机器码反汇编为可读的汇编代码。
例如,要将一个名为`test.o` 的目标文件反汇编,可以执行以下命令:bashobjdump -d test.o该命令将会输出test.o 中所有节的反汇编代码。
可执行目标文件详解可执行目标文件是计算机程序经过编译后生成的文件,在操作系统中直接可以执行的文件格式。
可执行目标文件是由机器指令、数据和符号表组成的。
机器指令是代码段中的指令,数据是数据段中的数据。
符号表中记录了程序中的各个符号的地址信息,用于链接时进行符号解析。
可执行目标文件的格式有多种,常见的有 ELF、PE、Mach-O 等。
这些不同的可执行目标文件格式在一些方面有所不同,但是它们都包含以下几个部分:1. 标头:包含了一些基本属性,如程序入口地址、段的数量、各段的偏移和大小等等。
2. 代码段:里面存储了程序的机器指令,是可执行程序的核心部分。
3. 数据段:里面存储了程序需要的数据,如字符串、数组、结构等等。
4. 符号表:记录程序中的符号,包括函数名、变量名称等等,用于链接时进行符号的解析。
5. 重定位表:记录程序中需要重定位的地址信息,是实现程序重定位的重要数据。
6. 调试信息:包含了程序的调试信息,如变量的值、函数的调用栈等等。
在 Linux 下,可执行目标文件的格式一般是 ELF (Executable and Linkable Format)。
ELF 文件的结构相对简单,易于使用,常用的工具有 objdump、nm、readelf 等等。
这些工具可以帮助开发者查看和调试可执行目标文件,对于检测程序中可能存在的安全漏洞、调试程序等等都是非常有用的工具。
在 Windows 下,可执行目标文件的格式一般是 PE (Portable Executable)格式。
PE 文件的结构相对复杂,但是它支持 Windows 下的多种可执行文件(如 .dll 文件等),是 Windows 下的主要可执行目标文件格式。
在 macOS 下,可执行目标文件的格式一般是 Mach-O (Mach object)格式。
Mach-O 文件的结构与 ELF 不同,但是它支持 macOS 下的各种可执行文件(如 .dylib 文件、.app 文件等),是 macOS 下的主要可执行目标文件格式。
Linux下分析bin⽂件的10种⽅法这世界有10种⼈,⼀种⼈懂⼆进制,另⼀种⼈不懂⼆进制。
——鲁迅⼤家好,我是良许。
⼆进制⽂件是我们⼏乎每天都需要打交道的⽂件类型,但很少⼈知道他们的⼯作原理。
这⾥所讲的⼆进制⽂件,是指⼀些可执⾏⽂件,包括你天天要使⽤的 Linux 命令,也是⼆进制⽂件的⼀种。
Linux 系统给我们提供了⾮常多⽤于分析⼆进制⽂件的⼯具,不管你在 Linux 下从事的是何种⼯作,知道这些⼯具也会让你对你的系统更加了解。
在本⽂中,将介绍⼏种最常⽤的⽤于分析⼆进制⽂件的⼯具及命令,这些⼯具在⼤部分发⾏版⾥可以直接使⽤,如果不能直接⽤的话,可以⾃⾏安装。
filefile 命令⽤于分析⽂件的类型。
如果你需要分析⼆进制⽂件,可以⾸先使⽤ file 命令来切⼊。
我们知道,在 Linux 下,⼀切皆⽂件,但并不是所有的⽂件都具有可执⾏性,我们还有各种各样的⽂件,⽐如:⽂本⽂件,管道⽂件,链接⽂件,socket⽂件,等等。
在对⼀个⽂件进⾏分析之前,我们可以⾸先使⽤ file 命令来分析它们的类型。
当然除此之外,我们还可以看到⼀些其它信息。
$ file /bin/pwd/bin/pwd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0d264bacf2adc568f0e21cbcc9576df434c44380, strippedlddldd 命令可以⽤于分析可执⾏⽂件的依赖。
我们使⽤ file 命令来分析⼀个可执⾏⽂件的时候,有时候可以看到输出中有dynamically linked这样的字眼。
这个是啥意思呢?⼤部分程序,都会使⽤到第三⽅库,这样就可以不⽤重复造轮⼦,节约⼤量时间。
最简单的,我们写C程序代码的话,肯定会使⽤到libc或者glibc库。
Pyinstaller打包详解---⽣成windows、linux下的整包可执⾏⽂件1、原理及作⽤:pyinstaller⾃⾝打包的流程:读取编写好的python脚本,分析其中调⽤的模块和库,然后收集这些⽂件的副本(包括Python的解释器)。
最后把副本与脚本,可执⾏⽂件等放在⼀个⽂件夹中,或者可选的封装在⼀个可执⾏⽂件中。
我们可以将⾃⼰的python代码打包成⼀个可执⾏⽂件。
起到代码保护和封装的作⽤。
2、打包流程:简略描述Pyinstall的打包流程:①下载好Pyinstaller之后,②拷贝上述spec⽂件并增删⾃⼰的⼯程代码路径(或使⽤pyi-makespec main.py命令⽣成.spec⽂件)③执⾏ pyinstaller main.spec④在dist中,检验⽣成的 main可执⾏⽂件2-1、安装pyinstallerpip install pyinstaller2-2、拷贝main.spec⽂件我们⽤main.spec⽂件记录打包需要的⽂件依赖等,pyinstaller参数的本质可认为是 .spec⽂件中的配置属性。
main.spec⽂件可按照类型直接在internet中拷贝模板即可,之后进⾏修改。
(下⽂有模板⽰例)以camera_stress_test_tool_main⼯具为例:将下⾯模板的camera_stress_test_tool_main.spec⽂件拷贝⾄和 main.py同⽬录下(亦可⽤pyi-makespec main.py⽣成 .spec⽂件,需注意按需使⽤参数)-F, –onefile打包⼀个单个⽂件-D, –onedir打包多个⽂件,在dist中⽣成很多依赖⽂件,适合以框架形式编写⼯具代码,我个⼈⽐较推荐这样,代码易于维护-K, –tk在部署时包含 TCL/TK-a, –ascii不包含编码.在⽀持Unicode的python版本上默认包含所有的编码.-d, –debug产⽣debug版本的可执⾏⽂件-w,–windowed,–noconsole使⽤Windows⼦系统执⾏.当程序启动的时候不会打开命令⾏(只对Windows有效)-c,–nowindowed,–console使⽤控制台⼦系统执⾏(默认)(只对Windows有效)pyinstaller -c xxxx.pypyinstaller xxxx.py –console-s,–strip可执⾏⽂件和共享库将run through strip.注意Cygwin的strip往往使普通的win32 Dll⽆法使⽤.-X, –upx如果有UPX安装(执⾏Configure.py时检测),会压缩执⾏⽂件(Windows系统中的DLL也会)(参见note)-o DIR, –out=DIR 指定spec⽂件的⽣成⽬录,如果没有指定,⽽且当前⽬录是PyInstaller的根⽬录,会⾃动创建⼀个⽤于输出(spec和⽣成的可执⾏⽂件)的⽬录.如果没有指定,⽽当前⽬录不是PyInstaller的根⽬录,则会输出到当前的⽬录下.-p DIR, –path=DIR 设置导⼊路径(和使⽤PYTHONPATH效果相似).可以⽤路径分割符(Windows使⽤分号,Linux使⽤冒号)分割,指定多个⽬录.也可以使⽤多个-p参数来设置多个导⼊路径,让pyinstaller⾃⼰去找程序需要的资源–icon=<FILE.ICO>将file.ico添加为可执⾏⽂件的资源(只对Windows系统有效),改变程序的图标 pyinstaller -i ico路径 xxxxx.py –icon=<FILE.EXE,N>将file.exe的第n个图标添加为可执⾏⽂件的资源(只对Windows系统有效)-v FILE, –version=FILE将verfile作为可执⾏⽂件的版本资源(只对Windows系统有效)-n NAME, –name=NAME可选的项⽬(产⽣的spec的)名字.如果省略,第⼀个脚本的主⽂件名将作为spec的名字camera_stress_test_tool_main.spec⽂件模板⽰例:(模板为单⽂件⽰范)# -*- mode: python ; coding: utf-8 -*-block_cipher = Nonea = Analysis(['main.py','./cfg/__init__.py','./cfg/setting.py','./comm/__init__.py','./comm/logger.py','./comm/utils.py','./handler/__init__.py','./handler/invite_handler.py','./handler/sip_parse_handler.py','./handler/subscribe_handler.py','./my_thread/__init__.py','./my_thread/alarm.py','./my_thread/audio_media.py','./my_thread/audio_media_rtp.py','./my_thread/broadcast.py','./my_thread/my_sip_epoll.py','./my_thread/timer.py','./my_thread/video_media.py','./redis_utils/__init__.py','./redis_utils/redis_keys.py','./redis_utils/redis_work.py','./__init__.py','./device.py','./std/ietf/__init__.py','./std/ietf/rfc1035.py','./std/ietf/rfc2198.py','./std/ietf/rfc2396.py','./std/ietf/rfc2617.py','./std/ietf/rfc2833.py','./std/ietf/rfc3261.py','./std/ietf/rfc3489.py','./std/ietf/rfc5658.py','./std/ietf/rfc6455.py','./std/ietf/rfc7064.py','./std/ietf/rfc7065.py','./std/itu_t/__init__.py','./std/w3c/__init__.py','./std/w3c/simplexml.py','./std/__init__.py'],pathex=['./'],binaries=[],datas=[],hiddenimports=[],hookspath=[],runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False)pyz = PYZ(a.pure, a.zipped_data,cipher=block_cipher)exe = EXE(pyz,a.scripts,a.binaries,a.zipfiles,a.datas,[],name='main',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,upx_exclude=[],runtime_tmpdir=None,console=True )2-3、修改main.spec⽂件⼀般来说,我们需要在“Analysis”这个item中,加⼊打包的代码的路径,(此处需注意路径相关依赖,与创建spec的路径相对应,⼀般在main.py⽂件基础上找,可写绝对及相对路径)。
本文讨论了UNIX/LINUX 平台下三种主要的可执行文件格式:a.out(assembler and link editor output 汇编器和链接编辑器的输出)、COFF(Common Object File Format 通用对象文件格式)、ELF(Executable and Linking Format 可执行和链接格式)。
首先是对可执行文件格式的一个综述,并通过描述ELF 文件加载过程以揭示可执行文件内容与加载运行操作之间的关系。
随后依此讨论了此三种文件格式,并着重讨论ELF 文件的动态连接机制,其间也穿插了对各种文件格式优缺点的评价。
最后对三种可执行文件格式有一个简单总结,并提出作者对可文件格式评价的一些感想。
可执行文件格式综述相对于其它文件类型,可执行文件可能是一个操作系统中最重要的文件类型,因为它们是完成操作的真正执行者。
可执行文件的大小、运行速度、资源占用情况以及可扩展性、可移植性等与文件格式的定义和文件加载过程紧密相关。
研究可执行文件的格式对编写高性能程序和一些黑客技术的运用都是非常有意义的。
不管何种可执行文件格式,一些基本的要素是必须的,显而易见的,文件中应包含代码和数据。
因为文件可能引用外部文件定义的符号(变量和函数),因此重定位信息和符号信息也是需要的。
一些辅助信息是可选的,如调试信息、硬件信息等。
基本上任意一种可执行文件格式都是按区间保存上述信息,称为段(Segment)或节(Section)。
不同的文件格式中段和节的含义可能有细微区别,但根据上下文关系可以很清楚的理解,这不是关键问题。
最后,可执行文件通常都有一个文件头部以描述本文件的总体结构。
相对可执行文件有三个重要的概念:编译(compile)、连接(link,也可称为链接、联接)、加载(load)。
源程序文件被编译成目标文件,多个目标文件被连接成一个最终的可执行文件,可执行文件被加载到内存中运行。
因为本文重点是讨论可执行文件格式,因此加载过程也相对重点讨论。
下面是LINUX平台下ELF文件加载过程的一个简单描述。
1:内核首先读ELF文件的头部,然后根据头部的数据指示分别读入各种数据结构,找到标记为可加载(loadable)的段,并调用函数mmap()把段内容加载到内存中。
在加载之前,内核把段的标记直接传递给mmap(),段的标记指示该段在内存中是否可读、可写,可执行。
显然,文本段是只读可执行,而数据段是可读可写。
这种方式是利用了现代操作系统和处理器对内存的保护功能。
著名的Shellcode(参考资料17)的编写技巧则是突破此保护功能的一个实际例子。
2:内核分析出ELF文件标记为PT_INTERP 的段中所对应的动态连接器名称,并加载动态连接器。
现代LINUX 系统的动态连接器通常是/lib/ld-linux.so.2,相关细节在后面有详细描述。
3:内核在新进程的堆栈中设置一些标记-值对,以指示动态连接器的相关操作。
4:内核把控制传递给动态连接器。
5:动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载。
6:动态连接器对程序的外部引用进行重定位,通俗的讲,就是告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内。
动态连接还有一个延迟(Lazy)定位的特性,即只在"真正"需要引用符号时才重定位,这对提高程序运行效率有极大帮助。
7:动态连接器执行在ELF文件中标记为 .init 的节的代码,进行程序运行的初始化。
在早期系统中,初始化代码对应函数_init(void)(函数名强制固定),在现代系统中,则对应形式为void__attribute((constructor))init_function(void){……}其中函数名为任意。
8:动态连接器把控制传递给程序,从ELF 文件头部中定义的程序进入点开始执行。
在a.out 格式和ELF格式中,程序进入点的值是显式存在的,在COFF 格式中则是由规范隐含定义。
从上面的描述可以看出,加载文件最重要的是完成两件事情:加载程序段和数据段到内存;进行外部定义符号的重定位。
重定位是程序连接中一个重要概念。
我们知道,一个可执行程序通常是由一个含有main() 的主程序文件、若干目标文件、若干共享库(Shared Libraries)组成。
(注:采用一些特别的技巧,也可编写没有main 函数的程序,请参阅参考资料2)一个 C 程序可能引用共享库定义的变量或函数,换句话说就是程序运行时必须知道这些变量/函数的地址。
在静态连接中,程序所有需要使用的外部定义都完全包含在可执行程序中,而动态连接则只在可执行文件中设置相关外部定义的一些引用信息,真正的重定位是在程序运行之时。
静态连接方式有两个大问题:如果库中变量或函数有任何变化都必须重新编译连接程序;如果多个程序引用同样的变量/函数,则此变量/函数会在文件/内存中出现多次,浪费硬盘/内存空间。
比较两种连接方式生成的可执行文件的大小,可以看出有明显的区别。
回页首a.out 文件格式分析a.out 格式在不同的机器平台和不同的UNIX 操作系统上有轻微的不同,例如在MC680x0 平台上有6 个section。
下面我们讨论的是最"标准"的格式。
a.out 文件包含7 个section,格式如下:exec header(执行头部,也可理解为文件头部)text segment(文本段)data segment(数据段)text relocations(文本重定位段)data relocations(数据重定位段)symbol table(符号表)string table(字符串表)执行头部的数据结构:struct exec {unsigned long a_midmag; /* 魔数和其它信息*/unsigned long a_text; /* 文本段的长度*/unsigned long a_data; /* 数据段的长度*/unsigned long a_bss; /* BSS段的长度*/unsigned long a_syms; /* 符号表的长度*/unsigned long a_entry; /* 程序进入点*/unsigned long a_trsize; /* 文本重定位表的长度*/unsigned long a_drsize; /* 数据重定位表的长度*/};文件头部主要描述了各个section 的长度,比较重要的字段是a_entry(程序进入点),代表了系统在加载程序并初试化各种环境后开始执行程序代码的入口。
这个字段在后面讨论的ELF 文件头部中也有出现。
由a.out 格式和头部数据结构我们可以看出,a.out 的格式非常紧凑,只包含了程序运行所必须的信息(文本、数据、BSS),而且每个section 的顺序是固定的。
这种结构缺乏扩展性,如不能包含"现代"可执行文件中常见的调试信息,最初的UNIX 黑客对a.out 文件调试使用的工具是adb,而adb 是一种机器语言调试器!a.out 文件中包含符号表和两个重定位表,这三个表的内容在连接目标文件以生成可执行文件时起作用。
在最终可执行的 a.out 文件中,这三个表的长度都为0。
a.out 文件在连接时就把所有外部定义包含在可执行程序中,如果从程序设计的角度来看,这是一种硬编码方式,或者可称为模块之间是强藕和的。
在后面的讨论中,我们将会具体看到ELF格式和动态连接机制是如何对此进行改进的。
a.out 是早期UNIX系统使用的可执行文件格式,由AT&T 设计,现在基本上已被ELF 文件格式代替。
a.out 的设计比较简单,但其设计思想明显的被后续的可执行文件格式所继承和发扬。
可以参阅参考资料16和阅读参考资料15源代码加深对a.out 格式的理解。
参考资料12讨论了如何在"现代"的红帽LINUX运行 a.out 格式文件。
回页首COFF 文件格式分析COFF 格式比a.out 格式要复杂一些,最重要的是包含一个节段表(section table),因此除了 .text,.data,和 .bss 区段以外,还可以包含其它的区段。
另外也多了一个可选的头部,不同的操作系统可一对此头部做特定的定义。
COFF 文件格式如下:File Header(文件头部)Optional Header(可选文件头部)Section 1 Header(节头部)………Section n Header(节头部)Raw Data for Section 1(节数据)Raw Data for Section n(节数据)Relocation Info for Sect. 1(节重定位数据)Relocation Info for Sect. n(节重定位数据)Line Numbers for Sect. 1(节行号数据)Line Numbers for Sect. n(节行号数据)Symbol table(符号表)String table(字符串表)文件头部的数据结构:struct filehdr{unsigned short f_magic; /* 魔数*/unsigned short f_nscns; /* 节个数*/long f_timdat; /* 文件建立时间*/long f_symptr; /* 符号表相对文件的偏移量*/long f_nsyms; /* 符号表条目个数*/unsigned short f_opthdr; /* 可选头部长度*/unsigned short f_flags; /* 标志*/};COFF 文件头部中魔数与其它两种格式的意义不太一样,它是表示针对的机器类型,例如0x014c 相对于I386 平台,而0x268 相对于Motorola 68000系列等。
当COFF 文件为可执行文件时,字段f_flags 的值为F_EXEC(0X00002),同时也表示此文件没有未解析的符号,换句话说,也就是重定位在连接时就已经完成。
由此也可以看出,原始的COFF 格式不支持动态连接。
为了解决这个问题以及增加一些新的特性,一些操作系统对COFF 格式进行了扩展。
Microsoft 设计了名为PE(Portable Executable)的文件格式,主要扩展是在COFF 文件头部之上增加了一些专用头部,具体细节请参阅参考资料18,某些UNIX 系统也对COFF 格式进行了扩展,如XCOFF(extended common object file format)格式,支持动态连接,请参阅参考资料5。
紧接文件头部的是可选头部,COFF 文件格式规范中规定可选头部的长度可以为0,但在LINUX 系统下可选头部是必须存在的。