STACKSEG SEGMENT PARA STACK 'STACK'
DB 64 DUP(?)
STACKSEG ENDS
DTSEG SEGMENT PARA 'DATA'
VAR WORD 1444
DB 62 DUP(?)
DTSEG ENDS
CDSEG SEGMENT PARA 'CODE'
MAIN PROC FAR
ASSUME CS:CDSEG, DS:DSEG, SS:STKSEG
MOV AX, DTSEG
MOV DS, AX
MOV BX, VAR
ASSUME SS:DTSEG
MOV SS, AX
PUSH BX
MOV AH, 4CH
INT 21H
MAIN ENDP
CDSEG ENDS
END MAIN
I'm using masm for dos,and this program assembles and links without any problem. What i've tried to do in this program,is change the SS value so that it points to dtseg instead of the "official" STACK SEGMENT. Then,i tried to push bx,hoping that it will be stored in memory locaton DTSEG:SP. However,when i used debug i got strange results... I used the "trace" command... every instruction was executing normally... However,i reached a point where "MOV SS, AX" was about to execute... I used again "trace"... Normally,i should get as an output the registers' values and then next instruction that is about to execute ("PUSH BX") Indeed,it showed me the registers' values but not the "PUSH BX" operation... Instead of that it showed me that next instruction for execution is "MOV AH, 4CH"! I also noticed,that the SP value had changed...Instead od the initial 40 value,3E was now in SP.... PLEASE....Does anyone know what happend here? I'd really appreciate any help... Thanks a lot.
🔒 Locked
DOS Assembly programming Problem....
Advertisement
Original Post
HI! I'd like to ask you some questions about stack segments when doing dos programming... I know that each exe can have only one stack segment... However...Look at this piece of code...
Every push/call/pop/ret instruction affects the stack pointer. When you push a value, SP is substracted 2 bytes, and the value is stored at SS:[SP]; when you pop, the value at SS:[SP] is returned and SP is added 2 bytes. So the observed behaviour is correct, SP goes from 40h to 3Eh and at SS(=DS):[3Eh] you have the value of BX.
Second: usually, when you change stacks, you have to modify both SS and SP and this is done (again usually) in consecutive instructions. Because there's a possibility of an interrupt happening between the update of SS and the update of SP (which would lead to an inconsistent stack during the interrupt routine), the processor disables interrupts after a move to SS and enables them after the next instruction (in your case PUSH BX). The TRACE command of your debuger, which hooks interrupt 1 (INT1 - Single Step), is thus unable to run until after PUSH BX is executed.
Second: usually, when you change stacks, you have to modify both SS and SP and this is done (again usually) in consecutive instructions. Because there's a possibility of an interrupt happening between the update of SS and the update of SP (which would lead to an inconsistent stack during the interrupt routine), the processor disables interrupts after a move to SS and enables them after the next instruction (in your case PUSH BX). The TRACE command of your debuger, which hooks interrupt 1 (INT1 - Single Step), is thus unable to run until after PUSH BX is executed.
Hi Nis.Thanks a lot for your answer.
I didn't know that the processor disables interrupts when it determines a move to ss...
oh...and bx is NOT in ds:sp memory location...
anyway...i still have some questions...(maybe silly though...)
so...if i understood correctly...
1) "mov ss, ax" is about to execute...
2) i enter the "trace" coomand...the processor determines that this is a mov to ss and disables interrupts...
now that's the confusing part for me...
i use "trace" to execute instructions ONE BY ONE...
So,if the processor disables interrupts,how is it possible for it to reenable interrupts if no instruction is executed?hm...what i just said is silly...
obviously after mov ss, ax is executed,control returns to "trace"...
also...when i hit the button "t" (for trace) and i see the letter "t" on my screen doen't that mean that an interrupt has been served?
It's SO confusing for me...
I'd REALLY appreciate if Nis or someone else could give some more explanations...
Thanks again...
I didn't know that the processor disables interrupts when it determines a move to ss...
oh...and bx is NOT in ds:sp memory location...
anyway...i still have some questions...(maybe silly though...)
so...if i understood correctly...
1) "mov ss, ax" is about to execute...
2) i enter the "trace" coomand...the processor determines that this is a mov to ss and disables interrupts...
now that's the confusing part for me...
i use "trace" to execute instructions ONE BY ONE...
So,if the processor disables interrupts,how is it possible for it to reenable interrupts if no instruction is executed?hm...what i just said is silly...
obviously after mov ss, ax is executed,control returns to "trace"...
also...when i hit the button "t" (for trace) and i see the letter "t" on my screen doen't that mean that an interrupt has been served?
It's SO confusing for me...
I'd REALLY appreciate if Nis or someone else could give some more explanations...
Thanks again...
There's really no need to disable interrupts. Just use the LSS instruction to set both the segment and stack pointer simultaneously.
Also, I wouldn't bother with debug. There's many other great debuggers around even for 16-bit DOS. Try turbo debugger, it's always worked for me and I think it's free too nowadays.
Also, I wouldn't bother with debug. There's many other great debuggers around even for 16-bit DOS. Try turbo debugger, it's always worked for me and I think it's free too nowadays.
There is an error in your 'assume' statement: You tell it DS points to DSEG, but it really points to DTSEG. I've no ide if that is causing the problems you're observing. Another possiblity is that debug is a REALLY REALLY old piece of software that isn't so great to say the least. Unfortunately, I don't know of any good 16 bit debuggers.
Quote:The processor won't disable and reenable interrupts automatically, you have to do it yourself with the cli/sei instructions (if needed, although it shouldn't be in this case).
Original post by law_order
Thanks doynax.
I'm just trying to underestand what exactly happens...
I'm really confused...
ok..the processor disables interrupts...
then "mov ss, ax" is executed....
I'm not sure why the debugger has problems. The stack segment should still point o a valid memory area in the middle of the data segment.
Quote:It's just part of how simple debuggers work.
Original post by law_order
what's the role of int 1?
To be able to execute a single instruction they patch the following one into an interrupt call (which reenters the debugger).
But if you need to know this then the debugger has done a poor job of hiding itself. It should be mostly transparent for anything you'd want to do.
after "mov ss, ax" is being executed
the registres' values are being showed on the screen...
ss points indeed to dteseg.
However sp has a value of 3e...
So it's like "push bx" has been partialy executed...
I'm saying that because sp's value has been decreased but bx's value hasn't been stored in dtseg at all...
In addition to this,i never had the chance to "trace" the execution of "push bx"...
So it's like debug executed "push bx"(even partialy) on its own!
the registres' values are being showed on the screen...
ss points indeed to dteseg.
However sp has a value of 3e...
So it's like "push bx" has been partialy executed...
I'm saying that because sp's value has been decreased but bx's value hasn't been stored in dtseg at all...
In addition to this,i never had the chance to "trace" the execution of "push bx"...
So it's like debug executed "push bx"(even partialy) on its own!
Ok, I'll try to make it more clear. The Step-By-Step debugger works this way:
There is a CPU signaled interrupt called Single-Step (INT1) -- just like Divizion-By-Zero (INT0), Breakpoint (INT3), etc. -- which executes after every instruction, if a special flag called TF (Trap-Flag) is set.
So, when you execute the "trace" command:
- debugger hooks INT1
- debugger sets TF
- jumps to CS:IP of the program
- an instruction is executed by the CPU
- CPU triggers INT1
- INT1 is handled by the debugger which updates its output
I hope this is clear... :)
Now, a MOV SS,... instruction inhibits all interrupts until the next instruction after it is executed. So when you "trace" the MOV SS, you won't end up after this instruction, but after the next one. For better understanding:
- debugger hooks INT1
- debugger sets TF
- jumps to CS:IP of the program
- the MOV SS, ... instruction is executed by the CPU
- because the instruction modifies the stack segment, the CPU DOES NOT trigger INT1
- the PUSH BX instruction is executed
- CPU triggers INT1
- INT1 is handled by the debugger which updates its output
In short: as you said, trace executes instructions one-by-one; this is the exception, when 2 instructions are treated like 1. Control returns to trace after PUSH BX, not after MOV SS.
As Doynax suggested you should use LSS. By the way, what debugger do you use (could be a crappy one)?
Hope this is clear enough :P.
Doynax:
A move to SS DOES disable interrupts; there is no problem with the code; the memory content at DS:[3Eh] DOES change.
There is no patching of the next instruction; the CPU provides this functionality through TF in FLAGS and INT1 (patching does occur with INT3 - breakpoint).
There is a CPU signaled interrupt called Single-Step (INT1) -- just like Divizion-By-Zero (INT0), Breakpoint (INT3), etc. -- which executes after every instruction, if a special flag called TF (Trap-Flag) is set.
So, when you execute the "trace" command:
- debugger hooks INT1
- debugger sets TF
- jumps to CS:IP of the program
- an instruction is executed by the CPU
- CPU triggers INT1
- INT1 is handled by the debugger which updates its output
I hope this is clear... :)
Now, a MOV SS,... instruction inhibits all interrupts until the next instruction after it is executed. So when you "trace" the MOV SS, you won't end up after this instruction, but after the next one. For better understanding:
- debugger hooks INT1
- debugger sets TF
- jumps to CS:IP of the program
- the MOV SS, ... instruction is executed by the CPU
- because the instruction modifies the stack segment, the CPU DOES NOT trigger INT1
- the PUSH BX instruction is executed
- CPU triggers INT1
- INT1 is handled by the debugger which updates its output
In short: as you said, trace executes instructions one-by-one; this is the exception, when 2 instructions are treated like 1. Control returns to trace after PUSH BX, not after MOV SS.
As Doynax suggested you should use LSS. By the way, what debugger do you use (could be a crappy one)?
Hope this is clear enough :P.
Doynax:
A move to SS DOES disable interrupts; there is no problem with the code; the memory content at DS:[3Eh] DOES change.
There is no patching of the next instruction; the CPU provides this functionality through TF in FLAGS and INT1 (patching does occur with INT3 - breakpoint).
Quote:So you're suggesting that instructions which modify the stack segment temporarily disable interrupts for one instruction? A quick googling didn't turn anything up so could you provide some reference?
Original post by Nis
Now, a MOV SS,... instruction inhibits all interrupts until the next instruction after it is executed. So when you "trace" the MOV SS, you won't end up after this instruction, but after the next one.
I guess you're right considering the OPs problems, it just seems like an odd side effect that could just as easily be achieved by disabling them manually.
Quote:Sorry for spreading disinformation.
Original post by Nis
There is no patching of the next instruction; the CPU provides this functionality through TF in FLAGS and INT1 (patching does occur with INT3 - breakpoint).
I made an assumption based on how certain other platforms handle this.
Quote:
Original post by doynax
So you're suggesting that instructions which modify the stack segment temporarily disable interrupts for one instruction? A quick googling didn't turn anything up so could you provide some reference?
Intel IA32 manual, volume 2A:
Loading the SS register with a MOV instruction inhibits all interrupts until after the execution of the next instruction. This operation allows a stack pointer to be loaded into the ESP register with the next instruction (MOV ESP, stack-pointer value) before an interrupt occurs1. Be aware that the LSS instruction offers a more efficient method of loading the SS and ESP registers.
The LSS instruction was not part of the original 8088/8086 instruction set. So, as explained above, the only way to initialise the stack was to do the following:
mov ax,stack_seg
mov ss,ax
mov sp,stack_top
but, if an interrupt occurred between the 'mov ss,ax' and 'mov sp,stack_top' then the stack will be invalid and the operation of the CPU will be unpredictable. So, to get round this, the CPU disables interrupt for one instruction after the 'mov ss,ax' instruction.
Skizz
mov ax,stack_seg
mov ss,ax
mov sp,stack_top
but, if an interrupt occurred between the 'mov ss,ax' and 'mov sp,stack_top' then the stack will be invalid and the operation of the CPU will be unpredictable. So, to get round this, the CPU disables interrupt for one instruction after the 'mov ss,ax' instruction.
Skizz
Thanks a lot guys..
I also made a research on INT 1 interrupt and now i fully underestand what happens...
Btw....Does anybody know why the 'MAIN" procedure has to be "FAR"?
As far as i know,the exe header provides information about the initial value of cs(though the loader relocates it of course...)
So what's the meaning of a "MAIN" proc being "FAR"?
I also made a research on INT 1 interrupt and now i fully underestand what happens...
Btw....Does anybody know why the 'MAIN" procedure has to be "FAR"?
As far as i know,the exe header provides information about the initial value of cs(though the loader relocates it of course...)
So what's the meaning of a "MAIN" proc being "FAR"?
The 'FAR' keyword affects the type of 'RET' opcode that is generated. There are two return types: a near and a far. In 16bit mode, a near only pops one word off the stack and the CS remains unchanged; for a far, two words are popped, one for CS and one for IP. Your entry point would have been called using a FAR call which would push CS and IP onto the stack.
In 32bit mode, the segment registers are not as straightforward (but you can do some neat tricks with them).
Skizz
In 32bit mode, the segment registers are not as straightforward (but you can do some neat tricks with them).
Skizz
Quote:
Original post by Skizz
The 'FAR' keyword affects the type of 'RET' opcode that is generated. There are two return types: a near and a far. In 16bit mode, a near only pops one word off the stack and the CS remains unchanged; for a far, two words are popped, one for CS and one for IP. Your entry point would have been called using a FAR call which would push CS and IP onto the stack.
In 32bit mode, the segment registers are not as straightforward (but you can do some neat tricks with them).
Skizz
Thanks skizz!I already know that...
i'm just wondering what serves the existance of a near or far keyword in the main module...
Since "main" is the basic module,is there any possibility that another piece of code actually makes a "call" to "main"?
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement