AUTOSAR入门-AS开源代码编译过程详解
原创 thatway 那路谈OS与SoC嵌入式软件 2022-03-08 18:03
AUTOSAR**入门-AS开源代码编译过程详解**
拿到一份完整的软件代码,面对成千上万的源码文件,都会觉得有点无从下手。这时候软件对你来说像一个黑盒子,里面有什么你不知道,想最快速的了解这个盒子里面有什么,就要去研究这个盒子
怎么造出
来的,反推第一步也就是编译
的过程。
分析代码
编译过程
,
我自己总结
有下面几点
好处
:
确定那些源码参与了编译,俗话说
顺藤摸瓜
,编译就是藤顺着走,有几个瓜,啥样都会清楚。要对代码进行修改移植调试
必须要了解编译的过程,要具有再造能力。很多
中间代码**的生成**
放在了编译过程,直接看代码是不完整的,像残缺的武功秘籍,缺失的部分需要在编译里面找。
1. Scons编译工具介绍
首先我们回顾下AS代码编译的过程,参考:[AUTOSAR入门-AS开源代码运行环境搭建](http://mp.weixin.qq.com/s?__biz=MzUzMDMwNTg2Nw==&mid=2247483761&idx=1&sn=ce5ae0a3ef9282fdbeabe0b949eca3e2&chksm=fa528755cd250e43d8903eb3591f4f2d5315d2b2856cce2ab28dbeb5c9ae742c6ef6454a21f6&scene=21#wechat_redirect)
中2.2节代码编译过程:
git clone https://github.com/thatway1989/as
cd as
scons
export BOARD=x86
export RELEASE=ascore
scons
注意
:如果你环境还没配置好,AS代码还没编译通过,可能你是伪程序员
,建议不用往下看了,本系列文章强调
动手调试
,直接
研究代码
,搞代码
才能有无穷乐趣,,
All is in the code. Code can tell you everything
。
可以看到编译的过程一直在用
scons命令
,那么scons是什么,下面进行介绍。
1.1 Scons介绍
文章封面中**编译器**
可以把c语言文件编程二进制,例如:gcc hello.c -o hello
但是对于大型工程项目,有很多c文件要一块编译成一个二进制文件,就需要制定一个编译规则
,规定那些文件参与编译,怎么去编译,这时有人会想到makefile
,的确makefile是干这个事的,但是时过境迁,很多懒人觉得makefile太麻烦了,makefile中的代码需要写的太多,能不能智能化一点,不关注过程,直接关注结果,过程让机器去搞,这样就可以少写一点编译脚本,然后就进化出来两个方向:
第一种 (cmake
) 你makefile麻烦,我造一个工具例如cmake来生成makefile,cmake脚本里面只关心要编译那些文件,编译出来的目标文件是什么,其他cmake工具去搞定;
第二种(scons
)你makefile麻烦,我不用你了,直接重新搞一套例如scons,直接调用编译器,同样达到cmake那些简单方便。另外scons的语法不是自己定义的,直接使用的python,对python程序员很友好。可以理解scons脚本实际就是python脚本
。
2.2 Scons使用基础
上面说到scons脚本里面都是用的python语言,scons基于python封装了一些库函数,下面挑一些主要的进行简单介绍
Program函数
:
src_files=Split('main.cpp file1.cpp file2.cpp')
Program('program', src_files)
Program函数规定编译的目标文件,这里为program,规定了要编译的源文件,这里为src_files 这个集合。
SConscript函数:
在配置文件SConstruct中可以使用函数SConscript()函数来定附属的配置文件。例如:
objs = SConscript('SConscr
ipt',variant_dir=BDIR,duplicate=0)
当然这里SConscript是配置文件,是用python语法写的,也可以随意命名。
Env环境:
构造Env环境,一个环境就是一些变量的集合。例如:
Env['CC']='arm-none-eabi-gcc -std=gnu99'
Env是一个变量集合,其中一个名为CC的变量值是gcc编译器。
参考scons官网文档:
https://scons.org/doc/production/PDF/scons-user.pdf
2. scons命令编译过程
**### 2.1 SConstruct和building.py
执行scons命令的时候,首先会执行根目录下as/SConstruct
,整体的执行流程为
PrepareEnv-》Conscript-》building
首先给sys添加了环境变量,方便直接访问这个目录
studio=os.path.abspath('./com/as.tool/config.infrastructure.system/')
sys.path.append(studio)
from building import *
asenv = PrepareEnv()
from building是python语法从building这个包里面加载类,这个包的位置在
./com/as.tool/config.infrastructure.system/
building.py
所以PrepareEnv函数就是在这个
building
.py里面声明的。进入这个函数,
ASROOT变量没赋值,先找到值
asenv=Environment(TOOLS=['ar', 'as','gcc','g++','gnulink'])
os.environ['ASROOT'] = ASROOT
asenv['ASROOT'] = ASROOT
构造环境asenv,然后把ASROOT变量加进去
同时把ASROOT也加到了os.environ里面
BOARD = os.getenv('BOARD')
if(BOARD not in board_list):
print('Error: no BOARD specified!')
help()
exit(-1)
检查系统环境变量里面有BOARD设置没,没设置就会提醒:
在com/as.tool/config.infrastructure.system/building.py中,PrepareEnv函数的最后
PrepareBuilding(asenv)
return asenv
PrepareBuilding函数,
GetConfig('%s/.config'%(env['BDIR']),env)
之后添加了一些变量,和一些选项
打印出来第一个参数为:as/build/posix/x86/ascore/.config,没这个文件
AddOption('--menuconfig',
dest = 'menuconfig',
action = 'store_true',
default = False,
help = 'make menuconfig for Automotive Software AS')
如果使用
scons --menuconfig
命令,则下面这个if就会进去,执行menuconfig函数,进行系统配置,详细说明见2.5使用AddOption函数定义自己的命令行选项。
if(GetOption('menuconfig')):
menuconfig(env)
获取环境变量后,就开始执行SConscript
函数,后面详细介绍最后还回到as/SConstruct,PrepareEnv()执行完,
objs = SConscript('SConscript',variant_dir=BDIR, duplicate=0)
objs
就是要编译的文件的集合,读取objs完毕后,执行编译
Building(target,objs)
这个Building函数还是在com/as.tool/config.infrastructure.system/building.py中定义target
打印出来为:as/build/posix/x86/ascore/x86
Building函数中,首先对sobjs中要编译的文件,进行了分类:arxml、xml、py、dts、其他。
2.2 SConscript生成objs
根目录下SConscript文件,如下
from building import *
objs = SConscript('com/SConscript')
objs += SConscript('release/SConscript')
Return('objs')
可以知道,这个脚本是一个嵌套格式,就是把要编译的文件加入到
objs
里面
如果想加入一个功能,需要修改这个脚本,让源码文件能编译进去。
2.3 arxml生成LCfg文件
这个流程也是xml生成c源码
的过程,Building函数中,如下对arxml进行了处理
if( ( (not os.path.exists(cfgdone)) and (not GetOption('clean')) )
or forceGen ):
MKDir(cfgdir)
RMFile(cfgdone)
xcc.XCC(cfgdir, env, True)
if(arxml != None):
arxmlR = PreProcess(cfgdir, str(arxml))
for xml in xmls:
MKSymlink(str(xml),'%s/%s'%(cfgdir,os.path.basename(str(xml))))
xcc.XCC(cfgdir)
argen.ArGen.ArGenMain(arxmlR,cfgdir)
MKFile(cfgdone, SHA256(glob.glob('%s/*xml'%(cfgdir))))
先判断cfgdone:
as/build/posix/x86/ascore/config/config.done是否存在,不然不存在,则
上面代码是对arxml的处理
新建文件夹cfgdir:as/build/posix/x86/ascore/config--生成代码位置
xcc.XCC(cfgdir, env, True)函数在xcc.py中定义
fp = open('%s/asmconfig.h'%(gendir),'w')
fp.write('#ifndef _AS_MCONF_H_\n\n')
if('MODULES' in env and env['MODULES'] is not None):
for m in env['MODULES']:
fp.write('#ifndef USE_%s\n#define USE_%s 1\n#endif\n\n'%(m,m))
if('CONFIGS' in env and env['CONFIGS'] is not None):
根据env里面MOUDLES生成宏例如:
defineUSE_ARCH_X86 1
根据env里面CONFIGS生成宏例如:#define ARCHx86写入asmconfig.h文件中。
最后,XCC函数里面还执行了两个生成函数
from argen.KsmGen import *
from argen.OsGen import *
__gen__ = [KsmGen,OsGen]
for g in __gen__:
print(' %s ...'%(g.__name__))
g(gendir)
OsGen在argen/OsGen.py中定义,这里因为没有xml文件,没有生成os的cfg文件
KsmGen在argen/KsmGen.py中定义,生成ksm_cfg.h
上面的过程可以理解为python代码生成.h代码的过程。
PreProcess(cfgdir, str(arxml))函数中arxdml打印出来为:com/as.application/common/autosar.arxml--xml文件位置
filR:as/build/posix/x86/ascore/config/autosar.arxml
filC:as/build/posix/x86/ascore/config/autosar.arxml.h为一个链接文件
autosar.arxml.h ->/home/XXX/autosar/as/com/as.application/common/autosar.arxml
执行命令:
gcc -E --include/home/XXX/autosar/as/build/posix/x86/ascore/config/asmconfig.h/home/XXX/autosar/as/build/posix/x86/ascore/config/autosar.arxml.h
GCC -E选项:对源程序做预处理操作,但是没有-o参数,就没有输出到目标文件。但是以字符串的形式存储到了txt变量里面
err, txt = RunSysCmd(cmd)
简单处理,去掉#注释后存入了filR:as/build/posix/x86/ascore/config/autosar.arxml文件
for xml in xmls:
MKSymlink(str(xml),'%s/%s'%(cfgdir,os.path.basename(str(xml))))
lwip.xml -> /home/XXX/autosar/as/com/as.application/common/config/lwip.xml
as/build/posix/x86/ascore/config/目录下的xml文件都建立了软连接到对应的xml文件
argen.ArGen.ArGenMain(arxmlR,cfgdir)
#生成config.done文件
MKFile(cfgdone, SHA256(glob.glob('%s/*xml'%(cfgdir))))
argen.ArGen.ArGenMain函数在argen/ArGen.py中定义
def ArGenMain(wfxml,gendir):
import xml.etree.ElementTree as ET
if(os.path.exists(wfxml)):
root = ET.parse(wfxml).getroot();
for arxml in root:
ArGen(arxml,gendir)
找一个简单的模块,例如Ea
wfxml是autosar.arxml,对这个文件进行xml解析,找到tag,就子目录的标识
<Ea> <General Comment="*" DevelopmentErrorDetection="ON" NvmJobEndNotification="NULL" NvmJobErrorNotification="NULL" SetModeApi="ON" VersionInfoApi="OFF" VirtualPageSize="8" />
<BlockList Max="TBD">
<Block ArraySize="2" BlockSize="32" Comment="*" ImmediateData="False" IsArray="False" Name="EaTest1" NumberOfWriteCycles="0xFFFFFFFF" />
<Block ArraySize="2" BlockSize="32" Comment="*" ImmediateData="False" IsArray="False" Name="EaTest2" NumberOfWriteCycles="0xFFFFFFFF" />
</BlockList>
</Ea>
--》engine(arxml,dir)
ArGen函数调用
--》‘Ea':GenEa
在argen/GenEa.py中,
def GenEa(root,dir):
global __dir
GLInit(root)
__dir = '%s'%(dir)
if(len(GLGet('BlockList')) == 0):return
GenH()
GenC()
print(' >>> Gen Ea DONE <<<')
GLInit函数在argen/GCF.py中定义
def GLInit(root): global __root
__root=root
如果没有BlockList,则推出。
我们查看arxml文件里面Ea下有这个的。
GLGet也在argen/GCF.py中定义,作用是找到xml里面的某项
先看GenC()生成Ea_Cfg.c文件
fp = open('%s/Ea_Cfg.h'%(__dir),'w')
fp.write(GHeader('Ea'))
整个过程就是根据arxml里面的生成c文件的,这里业务部分不具体分析了。
GHeader在argen/GCF.py中定义,用于生成版权文件头部分。
生成代码的位置为:./build/posix/x86/ascore/config/Ea_Cfg.c
GenH()也是同样的过程。
在Building函数中,如下定义了studio命令的处理方法,后续文章再详细分析
if(('studio' in COMMAND_LINE_TARGETS) and (env == Env)):
studio=os.path.abspath('%s/com/as.tool/config.infrastructure.system/'%(env['ASROOT']))
assert(arxml)
pd = os.path.abspath(cfgdir)
RunCommand('cd %s && %s studio.py %s'%(studio,env['python3'],pd))
exit(0)
2.4 DTS、OFS、SWCS编译
BuildDTS(dts,BDIR)
print('!!!Building ofs',)
BuildOFS(ofs)
print('!!!Building swcs',)
BuildingSWCS(swcs)
从打印上看,没有dts of和py文件文件,所以上面上个函数都没文件要处理。
在building函数的结尾,会定义默认编译为可以执行文件:
if(BUILD_TYPE == 'exe'):
env.Program(target, objs)
objs是SConscript生成的,分析见2.2中,objs里面还添加了生成的cfg文件。
target打印出来为:as/build/posix/x86/ascore/x86
2.5 生成TINIX.IMG
scons读完脚本后,就会去编译目标文件x86,这个过程是编译器gcc完成的
scons: done reading SConscript files.
scons: Building targets ...
scons: building associated VariantDir targets: build/posix/x86/ascore
会用gcc编译器对objs里面出现的c文件进行编译成o文件,最后进行链接为x86目标文件。
编译出来x86目标文件后,还执行了几个操作,在AddPostAction中定义。
AddPostAction
:scons中的库函数,安排在构建指定目标之后执行的指定操作命令。如下:
if('POSTACTION' in env):
for action in env['POSTACTION']:
env.AddPostAction(target, action)
target 为:as/build/posix/x86/ascore/
x86
action
打印出来为:
dd if=/dev/zero of=TINIX.IMG bs=512 count=2880
/home/XXX/autosar/as/com/as.infrastructure/system/fs/fatfs/fatfs.exe mkfs TINIX.IMG
dd conv=notrunc if=/home/XXX/autosar/as/com/as.infrastructure/arch/x86/boot/boot.bin of=TINIX.IMG bs=512 count=1
/home/XXX/autosar/as/com/as.infrastructure/system/fs/fatfs/fatfs.exe cp /home/XXX/autosar/as/com/as.infrastructure/arch/x86/boot/loader.bin /loader.bin TINIX.IMG
objcopy -S build/posix/x86/ascore/x86 kernel.bin
/home/XXX/autosar/as/com/as.infrastructure/system/fs/fatfs/fatfs.exe
cp kernel.bin /kernel.bin TINIX.IMG
dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。
进行制作文件系统,最后形成IMG文件。
• if=文件名:输入文件名,默认为标准输入。即指定源文件。
• of=文件名:输出文件名,默认为标准输出。即指定目的文件。
• bs=bytes:同时设置读入/输出的块大小为bytes个字节。
• count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
if=/dev/zero,就是创建一个新的文件,块大小为512个字节,有2880个块。
文件大小计算:2880*512/1024/1024=1.4M
FatFs是一个用于小型嵌入式系统的通用FAT/exFAT文件系统模块。下载下来在download/ff13文件夹下。
conv=notrunc:不截断输出文件。把boot.bin放到TINIX.IMG结尾
objcopy -S :不从源文件拷贝符号信息和relocation信息。
fatfs.exe cp命令,把二进制文件复制进IMG文件
生成的
TINIX.IMG
有三部分:
boot.bin
、
loader.bin
、
x86
-》kernel.bin
TINIX.IMG 镜像文件,怎么在qemu里面加载运行,涉及那部分代码,这里篇幅有限,下次更新了再详细说明。
后记:
首先感谢各位的关注,这里也不搞什么圈粉变现什么的,就是
开源软件分享
。说点感悟,用微信公众号来写文章,主要还是得用口语化的语言,图文并茂,方便轻松的去阅读。所以基本都是先不看其他的资料,先
凭自己的理解
口语化的表述出来,其实挺费时间,但这感觉可以锻炼表达能力,写作风格还是:
口语化表述
- code讲解
。
Talk is cheap
,show methe code
,持续更新,纯干货分享,无广告,不打赏,欢迎
转载
,欢迎
评论交流
!
往期回顾: