2

im trying to build myself a small bootloader/program for my old 286. i started doing this with fasm and quickly grew tired of writing assembly. so i figured i want to write it in c instead, but the choice of c toolchains targeting 286 seems to be slim. i opted for open watcom 1.9. so far i managed to get something to compile, but the output is broken, and i dont know why.

init.asm, written to be compatible with watcoms assembler, initializes registers, jumps into the c function kernelMain and provides a simple print function:

.code INIT
extern kernelMain_ : proc

org 7c00h

;export functions
PUBLIC initMain
PUBLIC _print
initMain:
;init stack to use the 30kb before this boot sector
;init other segment registers
mov bp,7C00h ; stack base pointer
xor ax,ax ; ax = 0
mov ds,ax ; data segment = 0
mov es,ax ; extra segment
mov ss,ax ; stack segment = 0
mov sp,bp ; stack pointer = 7C00h

jmp kernelMain_

;void __cdecl print(char* message, unsigned char length);
_print:
  pop si ; message
  pop cx ; length

printLoop: ; print string which is at adress si with length cx
  mov ah,0Eh ; TTY output function number
  mov bh,0 ; page 0
  mov bl,01h ; foreground color
  lodsb ; loads byte from [si] into al and advances pointer in si by 1
  int 10h ; BIOS video interrupt, write byte to screen
  loop printLoop ; loop till message is printed
  ret

end

test.c, contains the kernelMain function:

extern void __cdecl print(char* message, unsigned short length);


void kernelMain(void)
{
  print("this is a test", 14);
}

and this is what i use to build it

wasm -2 -ms init.asm
wcc -2 -d0 -wx -ms -s -zl test.c
wlink file init.obj file test.obj format raw bin name test.bin option NODEFAULTLIBS,verbose,start=initMain ORDER clname CODE segment INIT

the resulting binary is ~32KB instead of the expected few bytes because the linker interprets the org instruction to actually fill everything with 0 bytes before the code. removing the 0 padding before the code breaks the code completely. it doesnt print anything. removing the org instruction gives me code that executes and prints, but it also prints out some random garbage before the test message

can someone help me?

fredlllll
  • 75
  • 8
  • WATCOM should be a good choice here. Can it make `.com` style executables instead of `.exe` though? Also if you're using C library functions like `print` you may end up with an unexpectedly large executable due to dependency requirements. You might have to do that yourself with interrupt calls to BIOS. – tadman Jul 31 '23 at 22:59
  • @tadman i specified that im making a bootloader. its supposed to boot from that. no dos that can load an exe or com. if i could use dos for this i would, but the computer gets stuck in dos when i enable the hdd (which i want to recover data from). as i wrote in my question. the print function is my own. why do you think im using a library function? – fredlllll Jul 31 '23 at 23:09
  • What I mean by `.com` is those files are loaded and just straight up executed from byte zero. They're as raw as you can get, but also subject to a size limit of 64KB, much like a bootloader might be. Also I see your `print` function now, it seemed like you were using a library function at first due to the similar name. – tadman Jul 31 '23 at 23:11
  • @tadman i looked into it, but they get loaded into a different adress, so the linked adresses wouldnt be right anymore? – fredlllll Jul 31 '23 at 23:12
  • Yeah, it's possible your mixing and matching of C and assembly here is the root cause, as just one or the other is probably fine. I'm not sure the linker will understand the intended memory layout. In fact, I'm not sure you should be using a linker at all for a bootloader. Could you do this as one C file with inline assembly instead? – tadman Jul 31 '23 at 23:13
  • _when i enable the hdd (which i want to recover data from)._ Is your main goal to recover files from the hard drive? If so, there are pata/ide to usb adapters you can get for $25 where you can plug in the drive as an external drive to a modern computer running Winx (or Linux) and it will be able to mount/repair the FS – Craig Estey Aug 01 '23 at 00:04
  • See: https://www.amazon.com/Ide-Usb-Adapter/s?k=Ide+To+Usb+Adapter – Craig Estey Aug 01 '23 at 00:13
  • @craigestey call me back when you have an adapter for ST506 which is what the harddrive uses. if it had IDE this would be much easier – fredlllll Aug 01 '23 at 00:50
  • 1
    @tadman i played around with this idea. i got it so far to output the right assembly code and it seems to properly print 14 characters as expected from my code... BUT it prints garbage, because it expects the code to be at 0x00 and not at 0x7c00 where the bios actually puts it. any idea how to tell the compiler/linker that the code is going to be there? – fredlllll Aug 01 '23 at 00:53
  • I'd recommend removing the `org 0x7c00` from `init.asm` and have the linker set the ORG with `wlink file init.obj file test.obj format raw bin name test.bin option NODEFAULTLIBS,verbose,start=initMain,OFFSET=0x7C00 ORDER clname CODE segment INIT` . This should fix the bloated file and the generated C code should be relative to 0x7C00 rather than 0x0000. – Michael Petch Aug 01 '23 at 00:57
  • You have a problem with your `_print` function. Other major problem - when C call's a CDECL function it pushed the return address on the stack after your parameters. So you are popping the wrong values into `SI` and `CX`. The 16-bit calling conventions require SI, DI, SP, BP, and DS to have the same values when the function returns as they had when the function started. Usually you handle parameters with a stack frame. So instead of `pop si` `pop cx` do `push bp` `mov bp, sp` `mov si, [bp+4]` `mov cx, [bp+6]` `push si` . And then replace `ret` with `pop si` `mov sp, bp` `pop bp` `ret` – Michael Petch Aug 01 '23 at 00:57
  • `SI` was changed by the `lodsb` instruction and since it needs to be preserved by the function I push it to save and pop it at the end to restore it. `BP` and `SP` will also have the same values as they did on entry with that code. Calling convention is important because if you clobber a register that needs to be preserved then you can cause weird and wonderful undefined behaviour that you may not detect and it becomes difficult to track such a problem later on. – Michael Petch Aug 01 '23 at 00:59
  • 1
    @michaelpetch the offset in the start was kinda what i was looking for... BUT(!!!) watcom thinks its smarter than us and corrects it to 0x8000 with the warning "Warning! W1098: Offset option must be a multiple of 4096" so the adress pushed onto the stack now is 802A instead of the expected 7c2a and i get an empty output. i now have this c code https://pastebin.com/8TMSz6Ci that turns into this assembly https://pastebin.com/mghY1tXZ no more init.asm required. but the offset being ""corrected"" is a problem now (i did hopefully fix the shortcomings of the print function here) – fredlllll Aug 01 '23 at 01:16
  • Perhaps you should edit your question and post more information about your 286 system. The make/model of the motherboard, its bus (e.g. ISA) and the make/model of the disk controller, etc. There are ISA bus adapters where you can plug your ISA card [disk controller] into it and get USB out the other end. So, you can use your old disk controller on a modern system. FYI, I've done similar to get an old DDS-3 tape drive to work and read old tapes, so it's possible. – Craig Estey Aug 01 '23 at 01:23
  • I presume you don't have boot/install floppies for DOS for this system, otherwise you'd be using that? – Craig Estey Aug 01 '23 at 01:24
  • @craigestey i do have images of said install floppies, but i dont want to overwrite the data on the harddrive so i can save it :P i also plan on writing some other programs for this computer, so this isnt only for this. and throwing my time at it is free, buying obscure hardware for isa emulation probably isnt – fredlllll Aug 01 '23 at 01:26
  • @michaelpetch 1.9 as mentioned in the question. how do i obtain a build for windows for the 2.0 beta? – fredlllll Aug 01 '23 at 01:27
  • Oh I missed the v1.9 in the question. Probably the reason you see the old 4096 byte alignment. Things appear to work with the 2.0beta (I was able to assemble/compile/link) here. I have to look to see where a Windows installer for v2 can be found. – Michael Petch Aug 01 '23 at 01:35
  • You can find the latest releases and installer here. https://github.com/open-watcom/open-watcom-v2/releases . Go with the `Current Build`. If you click the little down arrow next to `Assets` you should see all the installers. The windows one is here for 64-bit Windows https://github.com/open-watcom/open-watcom-v2/releases/download/Current-build/open-watcom-2_0-c-win-x64.exe – Michael Petch Aug 01 '23 at 01:39
  • @michaelpetch aw crap, i looked at that a few days ago and only looked at the assets of the latest ci build.... didnt see that other one had installers. thx – fredlllll Aug 01 '23 at 01:44
  • _Side note:_ I've written device driver and firmware for ST506 drives for a VME-bus Unix supermicro with a custom disk controller designed/manufactured by the company I worked for, circa 1980s. And, data recovery with such drives with CRC errors. If it were me, I'd use the ISA bus to USB adapter first: To get a data backup. Then, you could install DOS on the hard drive [or debug your code] and restore/merge the data without fear of trashing the hard drive data. To me, this seems less risky than trying to develop a standalone boot disk that may only be partially debugged. YMMV – Craig Estey Aug 01 '23 at 01:45
  • @craigestey well the plan is to read the sectors onto floppy (if there are no read errors) and then piece it together into an image on a modern computer, and dissect it there for the data i want. – fredlllll Aug 01 '23 at 01:59
  • Depending on the relative size of the hard disk, shoving N floppies in/out (if you can find them) might be problematic. Compatible floppies might be hard to come by. Again [if it were me], I'd get the UART working [probably easier to get working than the floppy controller]. Connect it to the host machine (USB/UART). Then, I'd write a cheezy protocol to transfer data block-by-block onto the host machine. Although, UART is slower (e.g. 9600 baud), it's an _unattended_ fire-and-forget. It's approx 3MB / hour but no mechanical messing with floppies. And, do you have compat floppy drive on host? – Craig Estey Aug 01 '23 at 03:02
  • If the hard disk has CRC errors, ring me back as I have some suggestions (I even still have the recovery code I wrote in 1989 ;-) – Craig Estey Aug 01 '23 at 03:03

1 Answers1

2

thanks to the commenters i now got what i wanted.

fixes:

  1. abandon the asm seperate from c code idea and only use c with inline assembly
  2. upgrade to open watcom 2.0 beta from here https://github.com/open-watcom/open-watcom-v2/releases (current build contains builds and installers)
  3. add offset=0x7c00 option

test.c:

void kernelMain(void);
void initasm(void);
#pragma aux initasm = \
"mov bp,7C00h", \
"xor ax,ax", \
"mov ds,ax", \
"mov es,ax", \
"mov ss,ax", \
"mov sp,bp", \
"jmp kernelMain" \
modify [ AX ];

void __pascal print(char* message, unsigned short length);
#pragma aux print = \
"pop cx", \
"pop si", \
"printLoop:", \
"mov ah,0Eh", \
"mov bh,0", \
"mov bl,01h",\
"lodsb", \
"int 10h", \
"loop printLoop" \
modify [ SI CX AH BH BL];

void init(){
  initasm(); //jumps to kernelMain after initializing registers. seperate init was necessary as adding any actual function calls (like print) would add push instructions before the inline assembly which would be called before initializing the stack and registers
}

void kernelMain(void)
{
  print("this is a test", 14);
}

buildtest.bat:

wcc -2 -d0 -wx -ms -s -zl test.c
wlink file test.obj format raw bin name test.bin option NODEFAULTLIBS,verbose,start=init_,OFFSET=0x7C00

this gives me a raw binary linked at adress 0x7c00.

to make a bootable sector that can be written to a floppy(or be used by a virtual machine as floppy) you have to 0 pad the rest of the file to 510 bytes length, and add 0x55 0xAA as the last 2 bytes,(for a total of 512 bytes) which mark it as a bootable sector.

remarks: the seperate asm and c thing would probably still work as intended if the author (me) could actually write proper assembly

fredlllll
  • 75
  • 8
  • I'm curious about one thing. Is there a reason you want to specify a length? Are there cases where you'd don't expect a string to be NUL terminated? – Michael Petch Aug 01 '23 at 03:57
  • @michaelPetch this is an artifact of me just copying my asm routine to print text, which expected the length of the string in a register instead of calculating it. want to propose a change for a print that doesnt require the length? – fredlllll Aug 01 '23 at 22:35
  • You might want to take a look at https://pastebin.com/Mq3gDNUW . You'll notice what I did was put just the int 0x10/ah=0x0e into inline assembly using `#pragma aux` but I created an inline C function that loops through a string until a NUL char. I basically do bare minimum in assembly and do the rest in _C_. The optimizer in Watcom isn't perfect but it does a reasonable job. Example code also provides you with inline functions and externally callable functions. Sometimes using inline functions can bloat the code if called many times where as an externally callable version can save space. – Michael Petch Aug 02 '23 at 05:30
  • 1
    @michaelpetch thats much better. thanks. i also managed to actually do some bootloading now. bootloader just loads the next sectors of floppy and then jumps there to run the test program. that inline __asm is much easier to use than the pragma aux. thought watcom couldnt do anything else – fredlllll Aug 02 '23 at 23:18
  • I've made some adjustments from yesterday when I wrote it. I simpified it a bunch and added passing the drive number through. You may already have the latest, but if you don't I'm just giving you a heads-up. Glad you found some use for it. If you don't want the external functions in bios.c you can just choose not to call any of the functions in there and remove it from the linker step. – Michael Petch Aug 02 '23 at 23:29
  • @MichaelPetch seems that hard drive sector 1 is still readable https://i.imgur.com/rfp7Jej.jpg i disassembled this bootloader and it seems that it fails to read the first sector of the boot partition. but thanks to the c code i can now make software to analyze errors much more easily :) – fredlllll Aug 21 '23 at 20:32