使用GCC编译的DOS .COM文件末尾的额外字节

时间:2019-05-29 20:41:35

标签: gcc x86 dos linker-scripts

我有以下C源文件,带有一些 asm 块,这些块通过调用DOS系统调用来实现打印和退出例程。

__asm__(
    ".code16gcc;"
    "call dosmain;"
    "mov $0x4C, %AH;"
    "int $0x21;"
);

void print(char *str)
{
    __asm__(
        "mov $0x09, %%ah;"
        "int $0x21;"
        : // no output
        : "d"(str)
        : "ah"
    );
}

void dosmain()
{
    // DOS system call expects strings to be terminated by $.
    print("Hello world$");
}

链接脚本文件和构建脚本文件就是这样,

OUTPUT_FORMAT(binary)
SECTIONS
{
    . = 0x0100;
    .text :
    {
        *(.text);
    }
    .data :
    {
        *(.data);
        *(.bss);
        *(.rodata);
    }
    _heap = ALIGN(4);
}
gcc -fno-pie -Os -nostdlib -ffreestanding -m16 -march=i386 \
-Wl,--nmagic,--script=simple_dos.ld simple_dos.c -o simple_dos.com

我习惯于在汇编中生成.COM文件,并且我知道dos文件的结构。但是,对于使用GCC生成的.COM文件,我在末尾得到了一些额外的字节,而且我不知道为什么。 (阴影区域和下面的框内的字节是我们期望其他所有无法解释的字节。)

enter image description here

[enter image description here]

我的直觉是这些是GCC使用的一些静态存储。我认为这可能是由于程序中的字符串引起的。因此,我已经注释了行print("Hello world$");,但是多余的字节仍然存在。如果有人知道发生了什么并告诉如何防止GCC在输出中插入这些字节,这将有很大的帮助。

此处提供源代码:Github

PS:目标文件还包含这些额外的字节。

2 个答案:

答案 0 :(得分:2)

由于您使用的是本机编译器,而不是i686(或i386)交叉编译器,因此您可以获得大量的额外信息。它完全取决于编译器配置。我建议您执行以下操作以删除不需要的代码生成和部分:

  • 使用GCC选项-fno-asynchronous-unwind-tables删除所有.eh_frame部分。在这种情况下,这是附加在DOS COM程序末尾的有害数据的原因
  • 使用GCC选项-static进行构建而无需重新定位,以避免任何形式的动态链接。
  • 已将GCC将--build-id=none选项传递给具有-Wl的链接器,以避免不必要地生成任何.note.gnu.build-id节。
  • 修改链接描述文件以放弃任何.comment部分。

您的构建命令可能类似于:

gcc -fno-pie -static -Os -nostdlib -fno-asynchronous-unwind-tables -ffreestanding \
-m16 -march=i386 -Wl,--build-id=none,--nmagic,--script=simple_dos.ld simple_dos.c \
-o simple_dos.com

我将您的链接描述文件修改为:

OUTPUT_FORMAT(binary)
SECTIONS
{
    . = 0x0100;
    .text :
    {
        *(.text*);
    }
    .data :
    {
        *(.data);
        *(.rodata*);
        *(.bss);
        *(COMMON)
    }
    _heap = ALIGN(4);

    /DISCARD/ : { *(.comment); }
}

除了添加/ DISCARD /指令以消除任何.comment部分外,我还沿着*(COMMON)边添加了.bss。两者都是BSS部分。我还将它们移到数据部分之后,因为如果它们出现在其他部分之后,它们将不会占用.COM文件中的空间。我还将*(.rodata);更改为*(.rodata*);,并将*(.text);更改为*(.text*);,因为GCC可以生成以.rodata.text开头但后缀不同的段名在他们身上。


内联汇编

与您询问的问题无关,但很重要。在此内联汇编中:

__asm__(
    "mov $0x09, %%ah;"
    "int $0x21;"
    : // no output
    : "d"(str)
    : "ah"
);

Int 21h/AH=9h也掩盖了 AL 。您应该使用ax作为掩盖对象。

由于要通过寄存器传递数组的地址,因此您还需要添加memory遮盖符,以便编译器在发出内联程序集之前将整个数组实现到内存中。约束"d"(str)仅告诉编译器您将使用指针作为输入,而不是指针指向什么。

就像您在-O3进行优化编译一样,您可能会发现以下版本的程序甚至没有包含您的字符串"Hello world$",因为该错误:

__asm__(
        ".code16gcc;"
        "call dosmain;"
        "mov $0x4C, %AH;"
        "int $0x21;"
);

void print(char *str)
{
        __asm__(
                "mov $0x09, %%ah;"
                "int $0x21;"
                : // no output
                : "d"(str)
                : "ax");
}

void dosmain()
{
        char hello[] = "Hello world$";
        print(hello);
}

dosmain生成的代码在堆栈中为字符串分配了空间,但从未在打印字符串之前将字符串放在堆栈中:

00000100 <print-0xc>:
 100:   66 e8 12 00 00 00       calll  118 <dosmain>
 106:   b4 4c                   mov    $0x4c,%ah
 108:   cd 21                   int    $0x21
 10a:   66 90                   xchg   %eax,%eax

0000010c <print>:
 10c:   67 66 8b 54 24 04       mov    0x4(%esp),%edx
 112:   b4 09                   mov    $0x9,%ah
 114:   cd 21                   int    $0x21
 116:   66 c3                   retl

00000118 <dosmain>:
 118:   66 83 ec 10             sub    $0x10,%esp
 11c:   67 66 8d 54 24 03       lea    0x3(%esp),%edx
 122:   b4 09                   mov    $0x9,%ah
 124:   cd 21                   int    $0x21
 126:   66 83 c4 10             add    $0x10,%esp
 12a:   66 c3                   retl

如果您更改内联程序集以包含"memory"遮盖符,则如下所示:

void print(char *str)
{
        __asm__(
                "mov $0x09, %%ah;"
                "int $0x21;"
                : // no output
                : "d"(str)
                : "ax", "memory");
}

生成的代码可能与此类似

00000100 <print-0xc>:
 100:   66 e8 12 00 00 00       calll  118 <dosmain>
 106:   b4 4c                   mov    $0x4c,%ah
 108:   cd 21                   int    $0x21
 10a:   66 90                   xchg   %eax,%eax

0000010c <print>:
 10c:   67 66 8b 54 24 04       mov    0x4(%esp),%edx
 112:   b4 09                   mov    $0x9,%ah
 114:   cd 21                   int    $0x21
 116:   66 c3                   retl

00000118 <dosmain>:
 118:   66 57                   push   %edi
 11a:   66 56                   push   %esi
 11c:   66 83 ec 10             sub    $0x10,%esp
 120:   67 66 8d 7c 24 03       lea    0x3(%esp),%edi
 126:   66 be 48 01 00 00       mov    $0x148,%esi
 12c:   66 b9 0d 00 00 00       mov    $0xd,%ecx
 132:   f3 a4                   rep movsb %ds:(%si),%es:(%di)
 134:   67 66 8d 54 24 03       lea    0x3(%esp),%edx
 13a:   b4 09                   mov    $0x9,%ah
 13c:   cd 21                   int    $0x21
 13e:   66 83 c4 10             add    $0x10,%esp
 142:   66 5e                   pop    %esi
 144:   66 5f                   pop    %edi
 146:   66 c3                   retl

Disassembly of section .rodata.str1.1:

00000148 <_heap-0x10>:
 148:   48                      dec    %ax
 149:   65 6c                   gs insb (%dx),%es:(%di)
 14b:   6c                      insb   (%dx),%es:(%di)
 14c:   6f                      outsw  %ds:(%si),(%dx)
 14d:   20 77 6f                and    %dh,0x6f(%bx)
 150:   72 6c                   jb     1be <_heap+0x66>
 152:   64 24 00                fs and $0x0,%al

内联程序集的替代版本,它使用变量通过a约束通过子函数9并使用+将其标记为输入/输出(因为AX的返回值被破坏了) )可以通过以下方式完成:

void print(char *str)
{
    unsigned short int write_fun = (0x09<<8) | 0x00;
    __asm__ __volatile__ (
        "int $0x21;"
        : "+a"(write_fun)
        : "d"(str)
        : "memory"
    );
}

建议:请勿将GCC用于16位代码生成。内联程序集为difficult to get right,您可能会在底层例程中使用大量的内联程序集。您可以选择Smaller CBruce's C compilerOpenwatcom C作为替代。它们都可以生成DOS COM程序。

答案 1 :(得分:1)

额外的数据可能是DWARF展开信息。您可以使用-fno-asynchronous-unwind-tables选项阻止GCC生成它。

您还可以通过将以下内容添加到链接脚本的SECTIONS指令中,来使GNU链接器丢弃展开信息:

/DISCARD/ : 
{
     *(.eh_frame)
}

还要注意,由于字符串末尾为空字节,因此生成的COM文件将比您期望的大一个字节。