Search code examples
assemblytasm16-bit

ASM EXE program 16bit: Error changing size of memory


I write EXE program with SMALL model.
I want to load other programs with the help of my program. I read that first of all I must free some memory. I use DOS 4Ah INT 21h interrupt. But I have error 7 (control units memory is destroyed) in AX when use it. What I made incorrect?

;-------------------MACRO-----------------
println MACRO info
    push ax
    push dx

    mov ah, 09h
    mov dx, offset info
    int 21h

    ;print new line
    mov dl, 10
    mov ah, 02h
    int 21h

    mov dl, 13
    mov ah, 02h
    int 21h

    pop dx
    pop ax
ENDM
;-----------------end macro----------------

.model small

.stack 100h

.data

initToRunErrorText db "Bad init to run other programs", '$'

myDataEnd db '0'

.code

main:
    mov ax, @data
    mov es, ax
    mov ds, ax

    call initToRun

    mov ah, 4Ch
    int 21h

;   Result
;       ax = 0 => all is good
;       ax != 0 => we have an error
initToRun PROC
    push ax bx

    mov ah, 4Ah
    mov bx, offset myDataEnd + 100h
    shr bx, 4
    add bx, 2
    int 21h

    jnc initToRunAllGood

    add ax, '0'
    mov dl, al
    mov ah, 06h
    int 21h

    mov ax, 1
    println initToRunErrorText

    jmp initToRunEnd

initToRunAllGood:
    mov ax, 0

initToRunEnd:
    pop bx ax
    ret
ENDP

program_length equ $-main

end main

For compile I use TASM 16 bit with DOSBox 0.74


Solution

  • You don't need to free memory in an .exeprogram. Only a .com program reserves all memory. It's rather complicated to determine the real last byte of an .exe program and to find the segment of the allocated block. BTW: In a .com program you must adjust the stack pointer!

    As @RossRidge mentions, some linkers (e.g. TLINK) write into the header an information to allocate the maximum memory. This is annoying.

    You can modify this header item by using a tool like Microsoft's exemod which is contained in the MASM suites (versions < 6). A suite is downloadable here. The exemod utility can be found in DISK4. Use it like:

    exemod <exefile> /max 1
    

    Another option is to resize the allocated memory inside the program. Make first a simple program HELLO.EXE to be executed by a parent program:

    .MODEL small
    .STACK          ; default: 1000h
    .DATA
        hello db "Hello world", 13, 10, "$"
    .CODE
    main PROC
        mov ax, @data
        mov ds, ax
        mov dx, OFFSET hello
        mov ah, 09h
        int 21h
        mov ax, 4C00h
        int 21h
    main ENDP
    END main
    

    Now the parent:

    .MODEL small
    
    .STACK
    
    .DATA
        hello db "HELLO.EXE", 0
    
        params label word
            dw 0
            dw OFFSET command_line, SEG command_line
            dw 0ffffh,0ffffh ; fcb1
            dw 0ffffh,0ffffh ; fcb2
    
        command_line db 0,13
    
    .CODE
    
    free_memory PROC
        mov ax, sp              ; SS:SP -> nearly the end of program
        shr ax, 4
        mov bx, ss
        add bx, ax
        add bx, 2               ; BX = a paragraph beyond program
        mov ax, es              ; ES -> first paragraph of the program (containing PSP)
        sub bx, ax              ; BX = program size in paragraphs
        mov ah, 4ah             ; Resize memory block - http://www.ctyme.com/intr/rb-2936.htm
        int 21h                 ; Call MS-DOS
    
        ret
    free_memory ENDP
    
    execute_hello PROC
        push ds                 ; Save DS
        push es                 ; Save ES
    
        mov cs:[stk_seg],ss     ; Save stack pointer
        mov cs:[stk_ptr],sp
    
        mov ax, 4B00h
        mov dx, OFFSET hello
        mov bx, SEG params
        mov es, bx
        mov bx, OFFSET params
    
        mov ax,4b00h            ; Exec - load and/or execute program - http://www.ctyme.com/intr/rb-2939.htm
        int 21h                 ; Call MS-DOS
    
        cli                     ; Let no interrupt disturb
        mov ss,cs:[stk_seg]     ; Restore stack pointer
        mov sp,cs:[stk_ptr]
        sti                     ; Allow interrupts
    
        pop es                  ; Restore ES and DS
        pop ds
        ret
    
        ; Data for this function in the code segment
        stk_ptr dw 0
        stk_seg dw 0
    execute_hello ENDP
    
    main PROC
        mov ax, @data           ; Initialize DS
        mov ds, ax
    
        call free_memory        ; ES should point to PSP (default)
        call execute_hello
    
        mov ax, 4C00h           ; Terminate with return code - http://www.ctyme.com/intr/rb-2974.htm
        int 21h                 ; Call MS-DOS
    main ENDP
    
    END main
    

    The end of the program is determined by the stack pointer. You should therefore be sure that the STACK segment is the last segment in the program. Generate a .MAP file with the tlink command line option /s. It should look like:

     Start  Stop   Length Name               Class
    
     00000H 0005BH 0005CH _TEXT              CODE
     00060H 00079H 0001AH _DATA              DATA
     00080H 0047FH 00400H STACK              STACK
    
    
    Detailed map of segments
    
     0000:0000 005C C=CODE   S=_TEXT          G=(none)   M=INBBC8~1.ASM ACBP=48
     0006:0000 001A C=DATA   S=_DATA          G=DGROUP  M=INBBC8~1.ASM ACBP=48
     0006:0020 0400 C=STACK  S=STACK          G=DGROUP  M=INBBC8~1.ASM ACBP=74
    
    Program entry point at 0000:004C
    

    As you can see STACK is the last segment listed here.

    Now you can run the parent and read what the child (HELLO.EXE) has to tell :-)