我们现在开始编写程序并生成exe。

它的过程主要分为两步:

1.编写汇编源程序。

2.对源程序进行编译连接。

连接生成的exe为windows系统下的可执行文件,可执行文件包含两部分内容:程序(汇编指令翻译过来的机器码)和数据(源程序中定义的数据),相关的描述信息(占用内存等)。

3.执行可执行文件中的程序。

先进行相关初始化,比如调整CS:IP指针的位置,让CPU开始执行。

源程序

程序示例:

1
2
3
4
5
6
7
8
9
10
11
assume cs:codesg
codesg segment
mov ax,0123h
mov bx,0456h
add ax,bx
add ax,ax

mov ax,4c00h
int 21h
codesg ends
end

1.伪指令

指令分为汇编指令和伪指令,伪指令由编译器执行,汇编指令翻译成机器码由CPU执行。

segment和ends是一对成对的伪指令,功能是定义一个段。segment表示段开始,ends表示段结束。

而定义的方式也很形象,段名 + segment,如代码中所示,第二行即为开始。

一个有意义的汇编程序至少要有一个段,用来存放代码。

最后一行的end和ends是不同的,end代表整个汇编程序的结束。

assume代表某一个段寄存器和程序中某一个用segment ends定义的段相关联。

而CS后面也就是指定了CPU的指向的段地址,也就是直接去执行我们的汇编程序。

源程序中的程序

源程序中,最终由计算机执行,处理的指令或数据,称为程序。程序经过编译,连接后转为机器码,存储在可执行文件中。

image-20220419143448317-16503500895721

下面详解编写的过程

先定义一个段abc

1
2
abc segment
abc ends

在段中写入汇编指令

1
2
3
4
5
abc segment
mov ax,2
mov bx,2
add ax,bx
abc ends

然后指出程序在何处结束

1
2
3
4
5
6
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end

abc既然被当作代码段来使用,应该将abc与CS寄存器联系起来

1
2
3
4
5
6
assume CS:abc
abc segment
mov ax,2
mov bx,2
abc ends
end

大功告成!

程序加载和程序返回

一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载如入内存后,将CPU的控制权交给P2,P2才得以运行。P2开始运行后,P1暂停运行。

P2运行完毕后,应该将CPU的控制权交还给P1,然后P1继续运行,此交还的过程就称为程序返回。

要我们给它加代码才能执行程序返回,代码如下。

1
2
mov ax,4c00h
int 21h

目前记住这个指令即可,还不必去探究它的执行。

目前我们讨论了三个结束相关的词:段结束,程序结束,程序返回。

image-20220419144513584-16503507149152

语法错误和逻辑错误

编译时被编译器发现的位语法错误,运行时发生的错误位逻辑错误。

下面开始实操!

编辑源程序

打开编译器

image-20220419145736111

挂载之后打开,打开edit编辑器

image-20220419145856982

image-20220419150028978

把我们的程序输进去。然后在上面的选项中保存为1.asm文件,然后在file选项中点击退出即可。

编译

运行masm编译器,

image-20220419150253438

输入刚才的文件名1.asm

image-20220419150337810

后面的obj为编译输出的目标,剩下的nul.lst等都是中间产物,不用管,一路回车即可。

连接

将obj文件连接为exe文件。

运行link程序

image-20220419150602956

输入我们需要连接的obj名为1,然后一路回车即可,完成。 中间提示的.lib文件为程序中调用的库文件,我们并没有调用,直接回车即可。

连接的作用:

当源程序很大时,分成多个源程序来编译,生成的目标文件连接在一起,成为可执行文件。

程序中调用了某个库文件中的子程序,要将这个库文件和该程序生成的目标文件连接到一起,生成可执行文件。

简化编译和连接:

image-20220419151124866

windows的DOS命令中,有一个程序叫command也就是CMD,就是DOS的shell

在command中输入可执行程序时,command首先找到可执行文件,然后将这个可执行文件中的程序载入内存,设置CS:IP指向程序的入口,然后command暂停,CPU运行程序。程序运行结束后,返回到command中,command继续运行。

现在谁是P2,谁是P1已经显而易见了吧。

image-20220419151526780

跟踪和调试

用我们之前讲过的debug程序

image-20220419151618375

我们看到DS指向075A而CS直接指向076A,也就是直接从076A开始执行,这是怎么回事?

image-20220419151929695

也就是我们找到起始地址SA之后,还要设置一段PSP也就是程序段前缀,256个字节。加上256字节后,剧段地址和偏移地址的组合,可以有很多种表示,我们采用这样的计算:

1
sa x 16 + 0 --> sa x 16 + 0 + 256 == sa x 16 + 16 x 16 == (sa + 16) x 16

也就是把段地址左移一位。这样就解释了CS指向的地址。

用进制数学的角度来看,就是说256字节用16进制为100h,所以应该是sa:00 + 100h也就相当于在段地址上加10,因为段地址 +10相当于物理地址+100。当然这里是16进制,所以物理地址+100就相当于 + 256。

我们用u来查看所有汇编指令。

image-20220419153036883

注意,当加载到int 21即将执行的时候,我们知道这是程序要返回退出了,我们按p退出。从debug返回到command。