DOS .COM 文件末尾的额外字节,使用 GCC 编译
Extra bytes at the end of a DOS .COM file, compiled with GCC
我有以下 C 源文件,其中一些 asm 块通过调用 DOS 系统调用来实现打印和退出例程。
__asm__(
".code16gcc;"
"call dosmain;"
"mov [=10=]x4C, %AH;"
"int [=10=]x21;"
);
void print(char *str)
{
__asm__(
"mov [=10=]x09, %%ah;"
"int [=10=]x21;"
: // 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 文件,我在末尾得到了一些额外的字节,但我无法弄清楚原因。 (阴影区域内的字节和下面的方框是预期的,其他一切都下落不明)。
[]
我的直觉是这些是 GCC 使用的一些静态存储。我认为这可能是由于程序中的字符串所致。因此,我对 print("Hello world$");
行进行了注释,但额外的字节仍然存在。如果有人知道发生了什么并告诉如何防止 GCC 在输出中插入这些字节,那将会很有帮助。
此处提供源代码:Github
PS: 目标文件也包含这些额外的字节。
由于您使用的是本机编译器而不是 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
部分外,我还在 .bss
旁边添加了 *(COMMON)
。两者都是 BSS 部分。我还将它们移到数据部分之后,因为如果它们出现在其他部分之后,它们将不会占用 .COM 文件中的 space。我还将 *(.rodata);
更改为 *(.rodata*);
并将 *(.text);
更改为 *(.text*);
因为 GCC 可以生成以 .rodata
和 .text
开头但具有不同后缀的节名称在他们身上。
内联汇编
与您询问的问题无关,但很重要。在此内联程序集中:
__asm__(
"mov [=12=]x09, %%ah;"
"int [=12=]x21;"
: // no output
: "d"(str)
: "ah"
);
Int 21h/AH=9h 也破坏了 AL。您应该使用 ax
作为破坏者。
由于您通过寄存器传递数组地址,因此您还需要添加一个 memory
破坏符,以便编译器在发出内联汇编之前将整个数组存入内存。约束 "d"(str)
仅告诉编译器您将使用指针作为输入,而不是指针指向的位置。
如果您在 -O3
处进行了优化编译,您可能会发现由于这个错误,以下版本的程序甚至没有您的字符串 "Hello world$"
:
__asm__(
".code16gcc;"
"call dosmain;"
"mov [=13=]x4C, %AH;"
"int [=13=]x21;"
);
void print(char *str)
{
__asm__(
"mov [=13=]x09, %%ah;"
"int [=13=]x21;"
: // no output
: "d"(str)
: "ax");
}
void dosmain()
{
char hello[] = "Hello world$";
print(hello);
}
为 dosmain
生成的代码在字符串的堆栈上分配了 space,但在打印字符串之前从未将字符串放在堆栈上:
00000100 <print-0xc>:
100: 66 e8 12 00 00 00 calll 118 <dosmain>
106: b4 4c mov [=14=]x4c,%ah
108: cd 21 int [=14=]x21
10a: 66 90 xchg %eax,%eax
0000010c <print>:
10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx
112: b4 09 mov [=14=]x9,%ah
114: cd 21 int [=14=]x21
116: 66 c3 retl
00000118 <dosmain>:
118: 66 83 ec 10 sub [=14=]x10,%esp
11c: 67 66 8d 54 24 03 lea 0x3(%esp),%edx
122: b4 09 mov [=14=]x9,%ah
124: cd 21 int [=14=]x21
126: 66 83 c4 10 add [=14=]x10,%esp
12a: 66 c3 retl
如果您更改内联程序集以包含像这样的 "memory"
破坏程序:
void print(char *str)
{
__asm__(
"mov [=15=]x09, %%ah;"
"int [=15=]x21;"
: // no output
: "d"(str)
: "ax", "memory");
}
生成的代码可能看起来类似于:
00000100 <print-0xc>:
100: 66 e8 12 00 00 00 calll 118 <dosmain>
106: b4 4c mov [=16=]x4c,%ah
108: cd 21 int [=16=]x21
10a: 66 90 xchg %eax,%eax
0000010c <print>:
10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx
112: b4 09 mov [=16=]x9,%ah
114: cd 21 int [=16=]x21
116: 66 c3 retl
00000118 <dosmain>:
118: 66 57 push %edi
11a: 66 56 push %esi
11c: 66 83 ec 10 sub [=16=]x10,%esp
120: 67 66 8d 7c 24 03 lea 0x3(%esp),%edi
126: 66 be 48 01 00 00 mov [=16=]x148,%esi
12c: 66 b9 0d 00 00 00 mov [=16=]xd,%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 [=16=]x9,%ah
13c: cd 21 int [=16=]x21
13e: 66 83 c4 10 add [=16=]x10,%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 [=16=]x0,%al
内联汇编的替代版本,它使用变量通过 a
约束传递子函数 9 并将其标记为 input/output 和 +
(因为 return AX 的值被破坏)可以这样完成:
void print(char *str)
{
unsigned short int write_fun = (0x09<<8) | 0x00;
__asm__ __volatile__ (
"int [=17=]x21;"
: "+a"(write_fun)
: "d"(str)
: "memory"
);
}
建议:不要使用 GCC 生成 16 位代码。内联汇编是 difficult to get right and you will probably be using a fair amount of it for low level routines. You could look at Smaller C, Bruce's C compiler, or Openwatcom C 作为替代。都能生成DOS COM程序。
额外的数据可能是 DWARF 展开信息。您可以使用 -fno-asynchronous-unwind-tables
选项阻止 GCC 生成它。
您还可以让 GNU 链接器丢弃展开信息,方法是将以下内容添加到链接描述文件的 SECTIONS 指令中:
/DISCARD/ :
{
*(.eh_frame)
}
另请注意,由于字符串末尾的空字节,生成的 COM 文件将比您预期的大一个字节。
我有以下 C 源文件,其中一些 asm 块通过调用 DOS 系统调用来实现打印和退出例程。
__asm__(
".code16gcc;"
"call dosmain;"
"mov [=10=]x4C, %AH;"
"int [=10=]x21;"
);
void print(char *str)
{
__asm__(
"mov [=10=]x09, %%ah;"
"int [=10=]x21;"
: // 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 文件,我在末尾得到了一些额外的字节,但我无法弄清楚原因。 (阴影区域内的字节和下面的方框是预期的,其他一切都下落不明)。
[
我的直觉是这些是 GCC 使用的一些静态存储。我认为这可能是由于程序中的字符串所致。因此,我对 print("Hello world$");
行进行了注释,但额外的字节仍然存在。如果有人知道发生了什么并告诉如何防止 GCC 在输出中插入这些字节,那将会很有帮助。
此处提供源代码:Github
PS: 目标文件也包含这些额外的字节。
由于您使用的是本机编译器而不是 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
部分外,我还在 .bss
旁边添加了 *(COMMON)
。两者都是 BSS 部分。我还将它们移到数据部分之后,因为如果它们出现在其他部分之后,它们将不会占用 .COM 文件中的 space。我还将 *(.rodata);
更改为 *(.rodata*);
并将 *(.text);
更改为 *(.text*);
因为 GCC 可以生成以 .rodata
和 .text
开头但具有不同后缀的节名称在他们身上。
内联汇编
与您询问的问题无关,但很重要。在此内联程序集中:
__asm__(
"mov [=12=]x09, %%ah;"
"int [=12=]x21;"
: // no output
: "d"(str)
: "ah"
);
Int 21h/AH=9h 也破坏了 AL。您应该使用 ax
作为破坏者。
由于您通过寄存器传递数组地址,因此您还需要添加一个 memory
破坏符,以便编译器在发出内联汇编之前将整个数组存入内存。约束 "d"(str)
仅告诉编译器您将使用指针作为输入,而不是指针指向的位置。
如果您在 -O3
处进行了优化编译,您可能会发现由于这个错误,以下版本的程序甚至没有您的字符串 "Hello world$"
:
__asm__(
".code16gcc;"
"call dosmain;"
"mov [=13=]x4C, %AH;"
"int [=13=]x21;"
);
void print(char *str)
{
__asm__(
"mov [=13=]x09, %%ah;"
"int [=13=]x21;"
: // no output
: "d"(str)
: "ax");
}
void dosmain()
{
char hello[] = "Hello world$";
print(hello);
}
为 dosmain
生成的代码在字符串的堆栈上分配了 space,但在打印字符串之前从未将字符串放在堆栈上:
00000100 <print-0xc>: 100: 66 e8 12 00 00 00 calll 118 <dosmain> 106: b4 4c mov [=14=]x4c,%ah 108: cd 21 int [=14=]x21 10a: 66 90 xchg %eax,%eax 0000010c <print>: 10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx 112: b4 09 mov [=14=]x9,%ah 114: cd 21 int [=14=]x21 116: 66 c3 retl 00000118 <dosmain>: 118: 66 83 ec 10 sub [=14=]x10,%esp 11c: 67 66 8d 54 24 03 lea 0x3(%esp),%edx 122: b4 09 mov [=14=]x9,%ah 124: cd 21 int [=14=]x21 126: 66 83 c4 10 add [=14=]x10,%esp 12a: 66 c3 retl
如果您更改内联程序集以包含像这样的 "memory"
破坏程序:
void print(char *str)
{
__asm__(
"mov [=15=]x09, %%ah;"
"int [=15=]x21;"
: // no output
: "d"(str)
: "ax", "memory");
}
生成的代码可能看起来类似于:
00000100 <print-0xc>: 100: 66 e8 12 00 00 00 calll 118 <dosmain> 106: b4 4c mov [=16=]x4c,%ah 108: cd 21 int [=16=]x21 10a: 66 90 xchg %eax,%eax 0000010c <print>: 10c: 67 66 8b 54 24 04 mov 0x4(%esp),%edx 112: b4 09 mov [=16=]x9,%ah 114: cd 21 int [=16=]x21 116: 66 c3 retl 00000118 <dosmain>: 118: 66 57 push %edi 11a: 66 56 push %esi 11c: 66 83 ec 10 sub [=16=]x10,%esp 120: 67 66 8d 7c 24 03 lea 0x3(%esp),%edi 126: 66 be 48 01 00 00 mov [=16=]x148,%esi 12c: 66 b9 0d 00 00 00 mov [=16=]xd,%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 [=16=]x9,%ah 13c: cd 21 int [=16=]x21 13e: 66 83 c4 10 add [=16=]x10,%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 [=16=]x0,%al
内联汇编的替代版本,它使用变量通过 a
约束传递子函数 9 并将其标记为 input/output 和 +
(因为 return AX 的值被破坏)可以这样完成:
void print(char *str)
{
unsigned short int write_fun = (0x09<<8) | 0x00;
__asm__ __volatile__ (
"int [=17=]x21;"
: "+a"(write_fun)
: "d"(str)
: "memory"
);
}
建议:不要使用 GCC 生成 16 位代码。内联汇编是 difficult to get right and you will probably be using a fair amount of it for low level routines. You could look at Smaller C, Bruce's C compiler, or Openwatcom C 作为替代。都能生成DOS COM程序。
额外的数据可能是 DWARF 展开信息。您可以使用 -fno-asynchronous-unwind-tables
选项阻止 GCC 生成它。
您还可以让 GNU 链接器丢弃展开信息,方法是将以下内容添加到链接描述文件的 SECTIONS 指令中:
/DISCARD/ :
{
*(.eh_frame)
}
另请注意,由于字符串末尾的空字节,生成的 COM 文件将比您预期的大一个字节。