x86 中内存分段的混乱
Confusion in Memory segmentation in x86
这里我做了一个ASCII字符写入VGA内存的代码:
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4]
mov si, [bp + 6]
mov bx, 0xb800
mov ds, bx
mov [si], cx
add bx, 0x1
mov cx, 0x7
mov [si], cx
pop bp
ret
这是通过如下所示的 kernel.c 文件调用的:
void main()
{
extern void put_in_mem();
char c = 'e';
put_in_mem(c, 0xA0);
}
上面的代码本想在QEmu的第二行开头打印"e",但是并没有。我尝试使用 GDB 进行调试,发现命令
mov bx, 0xb800
在 GDB 中变成了
mov -0x4800,%bx
这条命令后ebx中的值为0x0。
为什么 bx 寄存器中没有加载值?
此外,我认为移动指令使用 ds 寄存器作为它们的段基址,并从 ds 的内容中偏移所有地址。所以根据这个推理,我假设当
mov [si], cx
指令将cx寄存器的内容放在地址0xb8a0。这个对吗? mov 指令是否也会受到任何其他段寄存器(如 cs、es 等)的影响?
例程 _put_in_mem 有几个问题,它不保留必须根据 16 位 x86 调用约定保留的寄存器 DS 和 SI,请参阅 [=13= 的第 6 节], 而且它没有正确存储字符和属性字节。
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4]
mov si, [bp + 6] # si must be preserved across function calls
mov bx, 0xb800
mov ds, bx # ds must be preserved across function calls
mov [si], cx
add bx, 0x1
mov cx, 0x7 # low byte 0x7, upper byte = character = 0x00
mov [si], cx # si has not changed... overwriting with 0x0007
pop bp
ret
这是一种修复方法:
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4] # cx = xxcc, where cc is ASCII character
mov ch, 0x7 # attribute byte: light-grey on black
mov bx, [bp + 6] # bx = offset into VGA video buffer
mov ax, 0xb800 # VGA video buffer base at 0xb800 x 16
mov es, ax # use ES segment register instead of DS
mov es:[bx], cx # store ASCII at es:[bx], attribute at es:[bx+1]
pop bp
ret
VGA attribute
字节跟在文本模式下的字符字节之后。 0x7 的属性表示在黑色背景上显示为浅灰色...请参阅 http://wiki.osdev.org/Printing_To_Screen and http://en.wikipedia.org/wiki/VGA-compatible_text_mode
确保它不是意外的内存引用。由于您的 gdb 输出看起来像 at&t,因此它通常会显示一个 $ 表示立即数。因此,您真正拥有的是用地址 0xb800 处的内存内容加载 bx 是值得怀疑的。你能检查一下机器码吗?
The machine code is 0xb8001e8b
的确,这是从内存加载的机器代码。查看as86 manual,可以看到:
# Prefix for immediate operands.
mov ax,#1234
Immediate value, ax becomes 1234.
因此,您应该在立即数前加上# 符号。请注意,这适用于所有立即数。
虽然这解决了您提出的问题,但请参阅@amdn 对您遇到的其他问题的回答。
这里我做了一个ASCII字符写入VGA内存的代码:
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4]
mov si, [bp + 6]
mov bx, 0xb800
mov ds, bx
mov [si], cx
add bx, 0x1
mov cx, 0x7
mov [si], cx
pop bp
ret
这是通过如下所示的 kernel.c 文件调用的:
void main()
{
extern void put_in_mem();
char c = 'e';
put_in_mem(c, 0xA0);
}
上面的代码本想在QEmu的第二行开头打印"e",但是并没有。我尝试使用 GDB 进行调试,发现命令
mov bx, 0xb800
在 GDB 中变成了
mov -0x4800,%bx
这条命令后ebx中的值为0x0。
为什么 bx 寄存器中没有加载值?
此外,我认为移动指令使用 ds 寄存器作为它们的段基址,并从 ds 的内容中偏移所有地址。所以根据这个推理,我假设当
mov [si], cx
指令将cx寄存器的内容放在地址0xb8a0。这个对吗? mov 指令是否也会受到任何其他段寄存器(如 cs、es 等)的影响?
例程 _put_in_mem 有几个问题,它不保留必须根据 16 位 x86 调用约定保留的寄存器 DS 和 SI,请参阅 [=13= 的第 6 节], 而且它没有正确存储字符和属性字节。
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4]
mov si, [bp + 6] # si must be preserved across function calls
mov bx, 0xb800
mov ds, bx # ds must be preserved across function calls
mov [si], cx
add bx, 0x1
mov cx, 0x7 # low byte 0x7, upper byte = character = 0x00
mov [si], cx # si has not changed... overwriting with 0x0007
pop bp
ret
这是一种修复方法:
.global _put_in_mem
_put_in_mem:
push bp
mov bp, sp
mov cx, [bp + 4] # cx = xxcc, where cc is ASCII character
mov ch, 0x7 # attribute byte: light-grey on black
mov bx, [bp + 6] # bx = offset into VGA video buffer
mov ax, 0xb800 # VGA video buffer base at 0xb800 x 16
mov es, ax # use ES segment register instead of DS
mov es:[bx], cx # store ASCII at es:[bx], attribute at es:[bx+1]
pop bp
ret
VGA attribute
字节跟在文本模式下的字符字节之后。 0x7 的属性表示在黑色背景上显示为浅灰色...请参阅 http://wiki.osdev.org/Printing_To_Screen and http://en.wikipedia.org/wiki/VGA-compatible_text_mode
确保它不是意外的内存引用。由于您的 gdb 输出看起来像 at&t,因此它通常会显示一个 $ 表示立即数。因此,您真正拥有的是用地址 0xb800 处的内存内容加载 bx 是值得怀疑的。你能检查一下机器码吗?
The machine code is 0xb8001e8b
的确,这是从内存加载的机器代码。查看as86 manual,可以看到:
# Prefix for immediate operands.
mov ax,#1234
Immediate value, ax becomes 1234.
因此,您应该在立即数前加上# 符号。请注意,这适用于所有立即数。
虽然这解决了您提出的问题,但请参阅@amdn 对您遇到的其他问题的回答。