MZ 실행 파일

덧붙인 머리

MZ를 만들어 낼 수 있는 다양한 제품들은 저마다 자신들만의 목적을 위해 기본 머리 뒷쪽에 뭔가를 덧붙이고 있다. 외부 링크 EXE Format (영어) 에 관련한 내용이 여럿 포함되어 있다. 대부분은 어떤 제품으로 만들어졌는지를 표시하는 정도이고, 진짜로 해당 제품만을 위한 것은 구체적인 정보가 없는 상태다.
마이크로소프트의 새 실행 파일 형식인 PE도 기본은 MZ이기 때문에 덧붙인 머리에 PE 머리에 대한 정보가 추가되어 있다. 아래 구조체는 MS의 SDK에 포함된 것이다. 앞 부분은 변수명만 다르게 정의되었을 뿐 기본 머리와 같다. PE에서 실질적인 의미가 있는 것은 맨 마지막의 e_lfanew 뿐이다. 새 형식의 머리가 어디에 있는지에 대한 정보다. 나머지 덧붙인 정보들은 PE가 만들어질 때까지 여러 제품들이 추가했던 정보들을 모두 반영하기 위한 형식적인 것이다.
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

실제

format MZentry _text:mainstack 80hsegment _textmain:
mov ax,_data
mov ds,ax

mov ah,9
mov dx,hello
int 21h

mov ax,4c00h
int 21hsegment _datahellodb 'Hello world!', 24h
FASM을 사용하면 이 프로그램은 77바이트의 아주 작은 MZ 파일이 된다.
0000000: 4d5a 4d00 0100 0100 0200 0800 ffff 0300  MZM.............0000010: 8000 0000 0000 0000 1c00 0000 0100 0000  ................0000020: b802 008e d8b4 09ba 0000 cd21 b800 4ccd  ...........!..L.0000030: 2100 0000 0000 0000 0000 0000 0000 0000  !...............0000040: 4865 6c6c 6f20 776f 726c 6421 240d 0a    Hello world!$..
기본 머리 부분을 읽기 쉽게 바꾸면
signature="MZ"extrabytes=77pages=1relocations=1headersize=2minmemory=8maxmemory=65535initSS=3initSP=128checksum=0initIP=0initCS=0reloctable=28overlay=0
당연히 시작은 "MZ"다. 그리고 파일이 77바이트이기 때문에 pages=1로 페이지는 하나뿐이고, 그 페이지의 사용 바이트는 extrabytes=77이다.
재배치 항목은 relocations=1개가 있고, 재배치 표는 reloctable=28에서 시작한다. 기본 머리의 크기가 28이므로 재배치 표가 머리 다음에 바로 이어지고 있다는 것을 알 수 있다. FASM은 별도의 추가 정보를 덧붙이지 않고 기본 머리만을 만들어내고 있는 것이다. 하나 있는 재배치 항목은 0000:0001이다.
headersize=2이므로 실제 코드의 시작은 2 * 16으로 32(0x20)부터다. 실제로 0x20의 있는 값들은 _text 세그먼트의 코드들이다. 이를 거꾸로 역어셈블리해보면

mov ax,0x0002
mov ds,ax
소스에서의 _data가 0x0002가 되어 있음을 확인할 수 있다. 파일 위치 0x20에서 실제 코드가 시작된다. 즉, 프로그램적으로는 여기가 시작점으로 0번지로 가정된다. 따라서 "Hello..."가 있는 0x40은 프로그램 번지로는 0x20이고, 그래서 세그먼크가 0x0002가 되는 것이다. 하지만 이 값은 실제로 이 프로그램이 메모리에 올려질 때 그 메모리 번지에 맞도록 고쳐져야 한다. 그래서 0000:0001 위치가 재배치 표에 올라 있는 것이다.
minmemory=8은 stack 80h 때문이다. stack은 초기화된다거나 하는 대상이 아니기 때문에 그냥 메모리만 확보하면 된다. 그래서 stack 80h를 한다고 하더라도 파일에 80h만큼이 공간이 확보된다거나 그러지 않는다. 단지 minmemory=8라는 설정을 통해 메모리가 추가로 80h만큼은 반드시 더 확보되도록 한다. maxmemory=65535은 남은 메모리를 모두 할당하도록 한다.
코드 부분인 _text의 크기가 0x20, _data의 크기는 0x0e지만 16바이트의 문단 단위로 메모리를 관리하기 때문에 stack은 그 이후에 잡힌다. 그래서 SS:IP가 0003:0080으로 된다. CS:IP는 0번지에서 바로 시작하므로 0000:0000이다. 만약 소스에서 _text와 _data의 위치를 서로 바꾼다면 다른 변화도 생기겠지만, CS:IP가 _data의 크기만큼 뒤로 밀리게 된다. _data은 문단 단위의 경계에 맞추려면 크기가 0x10이 되기 때문에 CS:IP는 0001:0000이 될 것이다.

호환성

MZ 실행 파일은 DOS와 윈도 9x 계열의 OS에서 실행된다. 32비트인 윈도 NT 계열에서도 가상 도스 머신이라는 방식으로 실행할 수 있지만, 그래픽 모드를 사용하는 일부는 안된다. 64비트 윈도에서는 실행할 수 없다. 대신에 DOSBox, DOSEMU, 와인 등을 사용해서 실행할 수 있다.
디지털 마스 Optlink, MS 링커, VALX, 오픈 왓콤의 WLINK, 등등의 링커로 MZ 실행 파일을 만들 수 있다. 덧붙여 FASM로는 바로 만들 수 있다.