无法从 kernel.s 打印,即使它已加载到内存中
Can't print from kernel.s even though its loaded onto Memory
我正在尝试通过编写自己的引导加载程序将内核加载到内存中。我已经能够成功地将内核加载到内存中——我知道因为我使用了 bochs 调试器将断点设置为 0x7c00 并单步执行,系统确实跳入了内核。问题是跳入内核后 none 的打印语句(在 kernel.s 中)起作用。这在终端上表明内核已加载到内存中。
这是 bootblock.s 文件(大部分相关代码位于标签 booter
:
# bootblock.s
# Empty boot block file
# .equ symbol, expression
# These directive set the value of the symbol to the expression
.equ BOOT_SEGMENT,0x07c0
.equ DISPLAY_SEGMENT,0xb800
.equ KERNEL_LOCATION, 0x1000
.equ STACK_SP, 0xffff
.equ STACK_SS, 0x0
.text # Code segment
.globl _start # The entry point must be global
.code16 # Real mode
_start:
###MAKE BOOTABLE###
#. = _start + 510
#.byte = 0x55
#.byte = 0xaa
jmp booter
os_size:
#Place where createimage writes the OS size
.word 0
.word 0
print:
movw $BOOT_SEGMENT,%ax
movw %ax,%ds
print_loop:
lodsb
cmpb [=11=],%al
je print_done
movb ,%ah
movl [=11=]x0002,%ebx
int [=11=]x10
jmp print_loop
print_done:
retw
booter:
###SET UP STACK###
#Allocating the stack
movw $STACK_SS, %ax
movw %ax, %ss
movw $STACK_SP, %sp
movl $allocating, %esi
call print
movl $done, %esi
call print
#Resetting the disk drive, setting %dl and calling int 0x13
#movb [=11=]x0, %ah
#movb [=11=]x0, %dl
#int [=11=]x13
movl $bootblock_test, %esi
call print
movl $hellostring, %esi
call print
###LOAD KERNEL###
movl $loadingkernel, %esi
call print
#Number of sectors to read
#movb [=11=]x24, %al
#movb [=11=]x80, %al
movb [=11=]x08, %al
movb [=11=]x02, %ah
#track number
#movb [=11=]x00, %ch
#which sector to read from (sector 2 where kernel should be)
movb [=11=]x02, %cl
#set up head number
movb [=11=]x0, %dh
#Set the drive number to 0x0 (floppy)
movb [=11=]x0, %dl
#Time to set es:bx to read from the correct place (0:1000)
movw [=11=]x0100, %bx
movw %bx, %es
movw [=11=]x0, %bx
#movw [=11=]x0, %ax
#Setting %ah = 2 and calling int 0x13 (read sector)
int [=11=]x13
movl $done, %esi
call print
#Booting up at 0x07c0
#movw $BOOT_SEGMENT, %ax
#movw %ax, %ds
#movl $bootmessage, %esi
#call print
#%dh/%ch control head numbers, setting them to 0
#movb [=11=]x0, %dh
#movb [=11=]x0, %ch
#movw %ds,
###INVOKE KERNEL###
#Kernel jump
movl $readymessage, %esi
call print
#Setting %ds = 0x7c0
movw [=11=]x0100, %ax
movw %ax, %ds
#Time to set es:bx to read from the correct place (0:1000)
movw [=11=]x0100, %bx
movw %bx, %es
movw [=11=]x0, %bx
movl [=11=]x1000, %ax
jmp %ax
mov [=11=]x0, %ax
#If any errors, message will be displayed here
movl $errormessage, %esi
call print
forever:
jmp forever
#Error handling
error:
movl $errormessage, %esi
call print
# messages
mystring:
.asciz "test.\n\r"
bootblock_test:
.asciz "\nBootblock Test\n\r"
hellostring:
.asciz "How are you today?\n\r"
myname:
.asciz "Welcome\n\r"
loadingkernel:
.asciz "Loading Kernel...\n\r"
done:
.asciz "Done!\n\r"
bootmessage:
.asciz "Booting up...\n\r"
readymessage:
.asciz "Sliding into yo Kernel like... \n\r"
errormessage:
.asciz "Something went terribly wrong...\n\r"
rebootmessage:
.asciz "Press any key to reboot the OS!\n\r"
allocating:
.asciz "Allocating Stack...\n\r"
这是 kernel.s 文件:
.data # Data segment
# Some strings
kernel:
.asciz "[Kernel]-> "
testing:
.asciz "Running a trivial test... "
works:
.asciz "Seems Ok. Now go get some sleep :)."
not:
.asciz "*Failed*"
# 'Newline' string ('carriage return', 'linefeed', '[=12=]')
newline:
.byte 10
.byte 13
.byte 0
# An integer
result:
.word 1000
.text # Code segment
.code16 # Real mode
.globl _start # The entry point must be global
#
# The first instruction to execute in a program is called the entry
# point. The linker expects to find the entry point in the "symbol" _start
# (with underscore).
#
_start:
pushw %bp # Setup stack frame
movw %sp,%bp
pushw $newline
call displayString # Print messages
pushw $kernel
call displayString
pushw $testing
call displayString
pushw 00
call trivialTest # trivialTest(1000)
addw ,%sp # Pop newline, kernel, testing, and '1000'
cmpw %ax,result
jne .L6 # If (trivialTest(1000) != 1000) goto L6
pushw $works
jmp .L12
.L6: # Test failed
pushw $not
.L12:
call displayString # Print ok/failed message
addw ,%sp
pushw $newline
call displayString
addw ,%sp
.L8: # Loop forever
jmp .L8
#
# int trivialTest(n)
# {
# if (n > 0) {
# trivialTest(n-1);
# }
# return n;
# }
trivialTest:
pushw %bp # Setup stack frame
movw %sp,%bp
movw 4(%bp),%ax # Move argument to ax
testw %ax,%ax # Logical compare (sets SF, ZF and PF)
jg .L2 # if (argument > 0) goto L2
xorw %ax,%ax # else return 0
popw %bp
retw
.L2:
decw %ax
pushw %ax
call trivialTest # trivialTest(argument - 1)
# (Recursive calls until argument == 0)
addw ,%sp # Pop argument
incw %ax
popw %bp
retw # Return (argument in ax)
displayString:
pushw %bp # Setup stack frame
movw %sp,%bp
pushw %ax # Save ax, bx, cx, si, es
pushw %bx
pushw %cx
pushw %si
pushw %es
movw %ds, %ax # Make sure ES points to the right
movw %ax, %es # segment
movw 4(%bp),%cx # Move string adr to cx
movw %cx, %si
loop:
lodsb # Load character to write (c) into al,
# and increment si
cmpb [=12=], %al
jz done # if (c == '[=12=]') exit loop
movb ,%ah # else print c
movw [=12=]x0002,%bx
# int 0x10 sends a character to the display
# ah = 0xe (14)
# al = character to write
# bh = active page number (we use 0x00)
# bl = foreground color (we use 0x02)
int [=12=]x10
jmp loop
done:
popw %es # Restore saved registers
popw %si
popw %cx
popw %bx
popw %ax
popw %bp
retw # Return to caller
再一次,我在调试器中检查到内核正在加载到内存 (0x1000) 中。我认为问题出在 setting/using bootblock.s 中的某些寄存器(主要是:ds
,ax
),但我不确定它是什么。
这些观察结果可能对您有所帮助:
尝试加载内核时,您应该取消注释设置轨道号的行。
在displayString例程中不需要设置ES寄存器。只需一个正确的 DS。
您正试图通过近距离跳转到内核! (jmp %ax
) 那是行不通的。你需要一个段间跳跃。使用 jmp [=11=]x0000:[=11=]x0100
你应该避免使用像 %esi 这样的 32 位寄存器来传递地址,因为这是 16 位代码。
代码有很多问题。大多数似乎适用于大多数 16 位 OS 引导加载程序的基本问题都可以在我最近的 (类似类型的问题)中找到。它涵盖了使用 SS/SP/ES/DS/CS 寄存器需要注意的事项,8086/8088 上的堆栈性能问题,以及一些需要注意的旧 buggy 8086/8088 问题。
你的代码有一个具体问题 - 如果你将 运行 你的代码在 8086/8088 系统或模拟器(不是 286、386 等)上,那么你应该坚持使用 16 位寄存器,因为32 位寄存器不可用。您的代码使用 ESI 和 EBX 寄存器(32 位)。你应该使用 SI 和 BX.
此代码中的主要问题是,在大多数情况下,内核是从磁盘读取的,但碰巧读取的扇区少于内核映像实际占用的扇区。这恰好导致本应由内核打印的变量未被加载。
我原本假设内核看起来很小,从磁盘读取 8 个扇区对于提供的示例代码来说绰绰有余:
#Number of sectors to read
movb [=10=]x08, %al
我发现楼主在做作业,有人发布了一些解决问题的关键信息。我已经分叉了 git project 以供参考。一些关键信息是正在使用的 Makefile 类型(略有不同)和为作业提供的名为 createimage
的程序。
Makefile 足够接近 类似于:
# Makefile for the OS projects.
# Best viewed with tabs set to 4 spaces.
CC = gcc -Wall -Wextra -std=c99 -g
LD = ld
# Where to locate the kernel in memory
KERNEL_ADDR = 0x1000
# Compiler flags
#-fno-builtin: Don't recognize builtin functions that do not begin with
# '__builtin_' as prefix.
#
#-fomit-frame-pointer: Don't keep the frame pointer in a register for
# functions that don't need one.
#
#-make-program-do-what-i-want-it-to-do:
# Turn on all friendly compiler flags.
#
#-O2: Turn on all optional optimizations except for loop unrolling
# and function inlining.
#
#-c: Compile or assemble the source files, but do not link.
#
#-Wall: All of the `-W' options combined (all warnings on)
CCOPTS = -c -fomit-frame-pointer -O2 -fno-builtin
# Linker flags
#-nostartfiles: Do not use the standard system startup files when linking.
#
#-nostdlib: Don't use the standard system libraries and startup files when
# linking. Only the files you specify will be passed to the linker.
#
#-Ttext X: Use X as the starting address for the text segment of the output
# file.
LDOPTS = -nostartfiles -nostdlib -Ttext
# Makefile targets
all: bootblock createimage kernel image boch_image
kernel: kernel.o
$(LD) $(LDOPTS) $(KERNEL_ADDR) -o kernel $<
bootblock: bootblock.o
$(LD) $(LDOPTS) 0x0 -o bootblock $<
createimage: createimage.o
$(CC) -o createimage $<
# Create an image to put on the floppy
image: bootblock createimage kernel
./createimage ./bootblock ./kernel
# Put the image on the floppy (these two stages are independent, as both
# vmware and bochs can run using only the image file stored on the harddisk)
#boot: image
# cat ./image > /dev/sda
#write image to boch disk image
boch_image: image
dd if=image of=bochs.img conv=notrunc
# Clean up!
clean:
rm -f *.o
rm -f createimage image bootblock kernel
# No, really, clean up!
distclean: clean
rm -f *~
rm -f \#*
rm -f *.bak
rm -f bochsout.txt
# How to compile a C file
%.o:%.c
$(CC) $(CCOPTS) $<
# How to assemble
%.o:%.s
$(CC) $(CCOPTS) $<
# How to produce assembler input from a C file
%.s:%.c
$(CC) $(CCOPTS) -S $<
根据原始发布者的后续评论,他们正在构建 elf_i386 二进制文件(Linux ELF 格式用于 32 位)。上面的 Makefile 表明内核被构建为 ELF 映像,然后放置在磁盘上。 ELF 格式为每个部分添加了相当数量的填充,因此假设从磁盘读取 8 个扇区 可能还不够。 Makefile 中没有引用 objcopy
或转换为平面二进制格式。我发现给定的 createimage 程序从 32 位 ELF 格式中提取内核映像,并生成输出,说明引导加载程序需要读取多少扇区才能获得整个内核。 createimage
代码是:
/* createimage.c -- create a bootable image in 16 real mode from several elf file
*/
#include <stdio.h>
#include <stdlib.h>
#include "createimage.h"
int file_process(FILE *elf_file, FILE *image, char *elf_filename);
long byte_get (unsigned char *field, int size);
int main (int argc, char ** argv)
{
//here hasn't check the magic numbers of elf
if (argc != 3) {
printf("USAGE:%s bootblock kernel\n", argv[0]);
return -1;
}
FILE *bootblock, *kernel, *image;
if ((bootblock = fopen (argv[1], "rb")) == NULL) {
printf("can't open %s\n", argv[1]);
return -1;
}
if ((image = fopen ("image", "wb")) == NULL) {
printf("can't open image!\n");
return -1;
}
if (file_process(bootblock, image, argv[1])) {
printf("process bootblock failed\n");
return -1;
}
if ((kernel = fopen (argv[2], "rb")) == NULL) {
printf("can't open %s\n", argv[2]);
return -1;
}
if (file_process(kernel, image, argv[2])) {
printf("process kernel failed\n");
return -1;
}
fclose(bootblock);
fclose(kernel);
fclose(image);
return 0;
}
long byte_get (unsigned char *field, int size)
{
switch (size)
{
case 1:
return *field;
case 2:
return ((unsigned int) (field[0])) | (((unsigned int) (field[1])) << 8);
case 4:
return ((unsigned long) (field[0]))
| (((unsigned long) (field[1])) << 8)
| (((unsigned long) (field[2])) << 16)
| (((unsigned long) (field[3])) << 24);
default:
printf("byte_get error\n");
return -1;
}
}
/* read information from elf file, and write LOAD segment to image file
*
* note: the structure in file is not aligned, we just read it from file byte
* by byte
*/
int file_process(FILE *elf_file, FILE *image, char *elf_filename)
{
unsigned int header_sz, pheader_sz;
unsigned long phoff;
unsigned int p_offset;
unsigned int p_filesz;
unsigned int p_memsz;
elf_header header;
elf_program_header pheader;
header_sz = sizeof (elf_header);
pheader_sz = sizeof(elf_program_header);
printf("processing %s:\n", elf_filename);
printf("header size is: %d\n", header_sz);
printf("program header size is: %d\n", pheader_sz);
if (header_sz != fread(&header, 1, header_sz, elf_file)) {
printf("read error!\n");
return -1;
}
//get program header's offset
phoff = byte_get(header.e_phoff, sizeof(header.e_phoff));
printf("Program header table offset in file is :\t %u\n", phoff);
if (fseek(elf_file, phoff, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
//printf("the current position: %d\n", ftell(elf_file));
if (pheader_sz != fread(&pheader, 1, pheader_sz, elf_file)) {
printf("read error at line %d!\n", __LINE__);
return -1;
}
//get the LOAD segment's offset, filesz, mensz
p_offset = byte_get(pheader.p_offset, sizeof(pheader.p_offset));
p_filesz = byte_get(pheader.p_filesz, sizeof(pheader.p_filesz));
p_memsz = byte_get(pheader.p_memsz, sizeof(pheader.p_memsz));
printf("p_offset: 0x%x\tp_filesz: 0x%x\tp_memsz: 0x%x\t\n", p_offset, p_filesz, p_memsz);
//write elf's LOAD segment to image, and pad to 512 bytes(1 sector)
char *buffer;
const unsigned int sector_sz = 512;
const char MBR_signature[] = {0x55, 0xaa};
unsigned int n_sector;
unsigned int n_byte;
if (p_memsz % sector_sz != 0)
n_sector = p_memsz / sector_sz + 1;
else
n_sector = p_memsz / sector_sz;
n_byte = n_sector * sector_sz;
if (!(buffer = (char *)calloc(n_byte, sizeof(char)))) {
printf("malloc buffer failed! at line %d\n", __LINE__);
return -1;
}
if (fseek(elf_file, p_offset, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
if (p_filesz != fread(buffer, 1, p_filesz, elf_file)) {
printf("read error at line %d!\n", __LINE__);
return -1;
}
if (n_byte != fwrite(buffer, 1, n_byte, image)) {
printf("write error at line %d!\n", __LINE__);
return -1;
}
//write MBR signature to image, which is 2 bytes
if (fseek(image, 510, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
if (2 != fwrite(MBR_signature, 1, 2, image)) {
printf("write error at line %d!\n", __LINE__);
return -1;
}
printf("write image:\n%d sectors,\t%d bytes\n", n_sector, n_byte);
return 0;
}
重要的是,在提取代码和数据部分后(将与 dd
放在磁盘映像中),它在底部提供类似于以下的输出:
processing ./kernel:
header size is: 52
program header size is: 32
Program header table offset in file is : 52
p_offset: 0x54 p_filesz: 0x10db p_memsz: 0x10db
write image:
9 sectors, 4608 bytes
dd if=image of=bochs.img conv=notrunc
这里有重要信息 9 sectors, 4608 bytes
。这表示内核在扇区中有多大。原始海报的代码必须确保他们在加载内核时读取了那么多扇区。所以简单的解决方法是将代码更改为:
#Number of sectors to read should match output from createimage
movb [=14=]x09, %al
我正在尝试通过编写自己的引导加载程序将内核加载到内存中。我已经能够成功地将内核加载到内存中——我知道因为我使用了 bochs 调试器将断点设置为 0x7c00 并单步执行,系统确实跳入了内核。问题是跳入内核后 none 的打印语句(在 kernel.s 中)起作用。这在终端上表明内核已加载到内存中。
这是 bootblock.s 文件(大部分相关代码位于标签 booter
:
# bootblock.s
# Empty boot block file
# .equ symbol, expression
# These directive set the value of the symbol to the expression
.equ BOOT_SEGMENT,0x07c0
.equ DISPLAY_SEGMENT,0xb800
.equ KERNEL_LOCATION, 0x1000
.equ STACK_SP, 0xffff
.equ STACK_SS, 0x0
.text # Code segment
.globl _start # The entry point must be global
.code16 # Real mode
_start:
###MAKE BOOTABLE###
#. = _start + 510
#.byte = 0x55
#.byte = 0xaa
jmp booter
os_size:
#Place where createimage writes the OS size
.word 0
.word 0
print:
movw $BOOT_SEGMENT,%ax
movw %ax,%ds
print_loop:
lodsb
cmpb [=11=],%al
je print_done
movb ,%ah
movl [=11=]x0002,%ebx
int [=11=]x10
jmp print_loop
print_done:
retw
booter:
###SET UP STACK###
#Allocating the stack
movw $STACK_SS, %ax
movw %ax, %ss
movw $STACK_SP, %sp
movl $allocating, %esi
call print
movl $done, %esi
call print
#Resetting the disk drive, setting %dl and calling int 0x13
#movb [=11=]x0, %ah
#movb [=11=]x0, %dl
#int [=11=]x13
movl $bootblock_test, %esi
call print
movl $hellostring, %esi
call print
###LOAD KERNEL###
movl $loadingkernel, %esi
call print
#Number of sectors to read
#movb [=11=]x24, %al
#movb [=11=]x80, %al
movb [=11=]x08, %al
movb [=11=]x02, %ah
#track number
#movb [=11=]x00, %ch
#which sector to read from (sector 2 where kernel should be)
movb [=11=]x02, %cl
#set up head number
movb [=11=]x0, %dh
#Set the drive number to 0x0 (floppy)
movb [=11=]x0, %dl
#Time to set es:bx to read from the correct place (0:1000)
movw [=11=]x0100, %bx
movw %bx, %es
movw [=11=]x0, %bx
#movw [=11=]x0, %ax
#Setting %ah = 2 and calling int 0x13 (read sector)
int [=11=]x13
movl $done, %esi
call print
#Booting up at 0x07c0
#movw $BOOT_SEGMENT, %ax
#movw %ax, %ds
#movl $bootmessage, %esi
#call print
#%dh/%ch control head numbers, setting them to 0
#movb [=11=]x0, %dh
#movb [=11=]x0, %ch
#movw %ds,
###INVOKE KERNEL###
#Kernel jump
movl $readymessage, %esi
call print
#Setting %ds = 0x7c0
movw [=11=]x0100, %ax
movw %ax, %ds
#Time to set es:bx to read from the correct place (0:1000)
movw [=11=]x0100, %bx
movw %bx, %es
movw [=11=]x0, %bx
movl [=11=]x1000, %ax
jmp %ax
mov [=11=]x0, %ax
#If any errors, message will be displayed here
movl $errormessage, %esi
call print
forever:
jmp forever
#Error handling
error:
movl $errormessage, %esi
call print
# messages
mystring:
.asciz "test.\n\r"
bootblock_test:
.asciz "\nBootblock Test\n\r"
hellostring:
.asciz "How are you today?\n\r"
myname:
.asciz "Welcome\n\r"
loadingkernel:
.asciz "Loading Kernel...\n\r"
done:
.asciz "Done!\n\r"
bootmessage:
.asciz "Booting up...\n\r"
readymessage:
.asciz "Sliding into yo Kernel like... \n\r"
errormessage:
.asciz "Something went terribly wrong...\n\r"
rebootmessage:
.asciz "Press any key to reboot the OS!\n\r"
allocating:
.asciz "Allocating Stack...\n\r"
这是 kernel.s 文件:
.data # Data segment
# Some strings
kernel:
.asciz "[Kernel]-> "
testing:
.asciz "Running a trivial test... "
works:
.asciz "Seems Ok. Now go get some sleep :)."
not:
.asciz "*Failed*"
# 'Newline' string ('carriage return', 'linefeed', '[=12=]')
newline:
.byte 10
.byte 13
.byte 0
# An integer
result:
.word 1000
.text # Code segment
.code16 # Real mode
.globl _start # The entry point must be global
#
# The first instruction to execute in a program is called the entry
# point. The linker expects to find the entry point in the "symbol" _start
# (with underscore).
#
_start:
pushw %bp # Setup stack frame
movw %sp,%bp
pushw $newline
call displayString # Print messages
pushw $kernel
call displayString
pushw $testing
call displayString
pushw 00
call trivialTest # trivialTest(1000)
addw ,%sp # Pop newline, kernel, testing, and '1000'
cmpw %ax,result
jne .L6 # If (trivialTest(1000) != 1000) goto L6
pushw $works
jmp .L12
.L6: # Test failed
pushw $not
.L12:
call displayString # Print ok/failed message
addw ,%sp
pushw $newline
call displayString
addw ,%sp
.L8: # Loop forever
jmp .L8
#
# int trivialTest(n)
# {
# if (n > 0) {
# trivialTest(n-1);
# }
# return n;
# }
trivialTest:
pushw %bp # Setup stack frame
movw %sp,%bp
movw 4(%bp),%ax # Move argument to ax
testw %ax,%ax # Logical compare (sets SF, ZF and PF)
jg .L2 # if (argument > 0) goto L2
xorw %ax,%ax # else return 0
popw %bp
retw
.L2:
decw %ax
pushw %ax
call trivialTest # trivialTest(argument - 1)
# (Recursive calls until argument == 0)
addw ,%sp # Pop argument
incw %ax
popw %bp
retw # Return (argument in ax)
displayString:
pushw %bp # Setup stack frame
movw %sp,%bp
pushw %ax # Save ax, bx, cx, si, es
pushw %bx
pushw %cx
pushw %si
pushw %es
movw %ds, %ax # Make sure ES points to the right
movw %ax, %es # segment
movw 4(%bp),%cx # Move string adr to cx
movw %cx, %si
loop:
lodsb # Load character to write (c) into al,
# and increment si
cmpb [=12=], %al
jz done # if (c == '[=12=]') exit loop
movb ,%ah # else print c
movw [=12=]x0002,%bx
# int 0x10 sends a character to the display
# ah = 0xe (14)
# al = character to write
# bh = active page number (we use 0x00)
# bl = foreground color (we use 0x02)
int [=12=]x10
jmp loop
done:
popw %es # Restore saved registers
popw %si
popw %cx
popw %bx
popw %ax
popw %bp
retw # Return to caller
再一次,我在调试器中检查到内核正在加载到内存 (0x1000) 中。我认为问题出在 setting/using bootblock.s 中的某些寄存器(主要是:ds
,ax
),但我不确定它是什么。
这些观察结果可能对您有所帮助:
尝试加载内核时,您应该取消注释设置轨道号的行。
在displayString例程中不需要设置ES寄存器。只需一个正确的 DS。
您正试图通过近距离跳转到内核! (
jmp %ax
) 那是行不通的。你需要一个段间跳跃。使用jmp [=11=]x0000:[=11=]x0100
你应该避免使用像 %esi 这样的 32 位寄存器来传递地址,因为这是 16 位代码。
代码有很多问题。大多数似乎适用于大多数 16 位 OS 引导加载程序的基本问题都可以在我最近的
你的代码有一个具体问题 - 如果你将 运行 你的代码在 8086/8088 系统或模拟器(不是 286、386 等)上,那么你应该坚持使用 16 位寄存器,因为32 位寄存器不可用。您的代码使用 ESI 和 EBX 寄存器(32 位)。你应该使用 SI 和 BX.
此代码中的主要问题是,在大多数情况下,内核是从磁盘读取的,但碰巧读取的扇区少于内核映像实际占用的扇区。这恰好导致本应由内核打印的变量未被加载。
我原本假设内核看起来很小,从磁盘读取 8 个扇区对于提供的示例代码来说绰绰有余:
#Number of sectors to read
movb [=10=]x08, %al
我发现楼主在做作业,有人发布了一些解决问题的关键信息。我已经分叉了 git project 以供参考。一些关键信息是正在使用的 Makefile 类型(略有不同)和为作业提供的名为 createimage
的程序。
Makefile 足够接近 类似于:
# Makefile for the OS projects.
# Best viewed with tabs set to 4 spaces.
CC = gcc -Wall -Wextra -std=c99 -g
LD = ld
# Where to locate the kernel in memory
KERNEL_ADDR = 0x1000
# Compiler flags
#-fno-builtin: Don't recognize builtin functions that do not begin with
# '__builtin_' as prefix.
#
#-fomit-frame-pointer: Don't keep the frame pointer in a register for
# functions that don't need one.
#
#-make-program-do-what-i-want-it-to-do:
# Turn on all friendly compiler flags.
#
#-O2: Turn on all optional optimizations except for loop unrolling
# and function inlining.
#
#-c: Compile or assemble the source files, but do not link.
#
#-Wall: All of the `-W' options combined (all warnings on)
CCOPTS = -c -fomit-frame-pointer -O2 -fno-builtin
# Linker flags
#-nostartfiles: Do not use the standard system startup files when linking.
#
#-nostdlib: Don't use the standard system libraries and startup files when
# linking. Only the files you specify will be passed to the linker.
#
#-Ttext X: Use X as the starting address for the text segment of the output
# file.
LDOPTS = -nostartfiles -nostdlib -Ttext
# Makefile targets
all: bootblock createimage kernel image boch_image
kernel: kernel.o
$(LD) $(LDOPTS) $(KERNEL_ADDR) -o kernel $<
bootblock: bootblock.o
$(LD) $(LDOPTS) 0x0 -o bootblock $<
createimage: createimage.o
$(CC) -o createimage $<
# Create an image to put on the floppy
image: bootblock createimage kernel
./createimage ./bootblock ./kernel
# Put the image on the floppy (these two stages are independent, as both
# vmware and bochs can run using only the image file stored on the harddisk)
#boot: image
# cat ./image > /dev/sda
#write image to boch disk image
boch_image: image
dd if=image of=bochs.img conv=notrunc
# Clean up!
clean:
rm -f *.o
rm -f createimage image bootblock kernel
# No, really, clean up!
distclean: clean
rm -f *~
rm -f \#*
rm -f *.bak
rm -f bochsout.txt
# How to compile a C file
%.o:%.c
$(CC) $(CCOPTS) $<
# How to assemble
%.o:%.s
$(CC) $(CCOPTS) $<
# How to produce assembler input from a C file
%.s:%.c
$(CC) $(CCOPTS) -S $<
根据原始发布者的后续评论,他们正在构建 elf_i386 二进制文件(Linux ELF 格式用于 32 位)。上面的 Makefile 表明内核被构建为 ELF 映像,然后放置在磁盘上。 ELF 格式为每个部分添加了相当数量的填充,因此假设从磁盘读取 8 个扇区 可能还不够。 Makefile 中没有引用 objcopy
或转换为平面二进制格式。我发现给定的 createimage 程序从 32 位 ELF 格式中提取内核映像,并生成输出,说明引导加载程序需要读取多少扇区才能获得整个内核。 createimage
代码是:
/* createimage.c -- create a bootable image in 16 real mode from several elf file
*/
#include <stdio.h>
#include <stdlib.h>
#include "createimage.h"
int file_process(FILE *elf_file, FILE *image, char *elf_filename);
long byte_get (unsigned char *field, int size);
int main (int argc, char ** argv)
{
//here hasn't check the magic numbers of elf
if (argc != 3) {
printf("USAGE:%s bootblock kernel\n", argv[0]);
return -1;
}
FILE *bootblock, *kernel, *image;
if ((bootblock = fopen (argv[1], "rb")) == NULL) {
printf("can't open %s\n", argv[1]);
return -1;
}
if ((image = fopen ("image", "wb")) == NULL) {
printf("can't open image!\n");
return -1;
}
if (file_process(bootblock, image, argv[1])) {
printf("process bootblock failed\n");
return -1;
}
if ((kernel = fopen (argv[2], "rb")) == NULL) {
printf("can't open %s\n", argv[2]);
return -1;
}
if (file_process(kernel, image, argv[2])) {
printf("process kernel failed\n");
return -1;
}
fclose(bootblock);
fclose(kernel);
fclose(image);
return 0;
}
long byte_get (unsigned char *field, int size)
{
switch (size)
{
case 1:
return *field;
case 2:
return ((unsigned int) (field[0])) | (((unsigned int) (field[1])) << 8);
case 4:
return ((unsigned long) (field[0]))
| (((unsigned long) (field[1])) << 8)
| (((unsigned long) (field[2])) << 16)
| (((unsigned long) (field[3])) << 24);
default:
printf("byte_get error\n");
return -1;
}
}
/* read information from elf file, and write LOAD segment to image file
*
* note: the structure in file is not aligned, we just read it from file byte
* by byte
*/
int file_process(FILE *elf_file, FILE *image, char *elf_filename)
{
unsigned int header_sz, pheader_sz;
unsigned long phoff;
unsigned int p_offset;
unsigned int p_filesz;
unsigned int p_memsz;
elf_header header;
elf_program_header pheader;
header_sz = sizeof (elf_header);
pheader_sz = sizeof(elf_program_header);
printf("processing %s:\n", elf_filename);
printf("header size is: %d\n", header_sz);
printf("program header size is: %d\n", pheader_sz);
if (header_sz != fread(&header, 1, header_sz, elf_file)) {
printf("read error!\n");
return -1;
}
//get program header's offset
phoff = byte_get(header.e_phoff, sizeof(header.e_phoff));
printf("Program header table offset in file is :\t %u\n", phoff);
if (fseek(elf_file, phoff, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
//printf("the current position: %d\n", ftell(elf_file));
if (pheader_sz != fread(&pheader, 1, pheader_sz, elf_file)) {
printf("read error at line %d!\n", __LINE__);
return -1;
}
//get the LOAD segment's offset, filesz, mensz
p_offset = byte_get(pheader.p_offset, sizeof(pheader.p_offset));
p_filesz = byte_get(pheader.p_filesz, sizeof(pheader.p_filesz));
p_memsz = byte_get(pheader.p_memsz, sizeof(pheader.p_memsz));
printf("p_offset: 0x%x\tp_filesz: 0x%x\tp_memsz: 0x%x\t\n", p_offset, p_filesz, p_memsz);
//write elf's LOAD segment to image, and pad to 512 bytes(1 sector)
char *buffer;
const unsigned int sector_sz = 512;
const char MBR_signature[] = {0x55, 0xaa};
unsigned int n_sector;
unsigned int n_byte;
if (p_memsz % sector_sz != 0)
n_sector = p_memsz / sector_sz + 1;
else
n_sector = p_memsz / sector_sz;
n_byte = n_sector * sector_sz;
if (!(buffer = (char *)calloc(n_byte, sizeof(char)))) {
printf("malloc buffer failed! at line %d\n", __LINE__);
return -1;
}
if (fseek(elf_file, p_offset, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
if (p_filesz != fread(buffer, 1, p_filesz, elf_file)) {
printf("read error at line %d!\n", __LINE__);
return -1;
}
if (n_byte != fwrite(buffer, 1, n_byte, image)) {
printf("write error at line %d!\n", __LINE__);
return -1;
}
//write MBR signature to image, which is 2 bytes
if (fseek(image, 510, SEEK_SET)) {
printf("fseek %s failed! at line %d\n", elf_filename, __LINE__);
return -1;
}
if (2 != fwrite(MBR_signature, 1, 2, image)) {
printf("write error at line %d!\n", __LINE__);
return -1;
}
printf("write image:\n%d sectors,\t%d bytes\n", n_sector, n_byte);
return 0;
}
重要的是,在提取代码和数据部分后(将与 dd
放在磁盘映像中),它在底部提供类似于以下的输出:
processing ./kernel:
header size is: 52
program header size is: 32
Program header table offset in file is : 52
p_offset: 0x54 p_filesz: 0x10db p_memsz: 0x10db
write image:
9 sectors, 4608 bytes
dd if=image of=bochs.img conv=notrunc
这里有重要信息 9 sectors, 4608 bytes
。这表示内核在扇区中有多大。原始海报的代码必须确保他们在加载内核时读取了那么多扇区。所以简单的解决方法是将代码更改为:
#Number of sectors to read should match output from createimage
movb [=14=]x09, %al