News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Building 16 bit Exe

Started by tda0626, May 14, 2024, 08:52:06 AM

Previous topic - Next topic

tda0626

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

zedd151

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.

NoCforMe

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 ...
Assembly language programming should be fun. That's why I do it.

NoCforMe

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

Assembly language programming should be fun. That's why I do it.

NoCforMe

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.
Assembly language programming should be fun. That's why I do it.

sinsi

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,,,,

tda0626

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.



sinsi

For a .com file, use the tiny model in the ASM file
.model tinyI think the linker has a /t switch for tiny model.

NoCforMe

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
Assembly language programming should be fun. That's why I do it.

tda0626

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


NoCforMe

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:
  • LOOPNZ/LOOPNE:  Loop while the zero flag isn't set
  • LOOPE/LOOPZ: the opposite (loop while the zero flag is set)

Let the processor work for you.
Assembly language programming should be fun. That's why I do it.

tda0626

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:


AFTER:



NoCforMe

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?
Assembly language programming should be fun. That's why I do it.

NoCforMe

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.)
Assembly language programming should be fun. That's why I do it.

NoCforMe

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.
Assembly language programming should be fun. That's why I do it.