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 может загружаться с любого места.
Загружаться -- да, в любое место памяти (выровненное по границе параграфа), но это не значит, что у самого файла точка входа может находиться в произвольном месте. 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
К сожалению, по ссылке http://old-dos.ru//index.php?page=files&mode=files&do=list&cat=235 - там не все распаковщики, по факту их раза в 4 больше. У меня их точно штук 150, всяких разных
В Турбо Паскале, помнится, была опция компилятора, с помощью которой можно было оверлеи делать. Давно это было. Сам никогда этим не пользовался, но возможность была.
Было такое дело. Насколько помню, в самой ДОС поддержки оверлеев не было, и они реализовывались программами самостоятельно (в отличие от более вменяемых систем, где они шли "из коробки").
Как-то написал такую тестовую программу посмотреть как работают оверлеи, но практической потребности в них так и не возникло. Оверлеи фактически для 80086/8, на двойках уже был защищённый режим и 7+ паскаль мог делать программы под него.
По сути, дело в объёме прямо доступной памяти. Оверлеи широко использовались на машинах, где логический адрес был ограничен 16-ю битами, т.е. программа в принципе не могла адресовать больше 64 Кбайт, даже если физически памяти было больше (скажем, на PDP-11 с MMU физический адрес составлял либо 18, либо 22 бита в зависимости от модели, но виртуальный/логический всё равно оставался 16-битным).
Помню занимательный факт с тех времён - если в заголовке экзешника поменять Z и M местами (то есть заголовок будет не MZ, а ZM), то программа по-прежнему будет запускаться и работать :)
Вещь в которой я не уверен на 100% за давностью лет. Exe, меньше определённого размера (возможно размера заголовка exe-файла) был бы запущен как com, даже при наличии MZ в начале.
Позанудствую.
"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) менеджеры дополнительной памяти.
А можно ссылку на "IBM PC Assembly Language and Programming"? Я нашел такую (eng) и такую (список)...
Хотя мне по вкусу философия Рэндэлла Хайда (Randall Hyde): "The Art of Assembly Language" (pdf)
Ооо и я накосячил.
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 байт? Впрочем, он согласился, что программа заявленную функциональность выполняет.
MZ-Executable | Исполняемые файлы и MS-DOS (переработка)