Поскольку вы используете собственный компилятор, а не кросс-компилятор 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
как clobber.
Поскольку вы передаете адрес массива через регистр, вы также захотите добавить Clobber 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"
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
Альтернативная версия встроенной сборки, которая передает подфункцию 9 через ограничение a
, используяпеременная и помечает ее как вход / выход с помощью +
(так как возвращаемое значение 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-битного кодапоколение.Встроенная сборка трудно понять правильно , и вы, вероятно, будете использовать ее достаточно для подпрограмм низкого уровня.В качестве альтернативы можно посмотреть Меньший C , Компилятор Брюса C или Openwatcom C .Все они могут генерировать DOS COM-программы.