Дополнительные байты в конце файла .COM DOS, скомпилированного с GCC - PullRequest
4 голосов
/ 29 мая 2019

У меня есть следующий исходный файл 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. Однако в случае файла .COM, созданного с помощью GCC, в конце я получаю дополнительные байты и не могу понять, почему. ( Байты, которые находятся внутри затененной области и прямоугольника ниже, - это то, что ожидается, все остальное не учтено ).

enter image description here

[enter image description here]

Я догадываюсь, что это какое-то статическое хранилище, используемое GCC. Я думал, что это может быть связано со строкой в ​​программе. Таким образом, я прокомментировал строку print("Hello world$");, но дополнительные байты все еще находятся. Будет очень полезно, если кто-то знает, что происходит, и расскажет, как предотвратить вставку этих байтов GCC в вывод.

Исходный код доступен здесь: Github

PS: объектный файл также содержит эти дополнительные байты.

Ответы [ 2 ]

3 голосов
/ 30 мая 2019

Поскольку вы используете собственный компилятор, а не кросс-компилятор 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-программы.

2 голосов
/ 30 мая 2019

Дополнительные данные, скорее всего, DWARF раскручивают информацию.Вы можете запретить GCC генерировать его с помощью опции -fno-asynchronous-unwind-tables.

У компоновщика GNU также можно отказаться от информации о раскрутке, добавив следующее в директиву SECTIONS вашего сценария компоновщика:

/DISCARD/ : 
{
     *(.eh_frame)
}

Также обратите внимание, что сгенерированный COM-файл будет на один байт больше, чем вы ожидаете, из-за нулевого байта в конце строки.

...