TL;DR: книга Питера Нортона верна. Вы должны определить стек с помощью директивы .STACK
при создании программы DOS EXE (MZ), чтобы избежать неопределенного поведения. В качестве альтернативы вы можете создать сегмент с именем STACK
с классом STACK
, используя директивы SEGMENT
/ENDS
с соответствующим оператором BYTE ### DUP(?)
, где ###
— размер стека в байтах.
У меня есть английская версия книги (3rd Edition), что похоже на то, что вы цитируете:
DOS всегда устанавливает указатель стека на самый конец сегмента при загрузке COM-файла в память. По этой причине вам не нужно объявлять сегмент стека (с .STACK) для COM-файлов. Что произойдет, если вы удалите директиву .STACK из TEST_SEG.ASM?
C>DEBUG TEST_SEG.EXE
R
AX=0000 BX=0000 CX=0004 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=3985 ES=3985 SS=3995 CS=3995 IP=0000 NV UP EI PL NZ NA PO NC
3090:0000 B44C MOV AH,4C
Теперь стек находится на 3995:0, что является началом вашей программы (CS:0). Это очень плохие новости. Вам не нужен стек рядом с кодом вашей программы. Поскольку указатель стека находится в SS:0, ему некуда расти (поскольку стек растет в памяти). По этим причинам вы должны объявить сегмент стека для EXE-программ.
Формат программы DOS EXE (MZ) включает заголовок, который включает поля, определяющие начальное значение стека < em>СС:СП. Значением SP будет размер стека, запрошенный директивой .STACK
. Если в директиве .STACK
не указано значение, обычно по умолчанию используется значение 1024 байта (0x400 байт) для большинства MASM и совместимых ассемблеров.
Когда вы укажете директиву .STACK
, компоновщику будет предложено сгенерировать значение SS:SP, которое не конфликтует с другими сегментами в программе EXE.
Формат файла DOS EXE позволяет перемещать сегменты при загрузке программы в память. Значение SS, записанное в заголовок для указателя стека, является значением относительно CS, которое изначально установлено равным нулю. Когда загрузчик DOS считывает программу EXE в память, он выполняет исправления программы, включая значение SS:SP в заголовке. Если, например, у вас есть программа DOS EXE, где SS:SP в заголовке компоновщик устанавливает на 0x0002:0x0400, а загрузчик DOS загружает вашу программу в сегмент 0x3995, тогда стек (SS :SP) будет установлено значение 0x3995+2:0x0400 = 0x3997:0x0400.
Компоновщик указывает, сколько памяти требуется программе, и записывает соответствующую информацию в заголовок. Когда загрузчик DOS читает заголовок, он проверяет, достаточно ли памяти, включая сегмент стека.
Что происходит, когда вы не используете .STACK в программе EXE?
Если .STACK
не указано в вашем ассемблерном коде, значение, записанное компоновщиком в поля SS:SP в заголовке, будет установлено на 0x0000:0x0000. Это означает, что когда загрузчик DOS перемещает сегмент и устанавливает SS:SP, эффективное место в памяти будет таким же, как CS:0x0000. Это означает, что если в вашем сегменте CS содержится 65 536 байт кода (достаточно, чтобы заполнить весь сегмент), ваш стек будет увеличиваться поверх него. Например, если вы поместите 16-битное значение в стек, из указателя стека будет вычтено 2, а значение будет записано в это место. Это будет CS:0xFFFE. Кроме того, на самом деле нет никакой гарантии, что память CS:0xFFFE и CS:0xFFFF доступна вашей программе! Когда вы не указываете стек, он не влияет на размер программы, записываемой компоновщиком в поля заголовка. Когда загрузчик DOS считывает его в память, он не будет знать, достаточно ли памяти для вашего стека или нет.
Вот почему большинство компоновщиков предупредят вас, что вы создаете EXE без определенного стека. Если вы не укажете стек, при загрузке в память он, вероятно, будет работать нормально, если расположение стека находится где-то в пространстве вашей программы или в неиспользуемой области пространства, не используемой DOS или другим приложением. Не стоит полагаться на удачу.
Что предлагает Питер Нортон, так это то, что вы всегда должны использовать директиву .STACK
или явно определять свой собственный сегмент стека, используя директиву SEGMENT
/ENDS
с соответствующим количеством байтов, выделенных для него. Это делается для того, чтобы ваша программа загружалась DOS так, как вы ожидаете, и выполнялась так, как вы ожидаете.
Преобразование программ EXE в COM с помощью ранних инструментов разработки
Одной из причин, по которой вы можете захотеть создать EXE-файл без стека, является использование старых версий MASM и компоновщика, который не может напрямую генерировать COM-программу. В ранних версиях MASM не было модели TINY
. Чтобы сгенерировать COM-программу, вы создали программу модели SMALL
, в которой не указан ни стек, ни перемещение сегментов, а затем вы использовали компоновщик для генерации EXE-программы. Если программа EXE соответствует требованиям программы COM, ее можно преобразовать из EXE. Программа EXE2BIN
могла попытаться сделать такое преобразование. COM-программа DOS при первоначальной загрузке имеет стек (SS:SP), установленный на адрес памяти, выровненный по последнему абзацу, доступный в конце сегмента кода. Затем он помещает 0x0000 в стек. Это должно было сохранить совместимость с CP/M. Загрузчик DOS помещает значение 0x0000 в стек, чтобы вы могли выполнить RET
для завершения программы. Адрес CS:0x0000 находится в DOS PSP и содержит Int 0x20
инструкция для завершения программы.
При загрузке COM-программы DOS: если загрузчик DOS обнаружит, что доступно все 64 КБ памяти, значение SS будет установлено на сегмент DOS Префикс сегмента программы (PSP) установлен, и он установит SP в 0x0000 и поместит 0x0000 в стек. Вот почему вы часто увидите начальный SS:SP в программе DOS COM, начинающейся с CS:0xFFFE при просмотре в отладчике.
Я не знаю, почему в книге Питера Нортона значение SP равно 0xFFEE (SP=FFEE
) в выводе трассировки отладки. Выглядит необычно, но по-прежнему актуален. Это может быть версия DOS, которую он использовал; объем доступной памяти; или его отладчик поместил что-то еще в верхние 16 байтов выше адреса возврата 0x0000.
person
Michael Petch
schedule
07.09.2020