汇编怎么从内存地址写入连续的数字_计算机自制操作系统(四):汇编语言烧脑的ORG问题...

60c6dd8fc900ab599e6526aeae590069.png

有读者给我留言,由于MBR程序是被加载到7C00H之处的,因此在自制操作系统的书籍中,都强调了程序开头必须要用 ORG 7C00H来定位,不然是没有办法正常运行程序的。但是在我的专栏中,程序中却没有见到一句 ORG的定义,但照样成功了。这是怎么回事?

这里我先给出结论,再来大篇幅的解释原理:是因为我已经熟知ORG的工作原理,所以不需要在程序里明文出现ORG的伪指令,也同样能达到程序成功运行和数据成功访问的目标。

那么,现在就开始来讲讲很多人感到疑惑的ORG问题。而且,这是一个非常重要的问题,对于从事汇编语言编程的人士来说,这是一个不能回避的问题,只有明白其机理,才能游刃有余。

一、ORG原理

ORG是什么?它是一条汇编语言的伪指令,所谓伪指令就是不产生具体的机器代码,只是用来辅助编译器进行编译的。ORG的作用不是指定程序的放置位置,它没有那样的功能和能力!这点,对于做过单片机项目的人来说,特别容易上当。如在程序的开头放上一句:ORG 500H,以为代表的含义就是程序从内存500H的地方开始放。其实,ORG语句是否需要、ORG语句后面的数值应该是多少,并不是写程序的人主观指定的,而是被动指定的!

  1. 为什么需要ORG?

我们写汇编程序的时候,程序最终以BIN形式放在计算机的内存什么位置是无法预知的。那么在编译阶段,如果程序需要从内存取数据进行操作,也就无从预知实际的物理地址,那么程序是不是就没有办法运行了呢?为了解决这个问题,编译原理只能将程序中的内存位置定义成相对位置,也即任何对内存地址的定位都翻译成离程序最开始的偏移量。这样,无论最终的程序从内存什么地方开始放,都不会影响程序的正确运行:

ef8d923a556c79b403dcd737d211be87.png
程序放置位置偏移和相对偏移的关系

可见,程序无论放在什么位置,只需要将相对偏移量+程序放置位置,就能得到准确的物理位置,最终得以保证程序正确运行。我们知道相对偏移量在编译阶段由编译器产生,那么程序放置位置怎么知道的呢?无疑,我们就只有用ORG来告诉编译器。

2. 哪些情况下需要ORG?

丛上面的图可以明显看出,当程序从数据段内非0偏移地址开始放置时,必须要加上一段ORG指定的偏移量才能准确定位内存访问位置。典型的以下两种场景:

<1>DOS下面的.com程序

关于DOS环境下,汇编语言的.com部分知识,请读者先自行学习,这里不做介绍。

由于程序被强制指定了只能放在段内PSP之后且在100H的地方,因此程序必须要用ORG。如果不用ORG 100H指令,当访问低端内存地址的时候(如MOV AX,[02H])就一定会错误的访问到PSP里面的数据了。而用了ORG 100H指令之后,以上语句,就能访问到正确的内存地址:100H+02H。

关于这块知识,我认为经典的教程在这个地方:

https://blog.csdn.net/ruyanhai/article/details/7177904​blog.csdn.net

<2>系统引导扇区程序:由于计算机强制指定了只能放在7C00H的内存地址,因此程序必须要用ORG。如果不用ORG 7C00H,那么语句MOV AX,[02H],访问到的内存地址也将是错误的地址02H,而用了ORG 7C00H之后,访问到的内存地址就是正确的地址: 7C00H+02H。

关于这块知识,我认为经典的教程在这个地方:

https://blog.csdn.net/daiyutage/article/details/8918290​blog.csdn.net

3. 哪些情况不需要ORG?

<1> 不涉及从内存读写数据的时候,不需要ORG。ORG有无不影响程序指令的顺序执行,也即不影响CS、IP等值。ORG的影响只是针对DS、ES等寄存器来说的。

<2> 程序从数据段内0偏移地址开始放置时,不需要ORG。这个从最上面的“程序放置位置偏移和相对偏移”的关系图就可以看出,无需ORG就能准确定位内存访问位置。

二、ORG测试

如果看了上面的原理都还不明白,那么下面的测试就非常有必要了。看完之后就非常清楚。

http://1.COM程序验证

<1>无ORG测试

4f9c0e847dbe52b21e2ef9799c3d66b6.png

该程序的目标是打印出welcome字符串,我们先编译成.COM:

d3ed0cecf350ca96df54ea48483329e1.png

运行结果,与我们的预期并不一致,我们只需要示“‘Welcome Jiang OS’”字符串,但是程序却打印出了额外的很多信息。为什么呢?我们来看看机器语言,打开org.lst:

3ea1010a0615b3090e8abc3d9436b4ce.png

可以看到,mov si, welcome 这语句取到的地址是0002。

再打开最终运行的二进制文件org.bin,看看:

e9733a874d3367b2f9ed280abf8ced31.png

可以看到,程序运行的时候最终数据读取的内存偏移地址是:0002,这与我们的初衷是不相符合的,因为.COM程序是强制安装在段内100H偏移处的,而我们要取的数据相对偏移又是0002H,因此最终的偏移地址应该是100H+0002H=0102H才对。所以,取到的数据就不正确。那么我们从0002H处误取到的数据都是些什么呢?就是PSP,也即DOS系统的.COM程序长度为256B的前缀!

ec651b2d39381fad5d6f05fd8921cfb0.png

<2> 有ORG测试

现在我们加上ORG再进行对比测试:

660fdce6543b7eb222859960ffc03ef9.png

编译后运行结果如下:

f36909f02aea20909907e18287905bfa.png

可以看到,这个就得到了我们想要的结果。我们同样来看看编译过程中的机器语言文件:org.lst

a8c9d5cee8470062b7cac0bebd355a16.png

可以看到,汇编器编译成机器语言之后,取数据这条指令的相对地址仍然是0002H。这就是我所说的,编译阶段,编译器能做的工作就只能得出相对地址,这个地址显然无法取得正确的数据。因此,最后的实际地址还必须看二进制文件,我们这次又来打开最终的二进制文件:

ccb393bff526de45f1d02e01a99b3181.png

很明显,这次取到的地址就不再是0002H,而是0102H!这就是我们真正需要取的正确的数据地址:这个地址也正确的跳过了.COM程序的PSP部分,避免造成错误。

通过2次调试过程,我们很明显的看出:在汇编成机器语言的时候,程序只是全部汇编出相对偏移地址。在生成2进制文件程序进驻内存的时候,编译器必须要“悄悄”的把ORG的地址和相对偏移地址相加,才能得到最终的数据访问地址。所以,我们要看最终的地址是否正确,一定要看最终的BIN二进制文件!

2.MBR程序验证

我们再用上面的方法验证一遍,计算机中最常用到的启动扇区MBR程序ORG 7C00H的案例。

<1>无ORG测试

9e1d3f92602518111dde8fba2c09345e.png

运行结果,必然是打印乱码,因为取内存数据welcome找错了地方:

edac187998a2bc6bf95099e95f196a85.png

显然从BIN来看,由于没有ORG 7C00H,本来应该取7C00H+0002H=7C02H的内存,结果取到了00002的内存,也就是中断向量区数据。

4cc6d37775fae06966a5a4609bea7388.png

<2> 有ORG测试

8ed7f4f3d60af47b09eee25d16066ba9.png

运行结果,必然是正确的,因为这次指定了ORG 7C00H。

85966f0a672040bac8d310c5cd4d131a.png

最后还得说点重要的内容:

对于COM程序和MBR程序,不知道细心的人看出区别没?那就是在MBR程序中,都有对DS(ES)的一个初始化操作:多数人习惯将DS(ES)置0,少数人习惯将DS(ES)置7C0H。不信的话你可以把所有自制操作系统的书中MBR程序翻出来看,都有这个操作。但是在COM程序中,你见不到对DS(ES)的重置操作,这是为什么呢?这个得从源头说起:

1.MBR程序:MBR程序是在没有操作系统支撑的情况下,BIOS用了一条指令:jmp 0:7c00h来强制修改CS值=0,当程序从7c00h开始运行的时候,CS变化了,但是DS、ES的值并没有做任何的改变。你需要记住:在汇编程序中,凡是在使用jmp AA:BB指令之后,都需要重置DSES的值,否则你就没有办法正常访问到新程序中的任何内存数据。MBR也不例外,这样就必须马上要带一个DSES的初始化操作:要么将DSES置0,同时带上ORG 7C00H;要么将DSES置7C0H,不带ORG。

http://2.COM程序:COM程序是DOS系统装载的,什么概念呢?就是说它有操作系统做支撑,DOS系统会把COM程序放在从内存:CS段+100H偏移地址开始的地方。程序运行的时候,有DOS系统护航,CS和DS、ES的值始终都是相等的,程序访问任何内存数据都没有问题,所以不需要再对DS、ES寄存器做任何设置。

三、如何避免使用ORG?

上面的内容都说清楚了:当程序从数据段内非0偏移地址开始放置时,必须要有ORG。但是,如果你也是像我样一个非常固执的人,也可以通过变通的方式来不用ORG。方法有两种:

(一) 手工设置程序位置偏移量

每次访问内存的时候,都自带程序位置偏移量。如要访问相对偏移量为02H的地址,需要手工在地址前加上程序位置偏移量,不要ORG,也能达到访问目的:

MOV AX, [7C00+02H]

但是,这种写法无疑增加了编码的逻辑和工作量,特别是大量的数据访问的时候太过麻烦,因此这种方法是不常用的。

(二) 把程序放置位置从数据段内非0偏移改为0偏移

  1. COM程序验证

由于程序放置的段内偏移地址是:100H,那么它的物理地址必然是:CS*10H+100H。访问数据的时候,要让这个物理地址变成数据段内内的0偏移地址,那么DS,ES就需要重新定义,假设DS新值记为DS',那么有以下公式成立:

DS'*10H+0=DS'*10H === CS*10H+100H=(CS+10H)*10H

这样,就求得DS'=CS+10H。这句话的含义是:我们只需重新定义DS的值,在CS的基础上加上10H,就能保证程序是从数据段的0偏移位置开始放置的,就完全可以不用ORG语句了。下面进行一下验证。

574900fab2ad6b067b16034dc3c3f75f.png

将程序编译并运行之后,结果果然如期:

fe31e5d284213964b1c5a7ff69e1950c.png

那我们再反过来思考一下,如此设置DS和ES之后,最终的二进制文件中的偏移地址是多少呢?应该是0002H,而不能再是1002H。因为现在的地址都应理解成从段内偏移地址为0的地方开始加载,打开最终的二进制文件验证如下:

d1a0f2406910b627abb33b85bf941ab4.png

2.MBR程序验证

推导过程同.COM:

DS'10H+0=DS'*10H === CS*10H+7C00H=(CS+7C0H)*10H

可以求得:DS'=CS+7C0H。

440e274a199411fe9fca817902f6ff95.png

程序果然能正常的运行:

85966f0a672040bac8d310c5cd4d131a.png

可以推测,由于没有使用ORG 7C00H且重新设定了段DS和ES的值,因此二进制程序的最终地址是0002H。验证如下:

5f8cec44fa820113322cb941e3923176.png

(三) 通用设置

通过以上的分析,我们可以得出普遍的规律就是,要想不使用ORG,只需根据CS和程序放置偏移地址计算并重置DS,ES即可:

DS'*10H=ES'*10H=CS*10H+ORG

DS'=ES'=(CS*10H+ORG)/10H=CS+ORG/10H

因此,无论程序从什么地方开始放置,只要用以下通用语句,就可以完全不用ORG语句。

mov ax, cs
add ax, orgaddr/10h 
mov ds, ax
mov es, ax

这就是无需使用ORG语句的通用设置。这个设置等效于很多教科书上讲到的如下方式:

%define DEBUG                    ;注释用于MBR,不注释用于COM调试
%ifdef DEBUG
org 0100h                        ;用于COM调试的设置
%else
org 07c00h                       ;用于MBR启动的设置
%else
%endif

那有人可能会说,既然是等效的,那我只需要用教科书上定义ORG值的方式就行了,不需要用你的这个通用设置格式啊。但是,前面我已经说过了:在程序运行时,.com不需要重置DS和ES的值,而mbr程序又需要重置DS和ES的值,所以我的格式对于统一程序结构是最好的。因为无论是.com或是mbr程序,最终我们的配置方法都可以统一成如下格式:

%define   com              ;注释用于MBR,不注释用于COM调试
%ifdef    com
orgaddr   equ   100h       ;用于COM调试的设置
%else
orgaddr   equ   7c00h      ;用于MBR启动的设置
%endif

mov ax, cs
add ax, orgaddr/10h
mov ds, ax
mov es, ax

这样就完美的解决了操作系统开发过程中调试和验证频繁切换的问题。下面来验证这个过程。

四、 操作系统调试和验证

有了以上基础,我们在操作系统的开发过程中,汇编程序就可以在.com(.exe)调试和mbr验证之间完美切换了。因为我们开发的时候需要频繁的调试程序,但如果每次都用mbr来启动虚拟机调试的话,就会非常的麻烦。所以我们很多时候可以把程序逻辑编译成.com(.exe)格式,在Dos下调试,这样会方便很多。

(一)DOSbox准备

.com(.exe)需要在DOS环境下运行,我们这里备份记录一下DOSbox工具的配置方法。

运行DOSbox启动之后,可以看到有两个窗口,我们按图中的配置文件进行配置:

56e50ffada4f5a9d0906517cf7658971.png

把我们需要运行.com或.exe的程序目录挂接到模拟C盘:

f58b27ec8b4554511b83909bdf5eea7e.png

这样,下次启动DOSbox之后,用命令C:就可以自动切换到程序目录了:

74916acda87e30c0031007ce0b382179.png

(二)程序验证

现在就来验证一下,用上一小节的设置方法来切换调试和验证模式。程序模板如下:

%define   com              ; 注释用于MBR,不注释用于COM调试
%ifdef    com
orgaddr   equ   100h       ;用于COM调试的设置
%else
orgaddr   equ   7c00h      ;用于MBR启动的设置
%endif


jmp start

welcome db 'Welcome Jiang OS','$'

start:
mov ax, cs
add ax, orgaddr/10h
mov ds, ax
mov es, ax

mov  si, welcome
call printstr

%ifdef    com           ;如果是.COM调试,需要调用DOS返回中断
mov ah,4ch
int 21h
%endif                  ;.COM程序将在此顺利返回Dos系统

jmp $                   ;如果是MBR启动,将在此停止运行


printstr:                  ;显示指定的字符串, 以'$'为结束标记
      mov al,[si]
      cmp al,'$'
      je disover
      mov ah,0eh
      int 10h
      inc si
      jmp printstr
disover:
      ret

times 510-($-$$) db 0
      db 0x55,0xaa

首先我们在程序中保留%define com语句,让其运行成.com调试模式,并将程序编译:

c14a34b4eb52f89c805ecb08a0e13734.png

然后在DOS中运行这个.com程序即可:

c70dd765b6d030c1265d746ca6f42c0b.png

可以看到程序正常运行,而且能返回Dos系统。

其次,我们在程序中注释掉%define com语句,让其运行成mbr模式,并将程序编译:

1223d08a159c6ac900f7f5e4cffd441c.png

在虚拟机中就可以正常运行mbr了:

9fcbaa52c0c4788806796cd49f928454.png

总结:有了以上的开发模式,我们以后的程序开发调试过程就会方便很多。

五、解答疑问

这样以来,就能清楚的解答我在上一章程序中的设置方法:

mbrseg         equ    7c0h     ;启动扇区存放段地址
mov   ax,mbrseg   
mov   ds,ax

它与上面的通用设置语句是一致的,因为这里CS=00H。

DS =CS+ORG/10H=00H+7C00H/10H=7C0H。

除此以外,上一章我最后还留下了两个问题给读者思考,就是操作系统跳转到新的地址(8000H)之后,为何所有的子函数都要重新定义,以及为何所有的内存地址访问都要减去512?

  1. 函数重新定义

跳转出MBR之后,所有寄存器的值都发生变化了。CS从00变成了800H。这时再用CALL调用MBR内的函数就无法定位了,因为汇编语言里CALL其实就是执行JMP指令,只不过JMP的是相对偏移量(从当前位置向前或向后跳转多少步)。在正常CALL函数的时候,CS地址不会变,只变IP地址,而现在CS地址都已经变化,显然就只能在新的段内跳转,当然跳不到MBR内的函数代码位置了。我们就只能在现在的段内重新定义函数。

2. 内存地址访问减去512

跳转出MBR之后,DS的值DS从7C0H变成了800H。而新程序存放位置就是从8000H开始的,那么新程序里面所有的数据内存地址相对与8000H来说,偏移量就只有物理地址减去8000H,但是由于新程序是和MRB内的一起编码和编译的,在编译出来的机器代码中,这个偏移量是包含了MRB的512B的,因此机器代码中的偏移量是:512+(物理地址-8000H)。现在DS已经变成800H的情况下,我们要访问新程序里面的内存地址,就必须把这512B的偏移量去除。

其实,这是一个比较麻烦的问题,因为操作系统在跳转出MBR之后,肯定会频繁的读写内存,如果每次都在访问内存的时候地址减去1个512,无疑又增加了麻烦。那有没有什么解决办法呢?留给读者思考,下章会给出答案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值