2013-11-30

2013-11-30 Doom Demo

Not too much time to investigate things today, but I thought I'd briefly look into the problem with the demo not playing in Doom.

Turns out the demo was for version 1.09 and the game is version 1.10.  Also turns out if you callously comment out the version check and pretend it's all ok, it works  :)


The frame rate is currently about 0.9 frames per second, but almost all of that time is my video routine drawing the 24-bit bitmap to the screen, plenty of room for improvement.  I'll probably make a video benchmarking function to maybe count the time it takes to render 50 frames so that I can clearly test each modification I make.  Maybe over Christmas ...

2013-11-30 Doom Progress Continued

So last night I cleared a few more bugs in my kernel and libraries, and got Doom to the point where it stopped outputting text to the console because it gone into its main loop ...

Loading Operating System ...
Kernel Initialised
Loading ELF library

                            DOOM Registered Startup v1.10
V_Init: allocate screens.
M_LoadDefaults: Load system defaults.
dos_openFile: Opening "devdatadefault.cfg".
Could not find the device.
Z_Init: Init zone memory allocation daemon.
W_Init: Init WADfiles.
dos_openFile: Opening "DH1:Doom/base/doom.wad".
 adding DH1:Doom/base/doom.wad
===========================================================================
                 Commercial product - do not distribute!
         Please report software piracy to the SPA: 1-800-388-PIR8
===========================================================================
M_Init: Init miscellaneous info.
R_Init: Init DOOM refresh daemon - [.....                     ]
InitTextures
InitFlats............
InitSprites
InitColormaps
R_InitData
R_InitPointToAngle
R_InitTables
R_InitPlanes
R_InitLightTables
R_InitSkyMap
R_InitTranslationsTables
P_Init: Init Playloop state.
I_Init: Setting up machine state.
D_CheckNetGame: Checking network game status.
startskill 2  deathmatch: 0  startmap: 1  startepisode: 1
player 1 of 1 (1 nodes)
S_Init: Setting up sound.
S_Init: default sfx volume 8
HU_Init: Setting up heads up display.
ST_Init: Init status bar.

My original plan was then to design and write the device management subsystem, migrate the vga driver code, implement the graphics library, then get Doom to open the graphics library in order to output to the screen.

Unfortunately (fortunately?), my impatience got the better of me and I thought I might just try a quick 'n' dirty system call to get the bitmap that Doom had generated onto the screen.  I pulled much of the code from the VGA driver that I used in the 16 colour image article, and called the initialise() function to switch from the mode 3h VGA text screen to the mode 12h VGA graphics screen, with 640x480 pixels by 16 colours.

Doom's bitmap uses 8 bits per pixel, each pixel's byte is used as an index into a colour mapping array to get a new byte, which is then used as an index into a palette array to get three bytes for red, green, and blue.  People familiar with the VGA hardware will recognise these two levels of translation as features that VGA provides natively.  I guess that's why the original Doom was written that way.  In any case, I figured I could get something recognisable if I just took the bitmap array and drew it directly, so the initial bitmap gave the value 0-255 for the blue component of each pixel.

I wrote a system call which accepted the bitmap pointer, width and height, and used a quick function to call the plot() function in the VGA driver, which gave me a quarter screen blue image, but one which bore a passing resemblance to the Doom intro screen.

This image only filled quarter of the screen, so a few tweaks had it full-screen.  Doom was designed to run a 320x200 mode 13h screen with 256 colours, so it needed enlarging.


After spending some time investigating the format of the Doom bitmap and finding out about the palette, I modified the Doom render routine.  I wanted to give the 24-bit bitmap to the system call, rather than worrying about passing it the colour map and palette.  I allocated a second screen buffer that was 3 bytes per pixel for the doom screen and added a loop to copy each pixel, translating it for the colour map and palette, and wrote the 24-bit colour to the second buffer.

I used the same bitmap drawing routine that was used for the parrot, and draw the image without dithering.  This was the first of the images that I posted yesterday.  Turned on the dithering and posted the second image.  My brain told me the image was a little "off", but I wasn't sure how.  I looked for a comparison image on Google and then spotted that red and blue were reversed.  Behold!


The vertical cyan lines are left-overs because I didn't clear the screen buffer after switching to Mode 12h, and the bitmap drawing routine supports transparency, which is why they show through the black areas.


Bugs fixed


These are the bugs I fixed in order to get Doom to its main loop.  We don't need to go into details ...

  • strncasecmp() was checking one more character than it should have been, so it couldn't find "SW1BRCOM".
  • I had stripped all of the netcode from Doom because I don't have my network stack in this kernel yet, but apparently some of it's required, so I had to put some of it back in.
  • I hadn't implemented the precision parameter of sprintf() so it didn't construct the font texture names correctly.  It was looking for "STCFN33" rather than "STCFN033".
  • sprintf() also wasn't writing a null-terminator, which apparently it needs (who knew?), so it was looking for "STTNUM10" rather than "STTNUM1".

Next Steps

When I next get time on this, I plan to address the following:

  • "Demo is for a different version of the game".  This well-known Doom error is causing the game to exit shortly after it starts.  If I can fix this, Doom should go into playing its opening demo.
  • Keyboard input.  I haven't looked into how Doom accesses the keyboard, but it will need at least key-up and key-down events which is more than the C library provides.  I'll need to think about this some more, but I'll probably create some form of message queue and port which Doom can open and listen for events.  This will be akin to how it will work under a window manager anyway.
  • Implement access().  Doom uses the access() function to determine which wad files it has available.  If I finish the implementation of this, I could upload an archive of the emulator and the game (without the wad file, maybe with a shareware wad), for people to toy with.
  • Write the device manager.  Not specifically for Doom, but the full implementation of the vga driver needs the device manager.
  • Write the proper vga driver (not the hacky one that's there now)
  • Write a sound library.  This'll probably need the device manager, and is the one big thing I don't have a pre-existing kernel for testing.
  • Make it faster!  It's not fast, we need to go faster, more speed!

2013-11-29

2013-11-29 Doom Progress


... and with a bit of parrot-based dithering ...


Oops, of course red and blue were backwards.



2013-11-24

2013-11-24 Doom Progress

Continuing my work on porting Doom ... actually, that isn't really fair.  I'm not making Doom work with my OS, I'm writing my OS to work for Doom, semantics.

Continuing my work on my OS, Doom is locating and opening the wad file now.  It uses the effectively finished path of Program -> C Library -> Kernel API -> DOS Layer -> File-system -> Block Device to locate the file and open it.  Still happy with that set-up.

Doom is, however, complaining that the wad file doesn't have the correct signature.  That may have something to do with not having connected the read() function into the DOS layer ... there, fixed.

The kernel is now exploding (technical term), screen gibberish and inconsistent results.  After an hour or so of debugging, I've tracked down the two causes:

  1. Doom was allocating 36k of memory on the stack ... the stack is only 16k in size.  The rest of the space was overwriting the kernel ... d'oh!  (Makes stack bigger ... shhhh!)
  2. It appears as if all the remaining memory in the memory allocator is being used suddenly, causing an out-of-memory situation.
Another hour of debugging later and I've found the problem.  Every allocation of memory has a header and a footer.  They both include information such as the length of the block, a magic number (to help detect overflows), and a flag which indicates if the block is currently used or free.


When freeing a block, the memory allocator looks to see if the block being freed and the preceding block can be combined to make a bigger free block, then does the same for the following block.  It examines the footer of the preceding block (which is immediately before the header of the block being freed, obviously) to see if that block is free.  If so, it uses the length stored in the tail of the preceding block to find the header of the preceding block, then updates everything to combine them.  The bug was that in one circumstance, the tail pointer free flag was not being set which created a block where one flag said it was free and the other said it wasn't.  :(

The allocator looked at the previous tail flag and it said the block was free, so it dutifully added the length of the block being freed onto the previous block.  Seeing that the following block was also free (the following block being all the remaining free memory on the system), it added that in as well to make one big contiguous free block ... except that the previous block wasn't free.  The tail flag said it was free, the head flag said it was in use.  In a flash, freeing a block of memory had consumed every available byte of memory  :(

Easy to fix, I'm glad that it happened now, and I'm glad that my memory allocator has one fewer bug in it.

Doom approves of my fix:

Loading Operating System ...
Kernel Initialised
Loading ELF library

                            DOOM Registered Startup v1.10

V_Init: allocate screens.
M_LoadDefaults: Load system defaults.
dos_openFile: Opening "devdatadefault.cfg".
Could not find the device.
Z_Init: Init zone memory allocation daemon.
W_Init: Init WADfiles.
dos_openFile: Opening "DH1:Doom/base/doom.wad".
 adding DH1:Doom/base/doom.wad
===========================================================================
                 Commercial product - do not distribute!
         Please report software piracy to the SPA: 1-800-388-PIR8
===========================================================================
M_Init: Init miscellaneous info.
R_Init: Init DOOM refresh daemon - Error: W_GetNumForName: PNAMES not found!
Exited!

Next time:  Investigating what PNAMES is and why it wasn't found.

2013-11-23

2013-11-23 Constructors and Library Initialisation

The last few weeks have been spent researching initialisation functions, constructors, and how to write and call them.

Now that libkernel and libc are separate entities, they both have some initialisation code that needs to be run.  At the very least, these two are going to have to resolve the kernel system call functions so that they can make syscalls.  Libc can cheat at this because it provides the start() function that is called first when the program is loaded, so it can take this opportunity to set itself up (this is fairly common amongst C libraries).  Libkernel has a slightly tougher job.  I'd rather not have the program need to explicitly initialise it, and libc shouldn't assume that libkernel should be there, so how do we do it?

Well, GCC already has a way of doing this which it uses for C++ static constructors and for a few rare cases in C, but the details of it are spread around so let's try to make sense of it for our specific situation.

The first port of call is to RTFM, which in this case is http://gcc.gnu.org/onlinedocs/gccint/Initialization.html .  Unfortunately, this article is very heavy going, talks in very abstract terms and isn't very easy to follow.  Also, searching around for some of the terms used, especially the #defines in this article doesn't help either.

The few sources I was able to find with information on how this works generally suggest that the ELF file contains a section called .ctors which contains a list of addresses to constructor functions which are to be called at the beginning of the program (and a section called .dtors for deconstructors for the end of the program).  It makes sense that this section should be created by the compiler itself (as the linker won't know which functions need to be in this) so the linker must just combine together the pointers from all the different object files.

Step 1 : Compiling


Let's have a look at this first hand.  We know that C++ does for some cases, so let's try this:

main()
{
 asm("int $1");
}

volatile int RandomRoll()
{
 asm("int $2");
 return 4;
}

int mTest = RandomRoll();

Which can be compiled into an object file with:
g++ hello.cpp -nostdlib
(ignoring the warning about the undefined entry symbol.)

This file needs some initialisation in order to work properly, specifically that the program must call RandomRoll() to set the value of mTest before main() is called, so we expect to find some clues in here.  The assembly interrupts aren't expected to be run, they are there to help us identify the code when we start disassembling it.

Having a look at the object file with objdump allows us to see what's going on.

> objdump a.out -h

a.out:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000052  08048074  08048074  00000074  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .eh_frame     00000098  080480c8  080480c8  000000c8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .ctors        00000004  08049160  08049160  00000160  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000004  08049164  08049164  00000164  2**2
                  ALLOC
  4 .comment      00000011  00000000  00000000  00000164  2**0
                  CONTENTS, READONLY



Ok, that's good, we have a section called .ctors with a size of four bytes which contains data.  This is exactly the right amount to hold an address on a 32-bit platform.

Back to objdump to make certain we understand this (I've skipped the data from some of the sections as they weren't relevant):

> objdump a.out -sS

a.out:     file format elf32-i386

Contents of section .ctors:
 8049160 aa800408                             ....

Disassembly of section .text:

08048074 <main>:
 8048074:       55                      push   %ebp
 8048075:       89 e5                   mov    %esp,%ebp
 8048077:       cd 01                   int    $0x1
 8048079:       b8 00 00 00 00          mov    $0x0,%eax
 804807e:       5d                      pop    %ebp
 804807f:       c3                      ret

08048080 <_z10randomrollv>:
 8048080:       55                      push   %ebp
 8048081:       89 e5                   mov    %esp,%ebp
 8048083:       cd 02                   int    $0x2
 8048085:       b8 04 00 00 00          mov    $0x4,%eax
 804808a:       5d                      pop    %ebp
 804808b:       c3                      ret

0804808c <_z41__static_initialization_and_destruction_0ii>:
 804808c:       55                      push   %ebp
 804808d:       89 e5                   mov    %esp,%ebp
 804808f:       83 7d 08 01             cmpl   $0x1,0x8(%ebp)
 8048093:       75 13                   jne    80480a8 <_z41__static_initialization_and_destruction_0ii data-blogger-escaped-x1c="">
 8048095:       81 7d 0c ff ff 00 00    cmpl   $0xffff,0xc(%ebp)
 804809c:       75 0a                   jne    80480a8 <_z41__static_initialization_and_destruction_0ii data-blogger-escaped-x1c="">
 804809e:       e8 dd ff ff ff          call   8048080 <_z10randomrollv>
 80480a3:       a3 64 91 04 08          mov    %eax,0x8049164
 80480a8:       5d                      pop    %ebp
 80480a9:       c3                      ret

080480aa <_global__sub_i_main>:
 80480aa:       55                      push   %ebp
 80480ab:       89 e5                   mov    %esp,%ebp
 80480ad:       83 ec 08                sub    $0x8,%esp
 80480b0:       c7 44 24 04 ff ff 00    movl   $0xffff,0x4(%esp)
 80480b7:       00
 80480b8:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 80480bf:       e8 c8 ff ff ff          call   804808c <_z41__static_initialization_and_destruction_0ii>
 80480c4:       c9                      leave
 80480c5:       c3                      ret

There's a lot going on here and I'm not sure I wrote all of it.  Let's have a look.

We can see our main() and RandomRoll() functions (not sure why the name is munged, not important right now) but we also have two new functions, _global__sub_i_main() and _z41__static_initialization_and_destruction_0ii().  _global__sub_i_main() seems to call _z41__static_init_blah_blah(), which in turn seems to call our RandomRoll() function so without getting into it too deeply, I'd be happy to assume that _global__sub_i_main() is our initialisation function.

At the top of the disassembly is the contents of the .ctors section which contains that one 32-bit value, which is (interpreting the little-endian encoding) 0x080480aa, which is exactly the memory address of the _global__sub_i_main().
\ o /

At this point, my Google searching failed me.  For the life of me I can't figure out how to coerce the C compiler to create a .ctors section and populate it in the way that C++ does (answers on a postcard), however, I have a plan.

At the end of the day, the value that C++ put into the .ctors section is just your average common-or-garden initialised pointer variable, but in a special place, so what about this:

void test_init()
{
asm("int $3");
}

void* __attribute__ ((section (".ctors"))) test_init_ref = test_init;

I have my function, and I have a global variable which points to my function.  I've used some GCC specific runes to say that I want my variable to live in a section called .ctors.  Let's build this and see if it gives us the desired result (some sections removed for brevity):

>gcc hello.c -nostdlib
>objdump a.out -sS

a.out:     file format elf32-i386

Contents of section .ctors:
 80490b4 74800408                             t...

Disassembly of section .text:

08048074 :
 8048074:       55                      push   %ebp
 8048075:       89 e5                   mov    %esp,%ebp
 8048077:       cc                      int3
 8048078:       5d                      pop    %ebp
 8048079:       c3                      ret

Success!  We can see our function, and we can see the entry in .ctors which points to it, just as we saw in C++.  It's not the prettiest of solutions, but it will work for now.

Step 2 : Running


While we've gotten that all working, that's only half of the battle,  we still need to actually call all these from our program.

For the most part, all our program needs to know is where the .ctors array is and how long it is.  It can then iterate all the constructors and run them.  (These are function pointers after all, so these will always be relocated as required by the loader.  If the relocation didn't update function pointers, the program has more to worry about than constructors.)

One of the other searches I was running while doing this was to investigate how other systems dealt with this problem.  While that search yielded nothing of note, it did give me the idea to check out the default GCC  link script.  Currently my kernel uses a custom link script, but the applications still use the default one, where I found something interesting:

  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }

So, my link-script-fu is not great, but I can understand enough of this to see that crtbegin.o and crtend.o have special handling when it comes to the .ctors section.  Anything for the .ctors section from crtbegin.o goes at the beginning of the output, and anything from crtend.o goes at the end, effectively book-ending the array.  (I'll admit I didn't know what crtbegin and crtend were for, but GCC complained that I didn't have them when I created libc, so I put in empty ones.)

This information, coupled with what we just did for shuffling variables into the .ctors section gives me an idea.

I can put a variable in crtend.c like this:
int __attribute__((section(".ctors"))) __libc_internal_ctor_end = 0;

... and then put this in crtbegin.c:
extern int __libc_internal_ctor_end;  // <<-- in crtend.o
int __attribute__((section(".ctors"))) __libc_internal_ctor_start = 0;

void call_ctors()
{
 // Loop between __libc_internal_ctor_start and __libc_internal_ctor_end
}

The linker will put these two variables at the beginning and end of the .ctors list, and then the call_ctors() function can use them to work out where all the other constructors are.  Note that the content of those two variables is completely irrelevant, only their locations in memory is useful.

Step 3 : Testing


This blog post is way longer than it was going to be, so I'll be brief and say what I usually say.  It failed a bunch of times because I made some stupid mistakes, but after much hammering, it all works.

crt0.s now performs calls:

  • the C Library initialisation function,
  • the constructors function in crtbegin.c, and
  • the program's main() function.
The really good article I found the other day ( https://blogs.oracle.com/ksplice/entry/hello_from_a_libc_free ) also helped me to confirm at least some of what this was supposed to do, but only after I know what I was looking for.

I also found out along the way that when a library (such as libkernel.a) is linked in, the linker only includes those object files which are actually needed.  This gave a minor testing headache because I expected my init() function and its .ctor reference to appear just because the library was linked, but it didn't.  That was easily fixed though.

2013-11-03

2013-11-03 Doom Continued, and printf upgrade.

With Doom reaching the main() function, it's time to start working through it making sure everything is implemented and working.

Bearing in mine that sound and video are a way off yet, the short term goal is getting Doom to display its opening text, loading what files it wants, then "running", then exiting.

So the next thing it wants is a WAD file.  Doom searches to see which .wad files are available, but for now I'll just hardcode the path to the .wad file.

I seem to getting a strange problem with printf().  It's printing text to the screen.  That may not sound that odd, except for the fact that I haven't written it yet.  It appears that GCC is optimising calls to printf() with a constant string to be calls to puts() instead (which is written) so that why some of them were working.  (I thought -O0 turned off optimisations?)

V_Init: allocate screens.
M_LoadDefaults: Load system defaults.
Z_Init: Init zone memory allocation daemon.
W_Init: Init WADfiles.

The next thing that Doom needs is a sprintf() function which I don't have.  Originally, my printf() routine was a simple routine written by someone else (one of the few pieces of code my OS has used that I didn't write).  It was designed for an embedded system and didn't support all the features and options of printf().

Not long ago I sat down and wrote my own version, adding a lot more support for the various tokens, but it would still only output to the screen.  This version of the function is still used in the kernel as kstdio_printf() which gets called with debug messages all over the place.

So today I've taken that function and extended it once more.  After researching variadic functions and how they are handled in C, I've written implementations for the eight different version of printf():

  • fprintf() - variadic arguments, outputting to a stream
  • printf() - variadic arguments, outputting to stdout (a special case of above).
  • sprintf() - variadic arguments, outputting to a buffer.
  • snprintf() - variadic arguments, outputting to a buffer with a limit.
  • vfprintf() - va_list arguments, outputting to a stream.
  • vprintf() - va_list arguments, outputting to stdout (a special case of above).
  • vsprintf() - va_list arguments, outputting to a buffer.
  • vsnprintf() - va_list arguments, outputting to a buffer with a limit.

All of these are actually serviced by a single internal printf function which is capable of performing them all which reduces code duplication.

With all of these in place, I think that's good place to leave it for today.


2013-11-02

2013-11-02 Doom!

I don't know if I mentioned, but a while ago I downloaded the source code to the Linux port of Doom with the intention of getting it working with my OS.

"Why Doom?" I hear you ask.

  • Doom is a complex C program with proven functionality.  While I'm making Doom work with my OS, it's going to require me to write a lot more of the basic kernel and C library functionality.  Also the code should work so if there's a problem, it's probably in my code.
  • Doom is well known.  When I mention to people that I'm writing an OS, I commonly get asked "Does it run World of Warcraft / Counter-Strike / Half-life 3 etc.?"  I like to think that they're trying to get an idea of the capabilities of my OS in terms that they understand, so by being able to say that it'll run Doom, they'll get exactly that.
  • Doom has modest system requirements.  Given that my test systems have at most maybe 128Mb and maybe a 1GHz processor, Doom should run nicely, even with the inevitable inefficiencies of my code.
  • Doom doesn't require multiple threads.  Doom is intended to run on a DOS like system, so has no inbuilt dependency on the operating system to provide multi-threading support.
  • Doom has modest graphical requirements.  Given that my OS is firmly seated in the world of 640x480x4 (16 colours) for now, having a program which doesn't need many colours or a large screen resolution is a good thing.
  • Doom is awesome.  I mean come on, it's DOOM!  (Remember when first-person shooters were called "Doom clones"?)


So all that aside, how do we do this?

Firstly, Doom has two files (i_video.c and i_sound.c) which handle ... well ... video and sound.  Given that both of these are going to need some special consideration and a lot of work to get going, we'll put these off until later, so let's strip out the content of the all the functions.  I've a lot of work before it will call any of these anyway.

It looks like Linux has two different interfaces for accessing files, the C library fopen() and fread() functions, and the open() and read() functions from fcntl.h and unistd.h.  Doom uses both of these in varying measure.  Rather than pollute my current libc with functions that don't belong there, I should probably create a libkernel to contain everything else.

... some time passes ...

Ok, so with a lot of reorganising of header files, adding a new library to the project, and writing a lot of stub functions (one of which being the scanf-type functions, that's not going to fun to write), the OctoDoom executable builds.

While I do have ELF loading code (used for the HelloWorld program), I still haven't wired it up to the file loading routines so that I can load an actual executable file (it's on my to-do list).  For now, I'll have to load the Doom executable the same why as I do the HelloWorld one, that is, using Bochs to load the binary into memory at some high address.

So the first test run of Doom wasn't very successful (when are they?).  I think that the problem might be that I'm loading a program that's 987Kb into a memory that was only 1Mb to start, probably not going to have a good time.  That and the ELF loader call to kmalloc wasn't sanity checking the return value for a valid pointer.  So I had run out of memory, malloc returned NULL and the ELF loader carried on trying to use that pointer, schoolboy error.

Oh yeah, now it's loading Doom and getting as far as the main() function ... this stuff just got real!