由 OpenWatcom 编译和链接的小型 DOS .exe 崩溃
Small model DOS .exe compiled and linked by OpenWatcom crashes
我正在尝试创建一个小型 DOS .exe 程序。我把入口点写在NASM assembly
; st.nasm
global _small_code_
global _printmsg_
extern _main0_
segment code
_small_code_:
..start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, stacktop
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
segment data
hello_msg: db 'Hello, World!', 13, 10, '$'
segment stack stack
resb 1024
stacktop:
请注意,我不确定 ..start:
代码和段应该是什么样子,我从某处复制粘贴了那部分。
我把主程序写在C:
/* prog.c */
void _printmsg(const char *msg);
int add(int a, int b) {
return a + b * 2;
}
void other() {
_printmsg("Hello!\r\n$"); /*CRASH*/
/*_printmsg(0);*/ /*OK*/
}
int _main0() {
return 5;
}
我用这个编译它:
$ nasm -f obj -o st.obj st.nasm
$ owcc -bdos -mcmodel=s -fno-stack-check -Os -s -march=i86 -o prog.exe prog.c st.obj
结果prog.exe是:
$ xxd prog.exe
00000000: 4d5a 8b00 0100 0200 0300 4000 ffff 0100 MZ........@.....
00000010: 4b04 0000 0a00 0100 2000 0000 0000 0000 K....... .......
00000020: 0b00 0100 1000 0100 0000 0000 0000 0000 ................
00000030: d1e2 01d0 c3b8 0000 e934 00b8 0500 c300 .........4......
00000040: 4865 6c6c 6f21 0d0a 2400 b801 008e d8b8 Hello!..$.......
00000050: 0100 8ed0 bc4b 04b4 09ba 3b00 cd21 e8da .....K....;..!..
00000060: ff89 c281 c23b 00b4 09cd 21b4 4ccd 21c3 .....;....!.L.!.
00000070: 5292 b409 ba3b 00cd 215a c348 656c 6c6f R....;..!Z.Hello
00000080: 2c20 576f 726c 6421 0d0a 24 , World!..$
反汇编prog.exe:
$ ndisasm -e 0x20 -b 16 prog.exe
00000000 0B00 or ax,[bx+si]
00000002 0100 add [bx+si],ax
00000004 1000 adc [bx+si],al
00000006 0100 add [bx+si],ax
00000008 0000 add [bx+si],al
0000000A 0000 add [bx+si],al
0000000C 0000 add [bx+si],al
0000000E 0000 add [bx+si],al
00000010 D1E2 shl dx,1
00000012 01D0 add ax,dx
00000014 C3 ret
00000015 B80000 mov ax,0x0
00000018 E93400 jmp 0x4f
0000001B B80500 mov ax,0x5
0000001E C3 ret
0000001F 004865 add [bx+si+0x65],cl
00000022 6C insb
00000023 6C insb
00000024 6F outsw
00000025 210D and [di],cx
00000027 0A24 or ah,[si]
00000029 00B80100 add [bx+si+0x1],bh
0000002D 8ED8 mov ds,ax
0000002F B80100 mov ax,0x1
00000032 8ED0 mov ss,ax
00000034 BC4B04 mov sp,0x44b
00000037 B409 mov ah,0x9
00000039 BA3B00 mov dx,0x3b
0000003C CD21 int 0x21
0000003E E8DAFF call 0x1b
00000041 89C2 mov dx,ax
00000043 81C23B00 add dx,0x3b
00000047 B409 mov ah,0x9
00000049 CD21 int 0x21
0000004B B44C mov ah,0x4c
0000004D CD21 int 0x21
0000004F C3 ret
00000050 52 push dx
00000051 92 xchg ax,dx
00000052 B409 mov ah,0x9
00000054 BA3B00 mov dx,0x3b
00000057 CD21 int 0x21
00000059 5A pop dx
0000005A C3 ret
0000005B 48 dec ax
0000005C 656C gs insb
0000005E 6C insb
0000005F 6F outsw
00000060 2C20 sub al,0x20
00000062 57 push di
00000063 6F outsw
00000064 726C jc 0xd2
00000066 64210D and [fs:di],cx
00000069 0A24 or ah,[si]
prog.exe
将 DOSBox 置于无限循环中。奇怪的是,如果我从 C 源文件中删除字符串文字(在甚至没有调用的 other
函数中),它会成功 returns。程序集文件有什么问题?
请注意,这是我第一次使用 OpenWatcom,也是我第一次构建 DOS .exe 文件。
我不想编写 main
函数,因为那样会导致 OpenWatcom libc 链接到输出可执行文件,使其变得不必要地大。
主要问题在于如何定义代码段。 Watcom C/C++ 编译器在使用 SMALL 内存模型时要求代码段被调用 _TEXT
,class 为 CODE
.汇编代码和 C 代码之间的这种不匹配导致代码段处于不同的物理段并且 call _main0_
跳转到内存中的错误位置导致抛出异常和程序挂起或崩溃。
您还可以让 Watcom 链接器在 DOS EXE 中生成所需的堆栈,方法是创建一个名为 _STACK
且属性为 STACK
和 class STACK
的段。如果以这种方式创建堆栈段,则无需在程序开头初始化 SS:SP。
Watcom 在 SMALL 内存模型中使用的其他部分是:
_DATA
段 class DATA
read/write 数据
CONST
段 class 为 DATA
的字符串文字(预计不会被修改)
CONST2
段 class 为 DATA
用于其他只读数据
_BSS
段 class 的 BSS
用于未初始化数据。
Watcom 期望分段 CONST
、CONST2
、_DATA
和 _BSS
都在同一个名为 DGROUP
的组中。同一组中的所有数据都可以通过组名来引用。当您以 Watcom 期望的方式设置 DGROUP
时,您所要做的就是将 DS 初始化为 DGROUP
段,而不是组中的各个段。
以 ..
开头的特殊标签作为执行开始的 DOS 入口点。因此 ..start
用于在 DOS EXE 头中生成一个入口点,告诉程序加载器当程序加载到内存中时从哪里开始执行。
您的汇编代码的修订版本可能如下所示:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Read/Write data here
segment _DATA class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
此代码假定 SS != DS,但是它必须使用 OWCC 的选项 -Wc,-zu
进行编译,该选项将 -zu
传递给 WCC(Watcom 编译器)。 -zu
修改代码生成,以便:
-zu SS != DGROUP (i.e., do not assume stack is in data segment)
如果你想设置 SS==DS==DGROUP 有很多方法可以做到。我可能建议的一个选项是将 _STACK
与所有其他程序数据一起放在 DGROUP
中。您需要在 resb 1024
之后添加一个标签,例如 stack_top:
,这样您就可以在设置 SS[=73= 后在启动时将该偏移量加载到 SP ] 与 DS 相同的值。此更改将导致汇编代码如下所示:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS _STACK
; _STACK has been added to DGROUP so we can set SS==DS==DGROUP
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ss, ax ; Set stack SS:SP to DGROUP:stack_top
mov sp, stack_top
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read/Write data here
segment _DATA class=DATA
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
stack_top:
我正在尝试创建一个小型 DOS .exe 程序。我把入口点写在NASM assembly
; st.nasm
global _small_code_
global _printmsg_
extern _main0_
segment code
_small_code_:
..start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, stacktop
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
segment data
hello_msg: db 'Hello, World!', 13, 10, '$'
segment stack stack
resb 1024
stacktop:
请注意,我不确定 ..start:
代码和段应该是什么样子,我从某处复制粘贴了那部分。
我把主程序写在C:
/* prog.c */
void _printmsg(const char *msg);
int add(int a, int b) {
return a + b * 2;
}
void other() {
_printmsg("Hello!\r\n$"); /*CRASH*/
/*_printmsg(0);*/ /*OK*/
}
int _main0() {
return 5;
}
我用这个编译它:
$ nasm -f obj -o st.obj st.nasm
$ owcc -bdos -mcmodel=s -fno-stack-check -Os -s -march=i86 -o prog.exe prog.c st.obj
结果prog.exe是:
$ xxd prog.exe
00000000: 4d5a 8b00 0100 0200 0300 4000 ffff 0100 MZ........@.....
00000010: 4b04 0000 0a00 0100 2000 0000 0000 0000 K....... .......
00000020: 0b00 0100 1000 0100 0000 0000 0000 0000 ................
00000030: d1e2 01d0 c3b8 0000 e934 00b8 0500 c300 .........4......
00000040: 4865 6c6c 6f21 0d0a 2400 b801 008e d8b8 Hello!..$.......
00000050: 0100 8ed0 bc4b 04b4 09ba 3b00 cd21 e8da .....K....;..!..
00000060: ff89 c281 c23b 00b4 09cd 21b4 4ccd 21c3 .....;....!.L.!.
00000070: 5292 b409 ba3b 00cd 215a c348 656c 6c6f R....;..!Z.Hello
00000080: 2c20 576f 726c 6421 0d0a 24 , World!..$
反汇编prog.exe:
$ ndisasm -e 0x20 -b 16 prog.exe
00000000 0B00 or ax,[bx+si]
00000002 0100 add [bx+si],ax
00000004 1000 adc [bx+si],al
00000006 0100 add [bx+si],ax
00000008 0000 add [bx+si],al
0000000A 0000 add [bx+si],al
0000000C 0000 add [bx+si],al
0000000E 0000 add [bx+si],al
00000010 D1E2 shl dx,1
00000012 01D0 add ax,dx
00000014 C3 ret
00000015 B80000 mov ax,0x0
00000018 E93400 jmp 0x4f
0000001B B80500 mov ax,0x5
0000001E C3 ret
0000001F 004865 add [bx+si+0x65],cl
00000022 6C insb
00000023 6C insb
00000024 6F outsw
00000025 210D and [di],cx
00000027 0A24 or ah,[si]
00000029 00B80100 add [bx+si+0x1],bh
0000002D 8ED8 mov ds,ax
0000002F B80100 mov ax,0x1
00000032 8ED0 mov ss,ax
00000034 BC4B04 mov sp,0x44b
00000037 B409 mov ah,0x9
00000039 BA3B00 mov dx,0x3b
0000003C CD21 int 0x21
0000003E E8DAFF call 0x1b
00000041 89C2 mov dx,ax
00000043 81C23B00 add dx,0x3b
00000047 B409 mov ah,0x9
00000049 CD21 int 0x21
0000004B B44C mov ah,0x4c
0000004D CD21 int 0x21
0000004F C3 ret
00000050 52 push dx
00000051 92 xchg ax,dx
00000052 B409 mov ah,0x9
00000054 BA3B00 mov dx,0x3b
00000057 CD21 int 0x21
00000059 5A pop dx
0000005A C3 ret
0000005B 48 dec ax
0000005C 656C gs insb
0000005E 6C insb
0000005F 6F outsw
00000060 2C20 sub al,0x20
00000062 57 push di
00000063 6F outsw
00000064 726C jc 0xd2
00000066 64210D and [fs:di],cx
00000069 0A24 or ah,[si]
prog.exe
将 DOSBox 置于无限循环中。奇怪的是,如果我从 C 源文件中删除字符串文字(在甚至没有调用的 other
函数中),它会成功 returns。程序集文件有什么问题?
请注意,这是我第一次使用 OpenWatcom,也是我第一次构建 DOS .exe 文件。
我不想编写 main
函数,因为那样会导致 OpenWatcom libc 链接到输出可执行文件,使其变得不必要地大。
主要问题在于如何定义代码段。 Watcom C/C++ 编译器在使用 SMALL 内存模型时要求代码段被调用 _TEXT
,class 为 CODE
.汇编代码和 C 代码之间的这种不匹配导致代码段处于不同的物理段并且 call _main0_
跳转到内存中的错误位置导致抛出异常和程序挂起或崩溃。
您还可以让 Watcom 链接器在 DOS EXE 中生成所需的堆栈,方法是创建一个名为 _STACK
且属性为 STACK
和 class STACK
的段。如果以这种方式创建堆栈段,则无需在程序开头初始化 SS:SP。
Watcom 在 SMALL 内存模型中使用的其他部分是:
_DATA
段 classDATA
read/write 数据CONST
段 class 为DATA
的字符串文字(预计不会被修改)CONST2
段 class 为DATA
用于其他只读数据_BSS
段 class 的BSS
用于未初始化数据。
Watcom 期望分段 CONST
、CONST2
、_DATA
和 _BSS
都在同一个名为 DGROUP
的组中。同一组中的所有数据都可以通过组名来引用。当您以 Watcom 期望的方式设置 DGROUP
时,您所要做的就是将 DS 初始化为 DGROUP
段,而不是组中的各个段。
以 ..
开头的特殊标签作为执行开始的 DOS 入口点。因此 ..start
用于在 DOS EXE 头中生成一个入口点,告诉程序加载器当程序加载到内存中时从哪里开始执行。
您的汇编代码的修订版本可能如下所示:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Read/Write data here
segment _DATA class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
此代码假定 SS != DS,但是它必须使用 OWCC 的选项 -Wc,-zu
进行编译,该选项将 -zu
传递给 WCC(Watcom 编译器)。 -zu
修改代码生成,以便:
-zu SS != DGROUP (i.e., do not assume stack is in data segment)
如果你想设置 SS==DS==DGROUP 有很多方法可以做到。我可能建议的一个选项是将 _STACK
与所有其他程序数据一起放在 DGROUP
中。您需要在 resb 1024
之后添加一个标签,例如 stack_top:
,这样您就可以在设置 SS[=73= 后在启动时将该偏移量加载到 SP ] 与 DS 相同的值。此更改将导致汇编代码如下所示:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS _STACK
; _STACK has been added to DGROUP so we can set SS==DS==DGROUP
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ss, ax ; Set stack SS:SP to DGROUP:stack_top
mov sp, stack_top
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read/Write data here
segment _DATA class=DATA
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
stack_top: