really weird behavior, 16 bit asm w/masm

This is a discussion on really weird behavior, 16 bit asm w/masm within the A Brief History of Cprogramming.com forums, part of the Community Boards category; I haven't done much assembly with my new computer. A while ago, I had to repair some files in my ...

  1. #1

    Join Date
    May 2005
    Posts
    1,041

    really weird behavior, 16 bit asm w/masm

    I haven't done much assembly with my new computer. A while ago, I had to repair some files in my system directory because it wasn't letting me even execute commands in a console window.

    Again, I'm having more weird behavior. I'm trying to execute a simple program that uses MS DOS interrupts to obtain the date using 2AH.

    Function 2ah is as follows:
    Returns the system date
    Parameters: NONE
    Returns:
    AL Day of week
    CX Year
    DH Month
    DL Day
    I Execute the following instructions (i.e just trying to properly call the DOS function, and see that the AH register contains reasonable results)

    Code:
    dosseg
    .model small
    .stack 100h
    .data
    
    	DAYLEN EQU 10
    	dow db 'Sunday,   '
    	    db 'Monday,   '
    	    db 'Tuesday,  '
    	    db 'Wednesday,'
    	    db 'Thursday, '
    	    db 'Friday,   '
    	    db 'Saturday, '
    	
    	dowlen db 7,7,8,10,9,7,9
    	
    	datestr db 30 dup('$') 
    .code
    main PROC ;everything near for now 
    	mov ax, @data ;don't to do need this in a86 typically
    	mov ds, ax	
    	
    	mov ah, 2ah
    	int 21h
    
    	mov ax, 4c00h;	Dos terminate program
    	int 21h
    main ENDP
    
    END main
    When I trace the program using debug, it gives me odd results:
    -t

    AX=0B9C BX=0000 CX=0089 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
    DS=0B8B ES=0B8B SS=0BA4 CS=0B9B IP=0013 NV UP EI PL NZ NA PO NC
    0B9B:0013 8ED8 MOV DS,AX
    -t

    AX=0B9C BX=0000 CX=0089 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
    DS=0B9C ES=0B8B SS=0BA4 CS=0B9B IP=0015 NV UP EI PL NZ NA PO NC
    0B9B:0015 B42A MOV AH,2A
    -t

    AX=2A9C BX=0000 CX=0089 DX=0000 SP=0100 BP=0000 SI=0000 DI=0000
    DS=0B9C ES=0B8B SS=0BA4 CS=0B9B IP=0017 NV UP EI PL NZ NA PO NC
    0B9B:0017 CD21 INT 21 2ah called here, AH should have value 3, i.e AX = 03xx, the next insruction listed should be mov ax, 4c00h
    -t

    AX=2A9C wtf BX=0000 CX=0089 DX=0000 SP=00FA BP=0000 SI=0000 DI=0000
    DS=0B9C ES=0B8B SS=0BA4 CS=00A7 IP=107C NV UP DI PL NZ NA PO NC
    00A7:107C 90 NOP wtf
    -
    Namely, the AH register never changes (AX high, the top two bytes, should be between 0 and 6), and right after it calls the first interrupt it goes directly to a bunch of NO operations, then never actually executes the last interrupt which should successfully ends the program.

    I hadn't touched anything assembly other than inline in visual studio on my new computer, but I'm quite certain i've gotten other simple programs I've written to work. I'm really just confused, but I'm hoping its just some type of noob error.

    I wasn't sure where to put this question.
    Last edited by BobMcGee123; 11-30-2005 at 12:07 PM.
    I'm not immature, I'm refined in the opposite direction.

  2. #2
    Registered /usr
    Join Date
    Aug 2001
    Location
    Newport, South Wales, UK
    Posts
    1,263
    Sounds like something up with MASM more than anything else. First, load your program into a hex editor. If there's a NOOP (hex 0x90) at the end of the file as debug suggests then MASM is doing "something" with your code. I don't use MASM myself, so I couldn't suggest any switches.

    Secondly, try getting hold of NASM and use its disassembler, NDISASM, to corroborate the theory. This should give you a virtually identical listing to what you started with, except things won't be so nicely organised. If it doesn't, then again MASM has issues.

  3. #3
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    The behavior is undefined because you frag the current DS which is basically killing the memory. You are also fragging your DS across interrupt 21h. DOS functions often change the DS to the internal DOS DS and they do not restore the DS at times - even though they should. So preserve the DS first before you change it to your data segment, and preserve it across interrupt 21h and then restore it likewise.

    And yes you do have to move the value into AX first and then into DS. Placing values in segment registers is not allowed in x86 asm. Intel has an opcode which does this quite nicely in fewer cycles, however it is normally used to load a full pointer. Since your DS lies well within your 64Kb and since everything is near you wouldn't need to load a full pointer and probably are not allowed to. Also the LES and LDS opcodes are usually used in preparation for a copy from ES:DI to DS:SI so you are doing that part correctly.

    I would say that if you add this it might do what you want. This is perhaps why the memory is not reading out correctly. But this is only conjecture as I see nothing else that sticks out in my mind as being the culprit. I'm too lazy right now to get my DOS 6.20 tech ref out but I seem to remember that you must preserve DS prior to invoking DOS interrupt 21h because some of the DOS functions alter the DS and so when those functions return, the DS no longer points to what you think it does.

    This code will preserve the previous DS, your DS, call the DOS function and no matter if DOS changes the DS to it's DS, yours and the previous will be restored upon returning from DOS.

    Now it is true that at first glance you might just say well my DS is what's in AX. However, interrupt 21h will alter AX and therefore if you do not preserve DS prior to the interrupt, you have to way to restore it to what it was prior to the interrupt.

    EDIT: I've also noticed that you move a 16-bit value into AX and then an 8-bit value into AH. However the lower 8 bits of AX are still unaltered when you invoke interrupt 21h. Perhaps after doing this:

    Code:
    ...
    mov ax,@data
    push ds
    mov ds,ax
    You should do a xor ax,ax to zero out ax completely. Even if the DOS function only uses 8 bits, I would ensure I didn't send it anything it didn't need. I don't think this would cause any issues, but it might.
    Code:
    dosseg
    .model small
    .stack 100h
    .data
    
    	DAYLEN EQU 10
    	dow db 'Sunday,   '
    	    db 'Monday,   '
    	    db 'Tuesday,  '
    	    db 'Wednesday,'
    	    db 'Thursday, '
    	    db 'Friday,   '
    	    db 'Saturday, '
    	
    	dowlen db 7,7,8,10,9,7,9
    	
    	datestr db 30 dup('$') 
    .code
    main PROC ;everything near for now 
    	mov ax, @data ;don't to do need this in a86 typically
    
    push ds      ;preserve previous DS 
    
                    mov ds, ax	
    	mov ah, 2ah       
    
    push ds      ;preserve our current DS across int 21h
    
    	int 21h               ;it is possible our DS will be fragged here and/or AX
    
                    
    pop  ds                    ;Restore our DS
    pop  ds                    ;Restore DS to entry value 
    
    	mov ax, 4c00h;	Dos terminate program
    	int 21h
    main ENDP
    
    END main
    Always preserve segment registers prior to changing their values, either through immediate values, pointers, function calls, or interrupt service routines. This will ensure that the segment registers are indeed pointing to what you want them to be pointing to.

    Otherwise in DOS, you could cause major problems since there is NO protection mechanism. You could point the DS to the FORMAT function in DOS or anything else on the system. It spells disaster.
    So the NOPs might not be from your code at all. Also are you sure it's just not tracing into the interrupt 21h service routine code? That would seem logical since that is what you called and that would alter CS:IP. Keep tracing it and it should get back to your code. It wouldn't be possible to check the address of the 21h sub-function 2Ah address since that is internal to the 21h handler, but you could trace it and read the 21h handler code. I'm sure you are just reading the very start of the Microsoft interrupt 21h handler code. No telling how large it is, but eventually as I said it should come back to your code.

    If you ran this code in DOS under XP, it would crash. If you called this function from C in it's current state, it would crash.

    And as always if you wish to use a function in asm that uses params and you want it to be callable from C, use this format.

    Code:
    MyProc  proc
    
      push bp
      mov bp,sp
      
      ...
      ...
    
      pop bp
      ret
    MyProc endp
    TASM has a nice feature macro that allows you to specify arguments, local variables, etc, so you don't have to refer to them by [bp+4],[bp+8], etc. I'm sure MASM has a similar macro.

    Gawd I love assembly!!

    Hope this information helps.
    Last edited by VirtualAce; 11-30-2005 at 03:46 PM.

  4. #4

    Join Date
    May 2005
    Posts
    1,041
    Well, holy jesus Bubba that was one hell of a reply. I very highly appreciate the response, and I thank god there's people on here that have a handle on assembly (I consider actually being able to code in MS DOS a world different from people that just jump into doing inline assembly in visual studio, as I did).

    I think it suffices to say that the mistake certainly falls under the category of noob. When doing debug, I was using the 't' command...effectively tracing into the int 21 function...using 'p', proceed, goes to the next instruction of my program...d'oh

    So, ultimately, the code does work fine, I'm just a friggin idiot and I viewed the results improperly.
    I'm not immature, I'm refined in the opposite direction.

  5. #5
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    Well I hope you are preserving DS because if it works in XP, it's because Windows is protecting you. Otherwise in pure DOS this code would crash because DS is never saved or restored so when you return to your function from interrupt 21h, DS could/probably is pointing at DOS DS and not yours.

    The program would work in DOS until you attempted to load something into memory from your DS. This would cause the crash.
    Last edited by VirtualAce; 11-30-2005 at 07:33 PM.

  6. #6
    Registered /usr
    Join Date
    Aug 2001
    Location
    Newport, South Wales, UK
    Posts
    1,263
    Well I've never had a problem with needing to save DS before 21h. And yes, I have tested the couple of programs I made that use it under Windows 98 DOS, which is about as real as you can get these days. Although I haven't tried every single function, just file open/read/write/close and string output.

    To be honest I'm surprised people still use debug, it's like going to the TV to change channel instead of using the remote, things have moved on...

  7. #7

    Join Date
    May 2005
    Posts
    1,041
    Really? No way!!! Nobody told me that what I was using was old and archaic.

    On that note, I respect people like Bubba that have experience with doing things like working in the old graphics modes, writing software renderers, using old tools/technology etc. Sure, trivial things become harder and require more attention to detail, but I believe doing things like that grants more knowledge than just downloading a tutorial online and just doing whatever the latest technology fad is.
    I'm not immature, I'm refined in the opposite direction.

  8. #8
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    That might be true Smurf but even the DOS 6.20 tech refs state that you should preserve DS. And you will have a problem, albeit eventually, but surely if you do this:

    Code:
    mov ax,[some_value]
    mov ds,ax
    ...
    ret
    DS is now fragged. Anytime you switch DS you should preserve it first. This is not true for ES because programs do not rely on ES for their data segment. But any operation altering DS requires that DS be preserved prior to the operation. Otherwise where is your DS immediately after this? It is at [some_value]. Now Bob's example worked because [some_value] just happened to be his DS. However, it's not a good practice to change DS at random w/o first preserving it on the stack.

    An example of this would be the following 16-bit copy operation.

    Code:
    Copy16 proc
    ARG: Source:DWORD,Dest:DWORD,Size:WORD
    
    ;Base pointer preservation (function callable from C)
    ;When using ARG directive this prefix is required
    push  bp
    mov   bp,sp
    
    ;Setup counter
    mov   cx,Size
    
    ;Preserve DS prior to loading DS:SI
    push  ds
    lds     si,Source
    
    ;Load ES:DI
    les     di,Dest
    
    ;Perform the copy operation
    rep    movsw
    
    ;Catch any hanging bytes
    and   ecx,03h
    rep   stosb
    
    ;Restore DS
    pop  ds
    
    ;Restore BP
    pop  bp
    
    ;Return to caller
    ret
    Copy16 endp
    Last edited by VirtualAce; 12-01-2005 at 05:49 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Copy bit to bit
    By Coder2Die4 in forum C Programming
    Replies: 15
    Last Post: 06-26-2003, 09:58 AM
  2. binary numbers
    By watshamacalit in forum C Programming
    Replies: 4
    Last Post: 01-14-2003, 10:06 PM
  3. 16 bit or 32 bit
    By Juganoo in forum C Programming
    Replies: 9
    Last Post: 12-19-2002, 06:24 AM
  4. Inline asm - I love it!
    By wavering in forum C Programming
    Replies: 2
    Last Post: 01-08-2002, 01:19 PM
  5. Array of boolean
    By DMaxJ in forum C++ Programming
    Replies: 11
    Last Post: 10-25-2001, 11:45 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21