User Tools

Site Tools


blog:pushbx:2026:0112_early_2026_work_on_emu2_etc_test_cases

Early 2026 work on emu2 etc, test cases

2026-01-11

lDOS kernel

ETA:

  • Harden CollectNextFreeMCB to check that the MCB to collect is contiguous to the collecting one. This is a cornercase that usually cannot occur, but it is possible to set up a pathological case in which the MCB chain gets corrupted. This is only a problem since the NOCOLLECTALLMCBS patch because prior to this CollectNextFreeMCB was always called with the "linear MCB flags".

patchini

steptrac

New script to step through an emu2-style CPU log. This log is similar to a list of lDebug trace register dumps, except both register lines and the disassembly line are all on the same line. step.pl allows to specify an input file and an output file to append to, and provides several commands to step through the input. The output is expanded into the lines as expected by TracList.

The input loop was copied from TracList to allow receiving keys without trailing linebreaks.

wwwecm scripts

Update sync-gitrepos-with-github to git fetch from origin, pass the -u switch, and pass '*:*'. This seems to do what we want, namely update the default log on bare repos. Repos need to be set up as follows:

  • In ~/gitrepos: git clone SOURCE -n REPO
  • In ~/mirror-gitrepos: touch REPO

emu2

Robert pointed me to emu2 recently again. Loving to work with simple 8086 DOS emulators, I picked it up and worked some on my own fork, based on amuramatsu's fork. Here's what I did:

Test programs

I uploaded a number of test programs that were used for recent changes:

testmcb.asm

testmcb.asm: Test program for the original emu2 repo, for a bug report on MCB coalescing. What it does: Allocate a number of 64 KiB memory blocks, free them in a certain order, then try to allocate a larger block.

	cpu 8086
	org 256
start:
	mov ah, 4Ah
	mov bx, 1000h
	int 21h
	mov di, array
	xor cx, cx
loopalloc:
	mov ah, 48h
	mov bx, 1000h
	int 21h
	jc endalloc
	stosw
	inc cx
	jmp loopalloc

endalloc:
	cmp cx, 4
	jb errorinit
	mov si, array
	mov cx, 4
loopfree:
	lodsw
	mov es, ax
	mov ah, 49h
	int 21h
	loop loopfree

	mov ah, 48h
	mov bx, 3000h
	int 21h
	jc errorexit
	mov dx, msg.success
	mov ah, 09h
	int 21h
	mov ax, 4C00h
	int 21h

errorexit:
	mov dx, msg.errorexit
	jmp error

errorinit:
	mov dx, msg.errorinit
error:
	mov ah, 09h
	int 21h
	mov ax, 4C01h
	int 21h

msg:
.errorinit:	db "Too small initial allocation!",13,10,36
.errorexit:	db "Failed last allocation!",13,10,36
.success:	db "Success!",13,10,36

	align 2
array:

testmcb2.asm

testmcb2.asm: Test program to check that lDOS kernel option COMPAT=COLLECTFREEMCB works as intended. What it does: Allocate several 64 KiB blocks, then free some of them in a certain order.

	cpu 8086
	org 256
start:
	mov ah, 4Ah
	mov bx, 1000h
	int 21h
	mov di, array
	xor cx, cx
loopalloc:
	mov ah, 48h
	mov bx, 1000h
	int 21h
	jc endalloc
	stosw
	inc cx
	jmp loopalloc

endalloc:
	cmp cx, 4
	jb errorinit

	mov es, word [array + 4]
	mov ah, 49h
	int 21h
	mov es, word [array + 2]
	mov ah, 49h
	int 21h
	mov es, word [array + 0]
	mov ah, 49h
	int 21h

	mov dx, msg.success
	mov ah, 09h
	int 21h
	mov ax, 4C00h
	int 21h

errorexit:
	mov dx, msg.errorexit
	jmp error

errorinit:
	mov dx, msg.errorinit
error:
	mov ah, 09h
	int 21h
	mov ax, 4C01h
	int 21h

msg:
.errorinit:	db "Too small initial allocation!",13,10,36
.errorexit:	db "Failed last allocation!",13,10,36
.success:	db "Success!",13,10,36

	align 2
array:

tsr.asm

tsr.asm: Test case for a simple TSR program that calls int 27h with dx equal to zero. Used to test the int 27h service in emu2. What it does: Close the first 20 process handles, free the environment block, and TSR using int 27h.

	cpu 8086
	org 256
start:
	mov cx, 20
	xor bx, bx
.loop:
	mov ah, 3Eh
	int 21h
	inc bx
	loop .loop

	xor ax, ax
	xchg ax, [2Ch]
	mov es, ax
	mov ah, 49h
	int 21h
	xor dx, dx
	int 27h
	mov ax, 4CFFh
	int 21h

testrep.asm

testrep.asm: Test case for tracing repeated string instructions. Used to develop emu2 Trace Flag support. What it does: Use rep lodsb with several different counter values in cx.

	cpu 8086
	org 256
start:
	xor ax, ax
	mov si, buffer
	mov cx, 0
	rep lodsb
	mov si, buffer
	mov cx, 1
	rep lodsb
	mov si, buffer
	mov cx, 2
	rep lodsb
	mov si, buffer
	mov cx, 3
	rep lodsb
	mov ax, 4C00h
	int 21h

	align 2
buffer:
	db 1, 2, 3, 4

testrepz.asm

testrepz.asm: Test case for repz and repnz prefixed string instructions to preserve all flags on execution with cx equal to zero. Used to develop emu2.

	org 256
start:
	xor cx, cx
	cmp al, al
	repe cmpsb
	or al, 1
	repe cmpsb
	cmp al, al
	repne cmpsb
	or al, 1
	repne cmpsb
	mov ax, 4C00h
	int 21h

int6.asm

int6.asm: Run an interrupt 6 by means of int instruction.

	cpu 8086
	org 256
start:
	int 6
	mov ax, 4C00h
	int 21h

testirq.asm

testirq.asm: Trigger IRQ #8 several times to test that the default ROM-BIOS handler correctly issues EOIs. This was used in conjunction with the int 2Dh test handler of emu2, to trigger the IRQ #8 (int 70h).

	cpu 8086
	org 256
start:
	mov ax, 3570h
	int 21h
	mov word [next + 2], es
	mov word [next + 0], bx
	mov ax, 2570h
	mov dx, handler
	int 21h

	in al, 0A1h
	and al, ~1
	out 0A1h, al

	xor ax, ax
	int 2Dh
	mov dx, [variable]
	mov ax, 2600h
	int 2Dh
	mov dx, [variable]
	mov ax, 3800h
	int 2Dh
	mov dx, [variable]

	lds dx, [next]
	mov ax, 2570h
	int 21h
	mov ax, 4C00h
	int 21h

handler:
	inc word [cs:variable]

	jmp 0:0
next: equ $ - 4

	align 2
variable:	dw 0

testxms.asm

testxms.asm: Test program to allocate or free XMS memory blocks. Not tested much. Used to check emu2 XMS allocation works and fails as expected.

%if 0

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif

	cpu 8086
	org 256
start:
	mov si, 81h
.skip:
    lodsb
    cmp al, 32
    je .skip
    cmp al, 9
    je .skip
	cmp al, '!'
	jne .notex
	not byte [wantfree]
.notex:
	dec si
	call getnumber

init:
	mov ax, 4300h
	int 2Fh
	cmp al, 80h
	je .gotxms
	mov dx, msg.noxms
	jmp errorexit

.gotxms:
	mov ax, 4310h
	int 2Fh
	mov word [handler], bx
	mov word [handler + 2], es
	rol byte [wantfree], 1
	jc free

alloc:
	mov dx, msg.alloc1
	call disp_msg
	mov ax, di
	call disp_ax_dec
	mov dx, msg.alloc2
	call disp_msg

	mov dx, di
	mov ah, 09h
	push cs
	call callhandler
	cmp ax, 1
	jne .error

	push dx
	mov dx, msg.alloc3
	call disp_msg
	pop ax
	call disp_ax_dec
	mov dx, msg.alloc4
	call disp_msg

	mov ax, 4C00h
	int 21h

.error:
	push dx
	mov dx, msg.alloc5
	call disp_msg
	mov al, bl
	call disp_al_hex
	mov dx, msg.alloc6
	call disp_msg
	pop ax
	call disp_ax_dec
	mov dx, msg.alloc7
errorexit:
	call disp_msg

	mov ax, 4C01h
	int 21h

free:
	mov dx, msg.free1
	call disp_msg
	mov ax, di
	call disp_ax_dec
	mov dx, msg.free2
	call disp_msg

	mov dx, di
	mov ah, 0Ah
	push cs
	call callhandler
	cmp ax, 1
	jne .error

	mov dx, msg.free3
	call disp_msg

	mov ax, 4C00h
	int 21h

.error:
	mov dx, msg.free5
	call disp_msg
	mov al, bl
	call disp_al_hex
	mov dx, msg.free6
	jmp errorexit


disp_msg:
	push ax
	mov ah, 09h
	int 21h
	pop ax
	retn


disp_ax_dec:
	push ax
	push cx
	push dx
	mov cx, 10
	mov dx, -1
	push dx
.loop1:
	xor dx, dx
	div cx
	push dx
	test ax, ax
	jnz .loop1

.loop2:
	pop ax
	add al, '0'
	jc .end
	xchg dx, ax
	mov ah, 02h
	int 21h
	jmp .loop2

.end:
	pop dx
	pop cx
	pop ax
	retn


disp_ax_hex:
    xchg al, ah
    call disp_al_hex
    xchg al, ah
disp_al_hex:
    push cx
    mov cl, 4
    rol al, cl
    call disp_al_nybble_hex
    rol al, cl
    pop cx
disp_al_nybble_hex:
    push ax
    push dx
    and al, 15
    add al, '0'
    cmp al, '9'
    jbe .got
    add al, 7
.got:
    xchg dx, ax
    mov ah, 02h
    int 21h
    pop dx
    pop ax
    retn


callhandler:
	jmp 0:0
handler equ $ - 4


		; INP:	si -> text
		; OUT:	si - 1 -> end (nondigit)
		;	di = number
getnumber:
.skip:
    lodsb
    cmp al, 32
    je .skip
    cmp al, 9
    je .skip
    cmp al, '0'
    jb error
    cmp al, '9'
    ja error
    xor di, di
    jmp .digit
.loop:
    lodsb
.digit:
	cmp al, '_'
	je .loop
    sub al, '0'
    jb .end
    cmp al, 10
    jae .end
    cbw
    add di, di  ; times 2
    mov bx, di
    add di, di  ; times 4
    add di, di  ; times 8
    add di, bx  ; times 10
    add di, ax
    jmp .loop

.end:
	retn

error:
	mov dx, msg.error
	jmp errorexit

wantfree:	db 0

msg:
.error:			db "Error!",13,10,36
.noxms:			db "No XMS entry found!",13,10,36
.alloc1:		db "Trying to allocate ",36
.alloc2:		db " KiB, ",36
.alloc3:		db "success, handle=",36
.alloc7:
.alloc4:		db 13,10,36
.free5:
.alloc5:		db "error, code=",36
.alloc6:		db "h, dx=",36
.free1:			db "Trying to free handle=",36
.free2:			db ", ",36
.free6:			db "h",13,10,36
.free3:			db "success",13,10,36

testxmsm.asm

testxmsm.asm: Test two XMS move calls with differing overlapping buffers, all in conventional memory. Used to check that the XMS specification is, indeed, wrong about the overlapping move direction. This test program was used during emu2 development to test the various combinations of checks and move implementations.

On the int3 breakpoints, the following debugger command can be used to view the "destination" memory buffer: d word [si + 4 + 6 + 2] l word [si]

	cpu 186
	org 256
start:
	mov ax, 4310h
	int 2Fh
	push es
	push bx
	mov bp, sp
	push cs
	push lower1		; dest
	push 0
	push cs
	push higher1		; source
	push 0
	push 0
	push length1		; length
	mov si, sp
	mov ah, 0Bh
	call far [bp]
	int3

	push cs
	push higher2		; dest
	push 0
	push cs
	push lower2		; source
	push 0
	push 0
	push length2		; length
	mov si, sp
	mov ah, 0Bh
	call far [bp]
	int3

	mov ax, 4C00h
	int 21h

	align 16, db 0
lower1:
	times 16 db 0
higher1:
	times 4 db "Foo bar baz quux@"
	align 16, db 0
length1 equ $ - higher1

	align 16, db 0
lower2:
	times 4 db "Foo bar baz quux@"
	align 16, db 0
length2 equ $ - lower2
higher2: equ lower2 + 16

testmcb3.asm

testmcb3.asm: Set up pathological MCB allocation situation to test that the lDOS kernel MCB collection is hardened appropriately. What it does: You need to run lDOS kernel with COMPAT=HIDEDOSENTRY in a dosemu2 instance, then run ldebug /t testmcb3.com so that the first MCB is the debuggee's environment block. Then, it will free its environment, check that the first MCB is free, get the first UMCB, walk until the 'Z' UMCB (last UMCB), check that this is an S MCB (owner 8, name "S", type 30h "excluded UMA") that's empty (size 0), and that the second-to-last UMCB is free. Then it sets up the memory allocation strategy 0382h and writes a 'Z' signature to the second-to-last UMCB. Finally, it tries to allocate memory with the impossible size (0FFFFh).

Prior to this lDOS kernel change, the allocation call would incorrectly collect the first LMCB upon encountering the last UMCB when both are free. This is because of the extended strategy:

  • 200h = Treat all dual-area strategies as "one area".
  • 100h = Ignore real status of the last LMCB's signature, enable UMCB use.
  • 80h = Use dual areas: UMA then LMA (crucially, after the last UMCB the allocation walker encounters the first LMCB).
  • 2 = Last fit (not needed here if the impossible size is used).
	cpu 8086
	org 256
start:
	xor di, di
	xor ax, ax
	xchg ax, [di + 2Ch]
	mov es, ax
	mov ah, 49h
	int 21h

	mov ah, 52h
	int 21h
	mov ax, [es:bx - 2]
	mov es, ax
	cmp word [es:di + 1], 0
	jne error

	mov ax, 1261h
	stc
	int 2Fh
	jc error
	xor bx, bx
.loop:
	mov ds, ax
	cmp byte [di], 'Z'
	je .last
	cmp byte [di], 'M'
	jne error
	mov bx, ax
	add ax, word [di + 3]
	inc ax
	jmp .loop

.last:
	test bx, bx
	jz error
	cmp word [di + 1], 8
	jne error
	cmp word [di + 3], 0
	jne error
	cmp word [di + 8], "S"
	jne error
	cmp byte [di + 10], 30h
	jne error

	mov ds, bx
	cmp word [di + 1], 0
	jne error

	mov ax, 5801h
	mov bx, 382h
	stc
	int 21h
	jc error

	int3
	mov byte [di], 'Z'

	mov ah, 48h
	mov bx, -1
	int 21h

	mov ax, 4C00h
	int 21h

error:
	int3
	mov ax, 4CFFh
	int 21h
You could leave a comment if you were logged in.
blog/pushbx/2026/0112_early_2026_work_on_emu2_etc_test_cases.txt · Last modified: 2026-01-12 20:21:05 +0100 Jan Mon by ecm