【问题标题】:Confusion in Memory segmentation in x86x86中内存分割的困惑
【发布时间】:2015-02-24 15:38:52
【问题描述】:

这里我做了一个代码,用于将 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 等)的影响?

【问题讨论】:

  • 这只是一个不稳定的调试器问题,它显示的值好像是一个有符号的数字。不是,0xb800 是一个无符号数。为什么它执行后为 0 是不可猜测的。也许你还没有执行它,也许你不应该在 16 位程序中查看 32 位寄存器。
  • 0xb800 == -0x4800 在 16 位意义上。
  • 好的...但是为什么它没有进入寄存器
  • 确保它不是意外的内存引用。由于您的 gdb 输出看起来像 at&t,它通常会显示一个 $ 用于立即数。因此,您真正拥有的是使用地址0xb800 的内存内容加载bx 是可疑的。能查机器码吗?
  • “它没有” 那么它在第二行放了什么did,或者放错了地方等等?

标签: c assembly x86


【解决方案1】:

例程 _put_in_mem 有几个问题,它不保留寄存器 DS 和 SI,必须根据 16 位 x86 调用约定保留,参见 this document 的第 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_Screenhttp://en.wikipedia.org/wiki/VGA-compatible_text_mode

【讨论】:

  • 您修改了ds 段寄存器以及si 寄存器。这些寄存器应该在函数调用中保留。使用es:[bx] 避免保存和恢复dssi
  • 谢谢...那个添加命令应该是 add si, 0x1..但除此之外为什么我们使用 es 而不是 ds 段。以及为什么移动 [si],cx 不起作用
  • @sarthak 正如 chqrlie 指出的那样,最初编写的函数修改了 DS 段寄存器和 SI 通用寄存器 - 这些必须由被调用者根据 16 位 x86 调用约定保留,请参阅第 6 节agner.org/optimize/calling_conventions.pdf。 mov [si],cx 即使增加 SI 也不起作用的原因是因为它具有 16 位(一个字)的隐含操作数大小,并且 x86 支持未对齐存储,因此它将存储属性但丢弃下一个字符(然后偏移 SI+1)。
  • @amdn es:[bx] 语法不适用于 as86。你知道它的语法吗??
  • @sarthak 我没有使用 as86,但是根据这个linuxmisc.com/16-linux-development/d1a164fce061567c.htm,您需要在单独的行中指定段覆盖eseg,然后使用商店mov [bx],cx ...不确定。
【解决方案2】:

确保它不是意外的内存引用。由于您的 gdb 输出看起来像 at&t,它通常会显示 $ 表示立即数。因此,您真正拥有的是使用地址 0xb800 处的内存内容加载 bx 是可疑的。可以查机器码吗?

机器码是0xb8001e8b

确实,这是从内存中加载的机器代码。查看as86 manual,可以看到:

#      Prefix for immediate operands.
       mov ax,#1234
       Immediate value, ax becomes 1234.

因此,您应该在立即数前面加上 # 号。请注意,这适用于所有立即数。

虽然这解决了您询问的问题,但请参阅 @amdn 的回答以了解您遇到的其他问题。

【讨论】:

    猜你喜欢
    • 2020-05-23
    • 1970-01-01
    • 2021-11-16
    • 2015-07-30
    • 1970-01-01
    • 2016-09-12
    • 2013-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多