如何在 x86 实模式非 OS 程序集中打印字符串
How to print a string in x86 real-mode non-OS assembly
我正在尝试实现一个函数,该函数尝试在 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) != '[=12=]')
{
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>
并且 link 他们使用:
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 [=16=]xc8,%bx
0x100a: mov %bx,-0x6(%bp)
0x100d: pushw -0x6(%bp)
0x1010: call 0x1058
对于要传递给 printString 函数的指针(在 kernel.c 文件中),传递的参数是 0xc8,其中包含 0xf000ff53。因此 53,即 S 的 ASCII 码被打印在屏幕上。
我应该如何将字符串传递给 printString 函数,为什么上面的代码不起作用?
请告诉我是否需要提供更多解释。
说明
- 我在设置 DS 数据段后让你的代码工作
在
kernel.c
早期使用内联汇编注册。
- 您正在
0x1000
加载您的内核映像并构建您的代码,以便它认为它从地址 0x0
开始。因此,要使数据访问工作,您需要设置 DS 数据段寄存器以将 0x1000
添加到数据访问指令中的内存地址。
- 例如,要访问位于内核映像偏移量
0xc8
处的 "Hello World!" 字符串的第一个字节,您需要访问物理内存地址 0x10c8
。通过将 DS 设置为 0x100
,对地址 0xc8
的数据存储器的访问被转换为对物理地址 $ds*0x10 + 0xc8 == 0x10c8
的访问,即我们的地址想要。
- 阅读 x86 segmentation 的前半部分了解详情。
- 内存访问发生在
printString()
中的 *(string + i)
表达式中,因此跨过 GDB 中 printString()
循环的汇编程序级指令也应该有所帮助。你反汇编的重点是错误的代码 main()
因为你不了解 x86 分段。
- 我不确定你是如何加载的 运行
地址
0x1000
处的代码与 Qemu。在我下面的设置中,我正在通过 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) != '[=11=]')
{
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
GNUmakefile
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
我正在尝试实现一个函数,该函数尝试在 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) != '[=12=]')
{
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>
并且 link 他们使用:
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 [=16=]xc8,%bx
0x100a: mov %bx,-0x6(%bp)
0x100d: pushw -0x6(%bp)
0x1010: call 0x1058
对于要传递给 printString 函数的指针(在 kernel.c 文件中),传递的参数是 0xc8,其中包含 0xf000ff53。因此 53,即 S 的 ASCII 码被打印在屏幕上。
我应该如何将字符串传递给 printString 函数,为什么上面的代码不起作用?
请告诉我是否需要提供更多解释。
说明
- 我在设置 DS 数据段后让你的代码工作
在
kernel.c
早期使用内联汇编注册。 - 您正在
0x1000
加载您的内核映像并构建您的代码,以便它认为它从地址0x0
开始。因此,要使数据访问工作,您需要设置 DS 数据段寄存器以将0x1000
添加到数据访问指令中的内存地址。- 例如,要访问位于内核映像偏移量
0xc8
处的 "Hello World!" 字符串的第一个字节,您需要访问物理内存地址0x10c8
。通过将 DS 设置为0x100
,对地址0xc8
的数据存储器的访问被转换为对物理地址$ds*0x10 + 0xc8 == 0x10c8
的访问,即我们的地址想要。 - 阅读 x86 segmentation 的前半部分了解详情。
- 内存访问发生在
printString()
中的*(string + i)
表达式中,因此跨过 GDB 中printString()
循环的汇编程序级指令也应该有所帮助。你反汇编的重点是错误的代码main()
因为你不了解 x86 分段。
- 例如,要访问位于内核映像偏移量
- 我不确定你是如何加载的 运行
地址
0x1000
处的代码与 Qemu。在我下面的设置中,我正在通过 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) != '[=11=]')
{
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
GNUmakefile
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