由于您使用的是本机编译器而不是 i686(或 i386)交叉编译器,因此您可以获得大量额外信息。它相当依赖于编译器配置。我建议执行以下操作以删除不需要的代码生成和部分:
- 使用 GCC 选项
-fno-asynchronous-unwind-tables 消除任何 .eh_frame 部分。在这种情况下,这是在您的 DOS COM 程序末尾附加不需要的数据的原因。
- 使用 GCC 选项
-static 在不重定位的情况下构建以避免任何形式的动态链接。
- 让 GCC 使用
-Wl 将 --build-id=none 选项传递给链接器,以避免不必要地生成任何 .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 文件中的空间。我还将*(.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 clobber,以便编译器在发出内联汇编之前将整个数组实现到内存中。约束"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" clobber:
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 C、Bruce's C compiler 或Openwatcom C 作为替代方案。都可以生成DOS COM程序。