Mat*_*hew 1 assembly nasm bootloader fasm x86-16
我一直在搞乱x86-16程序集并使用VirtualBox运行它.出于某种原因,当我从内存中读取并尝试将其作为角色打印时,我得到的结果与我期望的完全不同.但是,当我将字符硬编码为指令的一部分时,它工作正常.这是代码:
ORG 0
BITS 16
push word 0xB800 ; Address of text screen video memory in real mode for colored monitors
push cs
pop ds ; ds = cs
pop es ; es = 0xB800
jmp start
; input = di (position*2), ax (character and attributes)
putchar:
stosw
ret
; input = si (NUL-terminated string)
print:
cli
cld
.nextChar:
lodsb ; mov al, [ds:si] ; si += 1
test al, al
jz .finish
call putchar
jmp .nextChar
.finish:
sti
ret
start:
mov ah, 0x0E
mov di, 8
; should print P
mov al, byte [msg]
call putchar
; should print A
mov al, byte [msg + 1]
call putchar
; should print O
mov al, byte [msg + 2]
call putchar
; should print !
mov al, byte [msg + 3]
call putchar
; should print X
mov al, 'X'
call putchar
; should print Y
mov al, 'Y'
call putchar
cli
hlt
msg: db 'PAO!', 0
; Fill the rest of the bytes upto byte 510 with 0s
times 510 - ($ - $$) db 0
; Header
db 0x55
db 0xAA
Run Code Online (Sandbox Code Playgroud)
打印标签及其中的说明可以忽略,因为我还没有使用它,因为我一直试图打印存储在内存中的字符.我已经用FASM和NASM组装了它,并且遇到了同样的问题,这显然是我的错.
如果在汇编程序的顶部指定ORG指令ORG 0x0000,并且使用BITS 16,则通知NASM在解析代码和数据的标签时,将生成的绝对偏移量将基于ORG中指定的起始偏移量(16位代码将限制为WORD/2字节的偏移量.
如果您ORG 0x0000在开始处并start:在代码的开头放置一个标签,start则其绝对偏移量为0x0000.如果使用,ORG 0x7C00则标签start的绝对偏移量为0x7c00.这适用于任何数据标签和代码标签.
我们可以简化您的示例,以便在处理数据变量和硬编码字符时查看生成的代码中发生了什么.虽然此代码并不完全执行与代码相同的操作,但它足够接近以显示哪些有效,哪些无效.
使用ORG 0x0000的示例:
BITS 16
ORG 0x0000
start:
push cs
pop ds ; DS=CS
push 0xb800
pop es ; ES = 0xB800 (video memory)
mov ah, 0x0E ; AH = Attribute (yellow on black)
mov al, byte [msg]
mov [es:0x00], ax ; This should print letter 'P'
mov al, byte [msg+1]
mov [es:0x02], ax ; This should print letter 'A'
mov al, 'O'
mov [es:0x04], ax ; This should print letter 'O'
mov al, '!'
mov [es:0x06], ax ; This should print letter '!'
cli
hlt
msg: db "PA"
; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
Run Code Online (Sandbox Code Playgroud)
如果你在VirtualBox上运行它,前两个字符将是垃圾,同时O!应该正确显示.我将通过本答案的其余部分使用此示例.
在Virtual Box的情况下,在物理地址0x00007c00加载引导扇区后,它将有效地将FAR JMP 等效为0x0000:0x7c00.一个FAR JMP(或同等学历)不仅会跳转到指定的地址,它集CS和IP到指定的值.一个FAR JMP为0x0000:0x7c00将设置CS = 0×0000和IP = 0x7c00.
如果不熟悉16位段后面的计算:偏移对以及它们如何映射到物理地址,那么本文档是理解该概念的一个相当好的起点.从16位段获得物理内存地址的一般公式:偏移对是(segment<<4)+offset = 20-bit physical address.
由于VirtualBox使用CS:IP 0x0000:0x7c00,因此它将开始在物理地址(0x0000 << 4)+ 0x7c00 = 20位物理地址0x07c00处执行代码.请注意,并非所有环境都保证不会出现这种情况.由于segment:offset对的性质,有多种方法可以引用物理地址0x07c00.请参阅本答案末尾的部分,了解如何正确处理此问题.
假设我们使用VirtualBox并且上一节中的信息被认为是正确的,那么在进入我们的引导加载程序时CS = 0x0000和IP = 0x7c00.如果我们采用ORG 0x0000我在本答案的第一部分写的示例代码(Using )并查看反汇编信息(我将使用objdump输出),我们会看到:
objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x0000 boot.bin
00000000 <.data>:
0: 0e push cs
1: 1f pop ds
2: 68 00 b8 push 0xb800
5: 07 pop es
6: b4 0e mov ah,0xe
8: a0 24 00 mov al,ds:0x24
b: 26 a3 00 00 mov es:0x0,ax
f: a0 25 00 mov al,ds:0x25
12: 26 a3 02 00 mov es:0x2,ax
16: b0 4f mov al,0x4f
18: 26 a3 04 00 mov es:0x4,ax
1c: b0 21 mov al,0x21
1e: 26 a3 06 00 mov es:0x6,ax
22: fa cli
23: f4 hlt
24: 50 push ax ; Letter 'P'
25: 41 inc cx ; Letter 'A'
...
1fe: 55 push bp
1ff: aa stos BYTE PTR es:[di],al
Run Code Online (Sandbox Code Playgroud)
由于组装到二进制文件时ORG信息丢失,--adjust-vma=0x0000因此我使用第一列值(内存地址)从0x0000开始.我想这样做是因为我ORG 0x0000在原始的汇编代码中使用过.我还在代码中添加了一些注释,以显示我们的数据部分的位置(以及代码后面的字母P和A位置).
如果你在VirtualBox中运行这个程序,那么前两个字符就会变成乱码.那为什么呢?首先回想起VirtualBox通过将CS设置为0x0000并将IP设置为0x7c00 来达到我们的代码.此代码然后将CS复制到DS:
0: 0e push cs
1: 1f pop ds
Run Code Online (Sandbox Code Playgroud)
由于CS为零,因此DS为零.现在让我们来看看这一行:
8: a0 24 00 mov al,ds:0x24
Run Code Online (Sandbox Code Playgroud)
ds:0x24实际上是数据部分中msg变量的编码地址.偏移量0x24处的字节具有其中的值P(0x25具有A).你可能会看到事情可能出错的地方.我们的DS = 0x0000所以mov al,ds:0x24真的是一样的mov al,0x0000:0x24.这种语法无效,但我用DS0000 替换DS来表示观点.0x0000:0x24是执行时我们的代码将尝试读取我们的信件的P地方.可是等等!那是物理地址(0x0000 << 4)+ 0x24 = 0x00024.该存储器地址恰好位于中断向量表中间的存储器底部.显然这不是我们想要的!
有几种方法可以解决这个问题.最简单(也是首选的方法)是将实际的段放入DS中,而不是依赖于程序运行时CS可能是什么.由于我们将ORG设置为0x0000,因此我们需要一个数据段(DS)= 0x07c0.段:偏移对0x07c0:0x0000 =物理地址0x07c00.这是我们的引导加载程序的地址.所以我们要做的就是通过替换来修改代码:
push cs
pop ds ; DS=CS
Run Code Online (Sandbox Code Playgroud)
附:
push 0x07c0
pop ds ; DS=0x07c0
Run Code Online (Sandbox Code Playgroud)
在VirtualBox中运行时,此更改应提供正确的输出.现在让我们看看为什么.此代码未更改:
8: a0 24 00 mov al,ds:0x24
Run Code Online (Sandbox Code Playgroud)
现在执行时DS = 0x07c0.这就像说 mov al,0x07c0:0x24.0x07c0:0x24,它将转换为(0x07c0 << 4)+ 0x24 = 0x07c24的物理地址.这是我们想要的,因为我们的引导加载程序是从该位置开始由BIOS物理地放入内存中的,所以它应该正确引用我们的msg变量.
故事的道德启示?当你开始我们的程序时,你在ORG中使用什么应该在DS寄存器中有一个适用的值.我们应该明确地设置它,而不是依赖于CS中的内容.
用原始代码,前两个字符打印乱码,但最后两个没有.正如前一节所讨论的那样,有一个原因是前2个字符不能打印,但最后2个字符的作用是什么呢?
让我们O更仔细地检查第三个字符的反汇编:
16: b0 4f mov al,0x4f ; 0x4f = 'O'
Run Code Online (Sandbox Code Playgroud)
由于我们使用了立即(常量)值并将其移动到寄存器AL中,因此字符本身被编码为指令的一部分.它不依赖于通过DS寄存器访问存储器.因此,最后2个字符正确显示.
罗斯里奇建议我们使用ORG 0x7c00,你发现它有效.为什么会这样?这个解决方案是理想的吗?
使用我的第一个例子,修改ORG 0x0000到ORG 0x7c00,然后再组装起来.objdump会提供这个反汇编:
objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x7c00 boot.bin
boot.bin: file format binary
Disassembly of section .data:
00007c00 <.data>:
7c00: 0e push cs
7c01: 1f pop ds
7c02: 68 00 b8 push 0xb800
7c05: 07 pop es
7c06: b4 0e mov ah,0xe
7c08: a0 24 7c mov al,ds:0x7c24
7c0b: 26 a3 00 00 mov es:0x0,ax
7c0f: a0 25 7c mov al,ds:0x7c25
7c12: 26 a3 02 00 mov es:0x2,ax
7c16: b0 4f mov al,0x4f
7c18: 26 a3 04 00 mov es:0x4,ax
7c1c: b0 21 mov al,0x21
7c1e: 26 a3 06 00 mov es:0x6,ax
7c22: fa cli
7c23: f4 hlt
7c24: 50 push ax ; Letter 'P'
7c25: 41 inc cx ; Letter 'A'
...
7dfe: 55 push bp
7dff: aa stos BYTE PTR es:[di],al
Run Code Online (Sandbox Code Playgroud)
当VirtualBox 跳转到我们的引导加载程序时,它将CS设置为0x0000.然后我们的原始代码将CS复制到DS,因此DS = 0x0000.现在观察ORG 0x7c00指令对我们生成的代码的作用:
7c08: a0 24 7c mov al,ds:0x7c24
Run Code Online (Sandbox Code Playgroud)
请注意我们现在如何使用0x7c24的偏移量!这就像mov al,0x0000:0x7c24物理地址(0x0000 << 4)+ 0x7c24 = 0x07c24.这是加载引导加载程序的正确内存位置,是我们的msg字符串的正确位置.所以它有效.
使用ORG 0x7c00一个坏主意?不,没关系.但我们有一个微妙的问题需要应对.如果另一个Virtual PC环境或真实硬件没有使用CS:IP 0x0000:0x7c000 对我们的引导加载程序进行FAR JMP会发生什么?这个有可能.有许多具有BIOS的物理PC实际上相当于远程跳转.这也是我们已经看到的物理地址.在那种环境中,当我们的代码运行CS = 0x07c0时.如果我们使用将CS复制到DS的原始代码,DS现在也有0x07c0.现在观察在这种情况下该代码会发生什么:0x07c0:0x00000x07c00
7c08: a0 24 7c mov al,ds:0x7c24
Run Code Online (Sandbox Code Playgroud)
在这种情况下DS = 0x07c0.我们现在有一些类似于mov al,0x07c0:0x7c24程序实际运行的东西.哦,哦,看起来很糟糕.这转化为物理地址是什么?(0x07c0 << 4)+ 0x7c24 = 0x0F824.这是我们的引导加载程序之上的某个地方,它将包含计算机启动后发生的任何事情.可能是0,但应该假设它是垃圾.显然不是我们的msg字符串被加载的地方!
那么我们如何解决这个问题呢?要修改什么罗斯岭建议,并听取我以前给了有关设置明确的建议DS到我们真正想要的(不要以为段CS是正确的,然后盲目地复制到DS),我们应该把为0x0000到DS,当我们的bootloader启动如果我们使用ORG 0x7c00.所以我们可以改变这段代码:
ORG 0x7c00
start:
push cs
pop ds ; DS=CS
Run Code Online (Sandbox Code Playgroud)
至:
ORG 0x7c00
start:
xor ax, ax ; ax=0x0000
mov ds, ax ; DS=0x0000
Run Code Online (Sandbox Code Playgroud)
在这里,我们不依赖于CS中的不受信任的值.我们只是根据我们使用的ORG将DS设置为有意义的段值.您可以推送0x0000并将其弹出到DS中,就像您一直在做的那样.我更习惯于将寄存器归零并将其移至DS.
通过采用这种方法,CS可能用于访问我们的引导加载程序无关紧要,代码仍然会引用我们数据的相应内存位置.
在我在之前的StackOverflow答案中写的一般Bootloader提示中,提示#1非常重要:
- 当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x07c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.
BIOS可以使用FAR JMP(或等效)代码jmp 0x07c0:0x0000,并且一些仿真器和真实硬件就是这样做的.其他jmp 0x0000:0x7c00人像VirtualBox一样使用.
我们应该通过明确地将DS设置为我们需要的内容并将其设置为对我们在ORG指令中使用的值有意义的内容来解释这一点.
不要假设CS是我们期望的值,并且不要盲目地将CS复制到DS.明确设置DS.
如前所述ORG 0x0000,如果我们将DS适当地设置为0x07c0 ,那么您的代码可以被修复为使用您最初使用的代码.这可能看起来像:
ORG 0
BITS 16
push word 0xB800 ; Address of text screen video memory in real mode for colored monitors
push 0x07c0
pop ds ; DS=0x07c0 since we use ORG 0x0000
pop es
Run Code Online (Sandbox Code Playgroud)
或者我们可以ORG 0x7c00像这样使用:
ORG 0x7c00
BITS 16
push word 0xB800 ; Address of text screen video memory in real mode for colored monitors
push 0x0000
pop ds ; DS=0x0000 since we use ORG 0x7c00
pop es
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1116 次 |
| 最近记录: |