Author Topic: Some questions about 16bit DOS programming  (Read 35013 times)

Offline ben321

  • Full Member
  • **
  • Posts: 185
Some questions about 16bit DOS programming
« on: March 23, 2015, 02:58:06 AM »
Why do I get the error
Quote
error: invalid effective address

When trying to move data to the address 0xA0000. I thought that was the start of VGA video memory, according to some stuff I've read on the internet. So why does the NASM compiler tell me that it's an invalid address?

Offline z80a

  • Jr. Member
  • *
  • Posts: 20
Re: Some questions about 16bit DOS programming
« Reply #1 on: March 23, 2015, 03:16:25 AM »
I'd say your trying to access via "flat" memory model. DOS used segments with offsets, e.g. set DS to A000 then use registers to access. Remember by default a ".COM" program set all segments to same CS=DS=ES=SS, ".EXE" different. You can set DS to video memory (or else where) in a ".COM", just make sure you restore in need be. This is short answer.


Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #2 on: March 23, 2015, 03:19:28 AM »
I'd say your trying to access via "flat" memory model. DOS used segments with offsets, e.g. set DS to A000 then use registers to access. Remember by default a ".COM" program set all segments to same CS=DS=ES=SS, ".EXE" different. You can set DS to video memory (or else where) in a ".COM", just make sure you restore in need be. This is short answer.

Thanks, but what's the mnemonic to set the DS register? Please let me know, thanks.
According to http://ref.x86asm.net/coder32.html the mnemonic should simply be DS, like DS 0xA000 for setting it. But NASM says "invalid opcode" for that. So I assume that rather than going by official Intel x86 specs, the writers of NASM have invented their own mnemonic to do this. So please let me know what it is.
« Last Edit: March 23, 2015, 03:21:57 AM by ben321 »

Offline z80a

  • Jr. Member
  • *
  • Posts: 20
Re: Some questions about 16bit DOS programming
« Reply #3 on: March 23, 2015, 03:54:49 AM »
One way,

    mov    ax,A000h
    mov    ds,ax

Push and pops can also be used, especially if you are going to restore original value, save original.

Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #4 on: March 23, 2015, 04:16:54 AM »
One way,

    mov    ax,A000h
    mov    ds,ax

Push and pops can also be used, especially if you are going to restore original value, save original.

Once I have the ds register set, does that mean that all data accessed with MOV will from then on be in segment 0xA000 (and therefore in VGA video RAM)? For example, does this mean that if my next line is "MOV [128],255" that it will make the pixel in row0 column128 turn white?

Offline z80a

  • Jr. Member
  • *
  • Posts: 20
Re: Some questions about 16bit DOS programming
« Reply #5 on: March 23, 2015, 04:38:45 AM »
Yes, the way it works is

A000         Segment
  xxxx        Offset
Axxxx        EA

For example if you are in video text mode, B800 is segment, accessing 0000 and moving a 41h will place an upper case 'A' in top left corner. The next address contains 8 bit value which upper 4 bits is character color, lower 4 bits background color, then next character. I mention this because you can play with DEBUG.EXE or GRDB.EXE and directly assemble/disassemble and watch registers and play. If your running Linux or *BSD, you can use DOSBOX and safely play without crashing system. You can also run DOS version of NASM there.

After midnight here, probably going into shutdown til morning.

Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #6 on: March 23, 2015, 04:56:36 AM »
Yes, the way it works is

A000         Segment
  xxxx        Offset
Axxxx        EA

For example if you are in video text mode, B800 is segment, accessing 0000 and moving a 41h will place an upper case 'A' in top left corner. The next address contains 8 bit value which upper 4 bits is character color, lower 4 bits background color, then next character. I mention this because you can play with DEBUG.EXE or GRDB.EXE and directly assemble/disassemble and watch registers and play. If your running Linux or *BSD, you can use DOSBOX and safely play without crashing system. You can also run DOS version of NASM there.

After midnight here, probably going into shutdown til morning.


Ok. Then why isn't this working?

Here's my fully commented source code.
Code: [Select]
;This program should produce bands of multiple colors, but it doesn't work.
BITS 16 ;Let the NASM compiler know that this is to be compiled as a 16 bit program.

;There is no ORG command here, because it needs to start at offset 0 to be bootable from a floppy, or in this case a disk image.
;If it was 0x100, then the assembler would write its first assembled command to byte# 0x100 in the floppy disk image, but for a
;bootable floppy image, the first byte in the image file, MUST contain the first byte of assembled code, unlike for a COM file,
;which MUST be padded up to byte 0x100 with 0x00 bytes, and have the first byte of compiled code start at exactly file byte# 0x100.

mov ax,0x13 ;Put mode 0x13 (VGA mode) in ax register.
int 0x10 ;Call interupt 0x10 which is BIOS call needed to change video mode.
mov ax,0xA000 ;Put 0xA000 (the address for VGA video-ram) in ax register.
mov ds,ax ;Transfer the content of ax register to ds (data segment) register.
mov di,-1 ;Put -1 in the di register, as it will be incremented as the first step in the coming loop, and 0 should be its first value after INC.

LoopStart:
inc di ;Increment di (destination index)
mov ax,di ;Put the value of di in ax.
and ax,0xFF ;Make sure that ax stays within the bounds of 0 to 255.
mov [di],ax ;Move the value of ax into the memory at the address specified by di (and the segment which is 0xA000 instead of the default 0x0000).
cmp di,64000 ;Compare the current value of di to 64000.
jl LoopStart ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.
;There's no next command, but it doesn't matter. If the program crashes at this point, the stuff that was put into VRAM
;will still remain there.


Compile it with the command "nasm MySourceCode.asm" (note there is no need to use the "-f bin" command line switch, as bin is already the default format).
Now after this code has been compiled, the next step is to put it into a disk image. Take a hex editor and make an empty file that is exactly 1,474,560 bytes in size (the size of a standard 1.44MB floppy disk), which must be exact if Microsoft Virtual PC is going to recognize it as being a valid floppy image. Next, take that hex editor and copy the entire content from the compiled raw binary file, and replace the first however-many bytes of the blank floppy disk image (in the free HxD hex editor software, after selecting all the bytes from the source file do Ctrl+C to copy and then in the destination file do Ctrl+B to copy and replace, don't do Ctrl+V as that will copy and insert, changing the file size).


Next step is to make a new virtual machine in Microsoft Virtual PC, and load the floppy image into its virtual drive. Then start the virtual machine.

It should immediately boot into this program, which should display multiple diagonal bands of every color in the VGA palette. But it doesn't work. I end up getting just some randomly discolored pixels in the upper-left corner of the virtual machine's monitor display, as seen in the screencap below.



Please tell me what I'm doing wrong.

« Last Edit: March 23, 2015, 05:01:16 AM by ben321 »

Offline shaynox

  • Full Member
  • **
  • Posts: 118
  • Country: gr
Re: Some questions about 16bit DOS programming
« Reply #7 on: March 23, 2015, 05:20:35 AM »
Hi, sorry I don't know what is wrong in your program, anyway I post my own source who do the same job.


Enjoy
« Last Edit: March 23, 2015, 02:46:03 PM by shaynox »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Some questions about 16bit DOS programming
« Reply #8 on: March 23, 2015, 09:56:34 AM »
Code: [Select]
cmp di,64000 ;Compare the current value of di to 64000.
jl LoopStart ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.

But 64000, treated as a signed number, is negative - di isn't going to be less, so your loop never executes.  jl treats it as a signed nember. For an unsigned number, use jb.

I had to reboot THREE TIMES before I figured that out!

Best,
Frank


Offline z80a

  • Jr. Member
  • *
  • Posts: 20
Re: Some questions about 16bit DOS programming
« Reply #9 on: March 23, 2015, 03:57:17 PM »
Morning, scan for my changes, also, just for "proper" DOS, I compile:
  nasm prog.asm -o prog.com
".COM" files start with all segment registers the equal, carry over for older code/processors which could only access 64K and had no
concept of "segments".

Code: [Select]
;This program should produce bands of multiple colors, but it doesn't work.
BITS 16     ;Let the NASM compiler know that this is to be compiled as a 16 bit program.

;There is no ORG command here, because it needs to start at offset 0 to be bootable from a floppy, or in this case a disk image.
;If it was 0x100, then the assembler would write its first assembled command to byte# 0x100 in the floppy disk image, but for a
;bootable floppy image, the first byte in the image file, MUST contain the first byte of assembled code, unlike for a COM file,
;which MUST be padded up to byte 0x100 with 0x00 bytes, and have the first byte of compiled code start at exactly file byte# 0x100.

mov ax,0x13 ;Put mode 0x13 (VGA mode) in ax register.
int 0x10    ;Call interupt 0x10 which is BIOS call needed to change video mode.
mov ax,0xA000   ;Put 0xA000 (the address for VGA video-ram) in ax register.
mov es,ax    ;Transfer the content of ax register to ds (data segment) register.

mov cx,0FA00h   ;I don't like mixing decimal and hex, prefer hex so I
; can see bit activity, I'm also biased towards 0xxxxh format :).

mov di,-1  ;Put -1 in the di register, as it will be incremented as the first step in the coming loop, and 0 should be its first value after INC.

LoopStart:
inc di      ;Increment di (destination index)
mov ax,di    ;Put the value of di in ax.
;;;;and ax,0xFF ;Make sure that ax stays within the bounds of 0 to 255.
mov [es:di],al ;BYTE AL, ADDED SEGMENT OVERRIDE IN CASE YOUR NOT AWARE OF CAPABILITY

loop  LoopStart   ;Decrement CX loop if not zero

Plock   jmp Plock ;MAKING PROGRAM LOCK, YOU HAD NO TERMINATION
;
; YOU WOULD USE ONE OR OTHER OF BELOW
;
int     20h ;IF ABOVE LINE REMOVED, THIS IS OLD TERMINATE PROGRAM IN DOS
;
mov ax,4C00h ;MORE MODERN EXIT WITH ERROR CODE HELD IN REGISTER AL
int 21h ;

;;;;cmp di,64000    ;Compare the current value of di to 64000.
;;;;jl LoopStart    ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.
;There's no next command, but it doesn't matter. If the program crashes at this point, the stuff that was put into VRAM
;will still remain there.


Offline z80a

  • Jr. Member
  • *
  • Posts: 20
Re: Some questions about 16bit DOS programming
« Reply #10 on: March 24, 2015, 01:52:53 AM »
FYI, When I wrote device drivers and boot images, I found it easier to debug writing as a ".COM" file before committing to a boot or ".SYS" image.

Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #11 on: March 24, 2015, 03:36:21 AM »
Code: [Select]
cmp di,64000 ;Compare the current value of di to 64000.
jl LoopStart ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.

But 64000, treated as a signed number, is negative - di isn't going to be less, so your loop never executes.  jl treats it as a signed nember. For an unsigned number, use jb.

I had to reboot THREE TIMES before I figured that out!

Best,
Frank

Interesting. I'll have to look into that. I had no idea that assembly code even could handle signed numbers. I thought that at the most base assembly and "machine code" language, all numbers were unsigned, and that the concept of signed numbers was how higher level programming languages simply interpreted the values when implementing math functions. I didn't know that there was any more than one type of "jump if less than", and I thought that one type was jl. Never heard of jb before.

Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #12 on: March 24, 2015, 06:16:21 AM »
Morning, scan for my changes, also, just for "proper" DOS, I compile:
  nasm prog.asm -o prog.com
".COM" files start with all segment registers the equal, carry over for older code/processors which could only access 64K and had no
concept of "segments".

Code: [Select]
;This program should produce bands of multiple colors, but it doesn't work.
BITS 16     ;Let the NASM compiler know that this is to be compiled as a 16 bit program.

;There is no ORG command here, because it needs to start at offset 0 to be bootable from a floppy, or in this case a disk image.
;If it was 0x100, then the assembler would write its first assembled command to byte# 0x100 in the floppy disk image, but for a
;bootable floppy image, the first byte in the image file, MUST contain the first byte of assembled code, unlike for a COM file,
;which MUST be padded up to byte 0x100 with 0x00 bytes, and have the first byte of compiled code start at exactly file byte# 0x100.

mov ax,0x13 ;Put mode 0x13 (VGA mode) in ax register.
int 0x10    ;Call interupt 0x10 which is BIOS call needed to change video mode.
mov ax,0xA000   ;Put 0xA000 (the address for VGA video-ram) in ax register.
mov es,ax    ;Transfer the content of ax register to ds (data segment) register.

mov cx,0FA00h   ;I don't like mixing decimal and hex, prefer hex so I
; can see bit activity, I'm also biased towards 0xxxxh format :).

mov di,-1  ;Put -1 in the di register, as it will be incremented as the first step in the coming loop, and 0 should be its first value after INC.

LoopStart:
inc di      ;Increment di (destination index)
mov ax,di    ;Put the value of di in ax.
;;;;and ax,0xFF ;Make sure that ax stays within the bounds of 0 to 255.
mov [es:di],al ;BYTE AL, ADDED SEGMENT OVERRIDE IN CASE YOUR NOT AWARE OF CAPABILITY

loop  LoopStart   ;Decrement CX loop if not zero

Plock   jmp Plock ;MAKING PROGRAM LOCK, YOU HAD NO TERMINATION
;
; YOU WOULD USE ONE OR OTHER OF BELOW
;
int     20h ;IF ABOVE LINE REMOVED, THIS IS OLD TERMINATE PROGRAM IN DOS
;
mov ax,4C00h ;MORE MODERN EXIT WITH ERROR CODE HELD IN REGISTER AL
int 21h ;

;;;;cmp di,64000    ;Compare the current value of di to 64000.
;;;;jl LoopStart    ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.
;There's no next command, but it doesn't matter. If the program crashes at this point, the stuff that was put into VRAM
;will still remain there.


That fixed it! Changing jl to jb worked! THANKYOU THANKYOU THANKYOU!


Here's what it now looks like when I boot Microsoft Virtual PC with a disk image containing the assembled code as the first bytes in the disk image.



In theory, you should even be able to use a raw-write type software (or a hex editor like HxD that allows access to actual raw physical bytes  on a disk) to put this on a regular floppy disk and boot a real computer with it and get this graphics display. As far as I know, there's no difference between MS Virtual PC and real hardware in the way it responds to x86 machine code (but I could be wrong about that). Also you should be able to use this as a bootable image file in MagicISO as the "boot image" to put in a CD-ROM ISO disk image, which can then be burned to a CD-R disk and used to boot a machine that has an optical drive but no floppy (though I haven't tried this procedure yet, so I'm not sure, just an idea). And by boot I mean draw the above image on a screen in VGA mode (as opposed to a non bootable disk where the computer says it's not a valid boot disk), not actually boot to a DOS prompt.
« Last Edit: March 24, 2015, 06:22:14 AM by ben321 »

Offline ben321

  • Full Member
  • **
  • Posts: 185
Re: Some questions about 16bit DOS programming
« Reply #13 on: March 24, 2015, 06:53:02 AM »
Here's my final code for this program. And it works as intended.

Code: [Select]
;This program produces multiple bands of all the colors in the VGA palette. It is designed to be booted from a floppy disk.
BITS 16 ;Let the NASM compiler know that this is to be compiled as a 16 bit program.

mov ax,0x13 ;Put 0x13 (the mode number for VGA mode) in ax register.
int 0x10 ;Call interupt 0x10 which is the BIOS call needed to change video mode.
mov ax,0xA000 ;Put 0xA000 (the address for VGA video-ram) in ax register.
mov ds,ax ;Transfer the content of ax register to ds (data segment) register.
mov di,-1 ;Put -1 in the di register, as it will be incremented as the first step in the coming loop, and 0 should be its value after the first INC.

LoopStart:
inc di ;Increment di (destination index)
mov ax,di ;Put the value of di in ax.
and ax,0xFF ;Make sure that ax stays within the bounds of 0 to 255.
mov [di],ax ;Move the value of ax into the memory at the address specified by di (and the segment which is 0xA000 instead of the default 0x0000).
cmp di,64000 ;Compare the current value of di to 64000.
jb LoopStart ;If di is less than 64000, then loop back to LoopStart, else continue to the next command.

KeepRunning:
jmp KeepRunning ;Keep looping forever so the computer doesn't crash when it runs out of commands to execute and tries to read past the end of the program.

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Some questions about 16bit DOS programming
« Reply #14 on: March 24, 2015, 08:48:50 AM »
Quote
As far as I know, there's no difference between MS Virtual PC and real hardware in the way it responds to x86 machine code (but I could be wrong about that).
Real hardware is not all alike. I guess you'd say real hardware responds to x86 machine code the same, but how it treats your bootsector differs. The state that it leaves us when we get control - segment registers, in particular - and what it will accept as a "valid system disk" vary widely from machine to machine. Some machines would spit your gem on the floor and snarl "no system disk".

I'm glad to see that block of comments near the top of your earlier version gone. It was almost completely wrong.

There's still a sort-of error in it, but it doesn't do any harm.
Code: [Select]
    mov [di], ax
In mode 13h, each pixel is one byte. You write the pixel you're interested in from al, and then write the next pixel from ah (which you have zeroed). But then you advance one pixel and overwrite the "extra" one with what it's supposed to be, so it really doesn't matter. If you were setting pixels individually, you probably wouldn't want to set two... unless intentionally.

Anyway, I wrote it to a floppy with "dd" and it works on real hardware here...

Best,
Frank