I wrote a simple Win16 program in NASM assembly. It works on Windows 3.11. (Source code: https://github.com/pts/mininasm/blob/master/demo/hello/helljw16.nasm .)
When I tried to combine the code and data segments to a single segment, it stopped working. I don't get any error message from Windows, but the messagebox doesn't appear. Is it possible to have a combined segment? If so, what's wrong with my code?
Update: Please note that my code has very few read-write variables (currently none, except in registers and on the stack), so it would be OK to keep the read-only variables in the code segment, and use a few bytes of stack (populated manually) for the read-write variables. Thus the data segment entry in SegTab is not strictly necessary. But the program doesn't work without it, and I'm interested if there is a fix.
The relevant part below is after the SegTab label: it contains a definition for a single segment only.
; ; hellhw16.nasm: minimalistic hello-world Win16 Windows 3.0 8086 .exe ; by [email protected] at Tue Dec 20 22:40:03 CET 2022 ; ; nasm -O0 -f bin -o helljw16.exe helljw16.nasm ; ; See also: https://github.com/alexfru/Win16asm/blob/master/ne.inc ; See also: https://board.flatassembler.net/topic.php?t=21339 ; See also: https://devblogs.microsoft.com/oldnewthing/20071203-00/?p=24323 ; bits 16 cpu 8086 org 0 FILE_ALIGNMENT_SHIFT equ 1 mz_header: ; DOS .exe header: http://justsolve.archiveteam.org/wiki/MS-DOS_EXE .signature db 'MZ' .lastsize: dw (.end - .signature) & 0x1ff ; The value 0 and 0x200 are equivalent here. Microsoft Linker 3.05 generates 0, so do we. Number of bytes in the last 0x200-byte block in the .exe file. .nblocks: dw (.end - .signature + 0x1ff) >> 9 ; Number of 0x200-byte blocks in .exe file (rounded up). .nreloc dw 0 ; No relocations. .hdrsize dw 0 ; Load .exe file to memory from the beginning. ..@code: %if 1 ; Produces identical .exe output even if we change it to 0. .minalloc: mov ax, 0x903 ; AH := 9, AL := junk. The number 3 matters for minalloc, see below. .ss_minus_1: mov dx, .message+(0x100-$$) ; (0x100-$$) to make it work with any `org'. .sp: int 0x21 ; Print the message to stdout. https://stanislavs.org/helppc/int_21-9.html .checksum: jmp strict short .do_exit %else .minalloc dw 0x03b8 ; To have enough room for the stack, we need minalloc >= ss+((sp+0xf)>>4)-0x20 == 0x315. kvikdos verifies it. .maxalloc dw 0xba09 ; Actual value doesn't matter. .ss dw 0x0118 ; Actual value doesn't matter as long as it matches minalloc. dw message .sp dw 0x21cd ; Actual value doesn't matter. int 0x21 .checksum dw 0x21eb ; Actual value doesn't matter. %endif .ip dw ..@code+(0x100-$$) ; Entry point offset. (0x100-$$) to make it work with any `org'. .cs dw 0xfff0 ; CS := PSP upon entry. %if 0 .relocpos dw ? ; Doesn't matter, overlaps with 2 bytes of message: 'He'. .noverlay dw ? ; Doesn't matter. overlaps with 2 bytes of message: 'll'. ; End of 0x1c-byte .exe header. %endif ; OpenWatcom stub message. .message db 'This is a Win16 executable.', 13, 10, '$' .do_exit: mov ax, 0x4c01 ; Same exit code as in OpenWatcom stub. int 0x21 times 0x3c-($-$$) db 0 ; Pad to 60 bytes. dd ne_header-$$ .end: PFLAG: .INST equ 2 AFLAG: .WINCOMPAT equ 2 OSFLAG: .WINDOWS equ 2 ; Win16. ne_header: .Signature db 'NE' .LinkerVer db 5 .LinkerRev db 1 .EntryTabOfs dw EntryTab-ne_header .EntryTabSize dw EntryTab.end-EntryTab .ChkSum dd 0 ; Always 0. .ProgramFlags db PFLAG.INST .ApplicationFlags db AFLAG.WINCOMPAT .AutoDataSegNo dw 1 .HeapInitSize dw 0 .StackSize dw 0x100 .InitIp dw _start-segment_code .InitCsSegNo dw 1 .InitSp dw 0 .InitSsSegNo dw 1 .SegCnt dw (SegTab.end-SegTab)>>3 .ModCnt dw (ModRefTab.end-ModRefTab)>>1 .NonResNameTabSize dw NonResNameTab.end-NonResNameTab .SegTabOfs dw SegTab-ne_header .ResourceTabOfs dw ResourceTab-ne_header .ResNameTabOfs dw ResNameTab-ne_header .ModRefTabOfs dw ModRefTab-ne_header .ImpNameTabOfs dw ImpNameTab-ne_header .NonResNameTabOfs dd NonResNameTab-$$ .MovableEntryCnt dw 0 .SegAlignShift dw FILE_ALIGNMENT_SHIFT .ResourceSegCnt dw 0 .OsFlags db OSFLAG.WINDOWS .ExeFlags db 0 .FastLoadOfs dw 0 .FastLoadSize dw 0 .Reserved dw 0 .ExpectedWinVer dw 0x300 $SEG: .DATA equ 0x0001 .MOVABLE equ 0x0010 .SHARABLE equ 0x0020 .PRELOAD equ 0x0040 .REL equ 0x0100 .DPL3 equ 0x0c00 .DISCARDABLE equ 0x1000 SegTab: .seg1.fofs dw (segment_code-$$)>>FILE_ALIGNMENT_SHIFT .seg1.size dw segment_code_end-segment_code .seg1.flags dw $SEG.REL | $SEG.PRELOAD | $SEG.REL | $SEG.DPL3 | $SEG.DATA .seg1.minalloc dw segment_code_end-segment_code .end: ResourceTab: .end: ResNameTab: .entry0 db .entry0.end-.entry0-3, 'hellnw16', 0, 0 ; Module name. .entry0.end: db 0 ; End of table. ModRefTab: .user: dw ImpNameTab.mod1-ImpNameTab .kernel: dw ImpNameTab.mod2-ImpNameTab .end: ImpNameTab: db 0 .mod1 db .mod1.end-$-1, 'USER' .mod1.end: .mod2 db .mod2.end-$-1, 'KERNEL' .mod2.end: EntryTab: db 0 ; Why? db 0 .end: NonResNameTab: .entry0: db .entry0.end-.entry0-3, 'hellnw16.exe', 0, 0 ; Module description. .entry0.end: db 0 ; End of table. .end: before_segment_code times ($$-$)&((1<<FILE_ALIGNMENT_SHIFT)-1) db 0 segment_code: segment_data: instancedata: msg_hi db 'Hi!', 0 times (instancedata+0xa)-$ db 0 .id0xa dw 0 ; InitTask overwrites it to stack bottom. .id0xc dw 0 ; InitTask overwrites it to stack bottom. .id0xe dw 0 ; InitTask overwrites it to stack top. msg_hello db 'Hello, World!', 0 _start: push cs pop ds xor bp, bp ; !! Do we need this? Maybe on Windows 3.0? Microsoft libc has it. push bp ; Do we need this? Not on Windows 3.11. ..@reloc1 equ $+1 call 0:0xffff ; InitTask. 'INITTASK'.'KERNEL' @91 == @0x5b. test ax, ax jz strict short .fail push di db 0x33, 0xc0 ; xor ax, ax push ax ..@reloc2 equ $+1 call 0:0xffff ; WaitEvent. 'WAITEVENT'.'KERNEL' @30 == @0x1e. ..@reloc3 equ $+1 call 0:0xffff ; InitApp. 'INITAPP'.'USER' @5. test ax, ax jz strict short .fail xor ax, ax push ax push ds mov bx, msg_hello-segment_data push bx push ds mov bx, msg_hi-segment_data push bx push ax ..@reloc0 equ $+1 call 0x0:0xffff ; MessageBox. 'MESSAGEBOX'.'USER' @1. !! Why is segment -0? mov ax, 0x4c00 ; EXIT_SUCCESS == 0. int 0x21 .fail: mov ax,0x4c01 ; EXIT_FAILURE == 1. int 0x21 segment_code_end: $AT: ; .AddrType. .OFFSET equ 1 .SEGMENT equ 2 .FARPTR equ 3 .OFFSET16 equ 5 REL: ; .RelType. .IMPORDINAL equ 1 .IMPNAME equ 2 segment_code_relocs: .count: dw (.end-.count-2)>>3 ; Each relocation is 8 bytes, 1 line each below. dw $AT.FARPTR | REL.IMPORDINAL<<8, ..@reloc0 -segment_code, (ModRefTab.user -ModRefTab+2)>>1, 1 ; 'MESSAGEBOX'.'USER' @1. dw $AT.FARPTR | REL.IMPORDINAL<<8, ..@reloc1 -segment_code, (ModRefTab.kernel-ModRefTab+2)>>1, 0x5b ; 'INITTASK'.'KERNEL' @91 == @0x5b. dw $AT.FARPTR | REL.IMPORDINAL<<8, ..@reloc2 -segment_code, (ModRefTab.kernel-ModRefTab+2)>>1, 0x1e ; 'WAITEVENT'.'KERNEL' @30 == @0x1e. dw $AT.FARPTR | REL.IMPORDINAL<<8, ..@reloc3 -segment_code, (ModRefTab.user -ModRefTab+2)>>1, 5 ; 'INITAPP'.'USER' @5. .end: ; __END__