这是我3月2日视频分享的文字稿,从汇编语言的本质,学习汇编语言的意义讲到学习方法。
关键词
分享 知识系统 汇编语言 软件 系统软件 芯片 中间件 内核 系统调用 编程模型 汇编 应用层 反汇编 编译器 逻辑运算 操作系统 软硬件接口 体系结构
以下是文字记录
〇、作者简介
在正文之前给先大家做一个简单的自我介绍,自我介绍和后面的内容也是有一定联系的。我大学学的是微电子专业,也就是芯片专业。从2005年读研开始做单片机的开发,后来又去做Linux的系统移植。从那之后基本上一直都是做软件,芯片的东西也有关注,但是没有具体做过,最后一次具体做项目,应该是读研的时候实验室做过一些芯片相关的东西。所以我其实是没有接受过一个特别完整的计算机专业的受训经历的,很多东西都是用到了或者遇到问题的时候再去学习。我在学习的时候特别注重两点,一点是注重知识的系统,另一点是重视回到原始的文档。一、汇编语言的本质
今天的分享里面也会涉及到知识的系统和原始文档这两点。首先我们为什么要聊要不要学习汇编语言,因为我们不管在学习任何知识的时候,一定都要有两个视角。一个是在这个知识里面,搞清楚这个知识本身是怎么回事。另外一个就是跳出这个知识或者这个技能来看看,从外面往里面看,看看这是什么东西。所以今天的分享,我们先从外往里面先看一下。首先汇编语言是个什么东西,按照我的理解,汇编语言其实就是计算机软件和硬件的接口。所以这个接口意味着什么呢,意味着我们往下走,可以去了解计算机体系结构,我们往上走,可以去了解操作系统软件。二、学习汇编语言的意义
我自己一直是在做系统软件的开发,我的工作经验中其实有很多都是操作系统相关的软件。这里面有几个项目都是直接或者间接的用到了汇编语言的技能,给大家先举几个例子。第一个例子是,大概十年前我在中星微工作的时候,当时负责芯片平台的CPU和环境软件的支持。其中有一项任务是机台测试,机台测试是什么呢,就是我们要去支持在芯片的生产线上去筛选合格和不合格的芯片,这里面需要提供测试用例,有一部分代码必须要使用汇编去写,这个是我第一次手写汇编语言。不仅仅是我在这个经历里面手写了汇编语言,同时为了手写汇编语言还要去了解函数之间是怎么调用的。之前我也知道 ABI 这个概念,ABI 就是 Application Binary lnterface 应用程序的二进制接口。我知道这个概念,但是并不确切知道函数调用之间要符合什么样的规范。我也是在那次的机台测试的时候具体的看了ARM的AAPCS手册,也就是ARM Architecture Procedure Call Standard 的ABI手册。我了解到这个手册之后,我才对这个函数调用之间关系有一个系统的了解。这个也对应到前面我们说的点,就是要注重知识的系统和原始的文档。第二个例子是,我在SUSE工作的时候遇到的,大家都知道SUSE是第二大的Linux发行版公司,第一大的是RedHat。我当时是做虚拟化,主要负责的是中间件的东西,主要是libvirt和QEMU,也有一点点Xen的工作。这个工作本身它是比较靠上层的,那里面主要的libvirt和QEMU都是用C语言写的,这可以看成是一个C语言的应用开发,这个工作本身几乎是不涉及到汇编语言的。但是那时候发生个事情,就是我们在某一个场景下面,发现那个系统启动有问题。熟悉虚拟化的同学都知道,QEMU里面的系统启动方式跟我们物理机可以一样,也可以不一样。我当时就发现这个问题很奇怪,因为我之前一直做ARM的东西,对x86平台其实是不了解的。此时我有两个选择一个选择,一个是去看x86相关的资料,看一看BIOS这个流程怎么样,启动流程怎么样。但那时候我看了之后发现Google直接得到资料一般都不太具体,它比较抽象,讲的都是大概的流程,没有具体的东西。这个没法支持到我怎么去解决问题,于是那个时候我就尝试着通过汇编语言去看一下BIOS那个代码。我发现,它原来是在某个地方蹦到某个地址,再做跳转,从而完成了从BIOS到下一级BootLoader的启动。第三个例子就是,我在华为工作的时候做一个跟 ABI相关的事情,我之前也提到过, 就是AARCH64的ILP32的工作,相当于是我在64位的系统里面去用汇编,但是我要支持32位的ELF。这个里面基本上来说因为编译器当时已经差不多做好了,虽然有些bug,但是基本上是没问题的。因为我当时解决的主要是要完善glibc跟内核的这个工作本身几乎不涉及到汇编语言,除了 ld.so和系统调用,例如系统调用中我要考虑怎么去给应用程序给系统将传参。但是在这个项目里面,我发现了解汇编语言还是很有帮助的,为什么呢,因为你去解一些bug的时候,有些地方如果对汇编语言不了解,会很不方便。比如说那时候我们是大端系统,我们首先就要考虑两个问题,就是大小端的转换。我们既要保证这个代码大端是正确的,要保证小端是正确的。第二个事我们要考虑32位和64位的转换,因为我用户端是32位的ELF,进到内核里面是64位的,这两个地方它经常会出一些问题。这个时候除了做代码级的分析之外,另外一个方式就是看反汇编,看看它这个地方32位和64位的关系到底是怎么处理的。从这三个例子大家可以看到汇编语言其实不仅仅是我们做底层开发,比如开发操作系统,或者说调内核调驱动的时候需要,它其实也是我们去理解CPU的编程模型,去理解系统的一个很好的切入点。所以回到我们最原始的问题,要不要学汇编语言。我自己的体会是,如果工作是涉及到系统软件和中间件,这个是CPU相关的,这个汇编语言是要学习的。如果不是这样,这种工作可能比如说是JAVA 或者别的语言,我觉得相关的像ByteCode的这种相应字节码的一些相关的知识可能也是需要的。三、如何学习汇编语言
我们前面主要聊了一下要不要学汇编语言,我们下一段主要是分享如何学习汇编语言。那联想一下,前面咱们做内核知识的分享,当时我们分享的时候提到一个点就是软件的分层。大家应该对分层这点都比较熟悉了,我们在学习汇编语言的时候,也可以借助这样一个思想,我先学习什么样的汇编语言呢,先学习哪个层次跟相关的汇编语言呢,显然应用层可能会比较容易一点。刚开始去看汇编的时候,可以先看一看这个应用层汇编是怎么回事,应用层汇编包括什么内容,我们就可以回到基本的程序设计语言,它基本结构是怎么样的。就是这个顺序判断循环对吧,它这里面就包括了算术运算和逻辑运算,还包括判断和跳转这些基本的指令,有了这些指令之后,我们可以在比如说我们涉及汇编语言和C语言的调用,汇编语言和C++的调用,比如说涉及到的一些 ABI 相关的事情,我们可以有一个汇编的切入点可以去进行查看。同时,如果说crash了或者coredump了或者内核 crash 了,我们借助汇编语言,可以去看一下具体出错那个地方,因为反汇编得到的C语言的行号可能没有那么准确。那除此之外,我们在了解了算数运算逻辑运算这些基本的汇编之后,还可以再去深入的学习。在这个地方我们先停留一下,我们怎么去学呢,比较直接的办法当然是我们找一些资料去学习,这肯定是没问题的。还有什么办法呢,比如我去看体系架构的手册,比如看x86、ARM、RISC-V的架构手册,我们去看这些手册里面具体是怎么写的。我觉得这是一个最终的目标,如果最终我们能够基于这些手册去解决我们汇编语言相关的问题,那么这个汇编语言的学习就比较到位了,可以说是已经到了一个可以自己进行自我迭代的一个程度了。问题在于我们怎么能够进入到这样一个状态,直接去看这些架构手册的话还是非常有挑战性的。那么我们学习汇编语言的方式应该是什么呢,这个方式就可以是,我们自己写段代码,不要开启编译器的-O3等各种优化选项,然后看看反汇编是什么样子的。这样我们就能对基本的汇编语言有一个基本的了解。当然在实际工作中,我们可能会开启 -O3 -Os这些编译选项,这些都没关系的。我们再去对比它开与不开有什么区别,汇编指令有哪些不同,这样我们就有了第一手的素材,对汇编语言就有了一个基本的了解。同时在这个层次上可能还会出现一个什么情况,我们可能有时候会看到一些内嵌汇编,那看到内嵌汇编,比如说是GCC的内嵌汇编,根据我们前面说的我的学习方法,我们还要回到原始的文档。那么GCC的汇编语言,就要去看GCC的手册,GCC关于内嵌汇编有很详细的说明,详细的说明里面我们能不能根据详细的说明去理解,我该怎么去写这些GCC的内嵌汇编的。同时这个地方涉及到一些ABI的知识,就是什么情况下我需要用那些extra的一些flag(参见参考资料1)。那么这个地方你可以看到它其实又是一个软硬件的一个接口,我们其实是在学习汇编语言,其实看的是我们对于系统软件的了解,对于 CPU 架构的了解。那么我们对于简单的汇编如算术逻辑运算判断跳转有了一个了解之后,我们再往下走是什么呢。我们会看到一些比如说操作内核,它用的汇编跟应用的汇编有什么区别。区别就是操作系统内核是一个privilege,特权级别,这个特权级别它就会对应到一些特权相关的指令。比如说如何进入和退出一个特权级别,比如说如何去操作一些 CPU 的控制寄存器。这些控制寄存器可能涉及到操作系统的一些能力,比如说中断使能、系统调用的路由、中断路由这些事情。在这个地方其实我们有可以看到,汇编语言和CPU架构其实是高度相关的。也就是说我们可以在学习某个CPU架构的时候,我们去对照汇编去看一些操作系统比如Linux、RTT,它是怎么去写这个汇编语言的。同时我们也可以基于某一次对操作系统比如Linux的一个调试,我们基于调试中我们看到一些我们之前没有见过的汇编的一些语言,再切入回来,再去看一看它跟CPU架构有时么关系,哪些是跟CPU架构相关的。除了我们刚刚说的系统分层这块之外,系统分层里面还有什么内容呢。比如说Memory模型,像ARM和RISC-V,它都是统一寻址的,如果是x86,它其实还有传统的方式,还包括一个IO寻址的方式。这种东西它在汇编语言里面也有会有所区别,同时在Memory模型里面还有一块页表处理。页表处理里面页表基址在什么地方,有几个页表基址,这些又都涉及到汇编语言去访问特殊功能的寄存器。那通过这样一个学习呢,我们基本来说对于汇编语言就有了一个整体的了解了,不管是应用挂了还是操作系统内核挂了我们都可以去调试。那这个是不是就学完了呢,还没有,为什么呢,因为还有一个很重要的概念就是pseudo assembly,就是伪汇编。伪汇编这个东西,我们平时用得非常广泛。大家平时不知道有没有遇到过这样的问题,我看到一段代码,但是我去搜手册就是没搜到这个汇编在哪,但是它明明是汇编器能编译过的,还是能用的一个商业/开源软件。它为什么就不知道在哪了,那这个就是伪汇编,对吧。其实它并不单独对应某一条机器指令,它其实是某个汇编指令的一个变化,或者是多条指令的集合。那伪汇编其实是跟架构相关的,不同的CPU架构它会不一样。比如说RISC-V,它其实在RISC-V spec里面是有写到的。它也有专门的ASM手册,里面也会单独写都有哪些伪汇编指令。了解伪汇编指令,其实是很重要的,就是看我们的汇编语言学习到底是不是实操,到底我只是知道纸面上知道这些汇编语言,还是说我真正在工程里面调过汇编语言。因为工程里面它有很多都是伪汇编了,如果我没有看位汇编的话,那么可能在实际工程中看别人写的代码就会比较费劲。OK, 刚刚说到基本的算术逻辑运算判断跳转,还有特权指令,Memory模型、伪汇编,除此之外汇编语言里面还有什么内容呢?其实还有一些内容,比如说像x86的3DNow MMX这种多媒体指令。还有ARM的NEON指令,VFP的这种浮点指令。这些指令它也会对应的汇编。这些汇编和前面的逻辑差不多,我们也是要看基本的应用是怎么用的,然后再看它还要考虑一点,整数跟这种浮点之间它相互调用是怎么操作的,这个地方我就不展开讲了。四、推荐的书籍与文档
所以可以看到,其实我们在整个学习过程中,我觉得比较重要的一点就是注重回到原始文档。原始文档有什么,比如说 CPU的架构手册,比如操作系统的代码,比如操作系统的一些重要的书籍。我是建议大家日常至少应该有两本,一本是讲 CPU讲计算机组成原理的书,比如说《计算机组成原理与设计软硬件接口》那本书,然后还有一块应该就是操作系统的书了,比如说我最近正在看陈海波老师写的《现代操作系统》。我觉得经典的书都可以,也不用说去纠结到底看哪一本。我的建议是我们对照CPU和操作系统的两本书去看,我们会发现有些内容CPU的书里面讲的比较多,有些内容操作系统的书里面讲得比较多一点。我们要不断的注重回到原始的文档,除了计算机体系结构和操作系统的书之外呢,平时可能还需要看的就是这个ABI相关的这些手册。大家可以趁这个机会去看一看,大家自己平时用的常用的CPU就是 x86、ARM、RISC-V,那这里面哪些资料你可以再去看一看。大家可以先把自己手头的资料给它补齐,补齐之后呢我们基于问题再去对应相应的资料,最终目标应该是能够看到原始的文档去解决问题。五、总结
这个就是我今天的分享。总结一下,今天我是从自我介绍开始的。因为我是转专业的,所以我会比较重视知识的系统和原始的文档。今天我们是从汇编语言是软硬件接口开始分享。我首先举了三个我自己的项目经历,有些是明显要写汇编的,有些是看起来跟汇编语言无关,但是由于为了解决问题去看了汇编语言。还有一些,是跟体系结构操作系统有点关系,会比较多地涉及到通过汇编语言做调试。这也就是说,系统软件包括中间件这些软件可能我们在调试的时候了解汇编语言,对我们的工作可能会或多或少的有一些帮助。第二部分我分享的是如何学习汇编语言。我们站在分层的角度先去学习应用层次的汇编语言。然后再往深了一层去看特权级别的汇编语言有什么不一样,这涉及到一些特权指令,涉及到Memory模型的一些内容,最后我们说伪汇编和浮点指令这些内容。我是张健,前华为架构师,前创业公司技术合伙人,目前自由职业。同时我是两个孩子的爸爸。在职业发展,工作和生活如何平衡方面有很多第一手经验。欢迎大家与我探讨。- https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
张健老师的技术小班课:ARM架构与调测调优打通ARM架构和操作系统,想继续深化汇编语言,异常处理和内存管理的童鞋可以关注下。点击阅读原文直达课程介绍。