学习汇编的意义在哪里?
对于编程,一直是我钟情的爱好,可能因为骨子里刻着那种刨根问底的DNA吧。都说懒惰是人类进步的动力,我看也不尽然,因为学习一项新知识必要付出加倍的努力。
一开始只是为了摆脱枯燥重复的录入,自学了Python,设计出了人生第一款小软件,极大改善了工作效率。然而内心里不喜欢python隐藏一切细节的特点,想知道更多,于是就自学了C。面向过程,虽然复杂,却满足了我的未知欲。
但人类啊,终归是不懂得满足的生物,于是就继续自学了算法,享受那种大脑卡壳,数日苦思后或豁然见日,或萎靡不振的极限拉扯。
别人是越学越高级,我却是越学越回去,在自学了算法后,渐渐又接近了汇编语言。
对于我而言,那种短小精练的语句却有着别样的金属质感。有种手握硬件,画着路线图,将数据一一搬运的画面,不多久,就迷上了这个特别的语言。然而随着学习的深入,一个问题逐渐浮现到了脑海中,在当下百花齐放的编程语言中,汇编语言仍然值得花时间去学习甚至精通吗?
这里,有一个小小的例子。老人家平时用的一台老电脑安装了某个安全软件(老爷机还是装个安心),但这个软件居然和一些表格插件产生了兼容性问题,每次都作为病毒扫描出来,已经设定了排除规则但病毒库更新后还是报错,弄得老人家以为电脑出了什么问题。想换个安全软件,老人家说习惯了老界面,不想更改。头大。
当时上网查了一堆修改进程名称的方法,都不顶用。通过各种高级语言修改进程名都无效,因为必须先运行得起来才能修改,可这第一步就实现不了。DLL注入不稳定,被监测病毒。
随着学习汇编的深入,我对程序的构造及运行底层原理加深了认识。偶然灵光一现,心想,能否通过反汇编的方式进行修改。说干就干,开始X96DBG,IDA学起。。。几天后,那烦人的病毒提示再也不见了。
真是一件很典型的例子,汇编可以让你接近于底层视角审视程序的运行规则,从而在必要时为程序优化提供方案。用一句我很赞同的话作为总结,出自于首届图灵奖得主艾伦·佩利 ( Alan J.Perlis ):A language that doesn\’t affect the way you think about programming, is not worth knowing(一个不能影响你思考方式的编程语言是不值得了解的)。
所以汇编语言,值得学习吗?
系统启动流程及框架简介
嵌入式系统启动流程嵌入式的基本架构硬件基本由三大块组成:
- CPU
- DDR
- FLASH(EMMC)存储
软件结构
图中可以看到,软件结构基本可以分为四个部分。1、Bootloader区存放的是Bootloader,它负责嵌入式系统最初的硬件初始化、驱动和内核加载。2、参数区不是必须的,通常存放了一些系统参数,并且通常这个区是没有文件系统,参数以原始数据的格式来存放。3、内核镜像区存放的Linux内核压缩镜像,它被解压后运行于内存,作为嵌入式设备的Linux操作系统。4、文件系统区存放经过压缩的文件系统,它会被Linux内核解压并挂载,并作为各种应用程序、文件的主要载体。这四个部分都是存放在硬件的FLASH中。以mstar平台为例,BootLoader对应的是mboot,参数区对应的是mboot操作存储的那部分变量,这部分是mstar预留的一个特别分区,如果不执行emmc擦除,这部分在升级的时候是不会被清空的。内核映象对应的就是编译出来的内核,文件系统在安卓系统上可以看成system等分区。嵌入式系统从上电开始启动,后续基本分为三个过程:
- 启动引导程序BootLoader,mstar的是uboot,海思的是fastboot,基本上是一个东西。
- Boot运行后引导linux内核开始运行,加载相关必备驱动
- 内核加载文件系统等,执行相关开机应用,系统开始运行。
BootLoader的启动当芯片上电后,会从一个固定的地址开始运行一段汇编程序。这段程序的作用是配置ddr,cpu等一些基本属性,然后会调用boot的启动代码,之后boot就会开始运行。(ps:通常来说,cpu可以读写flash里面的程序,但是是不能直接运行程序的。一般的就有2种处理,硬件直接用Nor flash,cpu可以运行nor flash上的程序,另外一种就是把nand flash上的前4K数据通过硬件方法搬运到DDR,然后cpu从这个指定的地址运行。)以mstar平台为例,当拿到一块空片或者把EMMC整个擦除后,上电会看到一串DDR相关的打印。
这部分是由固化在主芯片里面的程序运行并打印的,程序运行后,会做主要硬件的自检,从这个打印一定程度上可以用来判断当前主板在DDR、主芯片是否有问题.内核的启动boot在启动完成后,会把内核解压到DDR指定的一个地址,之后就会跳转到这个地址开始运行。以mstar为例,在mboot命令行用print可以看到如下变量
其中的bootcmd就是启动内核的命令,在mboot命令行下运行这串命令,内核就会开始启动,并开始运行整个linux和安卓。而另一个bootargs变量是mboot传递给内核的环境变量,里面是一些对控制台串口,内存分配等一些配置。如debug口的复用,就是通过修改这里,将控制台配到另外一个串口,这样就实现了debug作为其它串口的复用。某些特殊情况下需要mboot传变量到内核,也可以通过在这里添加一个变量来实现。文件系统启动内核启动后,会开始初始化各个子系统,然后开始挂载文件系统。这里主要是创建各个 系统目录,文件系统建立后,linux系统基本上就已经运行起来了。安卓系统启动Linux在文件系统这些准备后,会启动一个init的进程。这个进程会加载init.rc,之后就开始一系列的配置,这里后续再说明。linux内核安卓是基于linux内核来实现,内核的主要功能是实现对硬件的控制。安卓系统实质上是一个运行在linux上的java虚拟机,因此内核大部分的操作跟嵌入式linux操作基本上是一样的,如使用make menuconfig添加或删除模块,驱动的添加和操作方式等等。但同时安卓针对内核也做了自己的改动,常见的如Binder IPC,OOM等。实际开发中内核部分涉及不多,主要可能会有部分打开某个驱动或者添加遥控器的需求,这里按照对应的说明添加即可。系统运行库这部分主要有两部分,一个是系统库,主要是提供了网络,图像,多媒体,数据库的支持。另外一个是运行时,这主要指安卓的核心库如framework中很多的 cpp文件编译的库,还有Dalvik虚拟机。目前在开发的平台中,像mstar的tvos,和海思的hippo部分,都是在这一层做处理。
应用框架层这层主要是供应用层调用的各种API,以及窗口,视图等的管理。这部分主要集中在framework部分,基本是java代码。这部分跟系统运行库部分接口较为紧密,很多功能最终都是通过jni调用系统运行库来实现。mstar和海思都有在这层做自己的接口以及处理,如mstar的tvapi和tv2、还是的hippo下的东西。这两部分是系统经常需要修改的地方。
应用层安装在手机上的应用程序都是在这个层。
中间件目前正在推进系统中间件的使用,整个的思路实际就是将应用层的框架打包成IST自己的接口,然后用调用这些接口来实现常用的逻辑功能和UI显示。一些原本放再系统的应用框架里面的逻辑功能和UI,都统一单独出来,放在中间件中来实现,而这部分基本是脱离于平台存在的,后续开发,也主要以这一块为主,底层只负责提供控制硬件的接口。因此应用的开发人员对于应用框架层都需要有了解,而不仅仅是UI那块东西。硬件基本架构目前公司在做的主板主要功能就是显示+触摸,同时配套其它需要功能。显示部分主要是主板接收各种信号源,比显示到屏幕上。这里涉及到两部分,点屏和信号源输入。每个主板在开始调试时,先要做的就是点屏。屏的类型主要有4K和2K,但是目前客户已经基本上在使用4K的屏幕,2K的是在部分或者小显示器上用到,配屏一般使用原厂提供的默认屏参就可以点亮。2K 一般用的是LVDS,主要涉及到位数(6/8/10bit),奇偶,参数4K的屏都是使用VBO信号,主要涉及到一个单双分区的修改。另外还有不是主芯片直接点屏,而是通过一个out芯片来点屏的,这个时候都是需要主芯片输出一个固定格式的信号给到out芯片,然后进行点屏。
信号源目前常用的信号源有:DTV/ATV,AV,YPBPR,VGA,HDMI,DP,DVI,TYPE-C。DTV/ATV,AV,YPBPR,VGA都是模拟信号,HDMI,DP,DVI,TYPE-C是数字信号。对于DTV/ATV,需要添加对应的tuner驱动才能接收到信号。信号中,AV/YPBPR/VGA/HDMI一般都是主芯片默认就有的接口,而DP,DVI,TYPE-C都是需要通道芯片转化成hdmi再给到主芯片。当主芯片的HDMI接口不够的时候,就需要使用到HDMI-SWITCH来扩展HDMI口。目前在用的HDMI-SWITCH有两种控制类型:I2C,IO口。当切换到HDMI的扩展口时,需要控制SWITCH芯片切换到对应的port口。
HDMIHDMI是目前最常用的信号,也是出现问题较多的信号。下面是HDMI经常涉及的几个概念,后续再针对HDMI做一些详细的说明。HDMI标准,HDMI1.4和HDMI2.0。最简单的区分就是能不能支持4k2k@60hz的。对于主板来说,一般都是支持的,对于信号源来说,有些信号源是支持的,有些是不支持。EDID,用来告知信号源当前的显示器对视频和音频的支持情况。常用的HDMI输出设备都是会先读取这个edid,然后视情况输出信号。所以有些时候没有信号的,排查EDID相关的问题。HDCP,HDMI用来加密信号的,分为HDCP1.4和HDCP2.2CEC,通过输入信号的设备来控制显示器HPD,信号源的一个检测机制,当信号源插入的时候,这个脚是高的,当拔出的时候是低的。经常通过操作这个脚来模拟HDMI的插拔。DP,DVIDP是通过芯片转成HDMI,使用基本跟HDMI类似TYPE-C音频在mstar的平台上,需要针对不同的信号源配置不同的音频输入。音频输出也分为I2S和模拟的,需要根据实际情况做匹配。
触摸触摸主要是供切到对应的信号源后,可以给对应的信号源设备提供一个触摸输入,这样可以直接在显示屏上操作信号源设备。目前常用的触摸用两种,串口+usb 、双usb。具体参见介绍触摸的文档。
USB跟随这部分跟触摸是一体的。当切换到不同信号源时,根据不同需求来控制public USB口,确认是否需要跟着信号源一起切换。这个时候将USB插入主板的的时候,USB直接是接在了当前显示信号源所在的设备上。
网络交换有的主板为了让OPS和主板通过一根网线就能上网,会在主板上加一个网络交换芯片。目前对于这个芯片一般不用做太多控制,但为了支持WOL功能,需要在待机的时候给网络交换芯片供电。WOL功能主要是指主板待机后,可以通过网络发送唤醒包来唤醒主板。芯片都是默认支持的,对于有网络交换芯片的主板,只需要给交换芯片待机的时候供电就可以。
WIFI主板的WIFI使用的都是USB接口,主板一般会有一些默认支持的芯片类型。对于没有的,需要自己重新编译和添加驱动。添加方法见相关文档。
RS232目前所有的主板都是需要支持使用RS232来控制主板,RS232用的也是串口协议。在mstar的主板上,RS232跟4pin的debug口是复用的同一个口。这里有一个拓展功能就是通过网络来实现RS232一样的功能。这部分只是在命令解析部分不同,最终命令处理的地方是一样的。
麦克风模组现在用的麦克风模组,就是一个USB麦克风。但是需要给模组输入一个I2S信号来做消回声处理,这里就涉及到I2S和模拟声音同时输出的问题。
RTC时钟芯片,用来在断电的情况下存储时间。海外的一般使用电容电池,国内的使用的是一般电池。
HDMIOUT可以同时点屏和输出一路HDMI信号,目前在用的主要是MN869121和NT68411。其中MN869121需要自己修改代码,NT68411是直接烧录固件。功放芯片调整芯片输出音频,需要配置输入的音频格式和芯片驱动,输入的音频格式为I2S或者模拟
01程序编译过程分析,预处理,编译,汇编,链接
前言
我们大多数开发人员在编写完程序之后都会通过相应的IDE执行编译的操作,将所编写的源代码转换成计算机可以识别并执行的文件。这个从源代码转换成可执行文件的过程有相应的IDE帮助我们完成,很多朋友对于这个过程都不太熟悉,今天我们就一起探讨下可执行文件的生成过程,这个过程又叫做程序的编译与链接。
一:hello word由浅入深
对于程序员开发者来说,hello world程序的编写,大家闭上眼睛都能写出来,今天我们就以hello world为例来探讨一下可执行文件的生成过程,废话不多说,下面就是hello word的源码
上面代码的功能就是输出hello,word。然而计算机不可能直接执行hello world.c下面我们用一张图来讲一下hello world.c是如何被被执行。
从上面的图中,我们大致可以看到,hello.c要想变成计算机可以执行的hello(可执行目标文件)需要经过预处理,编译,汇编,链接等步骤,这些步骤主要是干什么的,有各自有什么作用,我们下面一一进行分析。
二:预处理
首先我们先来看一下预处理的执行命令
– $gcc –E hello.c –o hello.i
– $cpp hello.c > hello.i
预处理执行的功能:
- 删除“#define”并展开所定义的宏
- 处理所有条件预编译指令,如“#if”,“#ifdef”, “#endif”等
- 插入头文件到“#include”处,可以递归方式进行处理
- 删除所有的注释“//”和“/* */”
- 添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
- 保留所有#pragma编译指令(编译器需要用)
经过预编译处理后,得到的是预处理文件(如,hello.i) ,它还是一个可读的文本文件 ,但不包含任何宏定义,上面所说的第3条插入头文件到“include”处,可以理解为将头文件里面的内容进行展开,下面用一张图来进行描述
三:编译
编译过程就是将预处理后得到的预处理文件(如 hello.i)进行词法分析、语法分析、语义分析、优化后,生成汇编代码文件。用来进行编译处理的程序称为编译程序(编译器,Compiler)
编译命令
– $gcc –S hello.i –o hello.s
– $gcc –S hello.c –o hello.s
– $/user/lib/gcc/i486-linux-gnu/4.1/cc1 hello.c
上面3条编译指令表明,我们可以通过.i文件输出汇编文件.s同时也可以直接将.c文件编译成.s文件。第3条指令则是通过编译器直接执行,与前两条执行效果一样。gcc命令实际上是具体程序(如ccp、cc1、as等)的包装命令,用户通过gcc命令来使用具体的预处理程序ccp、编译程序cc1和汇编程序as等
经过编译后,得到的汇编代码文件(如 hello.s)还是可读的文本文件,CPU无法理解和执行它,不要着急,我们接下来看下一步汇编命令的执行过程
四:汇编
首先我们先了解下汇编代码文件(由汇编指令构成)称为汇编语言源程序,其实就是上面编译过程结束之后生成的.s文件,这个文件就是汇编代码文件,改文件是有一条条汇编指令构成,汇编的作用就是讲这一条条汇编指令转换成对应的机器执行。
• 汇编程序(汇编器)用来将汇编语言源程序转换为机器指令序列(机器语言程序)
• 汇编指令和机器指令一一对应,前者是后者的符号表示,它们都属于机器级指令,所构成的程序称为机器级代码,汇编的过程比较简单,只需要将相应的汇编指令翻译成对应的机器指令即可,没有什么复杂的变化。
汇编命令
– $gcc –c hello.s –o hello.o
– $gcc –c hello.c –o hello.o
– $as hello.s -o hello.o (as是一个汇编程序)
汇编结果是一个可重定位目标文件(如,hello.o),其中包含的是不可读的二进制代码,必须用相应的工具软件来查看其内容
五:链接
哇,经过了上面一系列的流程,终于到了最后一步,我们的hello world 终于要完成了,下面我们就来看下最后一步链接的处理吧。
预处理、编译和汇编三个阶段针对一个模块(一个*.c文件)进行处理,得到对应的一个可重定位目标文件(一个*.o文件),但是在程序的编写过程中,我们都是多个.c文件的,这样经过上面的预处理,编译汇编的过程之后我们得到的也是多个.o(可重定位目标文件),但是我们在最终执行的时候是只有一个可执行文件的,这个过程就是连接的目的了。
链接过程将多个可重定位目标文件合并以生成可执行目标文件,大家可以看下最初时候的图,在最后一步连接的过程中,我们不只是要hell.o而且还需要printf.o(代码中含有printf函数),链接就是将这连个.o合并为一个生成可执行目标文件
链接命令
– $gcc –static –o myproc main.o test.o
– $ld –static –o myproc main.o test.o
–static 表示静态链接,如果不指定-o选项,则可执行文件名为“a.out”
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。