- 论坛徽章:
- 13
|
本帖最后由 _nosay 于 2016-09-16 15:15 编辑
汇编看起来是一门很装逼的语言,乱七八糟的,很多人对这门语言的第一错觉肯定是:大牛才会去碰。
其实不是这样,语言只负责表达,熟悉了都一样,高级语言在表达方面确实更有优势一些,但从整个编程的大局观来看,难的主要是构思和想法,表达是次要的,所以不要过于吹嘘自己用的语言最天下无敌,大型的程序不用面向对象语言就不行等等,举个最简单的例子,Linux内核这么大个程序,怎么都是c和汇编写的呢,是吧。每种语言都有适合使用它的场合,比如对内存/执行效率要求不高,对开发进度要求比较高的,c++可能比较合适,对内存/执行效率要求较高的,c可能就比较合适。
汇编在Linux内核中使用的场合大致有如下几点:
① 对指令块大小有特殊限制的,比如系统引导程序,由于要能装进引导区(即第一个扇区),所以必须用汇编写甚至机器码编写,才能做到精打细算;
分享一个有意思的东西,但需要一点背景知识:
@ 机器码“75 xx”,表示一条jnz汇编指令,跳转“xx+当前机器码长度(2byte)”这么远,比如“75 00”相当于“jnz 02”,“75 EF”相当于“jnz 0”(没跳!)
@ 机器码“04 AC”,表示一条“ADD AL, 0AC”汇编指令(先不要纠结机器码是怎么来的,不看Intel文档,自己看不出来规律的)
@ 机器码“AC”,表示汇编指令lodsb
OK,背景知识够了,其实就是知道三个对应关系:“75 xx”-jnz x、“04 AC”-ADD AL, 0AC、“AC”-lodsb。
现在如果想表达这样一段逻辑:
- ; 伪代码
- if ZF=0
- lodsb
- else
- add al, 0AC
复制代码 代码1,生成机器码“75 04 04 AC EB 01 AC”:- jnz 1
- add al, 0AC
- jmp 2
- 1:
- lodsb
- 2:
复制代码 代码2,生成机器码“75 01 04 AC”:
C程序→汇编程序→机器码,整个过程由编译器完成,所以这两个过程都可能有优化空间,比如上述例子经过手动优化一下,利用了“add al, 0AC”指令中现存的“AC”,就为最终的可执行程序节省了3个字节。
② 对执行效率要求极高的场合,同①,编译器编译出来的代码,在执行过程上可能不是最优;
③ 内核中经常要访问或设置一些寄存器,C语言里面没有这样的语法成分。
没有理解难度,需要查资料与Intel格式对比,不需要死记硬背,多看些代码自然就知道了。
Linux内核中有纯汇编的代码文件.S,没有什么好介绍的,所以主要看一段嵌入到C函数中的汇编代码:
① 无脑将最后面的部分编号(从0开始);
② 将这段代码分成两部分看:“{}”内除掉“__asm__ __volatile__();”(C地盘)、“__asm__ __volatile__();”(asm地盘)
③ 第一个冒号开始,表示“输出部”,表示这段代码执行完,哪些变量或寄存器的值从逻辑上讲是会被修改的;
第二个冒号开始,表示“输入部”,对寄存器或C地盘的变量进行初始化;
第三个冒号开始,表示“损坏部”,~!@#$^&……
其中"aa"(bb)这种,不妨认为它是一种“捆绑”操作,比如"=&c"(d0),就表示将ecx寄存器与d0变量“捆绑”,修改ecx就是修改d0,修改d0就是修改ecx(关于更多类似&c代表ecx这种,需要自己查资料总结,不需要死记硬背)。
另外,除了可以用"&c"表示“捆绑对象”外,还可以用编号,比如"1" ((long) to),表示将编号1处的edi寄存器/d1变量,与C的to参数“捆绑”。
④ 数据拷贝是个非常底层的操作,系统随便运行一会,就不知道要调用多少多少多少次,所以运行效率就非常重要,所以虽然__memcpy()是个C函数,但内部完全用汇编编写,会比C写出来再经过编译得到的更精悍。 分如下几个步骤分析它的逻辑:
@ 初始化(看输入部):0/ecx/d0 = n/4, q(eax/ebx/ecx/edx之一) = n, 1/eid = to, 2/esi = from;
@ "rep; movsl\n\t":重复执行movsl n/4(ecx)次,页movsl是从esi拷由4byte到edi,并esi+4, edi+4,执行完可能还剩1/2/3byte没有拷贝;
@ "testb $2,%b4\n\t":AT&T要求立即数前面要加$,%加编号表示去输入/输出部对应,所以这句就是判断%b4(n)的低第二位是否为1,如果为1,则至少还有2byte没有拷,则执行movsw再拷2byte,否则"je 1f\n\t"("f"表示forward,往前方最近的1标号处跳转,也有"je 1b\n\t");
@ "1:testb $1,%b4\n\t":经过以上的步骤,现在最多还剩1byte没的拷,如同上一步的的道理,进行处理,代码就执行完了。
|
|