Search
Write a publication
Pull to refresh

Comments 36

Я приведу таблицу, и там детальнее покажу все особенности.

Точка входа в COM-программу -- не "везде", а строго фиксированная -- её первый же байт. Максимальный размер COM-программы -- не 64 Кбайта, а 64 Кбайта минус 256 байт (для PSP). И, кстати, это не "команда", это именно COM-файл или COM-программа.

А вот у EXE-программы точка входа как раз не фиксированная, а может находиться в произвольном месте, поскольку её положение указывается в заголовке файла или в иной структуре, уж не помню точно.

Раньше ДОСа расширение .EXE для обозначения выполняемых файлов точно использовалось в VAX/VMS -- "мамаше" Винды НТ. Но, вполне может быть, что она тоже не была первой (хотя "бабка" Винды -- RSX-11 -- использовала расширение TSK).

В чуть позже, это будет называться "НеДалёкие указатели" (англ. "Near Pointers").

Вообще-то, эти указатели называются ближними, а не "недалёкими". А far pointers -- дальние указатели.

Поскольку, размер образ .EXE файла может быть значительно больше, (уже за пределами ОЗУ), -- в ОЗУ полностью он просто не поместится, а в "Program Memory" уж и подавно нет.

Неверно. В ДОСе EXEшки целиком загружались в память (точней, целиком загружались секции с кодом и данными; управляющая информация использовалась в процессе загрузки, но потом выкидывалась за ненадобностью для дальнейшей работы). Оверлеи же, если были, находились в другом файле -- о чём, собственно, Вы дальше пишите. Хотя у большинства ОС, поддерживающих оверлеи, они являются составной частью выполняемого файла, а не отдельным файлом.

В самом ABI системы

Это не ABI, это API. API -- набор системных сервисов, которые ОС предоставляет прикладным программам, откуда и его название. ABI же -- это, по сути, соглашения о связях между подпрограммами (через какие регистры передаются параметры и всё такое). В одной и той же системе ABI может быть несколько -- скажем, в 32-разрядной Винде, кажется, было не меньше четырёх способов передачи параметров. А вот API -- он один, и правила передачи параметров в него одни (но могут совсем не совпадать с ABI используемых языков программирования, особенно "в древности", в т.ч. в МС ДОС: тамошние системные вызовы рассчитаны на вызов из ассемблерных программ и не могут быть напрямую вызваны из кода на языке высокого уровня, ведь требуется занесение информации в определённые регистры, причём разные для разных функций, и выдача команды INT, которую трансляторы не используют).

Стоит помнить, что речь идет о временах, когда на уровне двоичных данных
не было понятия "секции"

Ещё очень много лет пройдёт, когда понятие "секции" появится у IBM и Microsoft. Поэтому после заголовков и таблиц релокаций в файле будут разграниченные области, но никаких .CODE или .DATA не будет!

Тоже неверно. Программы делились на секции очень давно, задолго до появления МС. Правда, ИБМ таки была :) В частности, у неё программы точно делились на секции в DOS/360 и OS/360 -- а первая версия первой из них появилась на рынке вместе с первыми машинами Системы 360, в 1965 году (OS/360 в жутко кастрированном виде -- на год позже).

Кстати говоря, формат PE COFF, что в Винде, пошёл с VAX/VMS, т.е. с середины 1970-х.

Ну и relocations -- всё-таки перемещения, а не релокации...

-- Почему ещё плюс 16 байт?!

Объясняю; потому что e_cparhdr (или длина заголовка в блоках) измеряется в блоках, а сегменты смещаются на 16 байт.

Блоки по 16 байт в x86 называются параграфами.

Доброго времени суток! Благодарю за отзыв. Я пересмотрю всё и исправлюсь. Про OS/360 однозначно буду читать сегодня.

Это не ABI, это API. API -- набор системных сервисов, которые ОС предоставляет прикладным программам, откуда и его название. ABI же -- это, по сути, соглашения о связях между подпрограммами (через какие регистры передаются параметры и всё такое).

Я сколько понимаю, (почему я писал именно про binary interface, а не API):
Дело приходится иметь с уже собранными компонентами системы или внешними модулями, соответственно и вызов функции (или прыжок на метку, как это раньше было?) подразумевает под собой двоичный контекст.

API это условно список функций или служб, которые предоставляет программа для публичного использования.

После компоновки и сборки, программа содержит этот самый список функций, но чтобы его обозначить, надо явно говорить компилятору, что функции будут использоваться во-вне. Итого, как я понимаю, все-таки используется ABI компонента, поскольку это те самые явно отмеченные функции.

Так же сколько я понимаю, Соглашения о вызовах говорят "Что надо делать" до/после вызова функции, поэтому. Но используют их вроде бы уже в модуле, который импортирует что-то извне? Приношу извинения, если я не прав - помогите.

Точка входа в COM-программу -- не "везде", а строго фиксированная -- её первый же байт. Максимальный размер COM-программы -- не 64 Кбайта, а 64 Кбайта минус 256 байт (для PSP). И, кстати, это не "команда", это именно COM-файл или COM-программа.
А вот у EXE-программы точка входа как раз не фиксированная, а может находиться в произвольном месте, поскольку её положение указывается в заголовке файла или в иной структуре, уж не помню точно.

Я на эту тему находил много интересных разногласий, и сам не до конца понимаю мысль.
Есть материалы, где говорится, что программа-COM может загружаться с любого места.
(например https://habr.com/ru/companies/timeweb/articles/880586/)

Вполне уверен, что я мог ошибиться

Вообще-то, эти указатели называются ближними, а не "недалёкими". А far pointers -- дальние указатели.

Я исправлю моменты в статье, чтобы не отлынивать от терминологии, спасибо.

Есть материалы, где говорится, что программа-COM может загружаться с любого места

Статью не читал, но думаю имеется ввиду любой сегмент.

Именно. В статье так и сказано.

"COM-образ загружается в любой сегмент памяти, причём, всегда со сдвигом 100h в рамках этого сегмента. "

Что в общем-то неудивительно, учитывая, что COM-образ всегда укладывается в один сегмент.

Я на эту тему находил много интересных разногласий, и сам не до конца понимаю мысль.Есть материалы, где говорится, что программа-COM может загружаться с любого места.

Загружаться -- да, в любое место памяти (выровненное по границе параграфа), но это не значит, что у самого файла точка входа может находиться в произвольном месте. EXEшники, кстати говоря, тоже могут загружаться в любое место памяти (собственно, и загружаются: заранее ведь нельзя сказать, с какого физического адреса будет производиться загрузка).

Я сколько понимаю, (почему я писал именно про binary interface, а не API):

Ну, когда говорят о вызовах системных сервисов из прикладных программ, говорят именно про API. Про ABI вспоминают при обсуждении правил передачи параметров между подпрограммами и тому подобных вещей, что к вызовам системных сервисов отношения не имеет (если подпрограмма A вызывает подпрограмму B, то они используют определённый ABI, при этом обе подпрограммы обычно являются частью одной программы). В общем, ABI -- про внутреннее устройство программы и взаимодействие её частей между собой, API -- про внешние связи между программами и ОС (или, шире, между разными программами или сервисами, или между, скажем, драйверами и функциями ядра ОС; во всех этих случаях общая суть в том, что один компонент обращается за услугами к другому компоненту, и оба этих компонента не являются частью одного модуля, т.е. что связи именно внешние).

Но вообще, эти термины, как и 95% других терминов, никак не регламентированы, из-за чего регулярно возникает путаница. Например, микроядерных ОС в природе не существует, если понимать этот термин так, как он понимался изначально; почти не существует сейчас и RISC-процессоров в изначальном смысле (в частности, ни ARM, ни RISC-V не являются RISC-процессорами, если исходить из того, что сделали разработчики первого RISC-процессора), ну и т.п. Лично я это называю "инфляцией терминологии" (да и сам термин "инфляция" тоже подвергся инфляции: зачастую под ним понимают рост цен, хотя это разные вещи).

> Блоки по 16 байт в x86 называются параграфами.

"а сегменты смещаются на 16 байт"

Чувствую кто не знаком с сегментной адресацией - ничего не понял. :) В 16-бит x86 адресация была фактически 32-битной. 16 бит номер сегмента и 16 бит смещение. Но адресовал процессор всего 1 мегабайт. Дело в том, что сегменты (размером в 64 Кб, что следует из 16 бит смещения) шли не друг за другом, а накладывались друг на друга с шагом в 16 байт. Т.е. нулевой байт памяти имел адрес 0:0, шестнадцатый 0:16, но одновременно 1:0; семнадцатый 0:17 и 1:1, 32-й 0:32, 1:16 и 2:0 соответственно.

Делалось это по простой причине. В процессоре фактически был аппаратный доступ к элементам массива произвольного размера, через чтение по смещению: индекс умноженный на константу. И если у вас были большие массивы, вы не хотели бы, чтобы они пересекли границу сегмента и вы лишились простого и удобного доступа к их элементам. Но шаг между сегментами в 16 байт позволял легко организовать выделение памяти, где смещение было бы равно нулю.

Неверно. В ДОСе EXEшки целиком загружались в память (точней, целиком загружались секции с кодом и данными; управляющая информация использовалась в процессе загрузки, но потом выкидывалась за ненадобностью для дальнейшей работы). Оверлеи же, если были, находились в другом файле

Размер файла определяется в MZ-заголовке 2-м и 3-м словом.
Всё, что за пределами этой черты, ОС не загружает в память, и этот остаток на жаргоне назывался "оверлеем" (там могли быть любые ресурсы, которые программа загружает по своему усмотрению).

У меня сейчас перед глазами файл TURBO.EXE (Turbo Pascal 7.0), его размер 0x628C7, размер в заголовке - 0x16060. На границе видно, что начинается "оверлей".

0000016050: 00 10 40 00 00 A0 00 B0 │ 00 B8 00 00 02 00 00 00   @    ° ё  
0000016060: 46 42 4F 56 7B ED 03 00 │ 55 89 E5 83 EC 02 80 3E  FBOV{н U‰еѓмЂ>
0000016070: C5 32 01 75 2F B8 6E 23 │ 50 31 C0 31 D2 52 50 C4  Е2u/ёn#P1А1ТRPД

Хм... Подзабыл о такой возможности, спасибо, что напомнили.

Нашел этот пост в интернете, как раз сегодня всякое -related гуглю.

Пару ссылок на тулзы, непосредственно связанные с темой

http://old-dos.ru//index.php?page=files&mode=files&do=list&cat=235 EXE-распаковщики

https://archive.org/details/anormal-executable-tools_202107 ANORMAL's executable tools

Надо заметить, ЕХЕ под досом эта та еще наркомания, https://fasmworld.ru/uchebnyj-kurs/031-segmentnaya-adresaciya

Данную ссылку правильно было бы назвать "как работает досовский ЕХЕ", а не "сегментная адресация", потому что тема адресации не раскрыта совсем, и вместо этого показывают то, как это реализовано в экзешниках. То есть эта статья не про механику сегментов в процессоре, а о том, как это в результате отразилось на ЕХЕ-шниках, и то без объяснения что и почему.

Особенно "приятно" было встретить эти "межсегменты" (характерные манипуляции с регистром DS) в досовской игре paratrooper.COM, хотя игра чисто комовский файл, просто решили так извратно работать с памятью.

Format.com кстати это EXE, преобразованный в com, с циклом настройки релокаций перед запуском.

Про модели памяти здесь описано https://habr.com/ru/companies/timeweb/articles/880586

Ознакомьтесь с Anormal Pack, там 2гб архив с подобным добром )

Благодарю за совет. Посмотрю. Может что-то и пригодится для следующей части моей статьи

В Турбо Паскале, помнится, была опция компилятора, с помощью которой можно было оверлеи делать. Давно это было. Сам никогда этим не пользовался, но возможность была.

Было такое дело. Насколько помню, в самой ДОС поддержки оверлеев не было, и они реализовывались программами самостоятельно (в отличие от более вменяемых систем, где они шли "из коробки").

Как-то написал такую тестовую программу посмотреть как работают оверлеи, но практической потребности в них так и не возникло. Оверлеи фактически для 80086/8, на двойках уже был защищённый режим и 7+ паскаль мог делать программы под него.

По сути, дело в объёме прямо доступной памяти. Оверлеи широко использовались на машинах, где логический адрес был ограничен 16-ю битами, т.е. программа в принципе не могла адресовать больше 64 Кбайт, даже если физически памяти было больше (скажем, на PDP-11 с MMU физический адрес составлял либо 18, либо 22 бита в зависимости от модели, но виртуальный/логический всё равно оставался 16-битным).

Помню занимательный факт с тех времён - если в заголовке экзешника поменять Z и M местами (то есть заголовок будет не MZ, а ZM), то программа по-прежнему будет запускаться и работать :)

В статье это есть — в описании MzHeader

Я помню, что в исходниках какого-то Win16 приложения был комментарий про это. Дословно не передам, но мысль была такова:

ZM is very old DOS 1.0 sign

Я не стал освещать этот момент, потому что, уж об этом нюансе точно ничего нормального не найдешь.

Это просто инвертированные инициалы. Они буквально есть в статье в описании MzHeader.

Да, они намеренно там есть. Просто хотел поделиться…

Вещь в которой я не уверен на 100% за давностью лет. Exe, меньше определённого размера (возможно размера заголовка exe-файла) был бы запущен как com, даже при наличии MZ в начале.

Не исключено. Но точно известно, что большой файл с расширением COM, имеющий заголовок MZ, будет загружаться и выполняться как EXE. Вероятно, если ОС смотрит на объём, то он должен быть как раз 64 Кбайта - 256 байт -- предельный размер настоящей COM-программы.

Позанудствую.

"Program Memory"

Что это такое? Под МС-ДОС программа могла скопировать себя (или свою часть), или просто загрузить произвольный двоичный файл в любую область адресного пространства 8086, где существовало ОЗУ, допускающее чтение и запись, и выполнить переход на адрес, находящийся в этой области. И никто ничего бы не заметил, никакой защиы памяти, равно как и деления на области чтение/запись/исполнение не было.
Теоретически, программа могда исполняться даже из неиспользуемых страниц видеопамяти, где отсутствовал риск быть затертой выводом на экран.

Этим пользовались вирусы, всякие хитрые резидентные программы и вполне официально - такая опция как BIOS in shadow RAM.
Также можно было поступить особо извращенно - снять read-only с ОЗУ, в котором находилась shadow-копия БИОСа, пропатчить код, установить read-only снова - и большинство антивирусов оказывались бессильны против такой модификации.
Так работали некоторые поздние русификаторы - вместо поддержки резидентными программами, БИОС патчился правильным знакогенератором.


Про указатели.
Помимо "близких" и "дальних" указателей, адресовать переменную или функцию можно было и по "короткому", a.k.a. "short" методу адресации, в предела -127..+128 байт от текущего адреса.

Существует отличная переводная книга "Язык ассемблера для IBM PC и программирования" ("IBM PC Assembly Language and Programming"), где все эти нюансы расписаны наиподробнейшим образом, безо всяких недалеких указателей.

Оверлеи - так это вообще малая часть всей истории. К тому времени, когда программы выросли до таких размеров, что перестали помещаться в ОЗУ, уже появились аппаратные (exTended) и программные (exPanded) менеджеры дополнительной памяти.

Ооо и я накосячил.
extended было начиная с 386, проц. работал в режиме virtual86.
expanded работало и на РС/XT, использовались спец. платы расширения с управляемым окном памяти в пределах первого мегабайта.

BIOS in Shadow RAM - это несколько другое. BIOS не копировался в какой-то другой кусок памяти (видео или иное). BIOS оставался на тех же адресах, но на эти адреса вместо ROM аппаратным способом подключался кусок из RAM и BIOS копировался туда ещё на этапе загрузки.

Оверлеи - так это вообще малая часть всей истории. К тому времени, когда программы выросли до таких размеров, что перестали помещаться в ОЗУ, уже появились аппаратные (exTended) и программные (exPanded) менеджеры дополнительной памяти.

Поправочка: это на ПК они являются малой частью истории. А если брать историю вычислительной техники вообще, то они широчайшим образом использовались все 1960-70-е годы.

Спасибо большое! Теперь к делу:

Что это такое?

Согласно описанию разметки ОЗУ в документах, у MS-DOS есть несколько разделов в ОЗУ

взял из своей первой статьи

Ещё раз спасибо за такую справку. Готовлю исправленную статью

Ок, действительно есть такой термин. Прикол в том, что резидентную часть command.com , а также большую часть IO.sys и msdos.sys можно было загрузить вообще за пределы 640К, если там имеется какое либо ОЗУ. И даже вообще за пределы адресного пространства 1Мб - это пресловутые ключи DOS = HIGH и DOS = UMB.

Строго говоря, для работы проца нужно обеспечить только валидную таблицу прерываний, первые 400h байт. Все остальное можно было перезаписать своей программой. Но при этом о возврате в ДОС можно забыть.

Привет! Я снова душнить пришёл.

Из-за сегментной архитектуры x86, далёкий адрес строится таким образом:

let far_addr: u32 = (segment << 4) | offset (20 бит физический адрес)*
А чтобы его разобрать на составные части, придется делать уже две
операции, а не одну.

let segment: u16 = far_addr >> 16;
let offset : u16 = far_addr & 0xFFFF;

Я очень рекомендую не использовать операцию or для склеивания segment и offset: если окажется, что offset больше, чем 15, то биты наложатся и будет очень плохо. Лучше использовать сложение. В операции разложения дальнего адреса на segment и offset у вас большие ошибки: segment надо сдвигать вправо не на 16, а на 4, а для получения offset делать & 0x000F.

И так, чисто для связности изложения: то, что у вас в формуле расчёта размера программы обозначено как pages - это e_cp из MZ-заголовка, а size - это e_cblp.

Приветствую! Спасибо за замечания. Скоро обновлю статью. Заодно комментари и в коде поставлю. (внезапно пропали при переносе сюда)

Какой то странный переход от ограничения ближних указателей к оверлеям. Код или данные в EXE могли быть больше 64k, при этом всё грузилось в память, но при необходимости использовались дальние указатели (в C компиляторах в связи с этим были разные модели памяти от tiny до huge с разными дефолтными типами указателей). Оверлеи нужны если программа не влезает в 640k.

Вспомнилась утилита exe2bin.exe, которая могла преобразовать некоторые программы exe в com.

Ну и, конечно же, самая дорогая и функциональная программа всех времён и народов (если судит из соотношения к размеру), позволявшая повторно запустить ранее запускавшуюся .com программу, не имея файла! Программа продавалась за 7 фунтов, и однажды один из клиентов задал вопрос - почему так дорого, если объём программы 0 байт? Впрочем, он согласился, что программа заявленную функциональность выполняет.

Да, если компилировать C код в tiny модели (код и данные живут в одном общем сегменте, используются только ближние указатели), exe2bin стабильно отрабатывал.

Sign up to leave a comment.

Articles