【问题标题】:Small model DOS .exe compiled and linked by OpenWatcom crashesOpenWatcom编译链接的小模型DOS.exe崩溃
【发布时间】:2020-06-19 15:10:10
【问题描述】:

我正在尝试创建一个小型 DOS .exe 程序。我在 NASM 程序集中写了入口点

; 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 函数中,甚至没有被调用),它会成功返回。汇编文件有什么问题?

请注意,这是我第一次使用 OpenWatcom,这是我第一次构建 DOS .exe 文件。

我不想编写 main 函数,因为这会导致 OpenWatcom libc 链接到输出可执行文件,使其变得不必要地大。

【问题讨论】:

    标签: c assembly x86 nasm watcom


    【解决方案1】:

    主要问题在于如何定义代码段。使用 SMALL 内存模型时,Watcom C/C++ 编译器要求代码段被称为 _TEXT,类为 CODE。汇编代码和 C 代码之间的这种不匹配导致代码段位于不同的物理段中,并且call _main0_ 跳转到内存中的错误位置导致抛出异常并且程序挂起或崩溃.

    您还可以让 Watcom 链接器在 DOS EXE 中生成所需的堆栈,方法是创建一个名为 _STACK 的段,其属性为 STACK 和类 STACK。如果以这种方式创建堆栈段,则无需在程序开始时初始化 SS:SP

    Watcom 在 SMALL 内存模型中使用的其他部分是:

    • 具有DATA 类的_DATA 段,用于读/写数据
    • 带有 DATA 类的 CONST 段,用于字符串文字(预计不会被修改)
    • CONST2 类为 DATA 的段,用于其他只读数据
    • _BSS 类为 BSS 的段,用于未初始化的数据。

    Watcom 预计段 CONSTCONST2_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 设置为与 DS 相同的值后,在启动时将该偏移量加载到 SP 。此更改将导致汇编代码如下所示:

    ; 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:
    

    【讨论】:

    • 这就像一个魅力。我不得不调整ss(以及相应的sp)使其等于ds,否则指向局部变量的指针不起作用。
    • 您的解决方案中也没有 1 KiB 膨胀。
    • @pts:我已经修改了我的答案,增加了关于生成 SS==DS==DGROUP 的版本。我还没有测试它,但它应该可以工作。
    • @pts :如果您删除 mov ss, axmov sp, stack_top 并使用默认值,那么编译器可能会将 SS 放在与 DS 不同的段中。如果发生这种情况,程序看起来可以工作,但堆栈顶部实际上不会位于正确的物理内存地址。
    • 为了更好地控制 .exe 标头字段,我最终编写了自己的链接器,针对 DOS .exe(使用小内存模型)和 DOS .com(使用小内存模型)。由于我的链接器正确填充了 ss 和 sp .exe 标头字段,因此在入口点执行 push sspop ds 就足够了。因此,.exe 文件也没有任何显式重定位。
    猜你喜欢
    • 2018-01-16
    • 2016-08-18
    • 1970-01-01
    • 2019-03-23
    • 2018-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多