我有以下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文件,我在末尾得到了一些额外的字节,而且我不知道为什么。 (阴影区域和下面的框内的字节是我们期望其他所有无法解释的字节。)
[]
我的直觉是这些是GCC使用的一些静态存储。我认为这可能是由于程序中的字符串引起的。因此,我已经注释了行print("Hello world$");
,但是多余的字节仍然存在。如果有人知道发生了什么并告诉如何防止GCC在输出中插入这些字节,这将有很大的帮助。
此处提供源代码:Github
PS:目标文件还包含这些额外的字节。
答案 0 :(得分:2)
由于您使用的是本机编译器,而不是i686(或i386)交叉编译器,因此您可以获得大量的额外信息。它完全取决于编译器配置。我建议您执行以下操作以删除不需要的代码生成和部分:
-fno-asynchronous-unwind-tables
删除所有.eh_frame
部分。在这种情况下,这是附加在DOS COM程序末尾的有害数据的原因。-static
进行构建而无需重新定位,以避免任何形式的动态链接。--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 C,Bruce's C compiler或Openwatcom C作为替代。它们都可以生成DOS COM程序。
答案 1 :(得分:1)
额外的数据可能是DWARF展开信息。您可以使用-fno-asynchronous-unwind-tables
选项阻止GCC生成它。
您还可以通过将以下内容添加到链接脚本的SECTIONS指令中,来使GNU链接器丢弃展开信息:
/DISCARD/ :
{
*(.eh_frame)
}
还要注意,由于字符串末尾为空字节,因此生成的COM文件将比您期望的大一个字节。