VOGONS

Common searches


First post, by WhiteFalcon

User metadata
Rank Member
Rank
Member

So I have been trying to create my first TSR that actually takes control instead of just doing something quick in the background. It is hooked to INT 0x09 and when it detects ALT + F being pressed, it is supposed to show a window (or at the moment just a Hello world message and wait for the user to press Esc). This part works.

But now I am trying to store the text screen (it will only be called from text mode 0x03) and then restore it again. And this works too, but only in the command line of DOSBox. Repeatedly, so its probably not breaking anything. Whenever I try to run it in Borland C++ 3.1 (which is the goal) or even MS-DOS EDIT, it does print the message, but then it hangs and I have to restart DOSBox.

I have narrowed it down to the commands that actually do the copying (MOVSW), it does not matter which direction (from the screen to the buffer or the opposite way), it causes the TSR to break something. As it works seemingly fine in the command line, I suppose I am not saving/restoring something. I have tried puttin CLI/STI in there, saving DS just before the copy loop, but no luck... Any idea what I may be missing here?
EDIT: I added the CLD command before both copy blocks as recommended.

#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <alloc.h>

void mainTSR(void);

void interrupt (*oldKeyHandler)(...);
unsigned char *screen_state, oldx, oldy;

void interrupt newKeyHandler(...)
{
asm cli
asm {
mov ax, 0x0040 // Check if ALT is pressed
mov es, ax
mov di, 0x0017
mov al, es:[di]
test al, 8
jz nothing

in al, 0x60 // Check if J is pressed
cmp al, 0x24
jnz nothing
}

mainTSR();

nothing:

oldKeyHandler();

asm sti
}

void StoreScreen(void)
{
asm {
push ds
push es
pusha
pushf

mov ah, 0x03 // Store cursor position
xor bh, bh
int 0x10
mov [oldx], dl
mov [oldy], dh

les di, screen_state // Store video memory
mov cx, 80 * 25
mov ax, 0xB800
mov ds, ax
xor si, si
cld
rep movsw

popf
popa
pop es
Show last 55 lines
		pop ds
}
}

void RestoreScreen(void)
{
asm {
push ds
push es
pusha
pushf

mov ah, 0x02 // Restore cursor position
xor bh, bh
mov dl, [oldx]
mov dh, [oldy]
int 0x10

mov ax, 0xB800 // Restore video memory
mov es, ax
xor di, di
lds si, screen_state
mov cx, 80 * 25
cld
rep movsw

popf
popa
pop es
pop ds
}
}

void mainTSR(void)
{
StoreScreen();
gotoxy(1, 1);
directvideo = 1;
cprintf("Hello world!\r\n");
directvideo = 0;
while (inportb(0x60) != 1) oldKeyHandler();
RestoreScreen();
}

void main(void)
{
screen_state = (unsigned char *) malloc(80 * 25 * 2);
if (screen_state == NULL) return;

oldKeyHandler = getvect(0x09);
setvect(0x09, newKeyHandler);

keep(0, _SS + (_SP/16) + 1 - _psp);
}
Last edited by WhiteFalcon on 2024-05-06, 11:18. Edited 1 time in total.

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 1 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 09:50:

I have narrowed it down to the commands that actually do the copying (MOVSW)

It seems you're not setting the direction flag explicitly with CLD/STD. So your code is running ok when the external conditions (the value of D flag) matches your assumptions, and breaks when other app changed D flag according to their needs. You need to put CLD before MOVS'ing, because it seems that is your assumption about SI/DI increments.

Reply 2 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
vstrakh wrote on 2024-05-06, 10:09:

It seems you're not setting the direction flag explicitly with CLD/STD.

Good point, thanks. Forgot to mention I tried that too, putting CLD just before MOVSW and it still doesnt work. But it sure is better to leave it there. It actually worked when I tried STD instead but my happiness was short lived - it copied only the first character on the screen obviously and ended, so it did not crash.

Also when I call the TSR within BC++ IDE, it crashes with the message: "Exit to error: JMP illegal descriptor type 0" if that says anything.

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 3 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 09:50:
[…]
Show full quote
		les di, screen_state		// Store video memory

What was the DS at the moment when you load the ES:DI pair from the screen_state location? Same with `lds si, screen_state`
Sure it's correct when the stuff is called from main(), but when called from the interrupt handler - where did you ensure that the variables are really accessible?
I don't remember what was the conventions with Borland, did it load the data segment register for you at the interrupt handler entry or not...

Reply 5 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
vstrakh wrote on 2024-05-06, 11:21:

What was the DS at the moment when you load the ES:DI pair from the screen_state location? Same with `lds si, screen_state`
Sure it's correct when the stuff is called from main(), but when called from the interrupt handler - where did you ensure that the variables are really accessible?
I don't remember what was the conventions with Borland, did it load the data segment register for you at the interrupt handler entry or not...

Now its time for me to admin I dont completely know what I am doing 😀 I am reading on how this all works, but as I always just wrote asm routines for gfx, I still dont know much about what happens with DS, ES, CS at various points in the life of a program. Thats why I am pushing all those registers maybe in a paranoid way. I usually try Turbo Debugger to trace whats happening with what register, but that seems to be completely impossible with a TSR as it just tells me something like "TSR quit with status 0" and finishes. Does not allow me to call the TSR to see whats happening inside.
So how can I find out what was in the DS as you ask?

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 6 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
jmarsh wrote on 2024-05-06, 11:38:

What are you using for a compiler? An ordinary pointer probably won't work with lds/les, it will need to be a far pointer.

I am using Borland C++ 3.1, Large mode, so everything should be far by default. And it seems to load the pointers correctly as it works fine from the command line - restores the screen to the previous state.

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 7 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 11:41:

So how can I find out what was in the DS as you ask?

Typically you don't. You assume the registers are in unspecified state at the entrance to the interrupt handler (unless documented otherwise or confirmed with disassembly), you push stuff you will be changing, and then you explicitly load the required segment registers. If you're building .exe you have relocations available, so can do something like "mov ax, DATA_SEG_NAME; mov ds,ax" - this name here is just a placeholder, put the actual name of the data segment in your setup (depends on compiler, memory model, etc).

Reply 8 of 51, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

your newkeyhandler is not preserving registers on entry/exit.

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 9 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
vstrakh wrote on 2024-05-06, 12:00:

If you're building .exe you have relocations available, so can do something like "mov ax, DATA_SEG_NAME; mov ds,ax" - this name here is just a placeholder, put the actual name of the data segment in your setup (depends on compiler, memory model, etc).

I see, I am afraid you cannot do that with inline asembler of BC++. There is no naming of segments unlike pure asm.

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 10 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
BloodyCactus wrote on 2024-05-06, 12:04:

your newkeyhandler is not preserving registers on entry/exit.

I am preserving them in both Store/Restore functions, where exactly would you place other pushes/pops? I tried adding PUSHA, PUSH ES at the very beginning of the new handler and POP ES, POPA at the very end. Thats the only things I am touching there I believe. But it did not help.

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 11 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 12:15:

I am preserving them in both Store/Restore functions

But you're destroying a bunch of registers before you enter the store/restore functions. The code that chooses what to do directly at the beginning of newKeyHandler() must save stuff before changing registers.

WhiteFalcon wrote on 2024-05-06, 12:10:
vstrakh wrote on 2024-05-06, 12:00:

If you're building .exe you have relocations available, so can do something like "mov ax, DATA_SEG_NAME; mov ds,ax"

I see, I am afraid you cannot do that with inline asembler of BC++. There is no naming of segments unlike pure asm.

It appears BC happily accepts the construct `mov ax, seg screen_state`, so you could try that.

Reply 12 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
vstrakh wrote on 2024-05-06, 12:40:

But you're destroying a bunch of registers before you enter the store/restore functions. The code that chooses what to do directly at the beginning of newKeyHandler() must save stuff before changing registers.

Alright, I have PUSHA/POPA and PUSH/POP ES there now to be safe. The old INT 09 handler probably does not need my care, I bet its doing its things better than I ever could so I placed it above the call to oldKeyHandler.

vstrakh wrote on 2024-05-06, 12:00:

It appears BC happily accepts the construct `mov ax, seg screen_state`, so you could try that.

Yes, that it does and thats what I originally had there. Is there any difference moving SEG and OFFSET of the pointer to ES:DI this way and doing it at once using LES DI, screen_state?

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 13 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 12:50:

Yes, that it does and thats what I originally had there. Is there any difference moving SEG and OFFSET of the pointer to ES:DI this way and doing it at once using LES DI, screen_state?

That's two unrelated things. The sreen_state is a variable that stores the address of the memory block, you load the content of that variable into ES:DI.
What you missed is that `LES DI,variable` implies DS register when referencing the variable. So it behaves as `LES DI, DS:[variable]`. The runtime content of DS defines where that varible will be looked from.
The offset to the screen_state is the offset to the variable, and not to the memory area you've allocated.

Reply 14 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
vstrakh wrote on 2024-05-06, 12:57:

That's two unrelated things. The sreen_state is a variable that stores the address of the memory block, you load the content of that variable into ES:DI.
What you missed is that `LES DI,variable` implies DS register when referencing the variable. So it behaves as `LES DI, DS:[variable]`. The runtime content of DS defines where that varible will be looked from.
The offset to the screen_state is the offset to the variable, and not to the memory area you've allocated.

You got me there, not only I did not realize DS was used in this way iplicitly even when not mentioned, but I have also been trying to wrap my head around how pointers in asm work. Or more like.. ahem.. pointers in general. And thats after about 25 years of coding, yeah. I always just used them, never thought how they work - I did not need it with C. But with asm, you just have to know what you are doing.

So for the slower of us (me):

mov ax, SEG screen_state
mov es, ax
mov di, OFFSET screen_state

is not the same as

les di, screen_state

because with the latter you are referencing the pointer screen_state implicitly with DS:[screen_state] and with the former the compiler knows the full 32bit address of where screen_state lies no matter the state of DS? Or is it again implicitly "mov ax, SEG DS:[screen_state]"?

Also I take it the dereferencing [] brackets do have their meaning even with a pointer, so it should be "les di, [screen_state]" instead (to load the address stored in the pointer and not the address OF the pointer)?

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 15 of 51, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
les di,[screen_state] 

loads a 32bit/far pointer, of segment:offset from the pointer variable screen_state into ES:DI

mov ax, SEG screen_state
mov es, ax
mov di, OFFSET screen_state

loads the SEGMENT (that the variable) screen_state (is part of), and then takes the OFFSET (the address) of the variable.

that does not do what you think it does, your basically getting the pointer TO the variable, not the _content_ of the variable itself, you'd then need to follow up

mov ax, SEG screen_state
mov es, ax
mov di, OFFSET screen_state
mov ax,[es: di]
mov di,[es: di+2]
mov es,ax

which is the same as

les di,[screen_state] (assuming screen_state is in your current DS) 

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 16 of 51, by vstrakh

User metadata
Rank Member
Rank
Member
WhiteFalcon wrote on 2024-05-06, 16:44:

and with the former the compiler knows the full 32bit address of where screen_state lies no matter the state of DS?
Or is it again implicitly "mov ax, SEG DS:[screen_state]"?

Compiler can't know what will be the runtime value of DS during interrupt.
That's why you must explicitly load DS prior to LES, so then LES will work in conditions that compiler was expecting.

The "seg something" works with a known symbol (variable/function). You write "seg something", compiler puts the reference to that something in the object file, and the linker then will create appropriate relocation record in the exe, so during execution that "seg something" turns into the real value of the data segment where the variable was declared.
I'm not sure if specifying all the extras (DS:[]) is accepted or ignored, seems replacing DS with ES produces exactly same result, so I'd say it's ignored, and only the variable name is important.

WhiteFalcon wrote on 2024-05-06, 16:44:

Also I take it the dereferencing [] brackets do have their meaning even with a pointer, so it should be "les di, [screen_state]" instead (to load the address stored in the pointer and not the address OF the pointer)?

This might depend on the assembler and its mode, [] brackets may be required, or may be omitted, because the action is implied by the instruction.
Like "mov ax, varname" is typically reading the variable value, and "lea ax, varname" is always loading the variable's offset. In some modes (like Turbo Assembler's Ideal vs MASM modes) missing the brackets when reading the content will produce error message, but will not generate errors in other mode.
In any case, adding [] brackets will not add any code to dereference the pointer.

In your case, the StoreScreen() needs:

mov ax, seg screen_state  // load DS with the segment of screen_state's location
mov ds, ax
les di, screen_state // load ES:DI from the content of DS:screen_state

Similarly the RestoreScreen() needs DS loaded with the segment of screen_state location before loading DS:SI from screen_state's content with LDS.

Reply 17 of 51, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

the alternate is to compile into tiny model so all segs are CS=DS/ES/SS and make all references through CS.

also popa/pusha are 286+ and wont run on an XT

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 18 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member
BloodyCactus wrote on 2024-05-06, 17:57:

loads a 32bit/far pointer, of segment:offset from the pointer variable screen_state into ES:DI

loads the SEGMENT (that the variable) screen_state (is part of), and then takes the OFFSET (the address) of the variable.

After reading those two lines a dozen times, I think I finally get the difference, thank you. To sum it up, LDS/LES and the rest of them are really meant to load the contents of a pointer variable, they are not intended for "normal" variables. If I had, say, "int x = 1;" and I used LES DI, [X} on it, it would actually make ES:DI pointe at address 0000:0001, right? While SEG/OFFSET would hold the address of the variable X, where the variable is stored.

Which means I approach in the TSR code is correct then? As I need to load the address of the memory buffer and not of the pointer variable screen_state itself?

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)

Reply 19 of 51, by WhiteFalcon

User metadata
Rank Member
Rank
Member

The "seg something" works with a known symbol (variable/function). You write "seg something", compiler puts the reference to that something in the object file, and the linker then will create appropriate relocation record in the exe, so during execution that "seg something" turns into the real value of the data segment where the variable was declared.

This is rather difficult to grasp, but I am starting to see the difference. I will look for more asm tutorial online to learn about how the segments (segment registers) work.

This might depend on the assembler and its mode, [] brackets may be required, or may be omitted, because the action is implied by the instruction.

As I am using BC++ 3.1 in the "compile through assembler" mode, everything should go through TASM. Not sure if the IDEAL or MASM mode, but apparently it ignores the [] brackets in places. I have used them in some places and even when I removed them, there was no error and the results were exactly the same. Which is even more confusing, so I will try to write them properly even when not necessary.

In any case, adding [] brackets will not add any code to dereference the pointer.

Ok so just to be sure - LES DI, variable is exactly the same as LES DI, [variable]?

[…]
Show full quote
mov ax, seg screen_state  // load DS with the segment of screen_state's location
mov ds, ax
les di, screen_state // load ES:DI from the content of DS:screen_state

I will try this approach, thank you.

Similarly the RestoreScreen() needs DS loaded with the segment of screen_state location before loading DS:SI from screen_state's content with LDS.

I understand, but what happens when you make sure DS is correct and then do LDS SI, (DS:)[variable]? Will it know where the variable is before it changes the DS or will it first change DS so it no longer has the correct segment of the variable, hence it loads some nonsesical adress from the wrong location?

Olivetti M4 P75, 32MB RAM, 4GB HDD, 8GB CF, CD-ROM, SoundBlaster AWE 64, Gravis Ultrasound MAX, Roland SCC-1, Roland MT-32, Roland CM-64
Intel 486DX2/66Mhz, 16MB RAM, VGA Trident 512kB, 1.6GB HDD WD, CD-ROM, 256MB CF, SoundBlaster 16 Pro (CT2910)