The MASM Forum

Miscellaneous => 16 bit DOS Programming => Topic started by: tda0626 on May 14, 2024, 08:52:06 AM

Title: Building 16 bit Exe
Post by: tda0626 on May 14, 2024, 08:52:06 AM
I am trying to build this code but can't seem to get it to work. Do I need to use a different program to get or am I doing something wrong?

ml /c /coff videowrite.asm

link /subsystem:console videowrite.obj

and I get this error

videowrite.obj : warning LNK4078: multiple ".data" sections found with different attributes (C0220040)


.model tiny


.data

.code

org 100h
_main proc

mov ax, 0b800h
mov es, ax
mov di,0
mov al, 'A'
mov ah, 0ffh
mov es:[di],  ax

mov ah, 0
int 16h

mov ax, 04c00h
int 21

_main endp
end _main
Title: Re: Building 16 bit Exe
Post by: zedd151 on May 14, 2024, 09:19:27 AM
Are you using the proper linker??
You need to use link16.exe to link it... the usual link.exe won't work for 16 bit code.

I don't know a whole lot about writing 16 bit code, but I do know that much.
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 14, 2024, 10:06:07 AM
That code is for a .COM program (the giveaway is the ORG 100H at the top; all .COM programs start at this address, after the DOS "PSP" (program segment prefix, which is 256 bytes long). .COM programs are pretty much dinosaurs nowadays; I guess you could create one and it should run in a "DOS box" (cmd.exe window), but it's hardly worth the effort. See below; create the program as an .exe[ instead. (16-bit)

No, you can't make this kind of program with the subsystem:console option; that's for creating Windows console programs, which are 32 bit (or 64) and are .exes, not .coms.

To create a .COM program like this you'll need to use the linker with a different option (don't know exactly which at the moment but I'll look it up). You also need to run the resulting .exe program through a program called EXE2BIN which creates a .COM program.

You could also create the program as a DOS 16-bit .exe rather than a .com. That would be the easier and better option here.

I think sudoku is correct that link16.exe is what you need to use. It's been a long time since I created any 16-bit programs ... ah, the good old days ...
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 14, 2024, 10:23:11 AM
Hmm, I lied about not having made a 16-bit app in a long time; made a couple just a couple months ago.
Here's the batch file I used:
@echo off

ml /c %1.asm
if errorlevel 1 goto errasm

link16 %1, %1, nul, nul, nul
if errorlevel 1 goto errlink
goto TheEnd

:errlink
echo _
echo Link error
goto TheEnd

:errasm
echo _
echo Assembly Error
goto TheEnd

:TheEnd
link16.exe is in the MASM32 \bin folder.
Here's some 16 bit code you can use to make your own program (.exe):
;==================================================
; 16-bit DOS program template
;==================================================
$videoSeg EQU 0A000h

Stack SEGMENT STACK
DW 256 DUP(?)
Stack ENDS

Codeseg SEGMENT
ASSUME CS:Codeseg, DS:Codeseg

;***** Data in the code segment:  *****

; put any data you need here (yes, in the code segment!)

;***** Program entry point:  *****
; Set DS to CS to be able to address your data:
start: PUSH CS
POP DS

; Put lots more code here ...

Codeseg ENDS

END start

Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 14, 2024, 10:37:28 AM
Ackshooly, since your little program has no data, you don't even need the PUSH CS/POP DS sequence. Just plug your code in and go.
Title: Re: Building 16 bit Exe
Post by: sinsi on May 14, 2024, 10:52:40 AM
Later versions of ML output object files as COFF but the 16-bit linker expects them as OMF so you might need to chanhe ML's arguments
ML /c /omf videowrite.asm
LINK16 videowrite,,,,
Title: Re: Building 16 bit Exe
Post by: tda0626 on May 14, 2024, 12:23:23 PM
I got it to do a com file.

ml /c videowrite.asm

link16 videowrite.obj

It gives me a "No stack segment defined". Now I know why thanks to NoCforMe's template. :biggrin: But it still works.

I ran it in DOSBox-X. I am using this example to show my brother how to directly write to video memory instead of using the BIOS video services.


Title: Re: Building 16 bit Exe
Post by: sinsi on May 14, 2024, 12:35:58 PM
For a .com file, use the tiny model in the ASM file
.model tinyI think the linker has a /t switch for tiny model.
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 14, 2024, 06:55:19 PM
Quote from: tda0626 on May 14, 2024, 12:23:23 PMIt gives me a "No stack segment defined". Now I know why thanks to NoCforMe's template. :biggrin: But it still works.
It works (a .com file with no stack segment defined) because when a .com file is loaded all the segment registers point to the same segment (with the stack pointer at the top), so the stack is implicitly defined.

CS=DS=ES=SS
Title: Re: Building 16 bit Exe
Post by: tda0626 on May 15, 2024, 10:11:53 AM
I was able to make an exe but I got this odd behavior in DOSBox-X. When I first run the program, I get some random junk along with my string being printed several times. However, ran it in debug to see what was happening and everything seemed ok in debug so I exited debug and ran the program again. This time, it ran as expected and filled the screen with my string of characters with no junk. That has me baffled.

When I first tried to get my code to work, I had trouble getting it to put the correct address to the string. I was doing this:

mov si, offset
mov al, ds:[si]

I was like hmmm, ok, why is it not working. So I came along this person's little bit of code and now it works but puts a different address in DS like below.

mov ax, @data
mov ds, ax

I dump memory of the DS through debug and my string is in the original DS location before it is changed. Someone explain that to me because I don't get it.


Full code


.model small


Stack    SEGMENT STACK
    DW 256 DUP(?)
Stack    ENDS

.data

alpha db    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

.code



_main proc

mov cx, 3999        ; Text Mode size is 80 x 25 x 2 bytes ( Attribute byte + Character byte ) -1 (because it starts at zero index)
mov ax, @data
mov ds, ax
mov si, offset alpha
xor di, di        ; Zero out our index into video memory
mov ax, 0b800h            ; Video memory for 80 x 25 16 color mode
mov es, ax        ; Move the address into the ES register

       
mov ah, 0fh        ; Bright White Characters


BeginLoop:

    mov bl,  ds:[si]            ; check to see if we are at the end of alpha
    .if bl>'Z'                   
        mov si, offset alpha        ; if so, reset the offset
    .endif

mov al, ds:[si]            ; move the value (letter) at the index pointed to si into al
mov es:[di],  al    ; move letter into video memory at index of di
inc di            ; inc index to next byte in video memory
mov es:[di], ah            ; move value 0Fh = Bright White character
inc di            ; inc index to next byte for next loop
inc si            ; inc index to next letter in array

cmp cx, 0        ; check our counter               
jz EndLoop        ; exit loop
dec cx            ; decrement our counter

jmp BeginLoop            ; Keep looping until jz EndLoop condition is met

EndLoop:

mov ah, 0        ; Wait for key press BIOS function to pause input
int 16h            ; BIOS Keyboard Services Interrupt



mov ax, 04c00h    ; Close program
int 21h            ; DOS Services Interrupt

_main endp
end _main

Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 15, 2024, 10:37:54 AM
Yes, in an .exe program like this you need to set DS to your data segment before accessing any of the data; the loader doesn't do this for you.

Instead of
mov al, ds:[si]  ; move the value (letter) at the index pointed to si into al
    . . .
inc si          ; inc index to next letter in array
you can do this:
lodsb
which automagically advances SI to the next character. No need even for ds:, as that's assumed with that instruction.
Likewise with storing characters; instead of
mov es:[di],  al  ; move letter into video memory at index of di
inc di            ; inc index to next byte in video memory
all you need is
stosb
which assumes AL--> ES:DI, advancing DI.

Also, instead of testing CX and decrementing it for a loop counter, you can do this:
    MOV    CX, <loop count>

beginLoop:
    ...  ; do loop stuff here
    ...
    LOOP beginLoop
which will automagically decrement CX and test it for zero.
One small gotcha: this only works for short distances. The LOOP instruction has to be within 127 bytes of the target label. In your case it'll work fine.

There are even a couple variants you can use:

Let the processor work for you.
Title: Re: Building 16 bit Exe
Post by: tda0626 on May 15, 2024, 11:48:00 AM
Check this out, the pictures below show the program being run before debug.exe and then after debug.exe. First time it doesn't work as intended but the second run works.  :undecided:

BEFORE:
(https://i.postimg.cc/svZ16B4y/beforedebug.jpg) (https://postimg.cc/svZ16B4y)

AFTER:
(https://i.postimg.cc/23tV7dv2/Afterdebug.jpg) (https://postimg.cc/23tV7dv2)

Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 15, 2024, 12:10:41 PM
That's weird. Nothing in your code jumps out at me. Have to take another look at it.
In the meantime, you could simplify your loop code:
        MOV  AH, 0Fh        ;Bright white attribyte
        MOV  SI, OFFSET alpha
        XOR  DI, DI        ;Zero offset into video memory
        MOV  CX, 80 * 25
cloop:  LODSB              ;Next char. from string
        STOSW              ;Store both AL & AH at once
        CMP  AL, 'Z'       ;Are we @ end of text?
        JNE  @F            ;  Nope, keep on truckin'
        MOV  SI, OFFSET alpha ;Yep, reset pointer.
@@:     LOOP cloop

Notice the modification: since we're writing a word at a time into video memory (using STOSW), the loop count should only be 80 * 25, not double that.
Maybe that was why it screwed up before? But then why was it OK the 2nd time?
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 15, 2024, 01:23:49 PM
Try this code. I tested it and it works:
.model small

Stack SEGMENT STACK
DW 256 DUP(?)
Stack ENDS

.data
alpha db "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

.code
_main proc
MOV AX, @data
MOV DS, AX
MOV AX, 0B800h ;Video segment for 80 x 25 16 color mode
MOV ES, AX ;Set ES to video seg.
       
        MOV AH, 0Fh ;Bright white attribyte
        MOV SI, OFFSET alpha
        XOR DI, DI ;Zero offset into video memory
        MOV CX, 80 * 25
cloop: LODSB ;Next char. from string
STOSW ;Store both AL & AH at once
CMP AL, 'Z' ;Are we @ end of text?
JNE @F ;  Nope, keep on truckin'
        MOV SI, OFFSET alpha ;Yep, reset pointer.
@@:     LOOP cloop

MOV AH, 0 ; Wait for key press BIOS function to pause input
INT 16h ; BIOS Keyboard Services Interrupt
MOV AX, 04C00h ; Close program
INT 21h ; DOS Services Interrupt

_main endp
end _main
Pretty sure the problem you had before was a loop count that was twice what it should be. (No need to offset by 1, either.)
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 15, 2024, 02:21:18 PM
One more small thing: since you're using the "simplified segment directives" here (.data, .code), best not to mix apples and oranges. So instead of
Stack    SEGMENT STACK
    DW 256 DUP(?)
Stack    ENDS
just use
.stack 512
(because .stack sets the stack size in bytes)
If you just use .stack with no argument, you get the default stack of 1K bytes.
Title: Re: Building 16 bit Exe
Post by: _japheth on May 15, 2024, 02:29:23 PM
QuoteI dump memory of the DS through debug and my string is in the original DS location before it is changed. Someone explain that to me because I don't get it.

This part of your program introduces hazardous behavior:

    mov bl,  ds:[si]            ; check to see if we are at the end of alpha
    .if bl>'Z'                 
        mov si, offset alpha        ; if so, reset the offset
    .endif

because there's nowhere in your string a character that's ">'Z'".


@NoCForMe: suggesting optimizations while a bug is to be fixed is not very "professional".
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 15, 2024, 02:55:08 PM
Quote from: _japheth on May 15, 2024, 02:29:23 PM@NoCForMe: suggesting optimizations while a bug is to be fixed is not very "professional".
Point taken. However, since my optimizations ended up fixing the problem I consider myself absolved here.
Title: Re: Building 16 bit Exe
Post by: sinsi on May 15, 2024, 03:09:48 PM
Quote from: NoCforMe on May 15, 2024, 02:55:08 PMPoint taken. However, since my optimizations ended up fixing the problem I consider myself absolved here.
Until we hear from the OP, it's not fixed yet.
Title: Re: Building 16 bit Exe
Post by: tda0626 on May 15, 2024, 09:12:55 PM
Quote from: _japheth on May 15, 2024, 02:29:23 PM
QuoteI dump memory of the DS through debug and my string is in the original DS location before it is changed. Someone explain that to me because I don't get it.

This part of your program introduces hazardous behavior:

    mov bl,  ds:[si]            ; check to see if we are at the end of alpha
    .if bl>'Z'                 
        mov si, offset alpha        ; if so, reset the offset
    .endif

because there's nowhere in your string a character that's ">'Z'".


@NoCForMe: suggesting optimizations while a bug is to be fixed is not very "professional".


Yep that was it. Thanks for pointing that out. I added a zero to the end of the string and changed the check to check for that instead. It appears to work ok now.


In my code, I use the

mov ax, @data

How would I do the same thing without using the @data?



I don't mind being given optimizations but would rather get my code working first before doing anything else to it but do appreciate the tips. Also, I wanted to keep it somewhat simple to read because I can send this to my brother to look over and he should be able to follow it.

Tim
Title: Re: Building 16 bit Exe
Post by: _japheth on May 15, 2024, 09:59:21 PM
Quote from: tda0626 on May 15, 2024, 09:12:55 PMIn my code, I use the

mov ax, @data

How would I do the same thing without using the @data?


Hm, that's actually not a trivial question. So the answer may also be a bit complicated:

@data is an alias for DGROUP and this is a segment reference, meaning the OS loader has to adjust this location when the program is loaded ( there are "segment fixups" in the MZ header for exactly this reason ).

To avoid segment fixups, the easiest way is to change the memory model from .small to .tiny ( but without adding "ORG 100h" at the program start, the program is still supposed to become an .EXE program ).

In model .tiny, @data isn't necessary anymore, because CS holds that value already. So instead of "mov ax, @data", you can now code "mov ax,cs".

Btw, it's a good idea to add the ".dosseg" directive just behind the ".model" directive. That tells the linker to organize segments is a somewhat smart way. Without that, your program will still run, but the stack may be located in the middle of the binary, wasting 512 bytes.
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 16, 2024, 12:09:59 PM
Quote from: _japheth on May 15, 2024, 09:59:21 PMTo avoid segment fixups, the easiest way is to change the memory model from .small to .tiny ( but without adding "ORG 100h" at the program start, the program is still supposed to become an .EXE program ).
So .tiny creates a .COM-like program except that it's actually an .EXE, right? (With the MZ header and all.) Otherwise it's like a .COM in that everything (code, data, stack) is in a single segment, correct?

Then to set DS, I always used
    PUSH  CS
    POP   DS
Title: Re: Building 16 bit Exe
Post by: sinsi on May 16, 2024, 12:21:56 PM
If you use simplified code, have a look at .startup and .exit, they do a lot of the work for you
.model tiny
.code
        .startup
            ;your code
        .exit
end
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 16, 2024, 12:52:05 PM
Hmm; never used those, never even heard of them.
.startup:
@Startup:
mov dx, DGROUP
mov ds, dx
mov bx, ss
sub bx, dx
shl bx, 1 ; If .286 or higher, this is
shl bx, 1 ; shortened to shl bx, 4
shl bx, 1
shl bx, 1
cli ; Not necessary in .286 or higher
mov ss, dx
add sp, bx
sti ; Not necessary in .286 or higher
(from the MASM 6.1 manual)
Title: Re: Building 16 bit Exe
Post by: daydreamer on May 16, 2024, 02:09:05 PM
Quote from: tda0626 on May 14, 2024, 12:23:23 PMI got it to do a com file.

ml /c videowrite.asm

link16 videowrite.obj

It gives me a "No stack segment defined". Now I know why thanks to NoCforMe's template. :biggrin: But it still works.

I ran it in DOSBox-X. I am using this example to show my brother how to directly write to video memory instead of using the BIOS video services.
Is dosbox-x the version which support MMX opcodes ?
Optimisations with dosbox should be done with old CPUs cycle count data
@nocforme
.com files might be dinosaurs because of age,but size is like that mosquito in Jurassic park :)
Title: Re: Building 16 bit Exe
Post by: sinsi on May 16, 2024, 02:24:06 PM
Quote from: NoCforMe on May 16, 2024, 12:09:59 PMSo .tiny creates a .COM-like program except that it's actually an .EXE, right? (With the MZ header and all.) Otherwise it's like a .COM in that everything (code, data, stack) is in a single segment, correct?
/tiny (or the linker option /AT) creates a .com file, no header, just a binary image.
The only thing that makes it a .com file is using ORG 100h (or .startup will do that for you).

If you use ORG 100h in a .exe, there would be 256 null bytes before your code/data (if I'm remembering right).
Title: Re: Building 16 bit Exe
Post by: _japheth on May 16, 2024, 03:19:36 PM
Quote from: NoCforMe on May 16, 2024, 12:09:59 PMSo .tiny creates a .COM-like program except that it's actually an .EXE, right? (With the MZ header and all.) Otherwise it's like a .COM in that everything (code, data, stack) is in a single segment, correct?

Yes. Technically, all that ".model tiny" does is to add segment _TEXT to DGROUP. You can stay with ".model small" and do that "manually":
.model small
.stack 256
DGROUP group _TEXT

.data

text db "hello",13,10,'$'

.code
start:
push cs
pop ds
mov dx, offset text
mov ah, 9
int 21h
mov ah, 4ch
int 21h

END start
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 16, 2024, 03:37:17 PM
Quote from: sinsi on May 16, 2024, 02:24:06 PMIf you use ORG 100h in a .exe, there would be 256 null bytes before your code/data (if I'm remembering right).
Yes, the ORG 100h puts the program entry point just past the PSP (program segment prefix), which is 256 bytes long. (Contains the file-control blocks (FCBs) and other stuff, including a pre-coded INT 20H for program exit.)

Although, nevermind, since you were describing an .exe and I was talking about a .com.
No need to further confuse the poor OP ...
Title: Re: Building 16 bit Exe
Post by: NoCforMe on May 17, 2024, 02:19:06 AM
Quote from: tda0626 on May 15, 2024, 09:12:55 PMI added a zero to the end of the string and changed the check to check for that instead. It appears to work ok now.
No need to do that. See my code in the post above (https://masm32.com/board/index.php?msg=130640). Sure it's nonstandard string formatting, but who cares? It's your program, you can do what you want, and it works. (And remember that old DOS programs used '$' as a delimiter way back when.)
Title: Re: Building 16 bit Exe
Post by: tda0626 on May 18, 2024, 11:17:04 PM
Quote from: daydreamer on May 16, 2024, 02:09:05 PMIs dosbox-x the version which support MMX opcodes ?
Optimisations with dosbox should be done with old CPUs cycle count data



It has the option of emulating the Pentium MMX in the CPU type menu.