35 comments

[ 1.4 ms ] story [ 86.2 ms ] thread
Awesome. I’ve wondered the same. Thanks for the tutorial.
Today is not the best day as a HongKonger. But that is a good source and I put aside when I have the heart and soul as a 386 hacker. God bless. Good work.
The code was surprisingly short and readable. If you have a minute, I’d suggest taking a peek; it’s pretty well designed.
Very refreshing approach, as someone deeply interested in this topic but without the time to dive in to the docs myself
> they only work in the real-address mode

So it's not for i386, it's for XT by the sounds of it.

XT implies 8086 and there are more real address mode instructions than those available in 8086. AFAIK real address mode can use pretty much any 32bit instructions with a prefix, which is what this project is doing.

(this incompatibility bit me a bit many years ago when i was writing a small real mode OS in assembly - i used a bunch of instructions i thought were available in 8086 but when i tried the OS on a real XT machine it would lock up :-P)

Targeting 8086-level of instructions was actually my first idea. It wasn't possible with gcc/clang, but Turbo C is freeware these days, so... ;) In case you're interested, see https://github.com/luke8086/nf/
As someone mentioned below you could also use OpenWatcom, if for no other reason than it should have a slightly better optimizer (though from my tests i haven't seen much of a difference between the two, at least for code i tried to optimize a lot myself). AFAIK one of the OWv2 contributors is working on a 16bit OS so the 16bit support should be excellent.

OWv2 is also available as a Linux binary so you could use it for crosscompiling in a similar way to GCC.

Good point, OpenWatcom is obviously miles ahead of Turbo C, but it's also much more complex. I tinkered with it for a while, and ended up using the latter for its simplicity.

DOSBox can be easily installed on any system, you can save the entire C: in a git repo and it'll probably keep working in any number of years from now :)

Meanwhile, OWC doesn't have packages in popular Linux distros, only a custom binary installer, and on MacOS it doesn't even have that.

No, he's using -m16 -march=i386 which creates 32 bit code that boots in 16 bit mode using the 32 bit size prefix on most instructions, and that will only work on 80386 and above.
This is so insane, I had no idea such a thing was possible:

16 vs 32 bit It is possible to use 32-bit instructions in the real-address mode, they just need to be marked with address-size and operand-size prefixes. The -m16 option for clang/gcc, and .code16 directive in GNU assembler, do exactly that. The resulting code is 32-bit, only marked everywhere with those prefixes. It's not compatible with actual 16-bit CPUs.

"Unfortunately the 32-bit addresses still cannot exceed the boundary of the segment (65535), otherwise they'll trigger an exception. QEMU doesn't emulate this behaviour, so it's useful to occasionally test with Bochs."

(comment deleted)
>> they only work in the real-address mode

Presumably it would work just fine in the "Unreal mode"[1] - one which is very similar to real, with the added benefit of being 32bit address space, and 32bit instructions by default

[1] https://en.wikipedia.org/wiki/Unreal_mode

I'm not sure if defaulting to 32-bit instructions would still allow calling BIOS, but good point!

In this case the design goal was to show the easiest way to jump into C and start building the actual application, so I just tried to avoid any trickery (even enabling the A20 line)

You just set your chosen segment register(s) in protect mode, jump back to real mode, and then can use this segment register(s) in combination with the 32bit prefix to make a 32 bit memory access. All other segment registers are untouched and you can call into the bios just fine.
We actually wrote an optionrom in C for qemu a few years back. The main challenge was making it work on all the different C compilers that qemu supports because it makes some assumptions about the layout of the final binary which are not necessarily true for modern optimizing compilers. Anyhow if ever use qemu on x86 with the -kernel option then you are running this code.

https://git.qemu.org/?p=qemu.git;a=blob;f=pc-bios/optionrom/...

I feel your pain, so sorry that you still need to support the .code16gcc hack! My first approach was actually to avoid GCC/clang altogether, and use Turbo C instead. It came with its own set of problems, but the output was much more predictable. In case you're interested, you can find the code at https://github.com/luke8086/nf
tkchia [0] has been working on getting gcc to emit 16-bit clean code (no 32-bit code prefixes) along with ancillary tooling like binutils, a couple different libc's, and some of the things you'd expect from a 16-bit x86 C compiler like far pointers and DOS-specific libc functions [0]. The use case is to build the FreeDOS Kernel and ELKS 16-bit Linux Kernel from a 32-bit or 64-bit Linux system.

[0] https://github.com/tkchia/gcc-ia16

[1] https://github.com/tkchia/build-ia16/releases

640KB ought be enough memory for anyone - Bill Gates
"I've said some stupid things and some wrong things, but not that. No one involved in computers would ever say that a certain amount of memory is enough for all time."

An actual Bill Gates quote

If you could get a TCP/IP stack working, it could be used for a simple IoT device.
I wonder what a minimal version of this would look like for x64 UEFI machines. Does anyone know whether such a project exists?
It's pretty easy. With CL.exe (Microsoft's Visual Studio compiler) just use the /SUBSYSTEM:EFI_APPLICATION flag. You may have to manually specify the entry point. Note that there's no default cstdlib, so if you want to use the EFI APIs (e.g. for a "printf" function or other things from the article), you'll need to tell the compiler about the header definitions (as you would any C API).
If the available RAM were somehow expanded to the full amount available in hardware, it seems like this could be an interesting type of HPC system. No bloat, simple execution.
Millennial discovers DOS. Film at 11.

;)

It's running only on BIOS, no operating system.
That's literally what DOS is... a loading program that itself ran from a boot sector and provided services layered on top of BIOS calls.
I "discovered" DOS 25 years ago, not sure what's your point?
Sorry, I read your comment as "he's running this on DOS" not "he's made something that runs at the same level as DOS".