精华内容
下载资源
问答
  • 目标文件是源代码经过编译但未进行链接的那些中间文件,在linux中的.o文件,它跟可执行文件的内容与结构很相似,所以一般与可执行格式采用一种方式存储,在linux下,我们可以将他们统称ELF文件。ELF文件标准里面把...

    ELF文件、目标文件、可执行文件的关系

    目标文件是源代码经过编译但未进行链接的那些中间文件,在linux中的.o文件,它跟可执行文件的内容与结构很相似,所以一般与可执行格式采用一种方式存储,在linux下,我们可以将他们统称ELF文件。ELF文件标准里面把系统中采用ELF格式的文件归为四类:

    ELF文件类型说明实例

    可重定位文件(Relocatable File)

    这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也属于这一类

    linux的.o

    windows的.obj

    可执行文件

    (Executable File)

    这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,它们一般都没有扩展名

    比如/bin/bash文件;window的.exe 

    共享目标文件

    (Shared Object File)

    这种文件包含了代码和数据,可以在以下两种情况下使用。一种是连接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分运行。

    linux的.so,如/lib/glibc-2.5.so

    windows的DDL

    核心转储文件

    (Core Dump File)

    当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件。linux下的core dump

    以以下代码编译出来的目标文件作为分析对象:

    #include <stdio.h>
    int global_init_var = 84;
    int global_uninit_var;
    
    void func1(int i)
    {
    	printf("%d\n",i);
    }
    
    int main(void)
    {
    	static int static_var = 85;
    	static int static_var2;
    	int a = 1;
    	int b;
    	func1(static_var + static_var2 + a + b);
    	return a;
    }
    

     使用objdump -h SimpleSection.o命令打印各个段的基本信息:

    如上图显示共有代码段、数据段、BSS段、只读段、注释信息段和堆栈提示段。对应的信息size表示段的长度,VMA File off表示段所在的位置。每个段的第二行中的“CONTENTS”、“ALLOC”等表示段的各种属性;其中“CONTENTS”表示该段在文件中存在。

    .text代码段:可以使用objdump 命令;-s 参数可以将所有段的内容以十六进制的方式打印出来。-d参数可以将所有包含指令段反汇编。我们将关于代码段的信息提取出来

    Contents of section .text下就是.text段的内容,总共0x51,与使用-h参数输出的.text长度一致,最左边是偏移量,中间是十六进制的内容,最右边是.text段的ASCII码形式。对照下边的反汇编结果,可以很明显的看到fun1和mian函数,.text段的第一个字节"0x55"就是func1()函数的第一条“push %ebp”指令,而最后“0x50”正好是main函数的最后一条指令“ret”。

     

    .data数据段:保存初始化了的全局变量和局部静态变量。global_init_var 和static int static_var。大小正好是8个字节。

     

    .bss段:存放的是未初始化的全局变量和局部静态变量。也就是说global_uninit_var和static_var2应该存放在.bss段,准确来说.bss段位他们预留了位置,本应该是8个字节,而我们使用objdump -h命令显示的size是4个字节。事实上只有static_var2存放在了.bss段,而global_uninit_var只是一个未定义的“COMMON”符号;这和编译器有关,有的编译器将全局未初始化变量存放在.bss段,有的则不存放,只是预留一个未定义的全局变量符号,等到最终链接后再分配到.bss段。

     

    .rodata段:存放的是只读数据,一般是程序里面的只读变量(如const修饰的变量)和字符串常量。在func1里面我们在调用“printf”的时候,用到了一个字符串常量“%d\n”,是4个字节与前面的长度符合。

    上面通过一个实例大致了解了ELF文件的基本轮廓,总的来说就包括了指令和数据。下图是ELF文件的总体结构:

    上面讲述了指令段和数据段,以下是ELF文件中其它的几个重要结构:

     

    文件头(ELF Header)

     

    ELF32_Ehdr和 ELF64_Ehdr结构体定义了ELF文件头的相关信息;这两个结构体的成员信息一致,只不过一个是32位版本的,另一个是64版本的。这里以ELF32_Ehdr为例,定义如下:

    我们可以使用readelf查看SimpleSection的目标文件头:

    e_ident(ELF魔数):该成员与readelf输出的Magic、Class、Data、Version、OS/ABI、ABI Version对应。 readelf输出的Magic的16字节正好对应e_ident这个成员;对于ELF文件前四个字节都必须相同,分别为0x7F、0x45、0x4c、0x46第一个字节对应ASCII控制,后面三个字节刚好是ELF这三个字母的ASCII,这4个字节称为ELF魔数,这种魔数用来确认文件的类型,操作系统在加载可执行文件的时候会确认魔数是否正确,如果不正确会拒绝加载。下一个字节对应Class用来表示ELF文件类的;第六个字节对应Data规定ELF文件是大端还是小端,第七个字节规定ELF文件的主版号,一般是1。对应的数据如下图所示

    e_type(文件类型):对应 readelf输出的Type表示ELF文件类型。包括REL (可重定位文件)、EXEC(可执行文件)、DYN(共享文件);系统是根据这三个常量判断文件类型,而不是扩展名。

    剩下的成员与readelf输出的参数,一一对应,此处不再赘述。综上文件头中描述整个文件的基本信息,以及段表的位置和大小、程序头的位置和大小。

     

    段表(Section header Table)

    段表是ELF文件中除了头文件以外最重要的结构,它描述了各个段的信息,比如段的段名、长度、在文件中的偏移、读写权限等等。段表是以一个元素是“ELF32_Shdr”结构体的数组。数组中的每一个元素对应一个段。ELF32_Shdr结构体的定义如下:

    使用readelf -S SimpleSection.o查看目标文件的段表

    根据上图,总共有11个元素,ELF段表文件的第一个元素时无效的,所以有效的段描述符有十个,也就是说有十个有效的段。readelf命令的输出与ELF32_Shdr结构体定义的成员一一对应,以下是对部分参数的详细解释:

    sh_type(段的类型):对应readelf命令输出的Type。段名只是在链接和编译过程中有意义,但是不能代表段的类型。比如我们有可以将一个数据段命名为.text。段对应的类型如下图所示:

    sh_flag(段的标志位):对应readelf命令输出的Flg;段的标志位是指该段在进程虚拟空间中的属性。比如是否可执行、可写。对应的值如下图所示:

    sh_link、sh_info(段的链接信息):如果段的类型是与链接有关的,比如重定位、符号表。那么这两个成员的意义如下图所示,对于其他段这两个参数没有意义。

     

    重定位表(Relocation Table)

    重定位是链接器对目标文件中的某些部分进行地址的重新定义。这些信息都会记录在重定位表中 。可能会注意到在讲述段表的时候,使用readelf -S命令输出中就有一个“rel.text”段,该段就是重定位表,并且是作用域.text段,它对应类型是“SHT_REL”。回顾我们最开始写的SimpleSetion文件有对“printf”函数的调用,这个就是引用了绝对地址。它所对应的sh_link的值9也就是说该重定位表使用的符号表在段表中的下标是9;h_info的值为1,表示该重定位表作用于的段在段表的下标,即.text。对比上图段表的信息,与此处结果符合。

     

    字符串表

    ELF文件中用到了很多的字符串,比如段表、变量名。因为每个字符串的长度不一,所以将所有的字符串放到一张表中,如下图所示:

    那么偏移与所对的字符串的关系:

    在使用字符串时,只需给出对应的偏移值即可。保存字符串常见的有.strtab和.shstrtab,分别存放普通字符串和段名字符串。在头文件结构中有e_shstrndx这样一个参数,表示是段表字符串在段表中的下标。所以,只有分析文件头,就可以得到段表和段表字符串的位置,从而解析整个elf文件。

     

    符号表(Symbol Table)

    符号的作用是当多个不同目标链接时函数和变量之间的相互引用。对于链接而言,只关心全局符号的相互引用。局部符号、段名、行号等符号是次要的。ELF文件中符号也是在一个符号表中,是一个ELF_Sym结构;结构定义如下:

    使用  readelf -s SimpleSection.o查看目标文件的符号表:

    详解:

    st_value(符号值):分为以下几种情况

    • 在目标文件中,如果是符号的定义并且该符号不是“COMMON”(未初始化的全局符号)类型,则st_value表示该符号在所在段中中的偏移,即符号所对应的函数或变量位于由st_shndx指定的段,偏移st_value的位置。
    • 在目标文件中。如果符号是“COMMON”类型的,则st_value表示该符号的对齐属性。
    • 在可执行文件中,st_value表示符号的虚拟地址。

    st_info(符号类型和绑定信息):低四位表示类型;高28位表示符号绑定信息。具体数值如下图所示:

    st_shndx(符号所在段):如果符号定义在本文件中,那么这个成员表示符号所在段在段表中的下标;还有几种特殊的情况,如下图所示:

     

    展开全文
  • linux 运行执行文件

    千次阅读 2021-02-27 21:56:15
    a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个文件头、代码段、数据段(已初始化)、从定位信息区、符号表及符号名字字符串构成,如下左图所示,经过链接后生成执行文件如下右图所...

    本文只为整理思路,供自己日后参考。现在就从从一个执行文件a.out的运行开始,自上而下地分析linux是如何运行一个执行文件的。

    1、首先,需要了解一下a.out这个目标文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个文件头、代码段、数据段(已初始化)、从定位信息区、符号表及符号名字字符串构成,如下左图所示,经过链接后生成执行文件如下右图所示,需要说明的是1).bss段在目标文件和执行文件中并不占用文件的空间,但是它在加载时占用地址空间;2)链接后各个段在虚拟空间上的地址就确定了,并且linux下,ELF可执行文件默认从地址0x080480000开始分配。

    img img

    我们知道在linux下运行一个程序只要在shell中执行 ./a.out 这个命令就OK了,剩下的事情操作系统会替我们完成。但是操作系统到底做了什么,它是怎么做的呢,接下来就来解析一下。

    2、linux系统中每个程序都运行在一个进程上下文中,这个进程上下文有自己的虚拟地址空间。当shell运行一个程序时,父shell进程生成一个子进程,它是父进程的一个复制品。子进程通过execve系统调用启动加载器。加载器删除子进程已有的虚拟存储段,并创建一组新的代码、数据、堆、栈段,新的堆和栈被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小组块,新的代码和数据段被初始化为可执行文件的内容,最后将CUP指令寄存器设置成可执行文件入口,启动运行。

    执行完上述操作后,其实可执行文件的真正指令和数据都没有别装入内存中。操作系统只是通过可执行文件头部的信息建立起可执行文件和进程虚拟内存之间的映射关系而已。现在程序的入口地址为0x08048000,刚好是代码段的起始地址。当CPU打算执行这个地址的指令时,发现页面0x8048000~0x08049000(一个页面一般是4K)是个空页面,于是它就认为是个页错误。此时操作系统根据虚拟地址空间与可执行文件间的映射关系找到页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,并在虚拟地址页面与物理页面间建立映射,最后把文件中页面拷贝到物理页面,进程重新开始执行。该过程如下图所示:

    img

    3、这里比较难理解的就是这个分页机制,讲到分页机制,就不得不提linux的分段与分页机制,这也是这篇文章的重点。我们先来看一张图:img

    这张图展示了虚拟地址进过分段、分页机制后转化成物理地址的简单过程。其实分段机制是intel芯片为兼容以前产品而保留下来的,然后linux中弱化了这一机制。下面我们先简单介绍一下分段机制:

    分段提供了隔绝各个代码、数据和堆栈区域的机制,它把处理器可寻址的线性地址空间划分成一些较小的称为段的受保护地址空间区域。如果处理器中有多个程序在运行,那么每个程序可分配各自的一套段。此时处理器就可以加强这些段之间的界限,并确保一个程序不会通过访问另一个程序的段而干扰程序的执行。为了定位指定段中的一个字节,程序必须提供一个逻辑地址,该地址包括一个段选择符和一个偏移量。实模式下,段值还是可以看作地址的一部分,段值位XXXXh表示以XXXX0h开始的一段内存。而保护模式下,段值仅仅变成了一个索引,只想一个数据结构的一个表项,该表项中定义了段的起始地址、界限、属性等内容。cs、ds等寄存器中存的就是这个段选择符,用段选择符中的段索引在GDT或LDT表中定位相应的段描述符,把段描述符中取得的段基地址加上偏移量,就形成了一个线性地址。

    得到了线性地址之后,我们再来看看分页机制如何把它转换成物理地址。处理器分页机制会把线性地址空间(段已映射到其中)划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上。分页与分段最大的不同之处在于分页是用来固定长度的页面(一般为4KB)。如果仅适用分段地址转换,那么存储在物理内存中的一个数据结构将包含器所有部分。但如果适用了分页,那么一个数据结构就可以一部分存储在物理内存中,而另一部分保存在磁盘中。

    处理器把线性地址转换成物理地址和用于产生页错误异常的信息包含在存储与内存中的页目录和页表中。也变可看作简单的4K为单位的物理地址数组。线性地址的高20位构成这个数组的引索值,用于选择对应页面的物理基地址。线性地址的低12位给出 了页面中的偏移量。页表中的页表项大小为32位。由于只需要其中20位来存放页面的物理基地址,因此剩下的12位可用于存放诸如页面是否存在等属性信息。如果线性地址引索的页表项被标注为存在,我们就从页面中取得物理地址。如果表项中不存在,那么访问对应物理页面时就会产生异常。

    页表含有2^20(1M)个表项,而每项占用4个字节。如果作为一个表来存放的话,最多将占用4MB内存。因此为了减少内存占用量,80x86适用了两级表。由此,高20位线性地址到物理地址的转换也被分成两步进行,每部适用其中10个比特。

    第一级表称为页目录。它被存放在1页4k 页面中,具有2^10(1k)个4字节长度的表项。这些表项指向二级表。它们由线性地址最高10位作为引索。

    第二级表称为页表,长度也是1个页面。线性地址高10位获取指向第二级页表的指针,再加上中间10位,就可以在相应页表中获得物理地址的高20位。而为地址的低12位就是线性地址的低12,这样就组成了一个完整的32位物理地址。分段、分页的整个过程可见下面这张图:

    img

    以上就是良许教程网为各位朋友分享的Linux相关知识。

    展开全文
  • C语言 目标文件和可执行文件(ELF文件)

    万次阅读 多人点赞 2019-05-15 18:55:15
    1.C语言创建程序 1.1C语言创建(分为4个步骤) 编辑 ...编译器的输出结果成为目标代码,存放它们的文件称为目标文件。扩展名为.o或者.obj。 (该部分编译是指汇编器编译汇编语言或者编译器编译...

    转自:https://www.jianshu.com/p/7c609b70acbd

    1.C语言创建程序

    1.1C语言创建(分为4个步骤)
    • 编辑
    • 编译
    • 链接
    • 执行

    编辑:就是创建和修改C程序的源代码-我们编写的程序称为源代码。
    编译:就是将源代码转换为机器语言。编译器的输出结果成为目标代码,存放它们的文件称为目标文件。扩展名为.o或者.obj。
    (该部分编译是指汇编器编译汇编语言或者编译器编译高级语言)
    链接:链接器将源代码由编译器产生的各种模块组合起来,再从C语言提供的程序库中添加必要的代码模块,将它们组成一个可执行的文件。在windows下扩展名为.exe,Unix下无扩展名。
    执行:运行程序。

    C.png
    1.2什么是源代码,目标文件,可执行文件。

    源代码 ——源文件就是存放程序代码的文件。通常我们编辑代码的文件就是源文件。

    • 源代码相对目标代码和可执行代码而言的。
    • 源代码就是用汇编语言和高级语言写出来的地代码。

    目标文件——指源代码经过编译程序产生的能被cpu直接识别二进制代码。

    • 目标代码指计算机科学中编译器或汇编器处理源代码后所生成的代码,它一般由机器代码或接近于机器语言的代码组成。
    • 目标文件包括着机器代码(可直接被计算机中央处理器履行)和代码在运行时使用的数据,如重定位信息,如用于链接或调试的程序符号(变量和函数的名字),另外还包括其他调试信息。
    gcc -c main.c 
    编译main.c ,生成目标文件main.o,但不进行link. 
    gcc -o main.o
    链接成可执行文件main

    可执行文件——可执行代码就是将目标代码连接后形成的可执行文件,当然也是二进制的。 连接程序系统库文件连接就生成可执行文件。

    例如:*.obj是程序编译之后生成的目标文件,连接程序再将这个文件与系统库文件连接就生成可执行文件
    
    1.3链接器的作用
    Screen Shot 2017-06-12 at 4.32.06 PM.png

    根据上面的图,我们可以看到链接器还额外链接了2个部分。

    目标代码文件中所缺少的第一个元素是一种叫做启动代码(Start-up code)的东西,此代码相当于您的程序和操作系统之间的接口。例如你可以在dos 或Linux下运行一个 IBM PC 兼容机,在两种情况中硬件是相同的,所以都会使用同样的目标代码,但是 DOS与Linux要使用不用的启动代码,因为这两种系统处理程序的方式不同的。

    所缺少的第二个元素是库例程的代码。几乎所有C程序都利用标准库中所包含的例程(称为函数)。例如,程序中的函数printf()。目标代码文件不包含这一函数的指令。实际代码存储在另一个称为“库”的文件中,库文件中包含许多函数的目标代码。

    链接器的作用是将这3个元素(目标代码、系统的标准启动代码和库代码)结合在一起,并将他们存放在单个文件,即可执行文件中。对库代码来说,链接器只从库中提取您所使用的函数所需的代码。

    可以得出结论:目标文件和可执行文件都是由机器语言指令组成的。但目标文件只包含您所编写的代码转换成的机器语言,而可执行文件还包含您所使用的库例程以及启动代码。

    下面这幅图能大致说明一下链接的情况。


    1.png

    这是一个main.o目标代码,内部有main,foo,bar三个函数。

    U main表示main这个符号在crtl1.o中用到了,但是没有定义。因此需要main.o提供定义并和crtl1.o链接在一起。main整个程序的入口实际上是_crtl1.o中的 _start,它做了一些初始化工作(启动历程),然后调用C代码中提供的main.c函数。libc是运行时候动态链接libc共享库(库中包含常用的函数)。

    所以程序的入口点其实是_start,main函数实际上是被_start调用。

    1.4gcc命令图
    QQ截图20170613151139.png

    2.ELF文件(该部分分析目标文件和可执行文件的,涉及部分汇编指令)

    ELF文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:

    • 可重定位的目标文件
    • 可执行文件
    • 共享库

    ELF文件格式提供了两种不同的视角,在汇编器和链接器看来,ELF文件是由Section HeaderTable描述的一系列Section的集合,而执行一个ELF文件时,在加载器(Loader)看来它是 由Program Header Table描述的一系列Segment的集合。如下图所示。


    Screen Shot 2017-06-10 at 10.01.54 PM.png

    左边是从汇编器和链接器的视角来看这个文件,开头的ELF Header描述了体系结构和操作系统 等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位 置,Program Header Table在汇编和链接过程中没有用到,所以是可有可无的,Section Header Table中保存了所有Section的描述信息。右边是从加载器的视角来看这个文件,开头 是ELF Header,Program Header Table中保存了所有Segment的描述信息,Section Header Table在加载过程中没有用到,所以是可有可无的。注意Section Header Table和ProgramHeader Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

    目标文件需要链接器做进一步处理,所以一定有Section Header Table;可执行文件需要加载运行,所以一定有Program Header Table;而共享库既要加载运行,又要在加载时做动态链接, 所以既有Section Header Table又有Program Header Table。

    • section:C语言内存中的.text,.data,.bss.....
    • Segment:是指在程序运行时加载到内存的具有相同属性的区域,由一个或多个Section组成,比如有两个Section都要求加载到内存后可读可写,就属于同一个Segment。有些Section只对汇编器和链接器有意义,在运行时用不到,也不需要加载到内 存,那么就不属于任何Segment 。

    2.1重定位目标文件

    在进行该部分之前,我们先查看一下网上的部分重定位目标文件的资料。

    资料一:

    汇编器所产生的目标文件至少包括三个区,即文本区(text),数据区(data)和bss区。文本区一般包括程序的代码和常量,数据区通常存放全局变量等内容,bss区用于存放未初始化的变量或作为公共变量存储空间。在一个目标文件中,其text区从地址0开始,随后是data区,再后面是bss区。而要运行程序,必须装载到内存中,所以这些区的地址需要在内存中重新安排,也就是重定位。

    资料二:

    编译器编译后产生的目标文件是可重定位的程序模块,并不能直接运行,链接就是把目标文件和其他分别进行编译生成的程序模块(如果有的话)及系统提供的标准库函数连接在一起,生成可运行的可执行文件的过程。
    重定位是链接器在完成符号解析后(知道了各个输入模块的代码段和数据段的大小)的一个步骤,其作用顾名思义就是重新定位,确定比如指令,全局变量等在运行时的存储器地址。

    资料三:

    比如说两个编译后的可重定位目标文件obj1.o和obj2.o
    在obj1.o里面定义了一个全局变量glob(在obj1里面记录了glob相对于该文件数据段的相对地址), 而obj2.0里面又引用了这个全局变量glob。
    链接的重定位就是要确定在链接后的可执行程序中glob的地址,而不是相对于obj1的地址,从而使obj2也能通过地址调用glob。
    当然重定位并不只是全局变量,还包括外部函数,指令等运行时地址的确定

    资料四:

    当你在程序中写上一个全局变量或者是一个函数时,这个定位过程会经历几个阶段:
    1.在这个目标文件中的相对定位,一个目标文件中对此文件中的所有函数,变量进行符号描述,比如一个变量A,它所占的相对地址是多少?是全局的?或者是静态的,或者是外部的??
    2.在连接多个目标成一个可执行文件时,会再次对这个变量进行重定位,也就是在这个可执行文件中进行对此变量进行描述,同目标文件中的描述差不多,只不过此变量不再有外部,内部之分,都成了本地变量,并且会将所有全局变量存放在一定的逻辑地址中,这是通过连接脚本文件与各个目标文件中的相对地址共同决定的
    3.最终的操作系统加载这个可执行文件时,会对这些变量与函数地址再次进行重定位,其方式就是首先分析这个可执行文件中的不同段,读出相应的描述表,然后通过逻辑地址与物理地址进行映射出,最终就将可执行的二进制码加进了真实的物理内存了,关于分析可执行文件格式与物理地址的转换,不同的CPU与操作系统的实现方式会有不同之处

    接下来我们开始实践部分,首先写一个求一组数的最大值的汇编程序max.s。

    Screen Shot 2017-06-11 at 3.42.29 PM.png

    现在有一个max.o目标文件,我们用readlf工具读取其ELF Header和Section Header Table


    Screen Shot 2017-06-10 at 10.20.59 PM.png

    ELF Header中描述了操作系统是UNIX,体系结构是80386。Section Header Table中有8个Section Header,从文件地址200
    (0xc8)开始,每个Section Header占40字节,共320字节,到文件地址0x207结束。这个目标文件没有Program Header。文件地址是这样定义的:文件开头第一个字节的地址是0,然后每个字节占一个地址。

    QQ图片20170611130217.png

    从Section Header中读出各Section的描述信息。
    Addr是这些段加载到内存中的地址(程序中的地址都是虚拟地址),加载地址要在链接时填写,现在空缺,所以是全0。
    OffSize两列指出了各Section的文件地址,比如.data段从文件地址0x60开始,一共0x38个字节,回去翻一下程序,.data段定义了14个4字节的整数,一共是56个字节,也就是0x38。

    根据以上信息可以描绘出整个目标文件的布局。

    QQ图片20170611132839.png

    ** Section Header Table**:读出各Section的描述信息。
    .shstrtab:保存着各Section的名字,比如.text,.data.....。
    .strtab:保存着程序中用到的符号的名字.比如汇编程序的start_loop:和loop_exit符号。(对应的就是for循环)。
    **.data **:保存程序中已初始化的全局变量和静态变量以及字符串常量。
    .bss:存放程序中未初始化的全局变量和静态变量。
    .text:存放程序执行代码。
    .rel.text:告诉链接器指令中的哪些地方需要做重定位。
    下节分析。

    Screen Shot 2017-06-11 at 2.40.30 PM.png

    我们看一下.text段内容


    Screen Shot 2017-06-11 at 3.44.53 PM.png

    ![Uploading QQ截图20170613113520_952796.png . . .]

    text段代码中,一些跳转指令和内存访问指令中的地址都是符号的相对地址,下一步链接器要修改这些指令,把其中的地址都改成加载时的内存地址,这些指令才能正确执行。

    2.2可执行文件

    现在分析可执行文件max。


    Screen Shot 2017-06-11 at 2.45.59 PM.png
    Screen Shot 2017-06-11 at 2.47.07 PM.png
    Screen Shot 2017-06-11 at 2.47.43 PM.png
    Screen Shot 2017-06-11 at 2.48.05 PM.png

    在ELF Header中,Type改成了EXEC,由目标文件变成可执行文件了多了两个Program Header,少了两个Section Header。

    在Section Header Table中,.text和.data的加载地址分别改成了0x0804 8074和0x0804 90a0。.bss段没有用到,所以被删掉了。.rel.text段就是用于链接过程的,链接完了就没用 了,所以也删掉了。

    多出来的Program Header Table描述了两个Segment的信息。.text段和前面的ELF Header、Program Header Table一起组成一个Segment(FileSiz指出总长度 是0x9e),.data段组成另一个Segment(总长度是0x38)。VirtAddr列指出第一 个Segment加载到虚拟地址0x0804 8000,第二个Segment加载到地址0x0804 90a0。Flg列指出第一个Segment的访问权限是可读 可执行,第二个Segment的访问权限是可读可写。

    Screen Shot 2017-06-11 at 2.54.13 PM.png

    原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了。

    我们查看一下.txt段内容。


    Screen Shot 2017-06-11 at 3.32.25 PM.png
    现在我们对比一下目标文件和可执行文件的不同。

    目标文件.text和.data段地址


    QQ截图20170613113520.png

    可执行文件.text和.data段地址


    QQ截图20170613113500.png

    目标文件中跳转指令


    Screen Shot 2017-06-11 at 3.35.56 PM.png

    可执行文件中跳转指令


    Screen Shot 2017-06-11 at 3.36.18 PM.png

    目标文件中内存访问指令


    Screen Shot 2017-06-11 at 3.37.08 PM.png

    可执行文件中内存访问指令


    • 可以看到指令中的相对地址都改成绝对地址了。
    • 结合上2部分分析,我们可以看到。
    • .text和.data段代码加载到内存中的地址由空缺0变成了具体地址。
    • .text段代码中一些跳转指令和内存访问指令中的地址由相对地址改成加载时的内存地址,
    • .data段代码也由相对地址改为绝对地址。

    3.静态库和共享库

    :有时候需要把一组代码编译成一个库,这个库在很多项目中都要用到,例如libc就是这样一个库,我们在不同的程序中都会用到libc中的库函数(例如printf)。

    共享库和静态库的区别:在链接libc共享库时只是指定了动态链接器和该程序所需要的库文件,并没有真的做链接,可执行文件调用的libc库函数仍然是未定义符号,要在运行时做动态链接。而在链接静态库时,链接器会把静态库中的目标文件取出来和可执行文件真正链接在一起。

    • 静态库链接后,指令由相对地址变为绝对地址,各段的加载地址定死了。
    • 共享库链接后,指令仍是相对地址,共享库各段的加载地址并没有定死,可以加载到任意位置。

    静态库好处:静态库中存在很多部分,链接器可以从静态库中只取出需要的部分来做链接 (比如main.c需要stach.c其中的一个函数,而stach.c中有4个函数,则打包库后,只会链接用到那个函数)。另一个好处就是使用静态库只需写一个库文件名,而不需要写一长串目标文件名。

    该部分是参照《一站式学习C编程》所做的总结,理解程度比较初步。

    展开全文
  • Linux下生成可执行文件

    千次阅读 2019-12-06 22:01:32
    我们先来复习一下一个可执行的程序是如何从源代码转变为一个可以运行的二进制文件的。 在这一部分,我们以简单的hello.c程序来说明: #include <stdio.h> int main() { printf("hello, world\n"); return 0;...

    一个可执行文件的生成

    我们先来复习一下一个可执行的程序是如何从源代码转变为一个可以运行的二进制文件的。
    在这一部分,我们以简单的hello.c程序来说明:

    #include <stdio.h>
    int main()
    {
        printf("hello, world\n");
        return 0;
    }
    

    预处理

    首先是预处理部分。
    预处理命令:

    • gcc -E hello.c -o hello.i
    • cpp hello.c > hello.i

    经过预处理的程序大致如下:

    # 1 "hello.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 2
    # 1 "hello.c"
    # 1 "/usr/include/stdio.h" 1 3 4
    # 27 "/usr/include/stdio.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
    # 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
    /*
    .
    .
    省略大部分内容
    .
    .
    */
    # 868 "/usr/include/stdio.h" 3 4
    # 2 "hello.c" 2
    # 2 "hello.c"
    int main()
    {
        printf("hello, world\n");
        return 0;
    }
    

    可见经过预编译处理后,得到的文件还是一个可读的文本文件 ,但不包含任何宏定义

    编译

    编译过程就是将预处理后得到的预处理文件(如 hello.i)进行词法分析、语法分析、语义分析、优化后,生成汇编代码文件
    编译命令:

    • gcc -S hello.i -o hello.s
    • gcc -S hello.c -o hello.s

    经过编译的程序如下:

    	.file	"hello.c"
    	.text
    	.section	.rodata
    .LC0:
    	.string	"hello, world"
    	.text
    	.globl	main
    	.type	main, @function
    main:
    .LFB0:
    	.cfi_startproc
    	pushq	%rbp
    	.cfi_def_cfa_offset 16
    	.cfi_offset 6, -16
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	leaq	.LC0(%rip), %rdi
    	call	puts@PLT
    	movl	$0, %eax
    	popq	%rbp
    	.cfi_def_cfa 7, 8
    	ret
    	.cfi_endproc
    .LFE0:
    	.size	main, .-main
    	.ident	"GCC: (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0"
    	.section	.note.GNU-stack,"",@progbits
    

    同样的,经过编译后,得到的汇编代码文件还是可读的文本文件,CPU仍然无法理解和执行它

    汇编

    汇编过程就是将编译后得到的汇编代码文件转换为机器指令序列,生成可重定位目标文件。
    汇编指令和机器指令一一对应,前者是后者的符号表示,它们都属于机器级指令,所构成的程序称为机器级代码
    汇编命令:

    • gcc -c hello.s -o hello.o
    • gcc -c hello.c -o hello.o

    汇编结果是一个可重定位目标文件,其中包含的是不可读的二进制代码,我们用工具查看它的大致内容:
    objdump hello.o -D

    
    hello.o:     文件格式 elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <main>:
       0:	55                   	push   %rbp
       1:	48 89 e5             	mov    %rsp,%rbp
       4:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # b <main+0xb>
       b:	e8 00 00 00 00       	callq  10 <main+0x10>
      10:	b8 00 00 00 00       	mov    $0x0,%eax
      15:	5d                   	pop    %rbp
      16:	c3                   	retq   
    
    Disassembly of section .rodata:
    
    0000000000000000 <.rodata>:
       0:	68 65 6c 6c 6f       	pushq  $0x6f6c6c65
       5:	2c 20                	sub    $0x20,%al
       7:	77 6f                	ja     78 <main+0x78>
       9:	72 6c                	jb     77 <main+0x77>
       b:	64                   	fs
    	...
    
    Disassembly of section .comment:
    
    0000000000000000 <.comment>:
       0:	00 47 43             	add    %al,0x43(%rdi)
       3:	43 3a 20             	rex.XB cmp (%r8),%spl
       6:	28 55 62             	sub    %dl,0x62(%rbp)
       9:	75 6e                	jne    79 <main+0x79>
       b:	74 75                	je     82 <main+0x82>
       d:	20 37                	and    %dh,(%rdi)
       f:	2e 34 2e             	cs xor $0x2e,%al
      12:	30 2d 31 75 62 75    	xor    %ch,0x75627531(%rip)        # 75627549 <main+0x75627549>
      18:	6e                   	outsb  %ds:(%rsi),(%dx)
      19:	74 75                	je     90 <main+0x90>
      1b:	31 7e 31             	xor    %edi,0x31(%rsi)
      1e:	38 2e                	cmp    %ch,(%rsi)
      20:	30 34 29             	xor    %dh,(%rcx,%rbp,1)
      23:	20 37                	and    %dh,(%rdi)
      25:	2e 34 2e             	cs xor $0x2e,%al
      28:	30 00                	xor    %al,(%rax)
    
    Disassembly of section .eh_frame:
    
    0000000000000000 <.eh_frame>:
       0:	14 00                	adc    $0x0,%al
       2:	00 00                	add    %al,(%rax)
       4:	00 00                	add    %al,(%rax)
       6:	00 00                	add    %al,(%rax)
       8:	01 7a 52             	add    %edi,0x52(%rdx)
       b:	00 01                	add    %al,(%rcx)
       d:	78 10                	js     1f <.eh_frame+0x1f>
       f:	01 1b                	add    %ebx,(%rbx)
      11:	0c 07                	or     $0x7,%al
      13:	08 90 01 00 00 1c    	or     %dl,0x1c000001(%rax)
      19:	00 00                	add    %al,(%rax)
      1b:	00 1c 00             	add    %bl,(%rax,%rax,1)
      1e:	00 00                	add    %al,(%rax)
      20:	00 00                	add    %al,(%rax)
      22:	00 00                	add    %al,(%rax)
      24:	17                   	(bad)  
      25:	00 00                	add    %al,(%rax)
      27:	00 00                	add    %al,(%rax)
      29:	41 0e                	rex.B (bad) 
      2b:	10 86 02 43 0d 06    	adc    %al,0x60d4302(%rsi)
      31:	52                   	push   %rdx
      32:	0c 07                	or     $0x7,%al
      34:	08 00                	or     %al,(%rax)
    	...
    

    目标文件

    目标文件有三种形式:

    • 可重定位目标文件 Relocatable object file (.ofile)
      • 由对应的 .c 文件通过编译器和汇编器生成的二进制代码,包含代码和数据,可以与其他可重定位目标文件合并创建一个可执行或共享的目标文件
    • 可执行目标文件 Executable object file (a.outfile)
      • 由链接器生成,可以直接通过加载器加载到内存中充当进程执行的二进制文件,包含代码和数据
    • 共享目标文件 Shared object file (.sofile)
      • 一种类型特殊的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并连接的二进制文件。Windows下被称为 Dynamic Link Libraries(DLLs)。

    格式 [1]

    上面提到的三种对象文件有统一的格式,即 Executable and Linkable Format(ELF),因为,我们把它们统称为 ELF binaries,具体的文件格式如下

    下面分别介绍一下各个部分:

    • ELF header
      • 包含 word size, byte ordering, file type (.o, exec, .so), machine type, etc
    • Segment header table
      • 包含 page size, virtual addresses memory segments(sections), segment sizes
    • .text section
      • 代码部分
    • .rodata section
      • 只读数据部分,例如跳转表
    • .data section
      • 初始化的全局变量
    • .bss section
      • 未初始化的全局变量
    • .symtab section
      • 包含 symbol table, procudure 和 static variable names 以及 section names 和 location
    • .rel.txt section
      • .text section 的重定位信息
    • .rel.data section
      • .data section 的重定位信息
    • .debug section
      • 包含 symbolic debugging (gcc -g) 的信息
    • Section header table
      • 每个 section 的大小和偏移量

    符号解析

    下面我们换用一个程序来进行说明:

    /* main.c */
    int sum(int *a, int n);
    
    int array[2] = {1, 2};
    
    int main() 
    {
        int val = sum(array, 2);
        return val;
    }
    
    /* sum.c */
    int array[2];
    int sum(int *a, int n)
    {
        int i, s = 0;
        static int sta = 50;
        for (i = 0; i < n; i++) { 
            s += a[i];
        }
        return s;
    }        
    

    在进行链接的时候,我们首先得做的一件事情就是符号的解析:
    符号指的是我们在代码中声明的变量、函数,所有的符号声明都会被保存在符号表(symbol table)中,而符号表会保存在由汇编器生成的object文件中(也就是.o文件)。
    每一个可重定位目标模块都有一个符号表,符号表实际上是一个结构体数组,每一个元素包含名称、大小和符号的位置。
    在连接器的上下文中,有三种不同的符号:

    • 全局符号 Global symbols
    • 由模块内定义,并能够被其他模块引用的符号
    • 非静态的C函数全局变量是全局符号
    • 对于main.o来说,array是全局符号
    • 外部符号 External symbols
    • 由其他模块内定义,并被本模块引用的符号
    • 在其他源代码之中定义的非静态的C函数全局变量是外部符号
    • 对于sum.o来说,array是外部符号
    • 本地符号 Local symbols
    • 在当前模块中定义,只能被当前模块引用的是本地符号
    • 带有属性的C函数全局变量是本地符号
    • 对于sum.o来说,sta是外部符号

    对于连接器来说,局部的非静态变量不在它的管辖范畴之内,因为局部的非静态变量不是符号,它一般都是保存在栈里面。而静态的变量,如果已经初始化,则会保存在.data节之中,否则保存在.bss节之中。
    如果遇到在同一个模块的不同函数里面出现了相同名字的静态变量,则会给同名的本地符号加上一个唯一的编号,用于标识这一个变量。
    但是如果遇到了同名的全局符号或是外部符号则怎么做呢?
    首先我们得知道,不同的符号是有强弱之分的:

    • 函数和已初始化的全局变量是强符号
    • main.o里面的array是强符号,因为它已经初始化
    • 而未初始化的全局变量是弱符号
    • sum.o里面的array是弱符号,因为它未初始化

    所以,根据强弱符号的定义,Linux的连接器在处理多重定义的符号时,采用以下三条规则:

    • 规则1:不能出现多个同名的强符号,不然就会出现链接错误
    • 规则2:如果有同名的强符号和弱符号,选择强符号
    • 规则3:如果有多个弱符号,随便选择一个

    所以在进行main.osum.o的链接时,连接器选择的是main.o中的符号array
    我们来看一个额外的例子:

    // 文件 h1.c
    #include <stdio.h>
    
    int x = 2018; 
    int y = 2019;
    
    void f();
    
    void main()
    {
    	printf("x = 0x%x ,y = 0x%x\n",x,y);
    	f();
    	printf("x = 0x%x ,y = 0x%x\n",x,y);
    }
    
    // 文件 h2.c
    double x;
    void f()
    {
    	x = -1;
    }
    

    运行的结果是:

    linux> ./h
    x = 0x7e2 ,y = 0x7e3
    x = 0x0 ,y = 0xbff00000
    

    h1x定义为int型数据占用4个字节,而在h2x定义为double型数据,在x86-64/Linux下,占用8个字节,则h2中的x = -1;不仅修改了x的内容,也修改了y的内容,这一种错误,在编译的时候只会触发一条警告。当程序到达了一定的规模后,这种类型的错误相当难以修正。
    所以说,如果可能,尽量避免使用全局变量

    如果一定要用的话,注意下面几点:

    • 使用静态变量
    • 定义全局变量的时候初始化
    • 注意使用extern关键字

    重定位

    在符号解析完成后,进行重定位工作,主要分为三步:

    • 合并相同的节
    • 将所有目标模块中的代码段与数据段分别合并为一个单独的代码段和数据段
    • 例如,所有.text节合并作为可执行文件中的.text
    • 对定义符号进行重定位(确定地址)
    • 确定新节中所有定义符号在虚拟地址空间中的绝对地址
    • 例如,为函数确定首地址,进而确定每条指令的地址;为变量确定首地址
    • 对引用符号进行重定位(确定地址)
    • 将可执行文件中符号引用处的地址修改为重定位后的地址信息
    • 修改.text节和.data节中对每个符号的引用(地址)

    在完成这三步工作后,链接也就基本完成了。


    参考资料

    展开全文
  • linux 运行文件

    千次阅读 2021-02-01 22:05:27
    a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个文件头、代码段、数据段(已初始化)、从定位信息区、符号表及符号名字字符串构成,如下左图所示,经过链接后生成执行文件如下右图.
  • linux修改文件为可执行文件

    千次阅读 2019-10-07 16:33:31
    修改shell为可执行文件 chmod +x test2.sh chmod751file 给file的属主分配读、写、执行(7)的权限,给file的所在组分配读、执行(5)的权限,给其他用户分配执 有可能没权限 chmod 777 转载于:...
  • linux的可执行文件是什么后缀

    千次阅读 2020-03-05 09:32:26
    只要设了执行权限,有elf文件头,就可以看成可执行文件,要是纯文本文件可以看成是可执行的脚本。一部分下载的商业软件安装程序之类的可执行文件有.bin的后缀。有些时候可执行文件的后缀名为.out。Linux下的后缀名...
  • linux下备份文件,linux备份文件命令

    千次阅读 2021-05-16 02:15:55
    linux备份文件命令1.cp命令cp[功能说明]文件的备份英文xxxx #cp命令将源文件复制到另外安全的地方,复制的文件和源文件是两个相互独立的文件,对认识一个文件的操作不影响另一个文件,但与符号链接文件中的硬链接是...
  • Linux系统中运行python文件

    千次阅读 2021-02-24 22:49:30
    Linux下可直接运行.exe文件一样运行.py文件,方法是在.py文件的第一行加上下面的任意一行: #!/usr/bin/python #!/usr/bin/env python 二者的区别在于: #!/usr/bin/python是告诉操作系统在调用脚本时调用/usr...
  • LinuxLinux文件文件的存储

    万次阅读 2018-07-31 18:39:51
    从进程的角度来看,文件是一种进程可以对它进行访问的,但独立于进程而存在的数据结构,文件中的信息不会因进程的死亡而消失,除非文件的属主显式地删除它;从操作系统的角度来看,文件是一个逻辑上连续的数据集合,...
  • 现在就从从一个执行文件a.out的运行开始,自上而下地分析linux是如何运行一个执行文件的。1、首先,需要了解一下a.out这个目标文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个...
  • Linux下gcc编译c文件为可执行文件分为四个步骤: 分别是 预编译、编译、汇编、链接。 1、预编译( 生成 hello.i 文件) 预编译的处理规则: 1. 将所有的 “#define” 删除,并展开所有的宏定义。 2. 处理...
  • 转自:http://w-tingsheng.blog.163.com/blog/static/2505603420124309130528/cp覆盖时,无论加什么参数-f之类的还是提示是否覆盖,当文件比较少的时候还可以按Y确认,当很多文件的时候就不好说了。用下面的方法可以...
  • Linux查看可执行文件依赖库

    千次阅读 2020-04-15 10:15:04
    Linux查看文件依赖库使用 ldd 命令查看依赖库曲线方式查看可执行文件依赖情况使用交叉编译工具链查看依赖情况 使用 ldd 命令查看依赖库 在Linux系统中,一般使用ldd指令查看某个可执行文件所依赖的动态库,命令如下 ...
  • Linux elf可执行文件加密

    千次阅读 2020-04-21 17:58:51
    ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。 那么如何对elf文件进行加密呢?...
  • Linux可以替换运行中的程序么? 今天被朋友问及“Linux可以替换运行中的程序么?”,以前依稀记得Linux下是可以的(而Windows就不让),于是随口答道“OK”。结果朋友发来一个执行结果:(test正在运行中) #...
  • Linux环境下查看目标文件生成过程

    千次阅读 2016-07-21 00:52:36
    1.使用vim编辑器编写源代码  以最简单的在屏幕输出“hello world!”为例。首先在vim编辑器里输入源代码: #include int main() ...显示当前文件夹内的所有文件,然后进入其中的workspace文件夹。  
  • 以下为你介绍在深度Deepin 15.9Linux操作系统下直接运行exe文件的方法,此方法基于deepin-wine实现,经测试,一些exe文件可以正常打开的,但部分可能会出现无法使用的情况,但是你可以尝试一下,以确定哪些是可以...
  • Linux下gcc编译c程序生成可执行文件的过程

    万次阅读 多人点赞 2018-10-23 11:29:42
    Linux下gcc编译c文件生成可执行文件 一、准备 hello.c 文件 1、在当前目录下新建c文件 $:vim hello.c 2、按i进入编辑模式。按esc退出编辑模式,输入源代码 #include &amp;amp;amp;lt;stdio.h&amp;amp;amp;gt...
  • Linux系统下可执行文件运行过程

    千次阅读 2015-06-12 14:35:11
    a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个文件头、代码段、数据段(已初始化)、从定位信息区、符号表及符号名字字符串构成,如下左图所示,经过链接后生成执行文件如下右图所...
  • Linux下为目标文件创建软链接

    千次阅读 2019-06-29 23:25:23
    正常情况下,进入当前目录下执行的命令: ln -s /path/to/one linkname 就会将 /path/to/one 文件链接到当前目录下,且命名为 linkname 但是偶尔会报这样的错误: ln: failed to create symbolic link ‘linkname’...
  • Linux监控文件读写

    千次阅读 2019-07-25 17:18:34
    如何去监测目标文件, 我思考了很久仍没有头绪,翻了翻开发大赛入围名单, 发现马艺诚同学使用了hook方法来监测进程操作目标文件。于是下午试了试, 确实可行(毕竟人家已经做出来了嘛)。下面讲解一下我下午尝试的...
  • linux文件重命名This tutorial will walk you through the simple steps to rename a file in Linux. 本教程将引导您完成在Linux中重命名文件的简单步骤。 It’s going to be a very simple and straightforward ...
  • Linux执行sql文件

    万次阅读 2018-10-15 21:01:50
    1.上传sql文件linux服务器。 可用rz命令上传。 2.进入sql命令行。 执行mysql -u xxx -p,输入用户名xxx,接下来输入密码进入sql命令行。 3.创建目标数据库并且use。 create database xxx; use xxx; 注意:...
  • linux 如何运行一个可执行文件

    万次阅读 2014-07-18 20:35:35
    现在就从从一个执行文件a.out的运行开始,自上而下地分析linux是如何运行一个执行文件的。 1、首先,需要了解一下a.out这个目标文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一...
  • 1、安装pyinstaller pip install pyinstaller 2、编译 pyinstaller -F -w game.py (-F表示...pyinstaller -F -w -i bitbug_favicon.ico game.py (-i用来设置编译成exe文件的图标,后面跟.ico格式的图片文件) ...
  • linux复制文件的命令是什么?

    千次阅读 2021-05-08 21:07:12
    linux复制文件的命令是:cp命令。...cp 命令的基本格式如下:cp [选项] 源文件 目标文件选项:-a:相当于 -d、-p、-r 选项的集合,这几个选项我们一一介绍;-d:如果源文件为软链接(对硬链接无效),则复...
  • linux执行文件格式

    万次阅读 2017-05-24 23:27:05
    连接(link): 多个目标文件 被连接成一个最终的可执行文件, 可执行文件的运行: 可执行文件被加载(load)到内存中执行。 2. a.out assembler and link editor output汇编器和链接编辑器的输出格式(简述)a.out 是一种...
  • 以下是在Deepin 15.9Linux操作系统下直接运行exe文件的方法. 这种方法是基于酒的. 经过测试,某些exe文件可以正常打开,但是其中一些可能无法使用. 情况,但您可以尝试确定哪些功能正常. 对于需要安装Wine4.0的用户...
  • Linux系统将执行程序或者文件输出到指定文件中 1、使用标准输出以及重定向 例如:echo “123” > /home/123.txt 1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null" 2 表示...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 438,363
精华内容 175,345
关键字:

linux目标文件可以直接执行

linux 订阅