org 0x7c00解疑

问:为什么编写NASM语法的系统引导程序,汇编代码的开始总是使用“org 7c00h”?为什么有时候去掉org指令程序也能正常执行?
       答:对于这个问题,我首先在《 NASM中文手册》中找到了org指令的解释: NASM汇编编译器为bin文件格式提供了额外的操作符org,它的功能是指定程序被载入内存时的起始地址。根据书中的解释,我们很容易想到,因为引导程序将会被加载到内存0x7c00处,而且引导程序一般都被编译成bin文件格式(bin文件格式没有文件头,它的文件映像与加载到内存运行时的内存映像是一致的),似乎在引导程序中用 org 7c00h是很符合规范的,可仔细一想,似乎又不对,按《 NASM中文手册》的说法,使用 org 7c00h 将会指定程序加载入内存的起始地址为7c00h,但我们都知道,引导程序加载到内存的7c00h处是一项标准,并不是在编程时决定的,经过试验也验证了我 的想法,将org后的数字改成其他值,bois程序一样将它加载到7c00处。那么是不是可以去掉org指令呢,因为程序被加载到哪里并不关它什么事。于 是我将org指令去掉,重新编译,写入软盘引导扇区,用它引导系统。出乎预料,程序不能正常运行——没有正常打印出提示信息!到底怎么回事呢?我决定一探 究竟。以下是我的引导代码:

boot.asm
1    %include "PrintLib.inc"
2    
3     org 07c00h
4     ;org 0100h
5     mov ax,cs
6     mov ds,ax
7     mov es,ax
8    
9     mov ah,10h
10     mov al,03h
11     mov bl,01h
12     int 10h
13    
14     PrintString BootMessage,LenOfBootMessage,display_mode_2,0h,(ATTR_BLACK<<4)|ATTR_GREEN,0000h
15    
16     hlt ;停机
17    
18    
19     BootMessage: db "Dreamix Starting Please wait..."
20     LenOfBootMessage equ $-BootMessage
21    
22     times 510-($-$) db 0
23     dw 0xaa55

         boot.asm 里引用到了PrintLib.inc文件,PrintLib.inc文件中定义了一个向屏幕输出字符串信息的宏,封装了部分10h BISO子功能,文件内容如下:

PrintLib.inc
1     %ifndef PrintLib
2         %define PrintLib
3    
4    ;此宏在实模式下使用,属于BIOS子功能调用
5    
6    ;显示模式
7    %define display_mode_1 00h ;字符串只包含字符码,显示之后不更新光标位置,属性值在BL中
8    %define display_mode_2 01h ;字符串只包含字符码,显示之后更新光标位置,属性值在BL中
9    %define display_mode_3 02h ;字符串包含字符码及其属性值,显示之后不更新光标位置
10   %define display_mode_4 03h ;字符串包含字符码及其属性值,显示之后更新光标位置
11    
12    ;背景及字体格式属性值
13    %define ATTR_BLACK   0h
14    %define ATTR_BLUE    01h
15    %define ATTR_GREEN   02H
16    %define ATTR_PURPLE   03h
17    %define ATTR_RED     04h
18    %define ATTR_MAGENTA   05h
19    %define ATTR_BROWN   06h
20    %define ATTR_GREYISH   07h
21    %define ATTR_GREY    08h
22    %define ATTR_LIGHTBLUE 09h
23    %define ATTR_LIGHTGREEN 0Ah
24    %define ATTR_LIGHTPURPLE 0Bh
25    %define ATTR_LIGHTRED  0Ch
26    %define ATTR_LIGHTMAGENTA 0Dh
27    %define ATTR_YELLOW   0Eh
28    %define ATTR_WHITE  0Fh
29    
30    ;参数: 1.要显示的字符串标号  2.要显示的字符串的长度值
31    ; 3.显示模式
32    ; 4.视频页号
33    ; 5.当显示模式选3和4时为0h,否则需要背景和字符的格式属性值
34    ; 6.显示的列和行
35    %macro PrintString 6
36    
37     push ax
38     push bp
39     push cx
40     push bx
41     push dx 42 43 mov ax,%1 44 mov bp,ax 45 mov cx,%2 46 mov ax,01300h + %3 47 mov bx,%4 + %5 48 mov dx,%6 49 int 10h 50 51 pop dx 52 pop bx 53 pop cx 54 pop bp 55 pop ax 56 %endmacro 57 58 %endif 

        以上两个文件中的代码都是正确的代码,其中的boot.asm文件使用了org 7c00h.当我把org 7c00h语句去掉后,编译的引导程序不能正常打印提示信息"Dreamix Starting Please wait...".

        用WinHex查看编译好的boot.bin文件,显示如下:

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
00000000   8C C8 8E D8 8E C0 B4 10  B0 03 B3 01 CD 10 50 55   ???????.°.?.?.PU
00000010   51 53 52 B8 2C 7C 89 C5  B9 1F 00 B8 01 13 BB 02   QSR?,|‰??..?..?.
00000020   00 BA 00 00 CD 10 5A 5B  59 5D 58 F4 44 72 65 61   .?..?.Z[Y]X?Drea
00000030   6D 69 78 20 53 74 61 72  74 69 6E 67 20 50 6C 65   mix Starting Ple
00000040   61 73 65 20 77 61 69 74  2E 2E 2E 00 00 00 00 00   ase wait........
00000050   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000060   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000070   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000080   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000090   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000A0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000B0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000F0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000100   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000120   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000130   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000150   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000160   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000170   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000180   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000190   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001A0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001B0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001F0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 AA   ..............U?

         可以看到字符串被编译到文件头偏移02Ch处,此时文件映像的内容也反映了它在内存中的映像内容,当boot.bin被加载到内存7c00h处时,字符串 "Dreamix Starting Please wait..."应该位于内存7c2ch处。我们用Bochs调试未加org 7c00语句的引导程序,看看它到底是如何运行的。将boot写入软盘镜像文件,把该镜像文件挂载为Bochs虚拟机的软驱,启动Bochs调试器,先用 pb命令在物理内存0x7c00处下断点,然后用c命令执行执行到断点(关于如何用Bochs调试操作系统请读者自己查阅相关资料,此处不再赘述),此时 boot.bin已经被加载到内存0x7c00处。用disassemble命令反汇编这段内存,得到如下结果:

<bochs:3> disassemble 0x7c00 0x7c30
00007c00: (                    ): mov ax, cs                ; 8cc8
00007c02: (                    ): mov ds, ax                ; 8ed8
00007c04: (                    ): mov es, ax                ; 8ec0
00007c06: (                    ): mov ah, 0x10              ; b410
00007c08: (                    ): mov al, 0x3               ; b003
00007c0a: (                    ): mov bl, 0x1               ; b301
00007c0c: (                    ): int 0x10                  ; cd10
00007c0e: (                    ): push ax                   ; 50
00007c0f: (                    ): push bp                   ; 55
00007c10: (                    ): push cx                   ; 51
00007c11: (                    ): push bx                   ; 53
00007c12: (                    ): push dx                   ; 52
00007c13: (                    ): mov ax, 0x2c              ; b82c00
00007c16: (                    ): mov bp, ax                ; 89c5
00007c18: (                    ): mov cx, 0x1f              ; b91f00
00007c1b: (                    ): mov ax, 0x1301            ; b80113
00007c1e: (                    ): mov bx, 0x2               ; bb0200
00007c21: (                    ): mov dx, 0x0               ; ba0000
00007c24: (                    ): int 0x10                  ; cd10
00007c26: (                    ): pop dx                    ; 5a
00007c27: (                    ): pop bx                    ; 5b
00007c28: (                    ): pop cx                    ; 59
00007c29: (                    ): pop bp                    ; 5d
00007c2a: (                    ): pop ax                    ; 58
00007c2b: (                    ): hlt                       ; f4
00007c2c: (                    ): inc sp                    ; 44
00007c2d: (                    ): jb .+0x7c94               ; 7265
00007c2f: (                    ): popa                      ; 61

        实际上可执行的代码只到00007c2b,后面的是数据和填充字符,我们看到,红色标出的两条指令负责在调用10h中断之前将需要打印的字符串的首地址装入约定的寄存器bp,它装入的是字符串相对于7c00的偏移 0x2c。我们知道,实模式下内存的寻址是通过段寄存器提供的段基址和偏移地址相组合的方式,这里的 0x2c相当于偏移地址。那么段基址是多少呢?我们在Bochs下用info registers命令查看,得到以下结果:

<bochs:7> info registers
eax            0xfffaa55        268413525
ecx            0xa0001          655361
edx            0x0              0
ebx            0x0              0
esp            0xfffe           0xfffe
ebp            0x0              0x0
esi            0xa070           41072
edi            0xffde           65502
eip            0x7c00           0x7c00
eflags         0x82             130
cs             0x0              0
ss             0x0              0
ds             0x0              0
es             0x0              0
fs             0x0              0
gs             0x0              0

         一般来说bp是进行堆栈寻址的,我不知道此时bp是和哪个段寄存器配合寻址,但我们发现所有6个段寄存器的值都是0,因此无论如何(即使引导程序一开始就 将cs寄存器的内容复制到了es和ds),用bp里的偏移0x2c和段基址0x0只能访问到内存单元0x2c,而不是0x7c2c(字符串 "Dreamix Starting Please wait..."被加载大内存0x7c2c处),这就是为什么程序不能正确打印提示字符的原因。受原来编程习惯的影响,我们想当然的认为段寄存器应该存着 程序被加载到的段的基址,也就是0x7c00,这样和偏移地址0x2c结合刚好可以正确找到字符串,但事实是引导程序被加载后,所有的段寄存器都被清零 了, 不知道这是不是IBM PC兼容机启动时的规范动作?知道的朋友可以给我留言。此时,由于段寄存器的值为0x0,偏移地址就变成了绝对(物理)地址,要访问内存0x7c2c,偏移地址就必须是0x7c2c;或者编程时将段寄存器的值赋为0x7c00,偏移地址0x2c保持不变,也可以达到预期目的。

        我们来看看boot.asm中加入org 7c00h指令后的情况,按前述步骤在Bochs中反汇编代码,结果如下:

<bochs:5> disassemble 0x7c00 0x7c30
00007c00: (                    ): mov ax, cs                ; 8cc8
00007c02: (                    ): mov ds, ax                ; 8ed8
00007c04: (                    ): mov es, ax                ; 8ec0
00007c06: (                    ): mov ah, 0x10              ; b410
00007c08: (                    ): mov al, 0x3               ; b003
00007c0a: (                    ): mov bl, 0x1               ; b301
00007c0c: (                    ): int 0x10                  ; cd10
00007c0e: (                    ): push ax                   ; 50
00007c0f: (                    ): push bp                   ; 55
00007c10: (                    ): push cx                   ; 51
00007c11: (                    ): push bx                   ; 53
00007c12: (                    ): push dx                   ; 52
00007c13: (                    ): mov ax, 0x7c2c            ; b82c7c
00007c16: (                    ): mov bp, ax                ; 89c5
00007c18: (                    ): mov cx, 0x1f              ; b91f00
00007c1b: (                    ): mov ax, 0x1301            ; b80113
00007c1e: (                    ): mov bx, 0x2               ; bb0200
00007c21: (                    ): mov dx, 0x0               ; ba0000
00007c24: (                    ): int 0x10                  ; cd10
00007c26: (                    ): pop dx                    ; 5a
00007c27: (                    ): pop bx                    ; 5b
00007c28: (                    ): pop cx                    ; 59
00007c29: (                    ): pop bp                    ; 5d
00007c2a: (                    ): pop ax                    ; 58
00007c2b: (                    ): hlt                       ; f4
00007c2c: (                    ): inc sp                    ; 44
00007c2d: (                    ): jb .+0x7c94               ; 7265
00007c2f: (                    ): popa                      ; 61

        可以看到,加入org 7c00h后,偏移被编译成了 0x7c2c,再次用info registers命令查看寄存器内容,发现还是全为0x0,因此程序通过基址加偏移的方式能够正确访问到字符串。

        通过以上分析,我们可以看出org指令的作用确实是指示出程序将要被加载到内存的起始地址,这里用“指示”比用原来的“指定”更确切点。“指示”有被动的 含义,org指令本身并不能决定程序将要加载到内存的什么位置,它只是告诉编译器,我的程序在编译好后需要加载到xxx地址,所以请你在编译时帮我调整好 数据访问时的地址。用“指定”有种主动的含义,容易引起误解。另外我们看出,org指令只会在编译期影响到内存寻址指令的编译(编译器会把所有程序用到的 段内偏移地址自动加上org后跟的数值),而其自身并不会被编译成机器码。

        到此我们已经初步了解到了org指令的作用,一句话, 就是为程序中所有的内部地址引用增加一个段内偏移值(引导程序可以看做是被加载到以0为基址的段,偏移为0x7c00)。

        为了方便调试,我们常常用 NASM将 程序编译为windows能直接执行的com文件,这样能在windows下直接运行观看其效果。其实com文件格式和bin文件格式并没有本质的区别, 它们都是纯二进制文件,即文件映像的内容和内存映像的内容相同。所不同的是,com文件总是被MS的操作系统加载到段偏移0100h处,因此com的汇编 源文件中需要加入语句org 100h。

        下面我们来看看com文件的寻址情况,我们分别用windows自带的debug和boland的turbo debuger 调试运行com版本的boot程序,分析其中的异同:


图1: Debug下调试执行boot.com


图2: Turbo Debuger下调试执行boot.com

        从图1和图2中可以看出,访问字符串的偏移地址都是012ch,即org指令指定的100h与字符串文件内偏移相加而得到的值。而两种情况下段寄存器的值 都不为0,一个是0BE2,一个是5328。为什么不同的调试器在加载同一个com文件时,加载到的段各不相同;而同一个调试器每次加载同一文件总在一个 段,这其中有什么规则,我不得而知,请知道的朋友留言告诉我。我们看到,虽然两种情况下的段基址各不相同,但用基址+偏移的方式总能正确访问到内存数据, 这归功于程序总被加载到段偏移0100h处,由此引出了以下结论:

         如果一个程序使用了org xxx指令,那么该程序只能被加载到段内偏移xxx处,否则将不能正常访问段内数据,这是本篇文章得出的最重要的结论。

        最后我们来解释剩下的问题,为什么有时候去掉org指令程序也能正常执行?实际上不加org就相当于没有指示出程序加载的段内偏移值,这时编译器会默认用 0做为偏移(相当于org 0x0的情况)。这样的程序只要加载到段内偏移0x0处都能正常执行,还有一种情况就是程序中没有对内存的寻址操作,那么也不会出错,因为org指令就是 调整段内地址引用值的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值