Admin | Edit

DOS tricks


Build an assembly file with NASM and run *.com on DOSBox

Here is a simple shell script to assemble a *.s file with NASM into a *.com file, it show the amount of bytes and run it under DOSBox :

set -eu
nasm $1.s -fbin -o $1.com
echo "bytes: "$(wc -c $1.com)
dosbox -c "mount c ${PWD}" -c "C:" -c $1.com > out.log 2> /dev/null

Usage (in same directory as myprogram.s): sh build.sh myprogram

Custom DOSBox config

To use a custom config specify it : -conf mycustom.conf

Most common stuff is to change machine field and memsize fields to get different display modes.

The DOSBox config can be found in ~/.dosbox on Linux and can be used as a template for a custom config file.

For mode 13h stuff (320x200) i prefer that my window fit to the resolution of the mode for accuracy (so no scale windowresolution=320x200 viewport_resolution=320x200, aspect change aspect=false) and i like a minimal window so i set window_decorations=false

Dissasemble a DOS *.com with NASM

Disassemble a 16-bit DOS program (use -b32 for 32-bit programs) : ndisasm -o100h prog.com

Run FreeDOS with QEmu (for SIMD etc.)

Running FreeDOS inside QEmu is easier than Bochs but is perhaps not as accurate.
For sharing files between host and FreeDOS a guide is available here.

Run FreeDOS with Virtual Box or VMWare player


A step by step guide is available here. Perhaps the most compatible option in term of maturity.

VMware Player

Mostly the same setup as VirtualBox but sharing files is a bit harder, vmsmount can apparently be used to mount shared folders but it was too cumbersome for me so i just created an ISO of my directory and mounted / accessed it as a drive : mkisofs -o share.iso ~/dos/share/

Run FreeDOS with Bochs emu (for SIMD etc.)

SIMD instructions are not available under DOSBox, FreeDOS and Bochs can be used (or QEmu, VirtualBox...) to test x86 code with SIMD (note that AVX require special setup code), Bochs also support more display modes than DOSBox so it is probably the way go to for a slightly more modern setup.

Here is a short setup of FreeDOS + Bochs with a sharable directory :

bochsrc edition for sharing host directory

Replace floppya line in bochsrc by this (also replace share path by the directory you want to share) :

floppyb: 1_44=vvfat:/home/julien/dos/share, status=inserted

Then you can access files in your share folder within FreeDOS by typing : b:

bochsrc edition for other issues

For some reasons (compiled from sources) i had to edit the FreeDOS bochsrc file to replace romimage and vgaromimage by :

romimage: file="/usr/share/bochs/BIOS-bochs-latest"
vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest.bin"

The VGA BIOS image might not be available, it can be downloaded here.

Also had issue with keyboard, just needed to specify keymap in bochsrc :

keyboard: type=mf, serial_delay=250, paste_delay=100000, keymap=/home/julien/bochs-2.7/gui/keymaps/x11-pc-fr.map

Doing code mistakes may output a lot of Bochs logs to the default log file used by FreeDOS bochsrc configuration so i edited it to output to the terminal instead: log: -

Install latest FreeDOS under Bochs

The pre-installed FreeDOS disk image for Bochs works but it is outdated and i had some issues with it (DIR command would show some errors and i couldn't edit FDCONFIG.SYS) so i installed the latest FreeDOS under Bochs with the bochsrc file from the pre-installed disk as template (edited with stuff above) and using these steps :
  • create a big enough (mine was at 200Mb) hd image file using Bochs tool : bximage
  • edit bochsrc and replace ata0-master line by : ata0-master: type=disk, path=path/to/your/disk/image.img, mode=flat
  • get latest FreeDOS floppy edition
  • copy the 144m directory from the ZIP file somewhere easily accessible (such as ~/144m), important as it save time later
  • edit bochsrc and replace floppya line by : floppya: 1_44=~/144m/x86BOOT.img, status=inserted
  • edit bochsrc and replace boot: c line by : boot: floppy
  • run Bochs, you should get the FreeDOS setup, follow the setup process
  • when it ask you for the next floppy go into Bochs CONFIG (icon on the menubar, for the X version at least), select 1, enter the floppy path and continue with the default options
  • eject / insert the new floppy by clicking on the A: icon on the menubar and continue the install process
  • repeat the floppy insertion until the install process is completed
  • once FreeDOS is installed edit bochsrc and replace boot: floppy line by : boot: c
  • you should now have the latest FreeDOS running under Bochs
If the install step fail it is likely that your drive image file does not have enough space.

My boshrc

megs: 32
romimage: file="/usr/share/bochs/BIOS-bochs-latest"
vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest.bin"
vga: extension=vbe, update_freq=15
floppya: 1_44=/home/julien/144m/x86BOOT.img, status=inserted
floppyb: 1_44=vvfat:/home/julien/dos/share, status=inserted
ata0-master: type=disk, path=/home/julien/freedos/hd1.img, mode=flat
boot: c
log: -
mouse: enabled=0
cpu: ips=15000000
keyboard: type=mf, serial_delay=250, paste_delay=100000, keymap=/home/julien/bochs-2.7/gui/keymaps/x11-pc-fr.map

Mode 13h (320x200 256 colors)

A good introduction on mode 13h setup is available here.


To change the VGA palette with short code see TomCat article, some of the code shown on this page is optimized to setup 13h mode and do the palette setup at the same time.

Here is an online tool (JavaScript code) to prototype a VGA palette.

Mode 13h switch and putting pixels

Mode 13h setup might highly depend on the program layout but i somewhat like this short approach for putting pixels :

mov al,0x13
int 0x10 ; switch to mode 13
les bx,[bx] ; trick to prepare segment register (es), disadvantage is a 16px offset when drawing

; generic way to put white centered pixel with fixed offset (+16, due to the trick above), could also be shorter by moving the calculation to "di" or the use of "stosb" which write al at es:di and increment di (depending on direction flag)
mov di,160+100*320
mov byte [es:di+16],0x0f

BIOS pixel write (see VESA below) can also be used to put pixels, it is slow but convenient sometimes due to working with x and y instead of an index.

High resolution (VESA) display mode NASM sample

About VESA / real mode

With VESA modes (high resolution / high bpp) and in real mode (DOS default) there is a limitation on how you write to the framebuffer since you cannot address all the framebuffer linearly so there is three solution, the ideal one involve to get into unreal mode / protected mode after display mode change so you can address the framebuffer linearly, the code to do that can be quite heavy (~40 bytes and perhaps more for the best compatibility), the two other lighter solution is to either use the BIOS pixel write command which is slow and only works at low bpp or select the appropriate active page (banking) to write to which is perhaps the best compromise here.

Note that unreal mode is active by default on DOSBox so there is no addressing issues if you target DOSBox, there is several limitations on DOSBox though such as unavailable SIMD instructions etc. so if you want to target real DOS with slightly modern legacy hardware or QEmu / Bochs / VBox with FreeDOS you might need to address this issue.

All this does not happen with the old modes such as 13h because it all fit into a 64 KiB segment so you get a linear framebuffer out of the box in real mode without access limitations.

BIOS pixel write (1280x1080 256 colors)

Here is a 1280x1024 256 colors NASM setup which write a centered white pixel, it avoid the addressing issues of real mode but it is slow  :

org 100h

mov ax,4F02h
mov bx,0x4107 ; 4 is to ask for screen clear 107 is display mode
int 10h
 mov ah,0x0c ; write pixel command
 mov al,0xf ; color
 mov bh,0 ; page number
 mov cx,640 ; x
 mov dx,540 ; y
 int 10h ; https://en.wikipedia.org/wiki/INT_10H

Paging / banking (1440x900 32bpp)

This has a heavier setup due to the more direct way to write to the framebuffer but it is faster than the BIOS pixel write when the bank is switched when necessary.

Some code shortcuts are possible by skipping VESA mode info and using a hardcoded VRAM write address / window granularity etc. but may be highly unsafe and may break the compatibility of your program on some hardware.

Note that this resolution is unusual so you may instead want to switch to a common high resolution if this fail.

org 100h

; VESA get mode info (to get VRAM write address and window granularity later)
mov di,$200 ; info block addr (will get written)
mov ax,$4F01
mov cx,$4180
int $10

mov ax,$4f02 ; switch display mode
mov bx,$4180 ; 4 is screen clear 180 is mode (1440x900 32bpp)
int 10h

; compute screen coordinate, bank number and write offset
mov ax, 900/2 ; y
mov bx, 5760 ; BytesPerScanLine (could also be taken from info block at : [es:di + 0fh])
mul bx
add ax, 1440/2*4 ; x
mov cx,[es:di + 04h] ; get window granularity (32k, 64k etc)
sal cx,10 ; * 1024 (cx = bankSize)
div cx ; index / bankSize -> dx = bank number and ax = bank offset
; get vram write addr from info block
mov es, [es:di + 08h]
mov di, dx ; write offset
mov dx, ax ; bank number

; switch bank with bank number in dx
xor bx, bx
mov ax, $4f05
int 10h

; plot at di within current bank
mov dword [es:di],0xffffff

Here is a highly unsafe slightly shortened version which makes many assumptions :

bits 16

org 100h

; switch display mode
mov ax,$4f02
mov bx,$4180 ; 4 is screen clear 180 is mode (1440x900 32bpp)
int 10h

; assume fixed write address
push 0xa000
pop es

; compute coordinate
mov eax,(1440/2+900/2*1440)*4
mov ecx,32*1024 ; assume dual 32k window
div ecx
mov di, dx ; get write offset
mov dx, ax ; get bank

; switch bank
xor bx, bx
mov ax, $4f05
int 10h

; plot at di within current bank
mov dword [es:di],0xffffff

Protected mode

See prods such as this one for a great example of a "modern" DOS setup. (protected mode, multi-threaded code, high resolution, FPU)


DEBUG.EXE can be used on DOS.

Here is a generic routine to print the value of a 8-bit register (AL) to the standard output (DOS console), can also be used as a crude debugging tool :

; example usage :
;  mov al,255
;  call printALHexValue
    mov ah,2
    mov dl,'0'
    int 21h
    mov dl,'x'
    int 21h

    mov bp,sp
    mov dh,[bp+14]
    mov dl,dh
    shr dl,4
    cmp dl,10
    jb l1
    add dl,7
    add dl,'0'
    int 21h

    mov dl,dh
    and dl,0xf
    cmp dl,10
    jb l2
    add dl,7
    add dl,'0'
    int 21h

    mov dl,0
    int 21h

Video capture

DOSBox has easy video capture by keys combination Ctrl + F7.

Then the video can be scaled with ffmpeg at higher resolution (for stream etc) by using nearest neighbor scaling and padding, here is the settings i use for 320x200 (5x scale and padding to 1920x1080) :

ffmpeg -r 60 -an -i input.avi -vf "scale=1600:1000,pad=width=1920:height=1080:x=160:y=40,format=yuv420p" -sws_flags neighbor -t 59 output.mp4


back to topLicence Creative Commons