3

I am attempting to load the kernel into memory by writing my own boot loader. I've been able to successfully load the kernel into memory - I know that because I used the bochs debugger set the breakpoint to 0x7c00 and stepped through and the system does jump into the kernel. The problem is that after jumping into the kernel none of the print statements (in kernel.s) work. That indicates on the terminal that the kernel has been loaded into memory.

Here is the bootblock.s file (majority of the relevant code resides at label 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  $0,%al
  je  print_done
  movb  $14,%ah
  movl  $0x0002,%ebx
  int  $0x10
  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 $0x0, %ah
    #movb $0x0, %dl
    #int $0x13

    movl $bootblock_test, %esi
    call print
    movl $hellostring, %esi
    call print

    ###LOAD KERNEL###
    movl $loadingkernel, %esi
    call print

    #Number of sectors to read
    #movb $0x24, %al
    #movb $0x80, %al
    movb $0x08, %al


    movb $0x02, %ah
    #track number
    #movb $0x00, %ch

    #which sector to read from (sector 2 where kernel should be)
    movb $0x02, %cl

    #set up head number
    movb $0x0, %dh

    #Set the drive number to 0x0 (floppy)
    movb $0x0, %dl

    #Time to set es:bx to read from the correct place (0:1000)
    movw $0x0100, %bx
    movw %bx, %es
    movw $0x0, %bx
    #movw $0x0, %ax

    #Setting %ah = 2 and calling int 0x13 (read sector)

    int $0x13

    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 $0x0, %dh
    #movb $0x0, %ch

    #movw %ds, 

    ###INVOKE KERNEL###

    #Kernel jump
    movl $readymessage, %esi
    call print

    #Setting %ds = 0x7c0
    movw $0x0100, %ax
    movw %ax, %ds

    #Time to set es:bx to read from the correct place (0:1000)
    movw $0x0100, %bx
    movw %bx, %es
    movw $0x0, %bx


    movl $0x1000, %ax
    jmp %ax
    mov $0x0, %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"

Here is the kernel.s file:

.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', '\0')
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   $1000
    call    trivialTest # trivialTest(1000)
    addw    $8,%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    $2,%sp
    pushw   $newline
    call    displayString
    addw    $2,%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    $2,%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    $0, %al     
    jz  done        # if (c == '\0') exit loop
    movb    $14,%ah     # else print c
    movw    $0x0002,%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 $0x10           
    jmp loop
done:
    popw    %es     # Restore saved registers
    popw    %si
    popw    %cx
    popw    %bx
    popw    %ax
    popw    %bp
    retw            # Return to caller

Once again, I've checked in the debugger that the kernel is being loaded into memory (0x1000). I believe the problem is with how I am setting/using certain registers in bootblock.s (mainly : ds,ax ) but I am not exactly sure what it is.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
DynamoBooster
  • 175
  • 12
  • Besides what was given in the answer by @user3144770 I can only assume that in your linker script or linker options that you have set the origin point for the bootloader and the kernel correctly. – Michael Petch Sep 20 '15 at 21:14
  • Based on the code the origin point would be set to `0x0000` for the bootloader since you are explicitly using the segment of `0x07C0` in your boot loader segment registers. Given how it appears you were intending to jump to your kernel (You need a far call as has been mentioned in the answer) it would *appear* without further information that you intended for the origin point of the kernel to be `0x1000`. If not set properly by your linker command or linker script then this can cause potential issues if you ever use non-relocatable code (absolute memory addressing within the current segment) – Michael Petch Sep 20 '15 at 21:26
  • This is a small nitpick beyond the use of 32 bit registers (like %esi) as mentioned in the answer. If your code were modified to use 16 and 8 bit registers, and you were to find a real 8086/8088(not an issue with 80286+) machine and run this you'd find that there would be a performance penalty for every access to the stack (4 clock cycles). The issue? You set SP(stack pointer) to -1(0xFFFF). Because -1 is an odd memory location you pay this penalty for each time the CPU retrieves something from the stack. The fix: change STACK_SP to be 0x0000 instead of 0xFFFF . – Michael Petch Sep 20 '15 at 23:01
  • And if you ask is it dangerous to set the stack to zero and have something written to location 0x00 when something is pushed on the stack for the first time? no. When pushing something on the stack, SP is decremented by 2 *first* and then a word stored. 0x0000-2=-2=0xFFFE. -2 is an even memory location so this performance penalty is avoided on real 8086/8088 processors. – Michael Petch Sep 20 '15 at 23:03
  • I earlier said `it would appear without further information that you intended for the origin point of the kernel to be 0x1000`. This was an observation based on your incorrect jmp of `movl $0x1000, %ax; jmp %ax`. I just noticed the code just above it seems to suggest you were intending to use 0x0100 as the segment and 0x0000 as the offset to jump to. This would mean that you are hopefully setting the origin point to 0x0000 (linker) for the kernel too (if that was the intention) – Michael Petch Sep 20 '15 at 23:24
  • Okay, another nitpick.If you were using a real floppy drive or older hard disk, generally it was a good idea to check for a disk read or write error (after a BIOS read/write call) and then retry a few times.This was most especially true for real floppy drives.On VMs and emulators they don't generally fake floppy drive errors so the reads or writes likely won't ever fail under most circumstances in those environments.On real hardware, it is a different story. Even worse is that usually before *every* read/write on a real floppy it was a good idea to reset the drive first or it may fail to work. – Michael Petch Sep 20 '15 at 23:28
  • @MichaelPetch Yes you're right, I did intend the origin point of kernel to be $0x1000. I was specifically asked to load the kernel at 0x1000 so I can't change that. I did try changing the stack to 0x0000 to no avail. Thanks for taking the time to answer! And yes I had the wronf address in the code above - I change it but I still don't the output I need. – DynamoBooster Sep 21 '15 at 03:36

2 Answers2

3

These observations might help you:

  • When trying to load the kernal you should uncomment the line that sets up the track number.

  • In the displayString routine there's no need to set up the ES register. A correct DS is all that is needed.

  • You are trying to jump to the kernal via a near jump! (jmp %ax) That won't work. You need an intersegment jump. Use jmp $0x0000:$0x0100

  • You should refrain from using 32 bit registers like %esi to pass addresses since this is 16 bit code.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • 1
    Something else that should be considered that in the kernel and bootloader code he is using `lodsb` and nowhere in his boot loader (or kernel) does he explicitly set the direction flag with CLD (CLD most likely is what he intended with the code that was written). The status of the direction flag can't be assumed to be either set or not so it is generally good practice to explicitly set it. – Michael Petch Sep 20 '15 at 20:56
  • 1
    yup, good point. In user-mode in a Linux program, the ABI requires that DF is cleared for a newly-started program (i.e. after execve). That's not the case here. – Peter Cordes Sep 20 '15 at 22:35
  • Yes agreed, my comment was within the context of a bare-metal environment (no OS) where there is no such requirement of the BIOS to set the direction flag to something specific before executing the boot loader. – Michael Petch Sep 20 '15 at 22:46
  • @user3144770 Hey I uncommented the track number, and tried changing jump to jmp $x0000:$0x0100 but I get a 'Junk after 0x0100 error' while making the file. I cannot change the kernel.s file as it was given to me. – DynamoBooster Sep 21 '15 at 03:21
  • @MichaelPetch I have to set lodsb and/or cld in the print function in bootblock.s? – DynamoBooster Sep 21 '15 at 03:27
  • 2
    You can't be guaranteed the direction flag is set by the BIOS. In your code you use `lodsb` which relies on the direction flag to determine whether to move forward or backward . Your `lodsb` code is written assuming `%di` is incremented by 1, so somewhere (you could do it right after you setup the stack) you need to place the CLD instruction (no operands).Read more about this instruction [here](http://x86.renejeschke.de/html/file_module_x86_id_29.html). The opposite to this instruction is STD which makes `lodsb`,`movsb` and related instructions decrements `%di`/`%si` after every use – Michael Petch Sep 21 '15 at 03:34
  • @DynamoBooster I just realized that the answer to this question is wrong. It should be `jmp $0x0000:$0x1000` NOT `jmp $0x0000:$0x0100` – Michael Petch Sep 21 '15 at 03:41
  • @DynamoBooster : I forgot to tag your name in my last response. See the comment a couple entries ago to see where I would place the `CLD` instruction (right after you setup the stack) – Michael Petch Sep 21 '15 at 03:43
  • @MichaelPetchI just tried that and I get 'Junk :0x1000 after expression and Invalid operands for jmp' error – DynamoBooster Sep 21 '15 at 03:45
  • 2
    @DynamoBooster DUH, dumb mistake sorry. With GNU assembler It should be a _comma_ between the segment and offset NOT a _colon_. So try `jmp $0x0000,$0x1000` – Michael Petch Sep 21 '15 at 03:49
  • @MichaelPetch that didn't give me an error, but the output still doesn't appear. I set the cld after setting up the stack. – DynamoBooster Sep 21 '15 at 03:51
  • Do you have a file in your project with an `.ld` extension? – Michael Petch Sep 21 '15 at 03:52
  • @MichaelPetch No I don't. – DynamoBooster Sep 21 '15 at 03:54
  • @MichaelPetch $(LD) $(LDOPTS) -Ttext $(KERNEL_ADDR) -o kernel $ – DynamoBooster Sep 21 '15 at 04:07
  • @MichaelPetch LDOPTS = -nostartfiles -nostdlib -elf_i386 LD = ld KERNEL_ADDR = 0x1000 – DynamoBooster Sep 21 '15 at 04:08
  • @MichaelPetch KERNEL_ADDR = 0x1000 – DynamoBooster Sep 21 '15 at 04:10
  • @DynamoBooster : If you are linking with -elf_i386 I will assume there is an objcopy command run right after or soon after? based on the LD command you gave I can confirm now that the proper way to jump to your code is `jmp $0x0000,$0x1000` , Right now I have concerns about -elf_i386 IF there is no other manipulation to convert it to a flat binary. That could be a problem, either that or your project has a requirement to load Elf binaries, looup the start location and then do a jump. Was this script/command provided to you or did you create it? – Michael Petch Sep 21 '15 at 04:16
  • the ld command was provided in the Makefile. What do I look up the start location? – DynamoBooster Sep 21 '15 at 04:20
  • 1
    @DynamoBooster Ignore the last things I suggested to you. I have found what appears to be the assignment elsewhere on the internet. Rather than use `objcopy` there is a program that is in your project called createimage that does something similar. I assume you have createimage? – Michael Petch Sep 21 '15 at 04:27
  • 1
    @DynamoBooster Looking at someone else who did the assignment it appears the professor intended to always have these kernels loaded at 0x0000:0x1000 so the original jmp code you had actually will work since it is within the same 64kb segment as the bootloader. So you can use the original as you had it and the answer that was given here doesn't apply. – Michael Petch Sep 21 '15 at 04:42
  • 1
    @DynamoBooster : Maybe I have also misunderstood something, when your bootloader runs does it show "Allocating Stack..." on the screen? – Michael Petch Sep 21 '15 at 04:44
  • @MichaelPetch Yes I do have create image and yes it does. – DynamoBooster Sep 21 '15 at 05:02
  • @DynamoBooster When the jmp is made to your kernel (it sounds like you are able to debug) do you see it executing the expected code? (but just without output to the screen). I'm just wondering if you know for certain that the kernel was actually loaded properly from disk – Michael Petch Sep 21 '15 at 05:04
  • @MichaelPetch Yes I see it executing the code in the kernel.s file starting from pushw %bp in _start: . It even calls the displayString function and I even stepped into displayString and saw that all the commands were executed - but there was no output on the screen. Do you know if I am using the ds register correctly? – DynamoBooster Sep 21 '15 at 05:08
  • @DynamoBooster That is what I am about to find out. You must not be setting something right and the DS segment is the likely culprit. Actually if you are in the debugger with bochs are you able to tell me what DS register has in it just before it makes the BIOS calls to display the string?. – Michael Petch Sep 21 '15 at 05:14
  • @DynamoBooster My guess - your DS is still 0x07c0 from when your boot loader was running. Based on what I see I think this line in the kernel is incorrect `movw %ds, %ax # Make sure ES points to the right`.I have a feeling you are reusing the DS from the bootloader of 0x07c0 which no longer applies after you jmped to your kernel at 0x1000.Your DS segment should now be set to zero with the code you are using. What you need to do in your kernel is REMOVE the movw instruction I mentioned and when you kernel starts do `xor %ax, %ax` then `mov %ax, %ds` then `mov %ax, %es` to ensure DS and ES are 0 – Michael Petch Sep 21 '15 at 05:34
  • @DynamoBooster I should point out that `xor %ax, %ax` is a method to set a register to 0 (in this case `%ax`). It is a slightly smaller way of doing `mov $0, %ax` – Michael Petch Sep 21 '15 at 05:43
  • @MichaelPetch I do set ds to zero before jumping into the kernel and it stays 0x0 after going into the kernel. – DynamoBooster Sep 21 '15 at 06:41
  • 1
    @DynamoBooster Well I decided to look a the createimage program from the other project I found, and I ran the Makefile and what I noticed is that your bootloader may not be reading enough sectors to include the strings you want to print. if you run this command in your kernel directory `ls -l bochs.img`, how big is the image? Any chance it is 5120? If it says 5120 then there is a problem when reading the sectors into memory. The line `movb $0x08, %al` might be reading one less sector than needed. Possibly `movb $0x09, %al` will work? – Michael Petch Sep 21 '15 at 06:42
  • @DynamoBooster Whatever size it is you need to make sure you read the number of sectors that appears in the createimage output that gets generated. There are lines that say `write image: 9 sectors 4608 bytes` That was what it said here. Whatever the number of sectors it says is the number your bootloader must read. If yours isn't 9(decimal) you must use whatever value your createimage program is displaying and modify your bootloader to read that many. – Michael Petch Sep 21 '15 at 06:54
  • 1
    THAT WORKED! Changing it to 0x09 made the output appear. Thanks so much for helping me and explaining this process in detail! If you post that as an answer I will mark it as the correct answer! Once again, thanks for helping me. – DynamoBooster Sep 21 '15 at 07:35
3

There are a number of issues with the code. Most of the fundamental ones that seem to apply to most 16-bit OS boot loaders can be found in my recent Stackoverflow answer (similar type of question). It covers things you need to be aware of with SS/SP/ES/DS/CS registers, stack performance issues on 8086/8088, some things to look out for on old buggy 8086/8088.

One specific issue with your code though - If you will be running your code on an 8086/8088 system or emulator (not 286, 386 etc) then you should stick to the 16 bit registers since the 32 bit registers aren't available. Your code uses ESI and EBX registers (32-bit). You should use SI and BX.

The primary problem in this code is that for the most part the kernel was being read from the disk okay, but it happened to read fewer sectors than the kernel image actually took. This just happened to lead to the variables that were meant to be printed by the kernel to not be loaded.

I had originally assumed that the kernel appeared so small and reading 8 sectors from the disk was more than enough for the sample code presented:

#Number of sectors to read
movb $0x08, %al

I discovered that the original poster was doing an assignment and that someone had posted some crucial information to resolve the problem. I have forked that git project for reference purposes. Some key pieces of information were the type of Makefile being used (with slight differences) and a program called createimage that is provided for the assignment.

The Makefile is close enough to something like:

# 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 $<

According to a followup comment by the original poster they are building elf_i386 binaries (Linux ELF format for 32-bit). The above Makefile suggests that the Kernel is being built as an ELF image and then placed on the disk. The ELF format adds a fair amount of padding for each section, so the assumptions that reading 8 sectors from the disk might not be enough. There is no reference to objcopy in the Makefile or converting to a flat binary format. I discovered that the createimage program that they are given extracts the Kernel image from the 32-Bit ELF format and produces output that says how many sectors the bootloader needs to read to get the entire kernel. createimage code is:

/* 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;
}

What is important is that after the code and data sections are extracted (to be put in a disk image with dd) it provides output similar to this at the bottom:

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

Important information here 9 sectors, 4608 bytes. This says how big the kernel is in sectors. The original poster's code must make sure that they read that many sectors when loading the kernel. So the trivial fix is to change the code to:

#Number of sectors to read should match output from createimage
movb $0x09, %al
Community
  • 1
  • 1
Michael Petch
  • 46,082
  • 8
  • 107
  • 198