makefile、shell、可执行文件等杂类

ps:makefile文件中必须使用tab千万不能用空格
ps:makefile、cmake、shell区别:
1当我们需要手动添加库时(许多库不手动链接会报错),2同时当程序只要一个cpp时使用gcc命令编译即可,但是有很多个源文件不可能靠gcc一个一个编译,3如果使用gcc编译,后期改动一个语句就需要全部源文件重新编译,浪费时间。
解决办法:使用makefile,在Makefile里面你可以设置你想要的编译规则,你想要编译哪些文件,哪些文件不需要编译等等都可以体现在Makefile中,而且支持多线程并发操作,可以减少编译的时间。
然而,还有另一个工具make,make是用来执行Makefile的,make可以说成一个音乐家,Makefile就是一篇乐谱,音乐家根据乐谱的内容奏乐,make就是根据Makefile中写的内容进行编译和链接,make更像是一个批处理的工具,可以批处理源文件,只要执行一条make命令,就可以实现自动编译。
当我们编译整个项目工程的时候,make只会编译我们修改过的文件,没有修改过的就不用重新编译,这样我们debug了一个小bug后重新编译就不用花费大量的编译时间。只要没有添加文件或者删除文件,Makefile的内容都是不需要修改的。所以使用make+Makefile极大的提高了我们的工作效率。
当工程小时可以手写makefile,但是工程非常大的时候,手写Makefile也是一件麻烦的事,而且Makefile又不是万能的,换了一个别的平台,Makefile又得重写,因此出现了写makefile的工具,cmake只需要把所有源文件读入就行,可以跨平台项目管理的工具cmake,cmake就可以生成Makefile文件给make去执行,这样就不用跨平台了还得去修改。
makefile:

shell基础:
shell有两者理解:脚本:本质是一个文件,文件里面存放的是 特定格式的指令,系统可以使用脚本解析器 翻译或解析 指令 并执行(它不需要编译),相当于把命令行输入到文本的格式运行。
1.是操作系统的终端命令行,为软件系统提供给用户操作的命令行界面,可以说它是人机交互的一种方式,具体来说就我们通过shell给软件系统输入命令然后回车执行,执行完后又回到shell命令行可以再次输入命令执行
2.shell是一类编程语言
编写shell脚本时使用的语言是shell语言,又叫脚本语言,在linux下常用的脚本语言其实就是bash,sh。脚本语言,在嵌入式中,主要用来做配置(配置的过程就是脚本语言来实现)
(1)脚本程序编写好源代码后可以直接运行(没有编译过程)
(2)shell程序是解释运行的,(顺序结构,从上到下,逐行运行)
(3)CPU实际只认识二进制代码,根本不认识源代码,脚本程序源代码其实也不是二进制代码,CPU也不认识,不能直接执行。只不过脚本程序的编译链接过程不是以脚本程序源代码为单位进行的,而是在脚本运行过程逐行的解释执行时才去完成脚本源程序转为二进制的过程(不一定是编译链接,因为这行脚本程序可能早就编译链接好了
1、shell 脚本就是一些命令的集合,它是个纯文本文件,命令从上而下,一行一行的开始执行。
2、shell 脚本提供数组、循环、条件判断等功能。
3、shell 脚本扩展名为 .sh。
shell 脚本第一行一定要为: #!/bin/bash -------> 表示使用 bash
shell脚本:
(1)当前目录下创建文件夹dir ,dir下创建文件b.txt

#! /bin/sh
mkdir dir
cd dir
touch b.txt
cd ..

区别:
ps:shell和makefile区别,makefile可以调用shell脚本,主要就是用来批量描述整个工程的编译、链接规则。而shell不只限制于执行工程编译链接,可以是修改名字和其他功能,比如我们如果要实现一个需求,需要在 shell 窗口中输入很多命令,可以直接在 shell 窗口中输入,但每次用到相同的需求时都需要重新敲,我们可以把这些命令记录在一个文档中,然后去执行这个文档中的命令,这样就能一步操作完成。
两者区别:
1.在 Makefile 中可以调用 shell 脚本
2.shell 中通配符 * 表示所有的字符
Makefile 中通配符 % 表示所有的字符
3.shell 不允许 “=” 号两边有空格;Makefile 允许变量赋值时,“=” 号两边留空格。
4.shell ( ) 放命令, () 放命令, ()放命令,{} 放变量
Makefile $() 和 ${} 都能进行取变量值

正文:

makefile是对大量源文件进行系统编译链接的过程
shell就是一个命令解释器负责讲用户的输入解释给操作系统例如要cat log.txt 你要读取log.txt用shell只能去操作系统调用open(),read()等函数只是用来链接人与操作系统。
gcc编译过程:
main.c预处理得到main.i(展开内部定义的宏并且查找内部定义的库)再编译得到main.s,最后再通过汇编得到main.o,再通过跟其他.o文件链接得到输出的led.elf文件(链接成应用程序)(记忆:ISO,i输入,s处理,o输出)ps:linux中是elf,win中是exe,交叉理解
命令:gcc-c -o hello.o hello.c(将.c文件一步到.o文件编译完成但是不链接)(-c把预处理,编译,汇编都做了,-o指定输出文件)
想要将多个文件链接成为一个应用程序,命令:gcc-o test main.c sub.c即可(类似于keil/vs中一键编译,将多个源文件联合编译生成最终一个窗口即应用程序)
将main.c和sub.c预处理,编译,汇编,再链接在一起成为应用程序test.elf。
当多个文件链接时不方便,修改一个文件全部文件都要修改,所以多文件采用
gcc-c-o main.o main.c
ps:这里理解对称结构:gcc将.c文件“转变”成.o文件名字为xx.o,使用xxx.c文件
gcc-c-o sub.o sub.c
gcc -o test main.o sub.o这样最后再链接,方便修改文件(文件个数很多时eg1000个等)。
ps:这里是gcc将.o文件“转变”成test,因为elf应用程序是最后一级不需要写.elf,直接写应用程序名字test即可,同时因为是文件链接,可以有多个.o文件,
这里一定要记住底层到顶层顺序:.c->.i->.s->.o->>>.elf
makefile(组织管理程序,一键编译一键清除等):相当于上面gcc的脚本
test:依赖于a.o b.o
gcc-o test a.o b.o
b.o:依赖于b.c
gcc-o b.o b.c
…等等一键生成。
当文件多了可以用替代(通配符)
同上:
%.o:%.c
gcc-c-o $@ < 1. 其中 < 1.其中 1.其中@代指目标文件,
2.$<后面第一个依赖文件
3.其中gcc-o test a.o b.o可改为gcc-o test 指 支持上面所有的依赖及时变量(简单变量) a : = x x x (及时生效)延时变量 b = x x x (使用到才生效) a ? = b ,当 a 前面有幅值就改变,当 a 没有幅值改变为 b a + = b , a 为字符串则后续再追加一个字符串 b 引用一个函数使用 ^指支持上面所有的依赖 及时变量(简单变量)a:=xxx(及时生效) 延时变量b=xxx(使用到才生效) a?=b,当a前面有幅值就改变,当a没有幅值改变为b a+=b,a为字符串则后续再追加一个字符串b 引用一个函数使用 支持上面所有的依赖及时变量(简单变量)a:=xxx(及时生效)延时变量b=xxx(使用到才生效)a=b,当a前面有幅值就改变,当a没有幅值改变为ba+=ba为字符串则后续再追加一个字符串b引用一个函数使用(xx)ps:xx代表函数名
常用函数
( f o r e a c h v a r , l i s t t e x t )循环 l i s t 中的 e g : l = a b c c = (foreach var, list text)循环list中的 eg:l=a b c c= foreachvarlisttext)循环list中的eg:l=abcc=(foreach f,$(l) f.o)
最终c=a.o b.o c.o
( f i l t e r p a t t e r n , t e x t )去除符合 p a t t e r n 的值 c = a b c d / D = (filter pattern, text)去除符合pattern的值 c=a b c d/ D= filterpatterntext)去除符合pattern的值c=abcd/D=(filter %/, $(c)
最终D=a b c其中含有/的被去除
$(filter-out pattern, text)留下符合xx的值
( w i l d c a r d p a t t e r n )取出符合 p a t t e r n 格式的文件 e g : f i l e = (wildcard pattern)取出符合pattern格式的文件 eg:file= wildcardpattern)取出符合pattern格式的文件eg:file=(wildcard *.c)将所有.c文件显示。
全部替换
( p a t s u b s t % . c , % . d , (patsubst %.c,%.d , patsubst.c,.d,(file))将file中的.c文件换位.d文件。
改进支持头文件依赖:
gcc-M c.c //打印出依赖
gcc-M -MF c.d c.c//把依赖写进c.d
gcc -c -o c.o c.c -MD -MF c.d //编译c.o把依赖写进c.d
在这里插入图片描述
源文件和头文件经过预编译器,由组合的.cpp(头文件和源文件合集)预编译成.i文件,其中预编译过程主要处理源代码文件中的以“#”开头的预编译指令,比如“#include”,“#define”等。主要作用如下:
(1)将所有“#define”删除,并且展开所有的宏定义。
(2)处理所有条件预编译指令,如“#if”,“#ifdef”,“#else”
(3)处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。
(4)删除所有注释
(5)添加行号和文件名标识
(6)保留所有#pragma编译器指令
之后再通过GCC编译器,将.I文件编译成.s文件。编译过程就是将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。编译过程一般分为6步:扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化。
之后就是汇编器assembly(类似ams文件存储汇编文件),将编译文件.S变成机器能够识别的汇编文件.o,汇编器是将汇编代码转变成机器可以执行的指令
最后就是链接,链接的主要内容就是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接。链接过程主要包括地址和空间分配、符号决议和重定位等这些步骤,每个模块的源代码文件(.c)经过编译器编译成目标文件(object file,一般扩展名为.o或.obj),目标文件和库一起链接形成最终可执行文件。其实就将各个链接(静态链接和动态链接)链接起来,使最终程序能够在各平台流程运行,即生成可执行文件(execulatable).out文件。
shell:

可执行文件:

一般文件格式,目标文件.o(windows .obj)、可执行文件(linux下的.out,windows下的.exe)、动态链接库.so(windows DLL)、静态链接库.a(windows .lib)都是按照可执行文件格式ELF(windows为PE)存储的。
静态库可以简单看成一组目标文件的集合,即很多目标文件经过压缩后形成的文件。静态链接的弊端:1.空间浪费,2.静态链接对程序的更新、部署和发布的影响,当依赖的任何静态文件更新后,程序需要重新编译下发。
动态链接:动态链接的思想是把链接推迟到运行时再进行。磁盘和内存中只存放一份动态链接库文件,不但节约内存,还可以减少物理页面的换入换出,也增加了CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上。(类似设备树,只保留了目录,需要用的时候可以通过内存内的地址找到库的位置)
bss:未初始化的全局变量和局部静态变量一般放在.bss的段中,它只是为这两种变量预留位置,不占空间。总体来说,程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss属于程序数据。跟linux下的虚拟地址映射类似(内存进程的布局)。
代码和数据分开的好处:1.当成程序装载时,数据和指令分别被映射到两个虚拟区域。由于数据区域可读写,而指令区域对于进程是只读的,可以分别设置权限来控制两个区域。

2.对于现代CPU,缓存(Cache)体系一般被设计成数据缓存和指令缓存分离,所以程序设计分开有助于提高CPU的缓存命中率。
3.最重要的原因是,当系统中运行着多个该程序的副本时,它们的指令都是一样的,所以内存中只需要保存一份程序的指令部分。节约大量内存。
我们平常写的头文件其实是一堆符号定义,也就是之后会被原封include进的,一个工程包括多个源文件,这多个源文件事实上都会被编译成多个对应的目标文件,而这些目标文件,最后进行静态链接产生了我们熟知的可执行文件。(暂且不提动态链接与运行库)
GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
gcc是GCC中的GUN C Compiler(C 编译器)
g++是GCC中的GUN C++ Compiler(C++编译器)
.c和.cpp的大致区别很明显就能看出来,一个表示C的源程序,一个表示C++的源程序

UNIX/LINUX 平台下三种主要的可执行文件格式:a.out(assembler and link editor output 汇编器和链接编辑器的输出)、COFF(Common Object File Format 通用对象文件格式)、ELF(Executable and Linking Format 可执行和链接格式)。
目前,PC平台流行的 可执行文件格式(Executable) 主要包含如下两种,它们都是 COFF(Common File Format) 格式的变种。
Windows下的 PE(Portable Executable)
Linux下的 ELF(Executable Linkable Format)

目标文件就是源代码经过编译后但未进行连接的那些中间文件(Windows的.obj和Linux的.o),它与可执行文件的格式非常相似,所以一般跟可执行文件格式一起采用同一种格式存储。在Windows下采用PE-COFF文件格式;Linux下采用ELF文件格式。
事实上,除了可执行文件外,动态链接库(DDL,Dynamic Linking Library)、静态链接库(Static Linking Library) 均采用可执行文件格式存储。它们在Window下均按照PE-COFF格式存储;Linux下均按照ELF格式存储。只是文件名后缀不同而已。
动态链接库:Windows的.dll、Linux的.so
静态链接库:Windows的.lib、Linux的.a

相对可执行文件有三个重要的概念:编译(compile)、连接(link,也可称为链接、联接)、加载(load)。源程序文件被编译成目标文件,多个目标文件(.o/.obj)被连接成一个最终的可执行文件,可执行文件被加载到内存中运行。
加载文件最重要的是完成两件事情:加载程序段和数据段到内存;进行外部定义符号的重定位。
ps重定位:一个可执行程序通常是由一个含有 main() 的主程序文件、若干目标文件、若干共享库(Shared Libraries)组成。一个 C 程序可能引用共享库定义的变量或函数,换句话说就是程序运行时必须知道这些变量/函数的地址。在静态连接中,程序所有需要使用的外部定义都完全包含在可执行程序中,而动态连接则只在可执行文件中设置相关外部定义的一些引用信息,真正的重定位是在程序运行之时。
静态连接方式有两个大问题:如果库中变量或函数有任何变化都必须重新编译连接程序;如果多个程序引用同样的变量/函数,则此变量/函数会在文件/内存中出现多次,浪费硬盘/内存空间。比较两种连接方式生成的可执行文件的大小,可以看出有明显的区别。
a.out 文件包含 7 个 section,格式如下:
exec header(执行头部,也可理解为文件头部)
text segment(文本段)
data segment(数据段)
text relocations(文本重定位段)
data relocations(数据重定位段)
symbol table(符号表)
string table(字符串表)

而执行头部即exec header由以下组成:
执行头部的数据结构:
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; /
数据重定位表的长度 /
};
ps:bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。
  bss是英文Block Started by Symbol的简称
  魔数:每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,通常被称为魔数(Magic Number)。通过对魔数的判断可以确定文件的格式和类型。如:ELF的可执行文件格式的头4个字节为0x7F、e、l、f;
文件头部主要描述了各个 section 的长度,比较重要的字段是 a_entry(程序进入点),代表了系统在加载程序并初试化各种环境后开始执行程序代码的入口。
ps:.out文件七大段,首先是执行头部主要是包含下面数据的长度信息和程序进入点,之后就是文本和数据段(静态),文本和数据重定位段(动态),以及两个表。
a.out 文件中包含符号表和两个重定位表,这三个表的内容在连接目标文件以生成可执行文件时起作用。在最终可执行的 a.out 文件中,这三个表的长度都为 0。a.out 文件在连接时就把所有外部定义包含在可执行程序中
之后的ELF文件也类似于上面展开:
ELF文件格式分析
ELF文件有三种类型:可重定位文件:也就是通常称的目标文件,后缀为.o。共享文件:也就是通常称的库文件,后缀为.so。可执行文件:本文主要讨论的文件格式,总的来说,可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。
首先看看ELF文件的总体布局:
ELF header(ELF头部)
Program header table(程序头表)
Segment1(段1)
Segment2(段2)
………
Sengmentn(段n)
Setion header table(节头表,可选)
ELF头部数据结构:
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /
魔数和相关信息 /
Elf32_Half e_type; /
目标文件类型 /
Elf32_Half e_machine; /
硬件体系 /
Elf32_Word e_version; /
目标文件版本 /
Elf32_Addr e_entry; /
程序进入点 /
Elf32_Off e_phoff; /
程序头部偏移量 /
Elf32_Off e_shoff; /
节头部偏移量 /
Elf32_Word e_flags; /
处理器特定标志 /
Elf32_Half e_ehsize; /
ELF头部长度 /
Elf32_Half e_phentsize; /
程序头部中一个条目的长度 /
Elf32_Half e_phnum; /
程序头部条目个数 /
Elf32_Half e_shentsize; /
节头部中一个条目的长度 /
Elf32_Half e_shnum; /
节头部条目个数 /
Elf32_Half e_shstrndx; /
节头部字符表索引 */
} Elf32_Ehdr;
总结:
不同时期的可执行文件格式深刻的反映了技术进步的过程,技术进步通常是针对解决存在的问题和适应新的环境。早期的UNIX系统使用a.out格式,随着操作系统和硬件系统的进步,a.out格式的局限性越来越明显。新的可执行文件格式COFF在UNIX System VR3中出现,COFF格式相对a.out格式最大变化是多了一个节头表(section head table),能够在包含基础的文本段、数据段、BSS段之外包含更多的段,但是COFF对动态连接和C++程序的支持仍然比较困难。为了解决上述问题, UNIX系统实验室(UNIX SYSTEM Laboratories USL) 开发出ELF文件格式,它被作为应用程序二进制接口(Application binary Interface ABI)的一部分,其目的是替代传统的a.out格式。例如,ELF文件格式中引入初始化段.init和结束段.fini(分别对应构造函数和析构函数)则主要是为了支持C++程序。
最后至于gcc/g++生成得a.out而不是elf ,只是.o文件名字但是实际上使用的是elf数据格式,看来a.out就只是个名字!!!!用的就是elf数据格式,但是名字没换,内容换了!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值