;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; CGSTUB.ASM - Alternate DJGPP stub that supports fixups ; Chris Giese http://SisAndHappy.com/ChrisGiese/ ; ; Based on existing DJGPP stub; which is Copyright (C) ; DJ Delorie, CW Sandmann, and others ; ; 7 Jan. 2015: initial release ; ; Directions: ; 1. Assemble and link: ; nasm -f obj cgstub.asm ; tlink /c/x cgstub.obj, cgstub.exe ; ; 2. Compile DJGPP programs as usual. Link as normal to ensure there are ; no unresolved symbols. Then make an unstubbed COFF file and link ; with these options: -Wl,-d,-r ; gcc -Wl,-d,-r -O2 -Wall -W -g -o hello.cof hello.c ; ; 3. Use CGSTUB.EXE as the stub: ; copy /b cgstub.exe + hello.cof hello.exe ; ; 4. Enjoy greatly simplified access to conventional memory, like this: ; #include ; #include ; #include ; int main(void) { ; clrscr(); ; strcpy((void *)0xB8000L, "h e l l o "); ; printf("\nhi\n"); ; return 0; } ; __djgpp_conventional_base == 0. __djgpp_nearptr_enable() will still ; work (if it worked before) but it's no longer needed. Code works ; under NTVDM (Windows NT 4.0, Windows XP). ; ; To do: ; - Move variables, _dpmi_regs, and stack into a BSS and zero it before use ; ; - Missing functionality that exists in the DJGPP stub: ; - support normal COFF executables without fixups ; - load CWSDPMI if DPMI not present ; - DOS "symlink" functionality using _stubinfo.basename ; - use INT 21h AH=58h AL=02h/03h to enable/disable use of UMBs ; ; - Adjust code segment limit to make data segment non-executable? ; ; - Stub that allocates conventional memory for and loads into that memory ; anything in the ".lowmem" section of the COFF/ELF file? ; This would be more useful if I could put static local variables into ; a different file section, but it seems to work only with globals: ; typedef struct { ; char sig[4], ver_major, ver_minor; ; /* ...other stuff; 512 bytes total... */ ; } vbe_info_t; ; ; vbe_info_t g_vbe_info __attribute__((section (".lowmem"))) ; = { "VBE2" }; ; ; - Stub for ELF executables ; ; - Dynamic linking of ELF executables; with libc.so, libm.so, etc. ; Also include dynamic linking functions in the C library (dlfcn.h): ; dlopen(), dlsym(), etc. ; ; - (Maybe) Code and data in special COFF file sections (or ELF file ; segments) that will be locked in memory (unpaged). Use this for ; interrupt handlers. ; ; - Other: ; - Stub for PE executables? Likely to confuse Windows... ; - Stub for LX/LE executables? (whatever 32-bit Watcom C makes) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The 32-bit executable (COFF file) is often larger than 64K, and DOS ; will barf on .COM files >64K, so this stub must be an .EXE file. ; _DATA is defined first so it comes first in the .EXE file. ; Also, I need stuff aligned on paragraph (16-byte) boundaries. SEGMENT _DATA PUBLIC CLASS=DATA ALIGN=16 SEGMENT _TEXT PUBLIC CLASS=CODE ALIGN=16 SEGMENT _STACK STACK CLASS=STACK ALIGN=16 GROUP DGROUP _DATA _TEXT _STACK SEGMENT _DATA ; From /djgpp/include/stubinfo.h ; minstack is not used by the stub but is used by the pmode code _stubinfo: .magic: db "go32stub, v 2.02" .size: dd _stubinfo.end - _stubinfo .minstack: dd 0x80000 ; 0.5 megabytes .memory_handle: dd 0 ; set below .initial_size: dd 0 ; set below .minkeep: dw 0x4000 ; might be changed below .ds_selector: dw 0 ; set below .ds_segment: dw 0 ; set below .psp_selector: dw 0 ; set below .cs_selector: dw 0 ; set below .env_size: dw 0 ; set below .basename: times 8 db 0 .argv0: times 16 db 0 .dpmi_server: db "CWSDPMI.EXE", 0, 0, 0, 0, 0 .end: ; this points just beyond "PATH=" in the environment variable memory block: _path_ptr: dw 0 ; this points beyond the variables in the environment memory block: _argv0: dw 0, 0 _mode_switch_entry: dw 0, 0 ; from INT 2Fh AX=1687h _extender_private_memsize: dw 0 ; in paragraphs _file: dw 0 ; file handle from DOS OPEN _coff_offset: dd 0 _sect_table_off: dd 0 ; file offset of COFF section hdr. table _num_sects: dw 0 ; number of sections in COFF file _mem_size: dd 0 ; this is from INT 31h AX=0501h (see also _stubinfo.memory_handle): _dpmi_mem_base: dd 0 ; NOTE: _pmode_cs must immediately follow _pmode_eip _pmode_eip: dd 0 ; entry point of pmode executable _pmode_cs: dw 0 _pmode_ds: dw 0 ; transfer buffer in conventional memory; for loading code and data ; before copying it to extended memory where it belongs: _tb_sel: dw 0 _tb_seg: dw 0 _tb_size: dw 0 ; general-purpose buffer: _buf: times 48 db 0 SEGMENT _TEXT CPU 8086 ; NASM syntax for entry point: ..start: push cs pop ds mov [_stubinfo.ds_segment],ds cld ; Get DOS version. Need DOS 3+ for full path to this executable ; (argv[0]) stored after variables in environment memory block. mov ah,0x30 int 0x21 cmp al,0x03 mov si,need_dos3_msg jnc dos_ok mexit: call puts exit: mov ax,0x4C01 int 0x21 dos_ok: ; Minimize memory consumption until the test for DPMI because we may ; need to load CWSDPMI. Return all unused memory to DOS, ; unless minkeep field says otherwise: mov ax,[_stubinfo.minkeep] or ax,ax jne .mem0 ; mov ax,0xFE00 ; 0 bytes? assume maximum (0xFFFF & ~511) mov ah,0xFE .mem0: mov bx,end cmp bx,ax jae .mem1 mov [_stubinfo.minkeep],bx .mem1: add bx,0x010F ; add 256 bytes for PSP, and round up to paragraph ; shr bx,4 ; (186+ instruction; can't use it yet) mov cl,4 ; convert bytes to paragraphs shr bx,cl ; ES still points to PSP mov ah,0x4A int 0x21 jnc .mem2 ; If that failed, BX will contain the number of paragraphs available. It ; will be at least enough to load this program, else we wouldn't be running. mov ah,0x4A int 0x21 ; shl bx,4 ; convert paragraphs back to bytes ; mov cl,4 ; CL was not modified; still =4 shl bx,cl mov [_stubinfo.minkeep],bx .mem2: ; find PATH variable in environment, and find argv0 at end of environment mov ax,0x3D00 mov es,[es:0x2C] xor di,di mov cx,0xFFFF path1: ; cmp dword [es:di + 0],0x48544150 ; "PATH"; 386+ instruction cmp word [es:di + 0],0x4150 ; "PA" jne path2 cmp word [es:di + 2],0x4854 ; "TH" jne path2 cmp byte [es:di + 4],ah ; "=" jne path2 lea bx,[di + 5] mov [_path_ptr],bx path2: repnz scasb ; find next zero byte cmp [es:di],al ; environment ends with two zeroes... jne path1 inc di mov [_stubinfo.env_size],di inc di ; ...then two more bytes... inc di mov [_argv0 + 0],di ; ...and there's argv[0] mov [_argv0 + 2],es ;; When we are spawned from a program which has more than 20 handles in use, ;; all the handles passed to us by DOS are taken (since only the first 20 ;; handles are inherited), and opening the .exe file will fail. ;; Therefore, we forcefully close handles 18 and 19, to make sure at least two ;; handles are available. mov ah,0x3E mov bx,19 int 0x21 mov ah,0x3E mov bx,18 int 0x21 ; check for DPMI mov ax,0x1687 int 0x2F mov [_extender_private_memsize],si mov si,need_dpmi_msg or ax,ax jnz mexit and bl,0x01 ; check if DPMI server supports 32-bit programs jz mexit mov [_mode_switch_entry + 0],di mov [_mode_switch_entry + 2],es ; ### - Load CWSDPMI if no DPMI. Look for it in the same directory ; as the executable, then search the PATH. Will need to copy argv0 ; and the PATH entries somewhere before modifying them. ; ### - overwrite filename part of argv[0] with _stubinfo.basename if defined ; 32-bit DPMI is available, so the CPU is at least a '386 CPU 386 ; open argv[0]; i.e. this file push ds lds dx,[_argv0] mov ax,0x3D00 int 0x21 pop ds mov si,cant_open_msg jc mexit ; can't happen! mov [_file],ax ; read .EXE header just to get header size mov di,_buf mov dx,di mov cx,10 mov bx,[_file] mov ah,0x3F int 0x21 mov si,bad_coff_msg jc mexit xor cx,cx mov dx,[di + 8] shl dx,4 ; seek beyond .EXE stub; i.e. this code add dx,end adc cx,byte 0 mov [_coff_offset + 0],dx mov [_coff_offset + 2],cx mov bx,[_file] mov ax,0x4200 ; AL=00h=SEEK_SET int 0x21 ; read 48 bytes (20-byte COFF file header and 28 bytes of the a.out header) mov di,_buf mov dx,di mov cx,48 mov bx,[_file] mov ah,0x3F int 0x21 mov si,bad_coff_msg jc mexit cmp ax,48 jne mexit ; validate COFF and a.out headers cmp word [di + 0],0x014C ; COFF file magic jne mexit cmp word [di + 20],0x010B ; a.out magic jne mexit mov eax,[di + 36] ; entry point mov [_pmode_eip],eax mov dx,[di + 16] ; a.out header size; usually = 28 sub dx,28 jb mexit ; must be COFF executable-with-relocations mov ax,[di + 18] test al,0x01 mov si,reloc_msg jne mexit ; ensure that linking with relocations did not leave unresolved symbols test al,0x02 mov si,extern_msg je mexit ; if a.out header is longer than the 28 bytes we read, skip the rest of it xor cx,cx ; DX was set above mov bx,[_file] mov ax,0x4201 ; AL=01h=SEEK_CUR int 0x21 ; save file offset of section header table mov [_sect_table_off + 0],ax mov [_sect_table_off + 2],dx mov cx,[di + 2] ; number of sections mov [_num_sects],cx mov edx,0xFFFFFFFF ; EDX = lowest virtual address seen mov ebx,0 ; EBX = highest virtual address seen scan1: push cx ; read section header push dx push bx mov dx,di mov cx,40 mov bx,[_file] mov ah,0x3F int 0x21 pop bx pop dx jc mexit cmp ax,40 jne mexit ; Find highest and lowest virtual addresses used. Could probably just ; use (bss_address + bss_size), like the standard DJGPP stub... mov eax,[di + 12] cmp eax,edx jnc scan2 mov edx,eax scan2: add eax,[di + 16] cmp eax,ebx jc scan3 mov ebx,eax scan3: pop cx loop scan1 ; from DJGPP stub: mov eax,0x00010001 ; make sure limit is > 64K... cmp ebx,eax jae .mem1 mov ebx,eax .mem1: add ebx,0x0000FFFF ; ...and rounded to 64K increment xor bx,bx mov [_mem_size],ebx ; allocate memory for "DOS extender private data" xor bx,bx or bx,[_extender_private_memsize] je .mem2 mov ah,0x48 int 0x21 mov si,dos_mem_msg jc mexit mov es,ax ; if memsize=0, ES value doesn't matter .mem2: ; enter (16-bit) pmode mov ax,1 ; want 32-bit pmode later call word far [_mode_switch_entry] mov si,pmode_msg jc mexit ; now in pmode: mov [_stubinfo.psp_selector],es mov [_stubinfo.cs_selector],cs mov ax,ds mov [_stubinfo.ds_selector],ax mov es,ax ; allocate memory for code and data mov cx,[_mem_size + 0] mov bx,[_mem_size + 2] mov ax,0x0501 int 0x31 mov si,dpmi_mem_msg jc mexit mov [_stubinfo.memory_handle + 0],di mov [_stubinfo.memory_handle + 2],si mov [_dpmi_mem_base + 0],cx mov [_dpmi_mem_base + 2],bx ; store "size" (in this stub, it's not the size any more, ; but the highest virtual address) mov eax,[_dpmi_mem_base] add eax,[_mem_size] mov [_stubinfo.initial_size],eax ; allocate code descriptor in LDT: page-granular, 32-bit, present, code, ; non-conforming, readable, accessed mov dx,0xC09B call get_desc mov si,selector_msg jc mexit mov [_pmode_cs],bx ; allocate data descriptor in LDT: page-granular, 32-bit, present, data, ; expand-up, writable, accessed mov dx,0xC093 call get_desc jc mexit mov [_pmode_ds],bx ; Create a "transfer buffer" in conventional memory to which we can ; read .text and .data from the COFF file and copy it to extended memory; ; where it belongs. NOTE: Buffer size must now be a multiple of 10. mov ax,0x0100 mov bx,0x0F00 ; 0xF00=3840 paragraphs =61440 bytes = 60K int 0x31 jnc .mem3 ; Alloc failed, BX=paragraphs of memory available. Round BX down until ; memory size is a multiple of 10 (the size of a DJGPP COFF relocation). xor dx,dx mov ax,bx mov bx,10 push bx div bx pop dx mul dx ; try allocation again mov bx,ax mov ax,0x0100 int 0x31 mov si,dos_mem_msg jc mexit .mem3: mov [_tb_seg],ax mov [_tb_sel],dx shl bx,4 mov [_tb_size],bx xor ax, ax jmp .load6 .load1: push ax ; Each section header in COFF file is 40 bytes. Seek to header #ax mov dx,40 mul dx add ax,[_sect_table_off + 0] adc dx,[_sect_table_off + 2] mov cx,dx mov dx,ax mov bx,[_file] mov ax,0x4200 ; AL=00h=SEEK_SET int 0x21 ; (Re-)read section header. DOS READ function uses a memory pointer ; (DS:DX) so we need to issue that call via INT 31h AX=0300h; ; instead of calling INT 21h directly. mov ax,[_stubinfo.ds_segment] mov dx,_buf mov bx,dx mov cx,40 call pm_read mov si,read_err_msg jc mexit mov edi,[bx + 12] ; section virtual address add edi,[_dpmi_mem_base] ; linear load address mov es,[_pmode_ds] ; zero the BSS cmp dword [bx + 36],0x80 ; section flags jne .load4 mov ecx,[bx + 16] ; section size; in bytes xor eax,eax push edi shr ecx,1 jnc .load2 a32 stosb .load2: shr ecx,1 jnc .load3 a32 stosw .load3: a32 rep stosd pop edi jmp short .load5 ; seek to code/data .load4: mov dx,[bx + 20] ; file offset mov cx,[bx + 22] add dx,[_coff_offset + 0] adc cx,[_coff_offset + 2] mov ax,0x4200 ; AL=00h=SEEK_SET push bx mov bx,[_file] int 0x21 pop bx mov ecx,[bx + 16] ; section size; in bytes ; load code/data call do_load mov si,read_err_msg jc mexit ; do relocations (fixups); if any .load5: call fixup pop ax inc ax .load6: cmp ax,[_num_sects] jb .load1 ; close .EXE file mov bx,[_file] mov ah,0x3E int 0x21 ; free the transfer buffer mov ax,0x0101 mov dx,[_tb_seg] int 0x31 ; fixup entry point mov eax,[_dpmi_mem_base] add [_pmode_eip],eax %if 0 mov bx,16 mov si,debug_msg0 call puts mov ax,[_dpmi_mem_base + 0] mov dx,[_dpmi_mem_base + 2] call wrnum mov si,debug_msg1 mov ax,[_pmode_eip + 0] mov dx,[_pmode_eip + 2] call puts call wrnum mov si,debug_msg2 call puts mov ax,[_mem_size + 0] mov dx,[_mem_size + 2] call wrnum mov si,debug_msg3 call puts mov ax,[_pmode_ds] lsl eax,ax push eax pop ax pop dx call wrnum mov si,debug_msg4 call puts mov ah,0x00 int 0x16 %endif ; make FS point to this 16-bit stuff; including stubinfo push ds pop fs ; set pmode DS mov ds,[_pmode_ds] ; jump to loaded pmode code (sets pmode CS): jmp dword far [fs:_pmode_eip] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; I'm amazed that it works... ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; fixup: o32 pusha mov ax,[_buf + 32] ; num. relocations or ax,ax je .fixup5 ; seek to relocation info mov dx,[_buf + 24 + 0] ; file offset of relocations mov cx,[_buf + 24 + 2] add dx,[_coff_offset + 0] adc cx,[_coff_offset + 2] mov bx,[_file] mov ax,0x4200 ; AL=00h=SEEK_SET int 0x21 ; let EDX = number of bytes of relocation info mov ax,[_buf + 32] ; num. relocations mov dx,10 ; each COFF relocation is 10 bytes mul dx push dx push ax pop edx ; variables used in the following loop (TB = transfer buffer): ; [_pmode_ds]:EDI = pointer to loaded image ; [_tb_sel]: SI = pointer into TB (0...CX in 10-byte increments) ; EDX = total number of bytes of relocation info ; CX = number of bytes of relocation info currently in TB ; EBX = scratch ; EAX = scratch .fixup1: ; fill transfer buffer with relocation info xor ecx,ecx mov cx,[_tb_size] cmp ecx,edx jbe .fixup2 ;mov ecx,edx mov cx,dx .fixup2: mov ax,[_tb_seg] push dx xor dx,dx call pm_read pop dx jc .fixup5 push es xor si,si .fixup3: ; point to 10-byte COFF fixup in transfer buffer mov es,[_tb_sel] ; type must be 6 ("RELOC_ADDR32", a/k/a "dir32") mov ax,[es:si + 8] cmp ax,6 jne .fixup4 ; get address from the relocation mov ebx,[es:si + 0] ; subtract section address to get offset-into-section sub ebx,[_buf + 12] mov es,[_pmode_ds] ; do fixup by adding linear load address mov eax,[_dpmi_mem_base] add dword [es:edi + ebx],eax .fixup4: add si,byte 10 cmp si,cx jne .fixup3 pop es sub edx,ecx jne .fixup1 .fixup5: o32 popa ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Loads ECX bytes of code/data from DOS file handle word [_file] ; to address ES:EDI. Returns CY=1 if error. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; do_load: o32 pusha mov esi,ecx .load1: ; read from file; fill transfer buffer xor ecx,ecx mov cx,[_tb_size] cmp ecx,esi jbe .load2 ;mov ecx,esi mov cx,si .load2: mov ax,[_tb_seg] xor dx,dx call pm_read jc .load5 ; copy code/data from TB to where it belongs push ds push esi push ecx xor esi,esi mov ds,[_tb_sel] shr ecx,1 jnc .load3 a32 movsb .load3: shr ecx,1 jnc .load4 a32 movsw .load4: a32 rep movsd pop ecx pop esi pop ds sub esi,ecx jne .load1 .load5: o32 popa ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; While in pmode, reads cx bytes from file handle word [_file] ; to real-mode address ax:dx in conventional memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pm_read: push es o32 pusha push ds pop es mov edi,_dpmi_regs mov [di + (_dpmi_regs.ds - _dpmi_regs)],ax mov [di + (_dpmi_regs.dx - _dpmi_regs)],dx mov [di + (_dpmi_regs.cx - _dpmi_regs)],cx mov bx,[_file] mov [di + (_dpmi_regs.bx - _dpmi_regs)],bx mov byte [di + (_dpmi_regs.ah - _dpmi_regs)],0x3F xor cx,cx mov bx,0x0021 mov ax,0x0300 int 0x31 ; ### - CY set here if error from INT 31h AX=0300h push word [di + (_dpmi_regs.flags - _dpmi_regs)] popf o32 popa pop es ret ; could move this (50 bytes) to "end" but then it would need to be zeroed _dpmi_regs: .edi: .di: dd 0 .esi: .si: dd 0 .ebp: .bp: dd 0 dd 0 ; reserved .ebx: .bx: .bl: db 0 .bh: db 0, 0, 0 .edx: .dx: .dl: db 0 .dh: db 0, 0, 0 .ecx: .cx: .cl: db 0 .ch: db 0, 0, 0 .eax: .ax: .al: db 0 .ah: db 0, 0, 0 .flags: dw 0 .es: dw 0 .ds: dw 0 .fs: dw 0 .gs: dw 0 .ip: dw 0 .cs: dw 0 .sp: dw 0 .ss: dw 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Creates pmode segment with flags=DH, access byte=DL, ; segment base address = 0, segment limit = ; dword [_dpmi_mem_base] + dword [_mem_size] - 1 ; Returns CY=1 if error, else selector in BX. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; get_desc: push cx push ax push dx ; allocate code descriptor in LDT mov cx,1 xor ax,ax int 0x31 jc .err mov bx,ax ; ...leave base address = 0 ; mov cx,[_dpmi_mem_base + 2] ; mov dx,[_dpmi_mem_base + 0] ; mov ax,0x07 ; int 0x31 ; jc .err ; ...set flags (CH; byte 6 of descriptor) and access byte (CL; byte 5) mov cx,cs and cx,0x03 shl cx,5 pop dx push dx or cx,dx mov ax,0x09 int 0x31 jc .err ; ...limit = linear address of memory block + size of loaded image - 1 ; (size of loaded image was rounded to 64K boundary above; ; therefore it's also on a 4K boundary) mov dx,[_dpmi_mem_base + 0] mov cx,[_dpmi_mem_base + 2] add dx,[_mem_size + 0] adc cx,[_mem_size + 2] sub dx,1 sbb cx,byte 0 mov ax,0x08 int 0x31 .err: pop dx pop ax pop dx ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; displays 0-terminated string at DS:SI on stdout ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; CPU 8086 puts: push si push dx push ax mov ah,0x02 jmp short puts2 puts1: int 0x21 puts2: mov dl,[si] inc si or dl,dl jne puts1 pop ax pop dx pop si ret %if 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; name: wrnum ; action: writes 32-bit value to text screen ; in: 32-bit unsigned value in DX:AX, radix in BX ; out: (nothing) ; modifies: (nothing) ; minimum CPU: 8088 ; notes: for debug only ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; times 40 db 0 _num_buf: db 0 wrnum: push si push dx push cx push ax mov si,_num_buf ; extended precision division from section 9.3.5 ; of Randall Hyde's "Art of Assembly" ; start: DX=dividend MSW, AX=dividend LSW, BX=divisor wrnum_1: push ax mov ax,dx xor dx,dx ; before div: DX=0, AX=dividend MSW, BX=divisor ; after div: AX=quotient MSW, DX=intermediate remainder div bx mov cx,ax pop ax ; before div: DX=intermediate remainder, AX=dividend LSW, BX=divisor ; after div: AX=quotient LSW, DX=remainder div bx ; end: DX=quotient MSW, AX=quotient LSW, CX=remainder xchg dx,cx add cl,'0' cmp cl,'9' jbe wrnum_2 add cl,('A'-('9'+1)) wrnum_2: dec si mov [si],cl mov cx,ax or cx,dx jne wrnum_1 call puts pop ax pop cx pop dx pop si ret debug_msg0: db "Mem alloc at 0x", 0 debug_msg1: db ", EIP=0x", 0 debug_msg2: db ", size=0x", 0 debug_msg3: db ", seg limit=0x", 0 debug_msg4: db 13, 10, "Press a key to continue", 13, 10, 0 %endif ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; strings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; need_dos3_msg: db "DOS version 3+ required" crlf_msg: db 13, 10, 0 need_dpmi_msg: ; db "32-bit DPMI required; install CWSDPMI", 13, 10, 0 db "32-bit DPMI required", 13, 10, 0 cant_open_msg: db "Can't open .EXE file", 13, 10, 0 bad_coff_msg: db "Missing/invalid COFF file attched to this .EXE", 13, 10, 0 reloc_msg: db "No fixups; do 'ld -d -r ...' or 'gcc -Wl,-d,-r ...'", 13, 10, 0 extern_msg: db "Unresolved external symbols!", 13, 10, 0 pmode_msg: db "Can't enter protected mode", 13, 10, 0 dos_mem_msg: db "No DOS memory", 13, 10, 0 dpmi_mem_msg: db "No DPMI memory", 13, 10, 0 selector_msg: db "Can't create DPMI selectors", 13, 10, 0 read_err_msg: db "Error reading file", 13, 10, 0 SEGMENT _STACK times 128 db 0 ALIGN 16 end: