程序员不懂汇编,还能在这个行业“混”吗?

Posted 码农翻身

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员不懂汇编,还能在这个行业“混”吗?相关的知识,希望对你有一定的参考价值。

“大师,程序员不懂汇编能在这个行业混吗?”

“当然可以了,你在应用层编程,不进入底层就没问题,但是有句话说得好,‘真正的程序员应该理解程序的每个字节’,理解了汇编,会对程序和计算机系统有个透彻的理解,对学习基础知识有很大的好处!”

“那汇编很难吗?”

“不难,它比你会的任何一门语言的语法都简单!”

“哦?那我可以学习一下!你教教我吧!”

“好吧,首先,汇编就是机器语言的助记符,这你肯定知道吧?”

“这我明白,然后呢?”

“然后你要理解CPU的寄存器。”

“这....... 不就是像内存一样,一个个小格子吗?为啥要有寄存器? CPU直接操作内存进行运算不就得了?”

“寄存器至少有两个好处: 1. CPU太快,比内存快100倍,CPU等不及内存。我们把寄存器放到CPU内部,紧邻ALU(算术逻辑单元),这样信号几乎可以立即传输了。”

“2. 使用寄存器还有个额外的好处,可以让指令更短,想想看,如果一条机器指令引用了两个64位的地址,它该多长啊!”

“明白了,把地址放到寄存器中,指令会短得多,那寄存器都是叫Register 1, Register 2....... 吗?”

“那肯定不是,按照不同的用途,可以把寄存器分类:”

“晕了晕了,你还说汇编简单,光是这些莫名其名的寄存器名称就让人崩溃。”

“别担心,我概要地介绍一下,你先有个基本的印象,上图中的EAX、EBX、ECX、EDX在‘大多数情况下’可以认为是‘通用寄存器’, 你可以随便使用。”

“为什么既有EAX, 还有AX、AH、AL ,他们之间有什么关?”

“最早的Intel 8086CPU中,寄存器AX、BX、CX、DX等是16位的,16位(AX)又分为高8位字节(AH),和低位字节(AL),后来Intel 推出32位的CPU,寄存器也就扩展(Extend)到了32位,EAX就出现了!”

“那64位CPU是不是得继续扩展到64位寄存器?”

“孺子可教,x86-64 CPU的寄存器是RAX、RBX、RCX、RDX......  哎哟,扯远了,我们接着说ESI、EDI这两个寄存器,SI是Source Index, DI 是Destination Index,你猜猜他们有什么用处?”

“Source ? Destination?  好像是复制数据时指定从某个源到某个目的地。”

“对喽,有些汇编指令是专门复制数据的,可以用上ESI和EDI。还有两个重要的寄存器EBP和ESP,是专门用来做函数调用的,我们一会儿再说。”

“好吧,大概记住了!”

“汇编确实很简单,你记住,汇编的指令主要是这三类:数据传输类,算术和逻辑运算类,控制类。”

数据传输类就是把数据从一个位置复制到另外一个位置,比如从内存到寄存器,或者从寄存器到内存, 或者从寄存器到寄存器。”

mov ax,3210H   ;将0x3210放入寄存器ax
mov ax,bx      ;将bx寄存器的值放入ax
mov ax,[3640]  ;将一个内存单元的值送入ax
mov [502c],bx  ;将bx寄存器的值送入内存单元

“有意思,都是把右边的值复制到左边。”

“这是Intel的汇编格式,在AT&T的汇编格式中,就是把左边值复制放到右边。”

“我看到了方括号[3640]、[502c] 这表示一个内存的物理地址吗?”

“嗯,这真是个好问题,涉及到段寄存器刚才忘了给你展示了,段寄存器在实模式保护模式下还不一样,展开讲就太麻烦了,我们先放下,暂时认为这是某个地址吧。再来看看算术和逻辑运算。”

算术和逻辑运算类无非就是加减乘除,AND, OR,  左移,右移

例如:

add ax  bx      ; 把ax和bx的值相加,把结果放入ax寄存器
add ax, [37a0]  ; 把ax和内存的值相加,结果放到ax寄存器
inc bx          ; 对bx的内容加1
shl bx  1       ; 把bx的值左移一位
and al, 11110110b   ; and操作,相当于清除位 0 和位 3 ,其他位不变

“非常容易理解,那第三类控制类指令是什么意思?”

“你想想,用高级语言写程序是不是有很多分支(if else)、循环(while)?”

“对啊,汇编中有这些指令吗?”

“没有,在CPU中实现流程控制的逻辑需要多方配合,在CPU中有很多标志位,例如著名的ZF(零标志位),如果最近的操作的结果为零,则ZF= 1,然后你就可以用另外一条语句判断ZF的值,进行跳转。”

cmp ax bx  ; 比较ax 和 bx ,如果相等,就把ZF标记为1
je  .L1    ;  如果ZF 为1 ,则跳转到.L1处
......代码略......
.L1  sub ax 10

“我的天啊,搞个跳转这么麻烦,还是高级语言好啊!”

“那可不,但是你也要知道,高级语言经过编译,最终都会变成汇编的形式,它是一切编程的本质!”

“嗯,现在程序可以实现顺序执行,按条件跳转,那函数调用该怎么实现?”

“终于到了关键问题了,函数的调用只使用寄存器是搞不定了,需要内存的配合,在内存中建立一个叫做栈的数据结构

“栈我知道,先进后出嘛!”

“在这个栈中,每个元素代表一个运行中的函数,比如有三个函数main ,add,square ,main 调用add,add调用square,那在运行时,函数栈是这样的:”

“咦,这个栈中每个元素占据的空间不一样啊?”

“每个函数可能有自己的局部变量和各种参数,那大小肯定不同, 我们把这每个元素称为“栈帧”,还记得我们刚才提到的EBP寄存器和ESP寄存器吗?现在就可以派上用场了,用他俩来指向当前栈帧的开始处和结束处。”

“看起来很有道理,但是,只有两个寄存器,函数调用可能有很多层,栈帧就有很多个,不够用啊?”

“所以,当main调用add 的时候,需要把main栈帧的开始地址(就是当前EBP的值)保存到add函数的栈帧中,这样从add返回,就能恢复main的ebp了。”

“明白了,每个栈帧的开始地址相当于一个'门牌号',写在EBP寄存器中,但是EBP只有一个,所以,需要把上个门牌号暂时保存到下一个函数栈帧中。”

“嗯,你这个比喻很到位,同理,当add 调用square,需要把add的EBP保存到square的栈帧中,以便返回时恢复!”

“如果当前函数执行完,栈帧也就不用了,在废弃掉之前,把内存中的保存的值恢复到EBP当中,并且移动ESP到上个栈帧的顶部,就OK了!”

“懂了,大师,这样仅使用两个寄存器,就能记录无穷无尽的函数调用了,真是妙啊,这办法是谁想出来的啊。”

“不知道是谁先想出来的办法,现在,你觉得汇编很难吗?”

“看起来似乎不难啊!”

“我今天给你说的只是入门罢了,还有很多细节,尤其是当你读操作系统源码的时候,涉及到大量Intel CPU的知识,实模式和保护模式的转换,页表的建立......那个时候就真的很麻烦了。”

“在哪儿能学习这些知识?”

“给你推荐一套闪客写的操作系统教程吧,里边把这些知识点都给覆盖了:”

第一部分 进入内核前的苦力活

开篇词

第一回 | 最开始的两行代码

第二回 | 自己给自己挪个地儿

第三回 | 做好最最基础的准备工作

第四回 | 把自己在硬盘里的其他部分也放到内存来

第五回 | 进入保护模式前的最后一次折腾内存

第六回 | 先解决段寄存器的历史包袱问题

第七回 | 六行代码就进入了保护模式

第八回 | 烦死了又要重新设置一遍 idt 和 gdt

第九回 | Intel 内存管理两板斧:分段与分页

第十回 | 进入 main 函数前的最后一跃!

第一部分完结 进入内核前的苦力活

第二部分 大战前期的初始化工作

第11回 | 整个操作系统就 20 几行代码

第12回 | 管理内存前先划分出三个边界值

第13回 | 主内存初始化 mem_init

第14回 | 中断初始化 trap_init

第15回 | 块设备请求项初始化 blk_dev_init

第16回 | 控制台初始化 tty_init

第17回 | 时间初始化 time_init

第18回 | 进程调度初始化 sched_init

第19回 | 缓冲区初始化 buffer_init

第20回 | 硬盘初始化 hd_init

这张图展示了整个系列的结构

以上是关于程序员不懂汇编,还能在这个行业“混”吗?的主要内容,如果未能解决你的问题,请参考以下文章

arcgis中不小心把数据删掉了,又不小心保存了。还能在恢复吗?

在支付圈里混不懂这些行业术语 都没脸说自己是做支付的!

经常听说AT&T汇编、Intel汇编,还能听到ARM汇编,这个ARM汇编与前两个有啥关联?

现在的delphi xe7写的程序还能被反编译吗

你能在IE的表格行上放一个渐变吗?

git合并分支后还能在旧分支推送吗