【问题标题】:How to print a string in x86 real-mode non-OS assembly如何在 x86 实模式非操作系统程序集中打印字符串
【发布时间】:2015-03-02 17:18:33
【问题描述】:

我正在尝试实现一个函数,该函数试图以 QEmu 上看到的 16 位模式打印字符串:
kernel.c 文件:

void main()
{
 char* str = "Hello World!";
 printString(str); 
}

printString 函数在另一个文件 printString.c 中定义:

int printString(char* string)
{ 
 int i = 0;
 while (*(string + i) != '\0')
   {
    char al = *(string + i);
    char ah = 0xe;
    int ax = ah * 256 + al;
    interrupt(0x10,ax,0,0,0);
    i++;
   }
 return i;
}

中断函数调用 BIOS 中断,函数的第一个参数给出,其他参数分别指定 ax、bx、cx 和 dx 寄存器的内容。这是代码

.global _interrupt
_interrupt:
push bp
mov bp, sp
push si
push ds
mov ax, #0x100
mov ds, ax
mov ax, [bp + 0x4]
mov si, #intr
mov [si + 1], al
pop ds
mov ax, [bp + 0x6]
mov bx, [bp + 0x8]
mov cx, [bp + 0xa]
mov dx, [bp + 0xc]
intr: int #0x0
pop si
pop bp
ret

我使用以下命令编译 .c 文件:

bcc -ansi -c -o <name.o> <name.c>

并使用以下方法链接它们:

ld86 -o kernel -d kernel.o interrupt.o printString.o

程序在屏幕上打印“S”,而不是打印“Hello World!”。我在地址 0x1000 加载了 kernel.c 文件。看到代码的反汇编:

0x1000: push   %bp
0x1001: mov    %sp,%bp
0x1003: push   %di
0x1004: push   %si
0x1005: dec    %sp
0x1006: dec    %sp
0x1007: mov    $0xc8,%bx
0x100a: mov    %bx,-0x6(%bp)
0x100d: pushw  -0x6(%bp)
0x1010: call   0x1058 

对于要传递给 printString 函数的指针(在 kernel.c 文件中),传递的参数是 0xc8,其中包含 0xf000ff53。因此,S 的 ASCII 码 53 会打印在屏幕上。
我应该如何将字符串传递给 printString 函数,为什么上面的代码不起作用?
如果需要更多解释,请告诉我。

【问题讨论】:

  • 听起来ds 设置不正确。
  • 天啊! 16位汇编!!!带回 90 年代的糟糕回忆!
  • @Jester 你对 ds 电阻器是正确的,并将其设置为 0x100 使其工作。但我不明白为什么这会奏效。即使我将 ds 寄存器更改为 0x100,上面的 mov 指令,即 mov $c8, %bx 也没有改变,写入 %bx 寄存器的值又是 0xc8。这些代码有什么区别?

标签: c assembly x86 osdev


【解决方案1】:

说明

  • 设置 DS 数据段后,您的代码可以正常工作 使用内联汇编在 kernel.c 尽早注册。
  • 您在0x1000 加载内核映像并构建您的代码,以便它认为它从地址0x0 开始。因此,要使数据访问正常工作,您需要设置 DS 数据段寄存器以将0x1000 添加到数据访问指令中的内存地址。
    • 例如,访问“Hello World!”的第一个字节位于内核映像偏移量0xc8的字符串,需要访问物理内存地址0x10c8。通过将DS设置为0x100,对地址0xc8的数据存储器的访问被转换为对物理地址$ds*0x10 + 0xc8 == 0x10c8的访问,即我们想要的地址。
    • 阅读前半部分x86 segmentation了解详情。
    • 内存访问发生在printString() 中的*(string + i) 表达式中,因此跳过GDB 中printString() 循环的汇编程序级指令也应该有所帮助。您通过反汇编 main() 专注于错误的代码,因为您不了解 x86 分段。
  • 我不确定您是如何加载和运行 使用 Qemu 的地址 0x1000 的代码。在下面的设置中,我通过 GDB 加载代码,并有一个小的引导扇区,它只是跳转到该地址。

kernel.c

void main()
{
#asm
    mov ax, #0x100
    mov ds, ax
#endasm
    char* str = "Hello World!";
    printString(str); 
}

printString.c

int printString(char* string)
{ 
 int i = 0;
 while (*(string + i) != '\0')
   {
    char al = *(string + i);
    char ah = 0xe;
    int ax = ah * 256 + al;
    interrupt(0x10,ax,0,0,0);
    i++;
   }
 return i;
}

interrupt.asm

.global _interrupt
_interrupt:
push bp
mov bp, sp
push si
mov ax, [bp + 0x4]
mov si, #intr
mov [si + 1], al
mov ax, [bp + 0x6]
mov bx, [bp + 0x8]
mov cx, [bp + 0xa]
mov dx, [bp + 0xc]
intr: int #0x0
pop si
pop bp
ret

boot.asm

.global _main
_main:
mov ax, #0x1000
jmp ax

引导扇区创建

#!/usr/bin/env python3
# Create an x86 boot sector
# Pad file to 512 bytes, insert 0x55, 0xaa at end of file

import sys
import os

def program_name():
    return os.path.basename(sys.argv[0])

def print_usage_exit():
    sys.stderr.write('usage: %s IN_FILENAME OUT_FILENAME\n' % (program_name(),))
    sys.exit(2)

def main(args):
    try:
        (in_filename, out_filename) = args
    except ValueError:
        print_usage_exit()

    buf = bytearray(512)
    f = open(in_filename, 'rb')
    f.readinto(buf)
    buf[510] = 0x55
    buf[511] = 0xaa
    fout = open(out_filename, 'wb')
    fout.write(buf)
    fout.close()

if __name__ == '__main__':
    main(sys.argv[1:])

kernel.gdb

set confirm 0
set pagination 0
set architecture i8086

target remote localhost:1234
set disassemble-next-line 1

monitor system_reset
delete

restore kernel binary 0x1000
continue

GNUmake 文件

DERVED_FILES := kernel kernel.o interrupt.o printString.o boot boot.o bootsect

.PHONY: all
all: boot kernel

bootsect: boot
    ./bootsector-create $< $@

boot: boot.o
    ld86 -o $@ -s -d $+

kernel: kernel.o interrupt.o printString.o
    ld86 -o $@ -s -d $+

%.o: %.c
    bcc -ansi -c -o $@ $<

%.o: %.asm
    as86 -o $@ $<

.PHONY: clean
clean:
    rm -f $(DERVED_FILES)

.PHONY: emulator
emulator: bootsect
    qemu-system-x86_64 -s -S bootsect

.PHONY: gdb
gdb:
    gdb -q -x kernel.gdb

示例会话

$ make emulator
(In a separate terminal)
$ make gdb

【讨论】:

  • 谢谢....但我不明白这是如何工作的。即使我将 ds 寄存器更改为 0x100,上面的 mov 指令,即mov $c8, %bx 并没有改变,写入 %bx 寄存器的值又是 0xc8。这些代码有什么区别?
  • @sarthak 请参阅答案中有关分段如何工作的链接。简而言之,每个内存访问都有一个关联的段,用于地址计算。如果您更改段,计算的地址也会更改。
  • @sarthak,阅读wiki.osdev.org/Segmentation 的前半部分如果您了解 x86 汇编,那么在 GDB 中的汇编级别跳过printString() 循环也应该有所帮助。
  • @sarthak,我已经用更详细的解释更新了我的答案。希望它有所帮助:)
猜你喜欢
  • 1970-01-01
  • 2015-02-20
  • 2010-12-04
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
  • 2021-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多