Thanks for the compliments and feedback.
I know this thread is rather long, but the major gist of it beyond me asking questions about BIOS or MASM is this:
I want to build a modular BIOS where a user inputs a config file and the build system takes care of either building the correct set of object files OR generates the correct set of commands to build the BIOS, if the user wishes to build and test directly on DOS. My build system of choice is SCons, which requires a modern version of Python. I can coerce it to generate a list of commands to run on a modern machine which can be piped to a batch file and run on the target machine. Perhaps in the future an NMAKE-compatible Makefile is possible as well.
Because I wanted the source to be modular with features (example- try to support/don't support over 640k, boot both floppies/only one, add XT extensions to a PC motherboard/stay true to the PC motherboard BIOS features, turbo/no turbo), I need a way to insert remove code at will. Such a setup is NOT conducive to a single source file that has possibly hundreds of ".ifdef" or "#ifdef" conditional compilation blocks, and the boundaries between where code from one module ends and a number of other modules to add next won't always match up (though I can always enforce a convention for this between modules- i.e. if Module A connects to Module B, certain assumptions about CPU state/the PC state must be made).
My original goal to build it in C, while possible (there is a proof of concept zip file somewhere in this mess of bare-metal C that boots and beeps the PC speaker), felt impractical, as short of a #pragma hack it was impossible to suppress saving all registers in the interrupts and wasting space.
So to that end, I decided to write it in MASM/WASM assembly and use multiple source files divided by purpose, using the SCons build script to choose or exclude certain files
in order (key here) depending on the user's preferences in a config file. This setup would work fine if it wasn't for me learning that IBM and most clone BIOSes have a number (about 30 or so) of data structures and entry points (mostly interrupts, but other code as well) at literally the same address for compatibility with the PC motherboard. It appears no one listened to IBM when they said "don't directly jump into BIOS directly- entry points may change at any time".
So for compatibility, any BIOS I create needs to make sure that each hardcoded data structure is a module, and that modules with "freely movable" code do not overwrite modules with hardcoded entry points due to being too large. The Generic XT BIOS solved this problem by using a single source file
with a single logical MASM segment, and a macro that calculates the empty space between the current address (after the preceding code is done of course
) and the start address of the next absolutely-positioned code. This macro then pads the empty space with 0xFF.
This does NOT work when multiple source files are used- although the GROUP directive treats logical segments as one large segment, all references to the current assembler address are relative to the current logical segment, instead of the beginning of the group... so if a new segment belong to a specific group starts at 0x300, '$' will point to 0, not 0x300. This makes all calculations to figure out the number of bytes to pad incorrect. From what I have gathered,
it is impossible to get the distance between two different segments in MASM alone. As each segment in different modules has a different name, I haven't tried the public "combine" type yet to see if the same thing happens.
With that solution pretty much shot, the other solution is to specifically require a linker which can position OMF object file segments absolutely. The only such linker I know that can do this is WLINK. But the linker command lines to do this are complicated, and just having this responsibility alone also prevents me from easily adding or removing functionality, since the linker command line, build scripts, and ordering must be changed accordingly when functionality is added or removed. Small changes become more difficult/tedious to test on real hardware.
Additionally, the hardcoded entry points may also complicate the order in which object files must be linked, since order now matters beyond just making sure the hardcoded 8088 jump is last and the jump to 0xF000:0xE05B is first. This is partially alleviated using MASM classes- each source module contains either 1 or a few segments using MASM classes that control the order that segments are loaded. This makes sure thas long as modules of a particular class are linked in the correct order, I need not worry about the exact ordering of modules. Any "extra features" modules are simply linked in last, and the assembler/linker makes sure they are placed next to the corresponding MASM class.
So right now, my problem of adding/removing functionality at will is partially solved, but preserving entry points, I'm a bit between a rock and a hard place deciding what to try next. Either forget the entry point compatibility, use a complicated linker setup, or use only a single source file that will become unwieldy to maintain. None of these options are appealing, though the last one may be the path I have to take- maybe I can figure out how to make a single source file easier to maintain with the ".include" directive.