561
Previews
17
Favorites
Purchase options
Better World Books
DOWNLOAD OPTIONS
No suitable files to display here.
14 day loan required to access EPUB and PDF files.
IN COLLECTIONS
Books to Borrow
Books for People with Print Disabilities
Internet Archive Books
Scanned in China
Uploaded by
booksale-cataloger4
on September 26, 2011
Страница 1 из 2
-
Mikl___
Супермодератор
Команда форума- Публикаций:
-
14
- Регистрация:
- 25 июн 2008
- Сообщения:
- 3.468
Привет всем!
Не поможет ли кто-нибудь мне найти электронные книги Мэтта Питрека «Секреты системного программирования Windows 95» (Киев: Диалектика 1996 ISBN: 966-506-023-6) и его же «Внутренний мир Windows: Реализация операционной среды Windows» (Киев: ДиаСофт 1995 ISBN 5-7707-7218-2). На сайте есть на английском, но мне не пошло, хотелось бы с переводом.
Поделитесь ссылочкой Pleeeeeeeeeeez! -
__sheva740
Active Member
- Публикаций:
-
0
- Регистрация:
- 18 окт 2017
- Сообщения:
- 309
М.Руссинович. Д.Саломон. Внутреннее устройство Microsoft Windows 2000 2003 XP.djvu
М.Руссинович. Д.Соломон. Внутреннее устройство Microsoft Windows.4-е изд. 2005г.djvu… не оно?
-
Mikl___
Супермодератор
Команда форума- Публикаций:
-
14
- Регистрация:
- 25 июн 2008
- Сообщения:
- 3.468
__sheva740,
Издеваешься? Русинович это совсем другой человек… -
Mikl___, ох е… я в год назад пол инета перерыл и куча народу поспрашивал по твоей просьбе. Ни у кого нету.
-
M0rg0t
Well-Known Member
- Публикаций:
-
0
- Регистрация:
- 18 окт 2010
- Сообщения:
- 1.551
Полгода назад на алибе было, но селлер уже продал кому-то.
Так то присоединяюсь, хотелось бы почитать Питрека на русском, проспонсирую скан/оцифровку, если кто найдет. -
_edge
Well-Known Member
- Публикаций:
-
1
- Регистрация:
- 29 окт 2004
- Сообщения:
- 632
- Адрес:
- Russia
Добавлю также книгу, разыскивается пусть даже в англ. виде,
Адриан Кинг «Windows 95 Изнутри»
Можно было в 2006 почитать онлайн на сайте издательства «Питер», но сейчас такого уже нет.
-
X-Shar
Active Member
- Публикаций:
-
0
- Регистрация:
- 24 фев 2017
- Сообщения:
- 354
Интересно, а в связи с чем такой интерес к 95 винде и досу ?
Она где-то используется еще, знаю места где это может применятся, но это оборудование 80 годов, которое уже заменяется и смысла в нем нет.
Или это ради фана все ?)
-
M0rg0t
Well-Known Member
- Публикаций:
-
0
- Регистрация:
- 18 окт 2010
- Сообщения:
- 1.551
X-Shar, многие вещи в винде не поменялись с тех времен , скажем книги Питрека, Петцольда и т.д. можно вполне себе сейчас изучать (с небольшими нюансами, вида устройства модели памяти). Так то все классические книги по сис.кодингу посвящены, на минутку, windows2000 — абсолютно все (Харт, Побегайло, Шрайбер, Небет, Фень Юань). Только Рихтер переиздался в 2005, добавив главу про Юак и все.
Это не веб кодинг, где за год все меняется кардинально, тут знания фундаментальные.
-
GRAFik
Active Member
- Публикаций:
-
0
- Регистрация:
- 14 мар 2020
- Сообщения:
- 263
Ну так вы же Юзефовича бросили переводить, что нам еще остается — только и читать всякое старье. ))
А если серьезно, то M0rg0t, истину глаголет — целиком и полностью разделяю его точку зрения. Да и потом, обратите внимание кто ищет эти книги — это же ни какие-то там ноунейм, типа меня)). Эти люди знают толк в хороших книгах и авторах.
Если бы Мэтт Питрек на русском нашелся было бы здорово, но раз до сих пор не нашелся, то скорее всего и не найдется, а ведь у кого-то, где-нибудь стоит на полке — пылится за ненадобностью. Кстати, и Адриан Кинг «Windows 95 Изнутри» — наверняка тоже классная книга.
-
X-Shar
Active Member
- Публикаций:
-
0
- Регистрация:
- 24 фев 2017
- Сообщения:
- 354
Я кстати не забросил, там 6 глав перевел, дело не быстро идет, т.к. много чем кроме переводов занимаюсь, не всегда находится время, а чаще всего больше лень даже, или проблемы мотивации….)))
Также если честно незнаю на сколько эти певеводы реально кому нужны, т.к. делаю больше для себя и сами переводы оставляют желать лучшего, это далеко не переводы например редакции, поэтому лучше читать конечно-же оригинал, он тут на васме если-что вроде есть.
Да конечно, согласен с @M0rg0t, системное программирование, это фундаментальные знания, вообще мне эта тематика очень интересна, причем интересна не только как хобби, а именно в плане работы, хочу в этом расти…
А работы в этой тематике очень много и в будущем будет еще больше, специалистов мало и можно попросить потом зарплату больше, короче кто реально шарит в системном программировании, разработке, тот без работы 100% неостанится.
Причем неважно направление, вайт это, или блек.
-
_edge
Well-Known Member
- Публикаций:
-
1
- Регистрация:
- 29 окт 2004
- Сообщения:
- 632
- Адрес:
- Russia
Тимур еще публиковался на http://whatis.ru/forum/viewforum.php?id=14
Его сайт http://tgsa.narod.ru/Не забываем также библиотеку Фроловых, https://frolov-lib.ru/bsp.html
Местами, можно найти книги на archive.org по поисковому запросу «undocumented», но, как правило стоящих книг просто так не дают, только borrow.
Последнее редактирование: 2 окт 2020
-
GRAFik
Active Member
- Публикаций:
-
0
- Регистрация:
- 14 мар 2020
- Сообщения:
- 263
Mikl___, а почему Коцит перестал заходить на ВАСМ, его, по-моему, тут обидели? Вы ему передайте, что дураков, к сожалению, везде хватает, а умные люди-то ни при чем )). Я, кстати, до сих пор под впечатлением его статьи про ->WinDbg<- нахожусь. Много чего хорошего он там описал. Можно, конечно, и еще два раза по столько написать про WinDbg, но это же все время, а время деньги, а денег обычно, или вообще, нет или не хватает)).
-
texaciri
Member
- Публикаций:
-
0
- Регистрация:
- 27 янв 2018
- Сообщения:
- 54
да чего только не встречается. Особенно в корпоративной среде, да и гос.структурах.
Встречал еще несколько лет назад ПО написаное на FoxPro для работы с базой данных и формированию отчетов. (и было еще что то чисто полностью самописное). Очень активно исопльзовалось, формировали отчеты в текстовики, а потом уже эксель через макросы в актуальные красивооформленные таблички втягивал.
И так как другого софта сертифицированного под это дело у них небыло, запускалось оно в виртуалке, в котором была ВинХР, у которой еще встроеный огрызок для эмуляции доса. Но именно для работы с такими данными, эта связка в виртуалке работала быстрее чем в разогнаном досбоксе (но досбокс им всё равно было нельзя) -
_edge
Well-Known Member
- Публикаций:
-
1
- Регистрация:
- 29 окт 2004
- Сообщения:
- 632
- Адрес:
- Russia
GRAFik
перестал заходить, также как и Thetrik, потому что https://wasm.in/threads/pro-debug-i-anti-debug.32102/page-2#post-389439
так то, под досом можно в usb, tcp (кое-где еще сетки работают, где виндовая машина работает совместно с досовской, по LAN), ntfs, pe-exe (DosWin32, HX DOS Extender), также как и на VB6 написать драйвер (в некотором смысле это и есть мастерство, умение применять инструмент для задач, для которых он не предназначен)
конечно, ребята вроде Зомбы или Gloomy в 1999 уже вовсю потрошили NT-undoc, но «все же не могут» (с) Ослик Иа-Иа
нужно ли? если «китаец-зумер на микросервисах делает сейчас то, что динозавры не могут осилить за полгода» (с) Aoizoraда и подумать, насколько эти ваши видеокарты развились за 20 лет, целый мир, никакое знание kernelmode не поможет
а Ведроид за десять с небольшим лет вообще зохавал мир, а мы до сих пор не умеем в его устройствоне сочтите за флуд
Вот немного сканированых WinDos Dev Journal девяностых годов, выловил сегодня с архив-орг
(в то время, как фанаты Amiga формируют свое Preservation Society, для сохранения образов амижных дискеток, у Wintel принято каждый новый выпуск Окон(тм) всё предыдущее объявлять deprecated, и это не есть хорошо)
https://yadi.sk/d/YEKu2-Iry87pwQ
Последнее редактирование: 2 окт 2020
-
GRAFik
Active Member
- Публикаций:
-
0
- Регистрация:
- 14 мар 2020
- Сообщения:
- 263
Спасибо за объяснение. Да, не прав, конечно, в споре с Инди Коцит. Единственное, чем можно его немного реабилитировать — это предположить, что он сыграл на опережение. Может он подумал, что типа, что это я буду ждать, когда на меня «навешают» всяких нехороших эпитетов — начну-ка я пожалуй, первым )). Ну учитывая, естественно, характер и манеры Инди )).
Так, вроде бы, Thetrik-то частенько заходит на ВАСМ. Буквально вот недавно, M0rg0t’а какому-то «высшему пилотажу» по COM обучал. Или это кто-то уже другой вместо Thetrik’а заходит под его ником? ))
По поводу нужны или нет — однозначно нужны. Даже и не сомневайтесь. Драйверы — это ж классика, системное программирование. Такой перевод и через 10-15 лет будет, по-моему, актуален. Так же как книги Питрека, Петцольда. И, кстати, читал я ваш перевод — ни разу не возникло мысли по поводу его улучшения. Когда хочется побыстрее усвоить какой-нибудь материал или тему и переводам Яшечки будешь рад )). У Яши, конечно, все переводы разные по качеству. По Иде я некоторые главы читал и ничего, вроде, жив здоров, умом не тронулся )).
Тогда вам обязательно нужно научиться компилировать Windows XP из утекших исходников. Заодно бы и тех, у кого с этим проблемы, научили, можно здесь, а можно и на вашем сайте, если у кого будет желание. Вот это бы дало вам приличных знаний по устройству операционных систем Windows-архитектуры.
-
Thetrik
UA6527P
- Публикаций:
-
0
- Регистрация:
- 25 июл 2011
- Сообщения:
- 759
Нет, нет. это я
-
GRAFik
Active Member
- Публикаций:
-
0
- Регистрация:
- 14 мар 2020
- Сообщения:
- 263
Thetrik, ну слава богу — успокоили )), а то у меня есть свой личный список людей, которыми может гордиться ВАСМ. И должен заметить, что вы там у меня далеко не последний в списке )). Вот собственно, этот список и удерживает меня на ВАСМе.
P.S. Thetrik, у меня есть ваш почтовый адрес, который вы мне когда-то давали. Там перед «собакой» последняя буква t. Вы его регулярно проверяете? А то может у меня к вам будет интересное дело, если «созрею». Ну а если нет — то на нет и суда нет. Вы-то от этого, в любом случае, ничего не теряете.
Последнее редактирование модератором: 4 окт 2020
-
Thetrik
UA6527P
- Публикаций:
-
0
- Регистрация:
- 25 июл 2011
- Сообщения:
- 759
Раз в месяц, мб чаще. Пишите тут в личку если что.
-
M0rg0t
Well-Known Member
- Публикаций:
-
0
- Регистрация:
- 18 окт 2010
- Сообщения:
- 1.551
Mikl___, есть скан Питрека «Внутренний мир Windows», но не могу загрузить в ресурсы (по размеру не проходит, хотя сжимал).
texaciri, _edge и GRAFik нравится это.
-
GRAFik
Active Member
- Публикаций:
-
0
- Регистрация:
- 14 мар 2020
- Сообщения:
- 263
M0rg0t, ОГРО-О-О-О-О-МНОЕ СПАСИ-И-И-БО!!! )) Год или даже больше безуспешно пытался найти перевод этой книги.
Я готов целов-А-А-А-ть песок по которому вы ходили. )))
P.S. Если не секрет где взяли? Поди купили и отсканировали?
Блин, рано обрадовался. )) Оказывается у Питрека есть две совершенно разные книги:1. Мэтт Питрек «Секреты системного программирования Windows 95» (Киев: Диалектика 1996 ISBN: 966-506-023-6)
2. Мэтт Питрек «Внутренний мир Windows: Реализация операционной среды Windows» (Киев: ДиаСофт 1995 ISBN 5-7707-7218-2)
Меня-то больше интересует 1-я: Секреты системного программирования Windows 95. Вот невезуха-то.)) Но все равно спасибо. Я думаю, что в этой книге тоже найдется что-нибудь интересное.
Страница 1 из 2
Содержание
- Мэтт питрек секреты системного программирования в windows 95
- Форматы РЕ и COFF объектных файлов
- Программа PEDUMP
- Основные сведения о форматах Win32 и РЕ
- Заголовок РЕ-файла
- WORD NumberOfSections
- DWORD TimeDateStamp
- DWORD PointerToSymbolTable
- DWORD NumberOfSymbols
- WORD SheOfOptionalHeader
- WORD Characteristics
- WORD Magic
- BYTE MajorLinker Version; BYTE MinorLinker Version
- DWORD SizeOfCode
- DWORD sizeOfInitializedData
- DWORD SizeOfUninltializedData
- DWORD AddressOfEntryPoint
- DWORD BaseOfCode
- DWORD BaseOfData
- DWORD ImageBase
- DWORD SectionAlignment
- DWORD FileAlignment
- WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion
- WORD MajorImageVersion; WORD MinorImageVersion
- WORD MajorSubsystem Version; WORD MinorSubsystem Version
- DWORD Reserved
- DWORD SizeOfImage
- DWORD SizeOfHeaders
- DWORD Checksum
- WORD Subsystem
- WORD DllCharacteristics (обозначен как вышедший из употребления в NT 3.5)
- DWORD SizeOfStackReserve
- DWORD SizeQfStackCommit
- DWORD SizeOfHeapReserve
- DWORD SizeOfHeapCommit
- DWORD LoaderFlags (обозначен как вышедший из употребления в NT 3.5)
- DWORD NumberOfRvaAndShes
- IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
- Таблица секций
- BYTE Name[IMAGE_SIZEOF_SHORT_NAME]
- DWORD VirtualAddress
- DWORD SizeOfRawData
- DWORD PointerToRawData
- DWORD PointerToRelocations
- DWORD PointerToLinenumbers
- WORD NumberOfRelocations
- WORD NumberOfLinenumbers
- DWORD Characteristics
- Часто встречающиеся секции
- Секция DATA
- Разнообразные секции
- Импортирование в РЕ-файлах
- DWORD Characteristics/OriginalFirstThunk
- DWORD TimeDateStamp
- DWORD ForwarderChain
- DWORD Name
- PIMAGE_THUNK_DATA FirstThunk;
- IMAGE_THUNK_DATA DWORD
- WORD Hint
- Сравнение IMAGE_IMPORT_DESCRIPTOR и IMAGE_THUNK_DATA
- Экспорт в РЕ-файлах
- DWORD Characteristics
- DWORD TimeDateStamp
- WORD MajorVersion;WORD MinorVersion
- DWORD Name
- DWORD Base
- DWORD NumberOfFunctions
- DWORD NumberOfNames
- PDWORD * AddressOfFunctions
- PDWORD * AddressOfNames
- PWORD * AddressOfNameOrdinals
- Передача экспорта
- Ресурсы РЕ-файла
- DWORD Characteristics
- DWORD TimeDateStamp
- WORD MajorVersion; WORD MinorVersion
- WORD NumberOfNamedEntries
- WORD NumberOfIdEntries
- IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]
- Базовые поправки РЕ-файла
- DWORD VirtualAddress
- DWORD SizeOfBlock
- WORD TypeOffset
- COFF-таблица символов
- union (Symbol name union)
- DWORD Value
- SHORT SectionNumber
- WORD Type
- BYTE StorageClass
- BYTE NumberOfAuxSymbols
- COFF-отладочная информация
- DWORD NumberOfSymbols
- DWORD LvaToFirstSymbol
- DWORD NumberOfLinenumbers
- DWORD LvaToFirstLinenumber
- DWORD RvaToFirstByteOfCode
- DWORD RvaToLastByteOfCode
- DWORD RvaToFirstByteOfData
- DWORD RvaToLastByteOfData
- COFF-таблица номеров строк
- WORD Linenumber
- Различия между РЕ-файлами и объектными COFF-файлами
- Файлы COFF LIB
- BYTE Name[16]
- BYTE Date[12]
- BYTE UserID[6]
- BYTE GroupID[6]
- BYTE Mode[8]
- BYTE Size[10]
- BYTE EndHeader[2]
- Члены компоновщика
- DWORD NumberOfSymbols
- DWORD Offsef[NumberOfSymbols]
- BYTE StringTable[?]
- DWORD NumberOfMembers
- DWORD Offsets[NumberOfSymbols]
- DWORD NumberOfSymbols
- WORD Indices[NumberOfSymbols]
- BYTE SiringTable[NumberOfSymbols]
- Член Longnames
- Резюме
Мэтт питрек секреты системного программирования в windows 95
Где можно достать книгу Matt Pietrek Windows 95 System Programming Secrets на РУССКОМ ЯЗЫКЕ? (В ЭЛЕКТРОННОМ ВИДЕ)
← →
bytebutcher ( 2002-08-18 11:09 ) [1]
А её вообще переводили?
← →
y-soft ( 2002-08-18 12:07 ) [2]
>bytebutcher © (18.08.02 11:09)
Мэтт Питрек «Секреты системного программирования в Windows 95»
«Диалектика», Киев, 1996
← →
y-soft ( 2002-08-18 12:35 ) [3]
← →
bytebutcher ( 2002-08-18 12:55 ) [4]
>y-soft
а бумажная у тебя есть.
Бумажная-то есть (447 стр.), да только географически я нахожусь далековато от Москвы 🙂
← →
bytebutcher ( 2002-08-18 13:15 ) [6]
← →
bytebutcher ( 2002-08-18 13:16 ) [7]
Источник
Форматы РЕ и COFF объектных файлов
Автор: Мэтт Питрек
Источник: Секреты системного программирования в Windows 95. Глава 8.
Опубликовано: 05.04.2001
Исправлено: 15.04.2009
Версия текста: 1.1
Формат исполняемого файла операционной системы в значительной степени отражает встроенные в операционную систему предположения и режимы поведения. Хотя освоение всех деталей формата исполняемого файла и не является одной из главных задач обучения программированию, тем не менее, из этого можно почерпнуть немало ценной информации об операционной системе. Динамическая компоновка, поведение загрузчика и управление памятью — это только три примера специфических свойств операционной системы, которые можно понять по мере изучения формата исполняемых файлов.
В этой главе мы совершим экскурс в переносимый исполняемый (РЕ — Portable Executable) формат файла, который фирма Microsoft разработала для использования во всех ее операционных системах Win32 (Windows NT, Windows 95, Win32s).
Возможно, читатель удивится тому, что я рассказываю о РЕ-формате в этой книге, тогда как несколько описаний этого формата можно найти на CD-ROM Microsoft Developer Network. Главная причина, по которой я решил описать РЕ-формат, — это тот факт, что внутри самой Windows 95 используются те же ключевые структуры данных, что и в файлах РЕ-формата. Так, например, Windows 95 отображает заголовок РЕ-файла в память и использует его для представления загружаемого модуля. Для того чтобы понять, как работает ядро Windows 95, необходимо разобраться с РЕ-форматом. Гарантирую, что это достаточно просто.
Другой причиной, по которой я решил описать РЕ-формат, является то, что описание РЕ-формата фирмы Microsoft, как и остальная документация этой фирмы, предполагает, что пользователь живет и дышит исполняемым форматом файла. Не будет преувеличением назвать документацию Microsoft отрывочной. Так что моей задачей в этой главе является «оживление» этой документации и соотнесение ее с привычными для каждого пользователя понятиями. По ходу дела я указал ряд способов, посредством которых РЕ-формат влияет на реализацию операционной системы и наоборот.
РЕ-формат играет и будет играть в обозримом будущем ключевую роль во всех операционных системах Microsoft, включая Cairo. Даже если вы программируете в Windows 3.1, используя Visual C++, вам все равно придется пользоваться РЕ-файлами (32-разрядные расширенные компоненты DOS в Visual C++ используют этот формат). А если вы собираетесь заняться любой работой, связанной с системным программированием низкого уровня в Windows 95, практические знания по РЕ-файлам просто необходимы.
При обсуждении РЕ-формата я не собираюсь тщательно перебирать груды шестнадцатеричных кодов или без конца объяснять назначения отдельных битов. Вместо этого я хочу изложить концепции, заложенные в РЕ-формат, и связать их с понятиями, которые ежедневно встречаются при программировании в Win32. Например, концепция локальных переменных для цепочек (а-ля __declspec (thread)) выводила меня из терпения до тех пор, пока я не увидел, с какой простотой и изяществом она реализована в исполняемом файле. Учитывая, что многие программисты имеют опыт работы в Win 16, я покажу, как связаны конструкции в РЕ-формате с их эквивалентами в 16-разрядных файловых форматах.
Вместе с новыми форматами исполняемых файлов Microsoft также ввела новые форматы объектных модулей и библиотек, создаваемые ее собственными компиляторами и ассемблерами, (Новый файловый формат LIB, по существу, представляет собой просто связку объектных файлов, упорядоченных с помощью индекса; когда в дальнейшем я ссылаюсь в этой книге на объектные файлы, я буду иметь в виду как COFF-объектные, так и LIB-файлы.) Эти новые объектные и LIB-файловые форматы имеют немало общих концепций с форматом РЕ. До настоящего времени не существовало общедоступного источника информации об объектных и LIB-файлах фирмы Microsoft, и даже к моменту написания этой книги информация очень скудна. Поэтому стоит осветить также форматы объектных файлов и LIB-файлов.
Общеизвестно, что Windows NT (первая из операционных систем Win32) унаследовала многое от VAX VMS и UNIX. Многие ведущие разработчики NT перед своим приходом в Microsoft программировали и работали именно над этими системами. Вполне естественно, что, когда им пришлось создавать NT, чтобы сохранить свое время и силы, они использовали ранее написанные и опробованные средства. Исполняемый формат и формат объектного модуля, который эти средства создавали и с которым они работали, называется COFF (Common Object File Format — стандартный формат объектного файла).
Относительно устаревшую (по компьютерным меркам) сущность COFF можно усмотреть в том, что некоторые поля в файлах имеют восьмеричный формат. COFF-формат был сам по себе неплохой отправной точкой, но нуждался в расширении, чтобы удовлетворить потребностям новых операционных систем, таких как Windows NT или Windows 95. Результатом такого усовершенствования явился РЕ-формат (не забывайте: РЕ означает Portable Executable — переносимый исполняемый). Этот формат называется переносимым, так как все реализации Windows NT в различных системах (Intel 386, MIPS, Alpha, Power PC и т.д.) используют один и тот же исполняемый формат. Конечно, имеются различия, например, связанные с двоичной кодировкой команд процессора. Нельзя запустить на Intel исполняемый РЕ-файл, откомпилированный в MIPS. Тем не менее существенно, что нет нужды полностью переписывать загрузчик операционной системы и программные средства для каждого нового процессора.
Microsoft стремилась усовершенствовать Windows NT, и это хорошо иллюстрируется тем, что Microsoft отказалась от своих существующих 32-разрядных средств и файловых форматов. Драйверы виртуальных устройств, написанные для Windows З.х, использовали другой 32-разрядный формат файла (LE-формат) задолго до появления NT на свет. Следуя принципу «Если не поломано, не надо и чинить», заложенному в Windows, Windows 95 использует как РЕ-, так и LE-формат. Это позволило Microsoft широко использовать существующие программы под Windows 3-х.
Вполне естественно ожидать совершенно другого исполняемого формата для совершенно новой операционной системы (какой является Windows NT), но другой вопрос— форматы объектных модулей (.OBJ и LIB). До появления 32-разрядной версии 1.0 Visual C++ все компиляторы Microsoft пользовались спецификацией Intel OMF (Object Module Format — формат объектного модуля). Компиляторы Microsoft для реализации Win32 создают объектные файлы в формате COFF. Некоторые конкуренты Microsoft, например Borland, отказались от формата COFF объектных файлов и продолжали придерживаться формата OMF Intel. В результате компании, производящие объектные и LIB-файлы, рассчитанные на использование с несколькими компиляторами, будут вынуждены возвратиться к системе поставок различных версий их продуктов для различных компиляторов (если они не сделали этого до сих пор).
Те пользователи, которые любят усматривать во всех действиях Microsoft скрытность, могут увидеть в смене объектных форматов стремление Microsoft воспрепятствовать своим конкурентам. Чтобы гарантировать «совместимость» с Microsoft вплоть до уровня объектных файлов, другие фирмы будут вынуждены конвертировать все свои 32-разрядные средства в форматы COFF OBJ и LIB. Подводя итог, можно сказать, что объектные и LIB-файловые форматы являются еще одним примером отказа Microsoft от существующих стандартов при выборе приоритетов развития этой фирмы.
Вместе с некоторыми определениями структур для объектных файлов формат COFF РЕ-формат задокументирован (в самом размытом смысле этого слова) в файле заголовка WINNT.H. (Я буду пользоваться именами полей из WINNT.H позже в этой главе.) Примерно посредине WINNT.H находится секция, озаглавленная «Image Format». Эта секция начинается с небольших фрагментов из старых добрых заголовков форматов DOS MZ и NE перед переходом к новой информации, связанной с РЕ. WINNT.H дает определения структур исходных данных, используемых РЕ-файлами, однако содержит всего лишь прозрачный намек на полезные комментарии, объясняющие назначение структур и флагов. Автор заголовочного файла для РЕ-формата (некий Michael J. O’Leary) определенно питает склонность к длинным, описательным именам, а также к глубоко вложенным структурам и макросам. Программируя с использованием WINNT.H, нередко можно встретить, например, такое выражение:
Вы, вероятно, захотите не просто прочесть о том, из чего состоят РЕ-файлы, но и самим просмотреть несколько РЕ-файлов, чтобы на практике увидеть, как реализуются представленные здесь концепции. Если вы используете средства Microsoft для Win32, то программу DUMPBIN из Visual C++, а также Win32 SDK, можно применить для того, чтобы «препарировать» файлы РЕ и COFF OBJ/LIB и представить их в удобоваримом виде. DUMPBIN даже имеет замечательный параметр для дизассемблирования программных секций изучаемого файла. Интересно отметить, что фирма Microsoft, которая запрещает пользователям дизассемблировать свои программы, в данном случае предоставила средство для простого дизассемблирования программ и библиотек динамической компоновки (DLL). Если бы возможность дизассемблировать ЕХЕ и объектные файлы не была бы полезна, зачем понадобилось бы Microsoft снабжать этим DUMPBIN? В этом случае можно сказать: «Делай, как мы говорим, а не так, как мы делаем».
Пользователи Borland могут использовать TDUMP, чтобы просматривать РЕ-файлы, однако TDUMP не воспринимает объектные файлы формата COFF. Здесь нет ничего страшного, так как компилятор Borland вообще не создает объектные файлы формата COFF. Бросая свой вызов, я тоже написал программу просмотра РЕ- и COFF-OBJ/LIB-файлов (PEDUMP), которая, на мой взгляд, осуществляет более наглядный вывод, чем DUMPBIN. Несмотря на то, что моя программа не может дизассемблировать, во всем остальном она полностью функционально эквивалентна DUMPBIN и, кроме того, содержит некоторые новые черты, делающие ее достойной внимания. Исходный текст программы PEDUMP находится на специальной дискете — вот почему я не привожу ее здесь полностью. Вместо этого я предоставляю образец вывода PEDUMP, чтобы проиллюстрировать концепции по мере их изложения.
Программа PEDUMP
По умолчанию ни один из ключей не активизирован. В этом случае большая часть необходимой информации будет доступна и не будет слишком громадного объема выведенных данных.
PEDUMP осуществляет вывод в стандартный выходной файл (например, на экран), так что этот вывод можно переадресовать в какой-либо файл с помощью знака «>» (больше), введенного в командной строке.
Исходные файлы для PEDUMP включены вместе с ней. PEDUMP была создана с помощью компилятора Microsoft Visual C++ 2.0, хотя я также компилировал эту программу по мере ее усовершенствования, пользуясь Borland C++ 4.x.
Основные сведения о форматах Win32 и РЕ
Перед тем как начать обсуждение формата РЕ-файла, я хотел бы рассмотреть несколько новых основных идей, позволивших создать такой формат. В процессе этого обсуждения я буду использовать понятие модуль, подразумевая под ним текст программ, данные и ресурсы исполняемого файла или DLL, которые были загружены в память. Помимо текста программы и данных, которые использует непосредственно программа, модуль также включает вспомогательные данные, используемые Windows для того, чтобы определить, где расположены в памяти текст программы и данные. В Win 16 вспомогательные структуры данных находятся в базе данных модуля (сегмент, на который ссылается HMODULE). В Win32 эта информация содержится в заголовке РЕ-файла (структура IMAGE_NT_HEADERS), о котором вскоре я подробно расскажу.
Самое важное из того, что следует знать о РЕ-файлах, это то, что исполняемый файл на диске и модуль, получаемый после загрузки, очень похожи. Причиной этого является то, что загрузчик Windows должен создать из дискового файла исполняемый процесс без больших усилий. Точнее говоря, загрузчик попросту использует отображенные в память файлы Win32, чтобы загрузить соответствующие части РЕ-файла в адресное пространство программы. Здесь уместна аналогия со строительством сборных домиков. У вас есть относительно немного элементов, расставляя их по своим местам и скрепляя стандартными соединениями, вы достаточно быстро собираете целый дом, — вся работа состоит из простого защелкивания таких стандартных соединений. И такой же простой задачей, как подключение электричества и водопровода к сборному домику, является соединение РЕ-файла с внешним миром (т.е. подключение к нему DLL и т.д.).
В Win32, напротив, память, используемая под программы, данные, ресурсы, таблицы ввода, таблицы вывода и другие элементы, представляет собой один сплошной линейный массив адресного пространства. Все, что достаточно знать в этом случае, — это адрес, в который загрузчик отобразил в памяти исполняемый файл. Тогда для того, чтобы найти любой элемент модуля, достаточно следовать указателям, которые хранятся как часть отображения.
Другим важным понятием, о котором необходимо знать, является RVA (Relative Virtual Address — относительный виртуальный адрес). Многие поля в РЕ-файлах задаются именно с помощью их RVA. RVA — это просто смещение данного элемента по отношению к адресу, с которого начинается отображение файла в памяти. Пусть, к примеру, загрузчик Windows отобразил РЕ-файл в память, начиная с адреса 0х400000 в виртуальном адресном пространстве. Если некая таблица в отображении начинается с адреса 0х401464, то RVA данной таблицы 0х1464:
Чтобы перевести RVA в указатель памяти, просто прибавьте RVA к базовому адресу, начиная с которого был загружен модуль. Термин базовый адрес представляет еще одно важное понятие, о котором следует помнить. Базовый адрес — это адрес, с которого начинается отображенный в память ЕХЕ-файл или DLL. Для удобства Windows NT и Windows 95 используют базовый адрес модуля в качестве дескриптора образца модуля (HINSTANCE — instance handle). To, что в Win32 базовый адрес называется HINSTANCE, может вызвать недоразумения, так как термин дескриптор образца происходит из 16-разрядной Windows. Каждая копия приложения в Win16 получает свой собственный сегмент данных (и связанный с ним глобальный дескриптор), который отличает эту копию приложения от других; отсюда и название дескриптор образца.
В Win32 нет нужды различать отдельные копии приложений, так как у них нет общего адресного пространства. Однако термин HINSTANCE сохранен, чтобы отразить преемственность Win32 по отношению к Win16. В случае Win32 существенно то, что можно вызвать GetModuleHandle() для любой DLL, которая используется в процессе, и получить указатель, который можно использовать для доступа к компонентам модуля. Под компонентами модуля я подразумеваю его импортируемые и экспортируемые функции, его перемещения, его программные секции и секции данных и т.п.
Еще одним понятием, с которым следует ознакомиться для того, чтобы исследовать РЕ- и COFF OBJ-файлы, является секция. Секция в файлах РЕ или COFF OBJ примерно эквивалентна сегменту или ресурсам в 16-разрядном NE-файле. Секции содержат либо код программ, либо данные. Некоторые секции содержат код и данные, непосредственно объявляемые и используемые программами, тогда как другие секции данных создаются компоновщиками и библиотекарями специально для пользователя и содержат информацию, необходимую для работы операционной системы. В некоторых описаниях формата РЕ фирмы Microsoft секции также называются объектами. Однако этот последний термин имеет так много (возможно, противоречащих друг другу) значений, что я решил придерживаться термина секции для обозначения им областей программного кода и данных. Секции рассмотрены более подробно в этой главе, в разделе «Часто встречающиеся секции»; пока что читателю достаточно просто знать, что такое секция.
Перед тем как погрузиться в детали РЕ-файла, советую изучить рис. 8.1, представляющий общий формат РЕ-файла. Несмотря на то, что я буду объяснять каждый элемент отдельно, полезно для начала рассмотреть их все вместе.
Рис. 8.1. Общий формат РЕ-файла
Заголовок РЕ-файла
Первым пунктом, на котором мы остановимся в нашем путешествии по РЕ-формату, будет заголовок РЕ-файла. Как и всякий другой исполняемый формат файла Microsoft, РЕ-файл имеет набор полей, расположенных в легко доступном (по крайней мере, легко находимом) месте файла; эти поля определяют, как будет выглядеть остальная часть файла. Заголовок РЕ-файла содержит такую важную информацию, как расположение и размер областей кода программ и данных, указание на то, с какой операционной системой предполагается использовать данный файл, а также начальный размер стека.
Как и в других исполняемых форматах от Microsoft, заголовок не находится в самом начале файла. Вместо этого несколько сотен первых байтов типичного РЕ-файла заняты под заглушку DOS. Эта заглушка представляет собой минимальную DOS-программу, которая выводит что-либо вроде: «Эта программа не может быть запущена под DOS». Все это предусматривает случай, когда пользователь запускает программу Win32 в среде, которая не поддерживает Win32, получая при этом приведенное выше сообщение об ошибке (звучит разочаровывающе, конечно). После того как загрузчик Win32 отобразил в память РЕ-файл, первый байт отображения файла соответствует первому байту заглушки DOS. И это не так уж плохо. С каждой запускаемой Win32 программой пользователь получает дополнительную DOS-программу, загруженную просто так! (В Win 16 заглушка DOS не загружается в память.)
Как и в других исполняемых форматах Microsoft, настоящий заголовок можно обнаружить, найдя его стартовое смещение, которое хранится в заголовке DOS. Файл WINNT.H содержит определение структуры для заголовка заглушки DOS, что делает очень простым нахождение начала заголовка РЕ-файла. Поле e_lfanew собственно и содержит относительное смещение (или, если хотите, RVA) настоящего заголовка РЕ-файла. Чтобы установить указатель в памяти на заголовок РЕ-файла, достаточно просто сложить значение в поле с базовым адресом отображения:
Настоящее веселье начинается, когда указатель установлен на основной заголовок РЕ-файла. Основной заголовок РЕ-файла представляет структуру типа IMAGE_NT_HEADERS, определенную в файле WINNT.H. Структура IMAGE_NT_HEADERS в памяти — это то, что Windows 95 использует в качестве своей базы данных модуля в памяти. Каждый загруженный ЕХЕ-файл или DLL представлены в Windows 95 структурой IMAGE_NT_HEADERS. Эта структура состоит из двойного слова и двух подструктур, как показано ниже:
Поле Signature (сигнатура — подпись), представленное как ASCII код, — это РЕ (два нулевых байта после РЕ). Если поле e_lfanew в заголовке DOS указало вместо обозначения РЕ обозначение NE в этом месте, значит, вы работаете с файлом Win 16 NE. Аналогично, если указано обозначение LE в поле Signature, то это файл VxD (VirtualDeviceDriver— драйвер виртуального устройства). Обозначение LX указывает на файл старой соперницы Windows 95 — OS/2.
За двойным словом — сигнатурой РЕ, в заголовке РЕ-файла следует структура типа IMAGE_FILE_HEADER. Поля этой структуры содержат только самую общую информацию о файле. Структура не изменилась по сравнению с исходными COFF-реализациями. Эта структура является частью заголовка РЕ-файла, кроме того, появляется в самом начале объектных COFF-файлов, создаваемых компиляторами Microsoft Win32. Далее приводятся поля IMAGE_FILE_HEADER.
Это центральный процессор, для которого предназначен файл. Определены следующие идентификаторы процессоров:
WORD NumberOfSections
Количество секций в ЕХЕ- или OBJ-файле.
DWORD TimeDateStamp
Время, когда файл был создан компоновщиком (или компилятором, если это OBJ-файл). В этом поле указано количество секунд, истекших с 16:00 31 декабря 1969 года.
DWORD PointerToSymbolTable
Файловое смещение COFF-таблицы символов. Это поле используется только в OBJ- и РЕ-файлах с информацией COFF-отладчика. РЕ-файлы поддерживают разнообразные отладочные форматы, так что отладчики должны ссылаться ко входу IMAGE_DIRECTORY_ENTRY_DEBUG в каталоге данных (будет определен позднее).
DWORD NumberOfSymbols
Количество символов в COFF-таблице символов. См. предыдущее поле.
Размер необязательного заголовка, который может следовать за этой структурой. В исполняемых файлах — это размер структуры IMAGE_OPTIONAL_HEADER, которая следует за этой структурой. В объектных файлах, по утверждению Microsoft, это поле всегда содержит нуль. Однако при просмотре библиотеки вывода KERNEL32.LIB можно обнаружить объектный файл с ненулевым значением в этом поле, так что относитесь к высказыванию Microsoft с некоторым скептицизмом.
WORD Characteristics
Флаги, содержащие информацию о файле. Здесь описываются некоторые важные поля (другие поля определены в WINNT.H).
Третьим компонентом заголовка РЕ-файла является структура типа IMAGE_OPTIONAL_HEADER. Для РЕ-файлов эта часть является обязательной. Формат COFF разрешает индивидуальные реализации для определения структуры дополнительной информации, помимо стандартного IMAGE_FILE_HEADER. В полях в IMAGE_OPTIONAL_HEADER разработчики РЕ-формата поместили то, что они посчитали важным дополнением к общей информации в IMAGE_FILE_HEADER.
Для пользователя не является критическим знание всех полей в IMAGE_OPTIONAL_HEADER, Наиболее важными полями являются поля ImageBase и Subsystem. По желанию читатель может бегло просмотреть или пропустить следующее описание полей.
WORD Magic
Слово-сигнатура, определяющее состояние отображенного файла. Определены следующие значения:
BYTE MajorLinker Version; BYTE MinorLinker Version
Версия компоновщика, который создал данный файл. Числа должны быть представлены в десятичном виде, а не в шестнадцатеричном. Типичная версия компоновщика 2.23.
DWORD SizeOfCode
DWORD sizeOfInitializedData
Предполагается, что это общий размер всех секций, состоящих из инициализированных данных (не включая сегменты программного кода.) Однако не похоже, чтобы это совпадало с размером секций инициализированных данных в файле.
DWORD SizeOfUninltializedData
DWORD AddressOfEntryPoint
DWORD BaseOfCode
RVA, с которого начинаются программные секции файла. Программные секции кода обычно идут в памяти перед секциями данных и после заголовка РЕ-файла. Этот RVA обычно равен 0х1000 для ЕХЕ-файлов, созданных компоновщиками Microsoft. Для TLINK32 (Borland) значение этого поля равно 0х10000, так как по умолчанию этот компоновщик выравнивает объекты на границу в 64 Кбайт в отличие от 4 Кбайт в случае компоновщика Microsoft.
DWORD BaseOfData
RVA, с которого начинаются секции данных файла. Секции данных обычно идут последними в памяти, после заголовка РЕ-файла и программных секций.
DWORD ImageBase
Когда компоновщик создает исполняемый файл, он предполагает, что файл будет отображен в определенное место в памяти. Вот именно этот адрес и хранится в этом поле. Знание адреса загрузки позволяет компоновщику провести оптимизацию. Если загрузчик действительно отобразил файл в память по этому адресу, то программа перед запуском не нуждается ни в какой настройке. Я расскажу об этом немного подробнее при обсуждении перемещений относительно базы. В исполняемых файлах NT 3.1 адрес отображения по умолчанию равен 0х10000. В случае DLL этот адрес по умолчанию равен 0х400000. В Windows 95 адрес 0х10000 нельзя использовать для загрузки 32-разрядных файлов ЕХЕ, так как он лежит в пределах линейной области адресного пространства, общего для всех процессов. Поэтому для Windows NT 3.5 Microsoft изменила для исполняемых файлов Win32 базовый адрес по умолчанию, сделав его равным 0х400000. Более старые программы, которые были скомпонованы в предположении, что базовый адрес равен 0х10000, загружаются Windows 95 дольше, потому что загрузчик должен применить базовые поправки. Я опишу базовые поправки немного позже.
DWORD SectionAlignment
После отображения в память каждая секция будет обязательно начинаться с виртуального адреса, кратного данной величине. С учетом подкачки страниц минимальная величина этого поля 0х1000 используется компоновщиком Microsoft по умолчанию. TLINK в Borland C++ использует по умолчанию 0х10000 (64 Кбайт).
DWORD FileAlignment
В случае РЕ-файла исходные данные, которые входят в состав каждой секции, будут обязательно начинаться с адреса, кратного данной величине. Значение, устанавливаемое по умолчанию, равно 0х200 байт и, вероятно, выбрано так для того, чтобы начало секции всегда совпадало с началом дискового сектора (0х200 байт — это как раз размер дискового сектора). Это поле эквивалентно размеру выравнивания сегмента/ресурса в NE-файлах. В отличие от NE-файлов, РЕ-файлы не состоят из сотен секций, так что память, теряемая при выравнивании секций файла, обычно очень незначительна.
WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion
WORD MajorImageVersion; WORD MinorImageVersion
Определяемое пользователем поле. Это поле позволяет иметь различные версии ЕХЕ-файлов и DLL. Эти поля устанавливаются с помощью ключа компоновщика /VERSION, например:
WORD MajorSubsystem Version; WORD MinorSubsystem Version
Это поле содержит самую старую версию подсистемы, позволяющую запускать данный исполняемый файл. Типичное значение в этом поле 4.0 (обозначает Windows 4.0, что равносильно Windows 95).
DWORD Reserved
Это поле, по-видимому, всегда равно нулю.
DWORD SizeOfImage
Представляет общий размер всех частей отображения, находящихся под контролем загрузчика. Эта величина равна размеру области памяти, начиная с базового адреса отображения и заканчивая адресом конца последней секции. Адрес конца секции выровнен на ближайшую верхнюю границу секции.
Размер заголовка РЕ-файла и таблицы секции (объекта). Исходные данные для секций начинаются сразу после всех составляющих частей заголовка.
DWORD Checksum
Предположительно отвечает контрольной сумме контроля циклическим избыточным кодом (CRC-контроль) для данного файла. Как и для других исполняемых форматов Microsoft, это поле обычно игнорируется и устанавливается в нуль. Однако для всех DLL драйверов, DLL, загруженных во время загрузки ОС, и серверных DLL эта контрольная, сумма должна иметь правильное значение. Алгоритм для контрольной суммы можно найти в IMAGEHLP.DLL. Исходники IMAGEHLP.DLL поставляются в WIN32 SDK.
WORD Subsystem
Тип подсистемы, которую данный исполняемый файл использует для своего пользовательского интерфейса. WINNT.H определяет следующие значения:
WORD DllCharacteristics (обозначен как вышедший из употребления в NT 3.5)
Набор флагов, показывающий, при каких обстоятельствах будет вызываться функция инициализации DLL (например, DllMain()). Эта величина, по-видимому, всегда должна устанавливаться в нуль, однако операционная система вызывает функцию инициализации DLL для всех четырех случаев. Определены следующие значения:
DWORD SizeOfStackReserve
Объем виртуальной памяти, резервируемой под начальный стек цепочки. Однако не вся эта память выделяется (см. следующее поле). По умолчанию это поле устанавливается в 0х100000 (1 Мбайт). Если пользователь указывает 0 в качестве размера стека в CreateThread(), получившаяся цепочка будет иметь стек того же размера.
DWORD SizeQfStackCommit
Количество памяти, изначально выделяемой под исходный стек цепочки. Это поле по умолчанию равно 0х1000 байт (1 страница) для компоновщиков Microsoft, тогда как TLINK32 делает его равным 0х2000 (2 страницы).
DWORD SizeOfHeapReserve
Объем виртуальной памяти, резервируемой под изначальную кучу программы. Этот дескриптор кучи можно получить, вызвав GetProcessHeap(). Однако не вся эта память выделяется (см. следующее поле).
DWORD SizeOfHeapCommit
Объем виртуальной памяти, изначально выделяемой под кучу процесса. По умолчанию компоновщик делает это поле равным 0х1000 байт.
DWORD LoaderFlags (обозначен как вышедший из употребления в NT 3.5)
Как следует из WINNT.H, эти поля, по-видимому, связаны с поддержкой отладчика. Мне никогда не приходилось видеть исполняемый файл, у которого хотя бы один из этих битов был активизирован. Мне также неясно, как с помощью компоновщика устанавливать их. Определены следующие значения:
DWORD NumberOfRvaAndShes
Количество входов в массиве DataDirectory (см. описание следующего поля). Современные программные средства всегда делают это значение равным 16.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
Массив структур типа IMAGE_DATA_DIRECTORY. Начальные элементы массива содержат стартовый RVA и размеры важных частей исполняемого файла. В настоящее время некоторые элементы в конце массива не используются. Первый элемент массива — это всегда адрес и размер экспортированной таблицы функций (если она присутствует). Второй элемент массива — адрес и размер импортированной таблицы функций и т.д. Для того чтобы увидеть полный перечень определений элементов массива, см. IMAGE_DIRECTORY_ENTRY_xxx директивы #define в WINNT.H.
Этот массив предназначен для того, чтобы загрузчик мог быстро найти определенную секцию отображения (например, импортированную таблицу функций) без последовательного перебора всех секций отображения, чтобы каждый раз не сравнивать имена.
Таблица секций
Между заголовком РЕ-файла и исходными данными для секций отображения находится таблица секций. Эта таблица содержит информацию о каждой секции отображения. Секции в отображении упорядочены по их стартовому адресу, а не в алфавитном порядке.
К настоящему моменту необходимо четко разъяснить, что же такое секция. В NE-файле программный код и данные хранятся в различных сегментах в файле. Часть заголовка NE-файла представляет собой массив структур — по одной для каждого сегмента, используемого программой. Каждая структура массива содержит информацию об одном сегменте. Хранимая информация включает тип сегмента (программа или данные), его размер и его расположение, где бы он ни находился в файле. В РЕ-файле таблица секций аналогична таблице сегментов в NE-файле.
Однако, в отличие от таблицы сегментов NE-файла, таблица секций РЕ-файла не хранит значение селектора для каждого куска программного кода или данных. Вместо этого каждый элемент таблицы секций хранит адрес, по которому исходные данные файла были отображены в память. Несмотря на то, что секции аналогичны 32-разрядным сегментам, они на самом деле не являются индивидуальными сегментами. Вместо этого секция просто отвечает диапазону памяти виртуального адресного пространства процесса.
Другим отличием РЕ-файлов от NE-файлов является то, как они управляют вспомогательными данными, которые используются не программой, а операционной системой. В качестве двух примеров можно привести перечень DLL, используемых исполняемыми файлами, и местонахождение таблицы привязки (fixup). В NE-файлах ресурсы не считаются сегментами. И хотя они имеют приписанные им селекторы, информация о ресурсах не хранится в таблице сегментов заголовка NE-файла. Вместо этого ресурсы сведены в отдельную таблицу в конце заголовка NE-файла. Информации об импортированных и экспортированных функциях тоже не гарантируется выделение своего собственного сегмента, она накапливается в пределах заголовка NE-файла.
Другая ситуация в случае РЕ-файла. Все, что считается важным программным кодом или данными, хранится в полнокровной секции. Таким образом, информация об импортированных функциях хранится в своей собственной секции так же, как и таблица экспортируемых модулем функций. То же самое справедливо и для данных настройки. Любая программа или данные, которые могут понадобиться программе или операционной системе, получают свою собственную секцию.
Вскоре я начну рассматривать отдельные секции, но сначала мне нужно описать данные, с помощью которых операционная система работает с секциями. Сразу после заголовка РЕ-файла в памяти следует массив из IMAGE_SECTION_HEADER. Количество элементов этого массива задается в заголовке РЕ-файла (поле IMAGE_NT_HEADER.FileHeader.NumberOfSections). Программа PEDUMP выводит таблицу секций, все поля и атрибуты секций. Рис. 8.2 показывает вывод с помощью PEDUMP таблицы секций для типичного ЕХЕ-файла. Рис. 8.3 показывает вывод с помощью PEDUMP таблицы секций для типичного OBJ-файла.
Рис. 8.2. Типичная таблица секций ЕХЕ-файла
Рис. 8.3 Типичная таблица секций объектного файла
Каждый IMAGE_SECTION_HEADER представляет собой полную базу данных об одной секции файла ЕХЕ или OBJ и имеет следующий формат.
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]
Это поле имеет различные назначения в зависимости от того, встречается ли оно в ЕХЕ- или OBJ-файле. В ЕХЕ-файле оно содержит виртуальный размер секции программного кода или данных. Это размер до округления на ближайшую верхнюю границу файла. Поле SizeOfRawData дальше в этой структуре содержит это округленное значение. Интересно, что Borland TLINK32 меняет местами значение этого поля и поля SizeOfRawData и, тем не менее, остается правильным компоновщиком. В случае OBJ-файлов это поле указывает физический адрес секции. Первая секция начинается с адреса 0. Чтобы получить физический адрес следующей секции, надо прибавить значение в SizeOfRawData к физическому адресу данной секции.
DWORD VirtualAddress
В случае ЕХЕ-файлов это поле содержит RVA, куда загрузчик должен отобразить секцию. Чтобы вычислить реальный начальный адрес данной секции в памяти, необходимо к виртуальному адресу секции, содержащемуся в этом поле, прибавить базовый адрес отображения. Средства Microsoft устанавливают по умолчанию RVA первой секции равным 0х1000. Для объектных файлов это поле не несет никакого смысла и устанавливается в 0.
DWORD SizeOfRawData
В ЕХЕ-файлах это поле содержит размер секции, выровненный на ближайшую верхнюю границу размера файла. Например, допустим, что размер выравнивания файла 0х200. Если поле VirtualSize указывает, что длина секции Ох35А байт, то в данном поле будет указано, что размер секции 0х400 байт. Для OBJ-файлов это поле содержит точный размер секции, сгенерированной компилятором или ассемблером. Другими словами, для OBJ-файлов оно эквивалентно полю VirtualSize в ЕХЕ-файлах.
DWORD PointerToRawData
Это файловое смещение участка, где находятся исходные данные для секции. Если пользователь сам отображает в память РЕ- или COFF-файл (вместо того, чтобы доверить загрузку операционной системе), это поле важнее, чем поле VirtualAddress. Причиной является то, что в этом случае получится абсолютно линейное отображение всего файла, так что данные для секций будут находиться по этому смещению, а не по RVA, указанному в поле VirtualAddress.
DWORD PointerToRelocations
В объектных файлах это файловое смещение информации о поправках для данной секции. Информация о поправках в любой секции объектного файла следует за исходными данными для этой секции. В ЕХЕ-файлах это (и следующее) поле не несет смысловой нагрузки и устанавливается в нуль. Когда компоновщик создает ЕХЕ-файл, он разрешает большинство привязок, а во время загрузки остается разрешить только базовые адресные поправки и импортированные функции. Информация о базовых поправках и импортированных функциях хранится в секциях базовых поправок и импортированных функций, так что нет необходимости в ЕХЕ-файле помещать данные поправок для каждой секции после исходных данных секции.
DWORD PointerToLinenumbers
WORD NumberOfRelocations
Количество перемещений в таблице поправок для данной секции (поле PointerToRelocations приведено выше). Это поле используется, по-видимому, только в объектных файлах.
WORD NumberOfLinenumbers
Количество номеров строк в таблице номеров строк для данной секции (поле PointerToLinenumbers приведено выше).
DWORD Characteristics
То, что большая часть программистов называет флагами (flags), формат COFF/PE называет характеристиками (characteristics). Это поле представляет собой набор флагов, которые указывают на атрибуты секции (программа/данные, предназначен для чтения, предназначен для записи и т.п.). За полным перечнем всех возможных атрибутов секции обращайтесь к IMAGE_SCN_XXX_XXX defines в файле заголовка WINNT.H. Некоторые из самых важных флагов приведены в табл. 8.1.
Таблица 8.1. Флаги COFF-секций
Интересно отметить, чего не хватает в информации, хранящейся в каждой секции. Во-первых, следует обратить внимание на отсутствие любых атрибутов PRELOAD. Файловый формат NE позволяет пользователю определять атрибуты PRELOAD для сегментов, которые должны быть загружены сразу во время загрузки модуля. Файловый формат OS/2 2.0 LX имеет нечто похожее, что позволяет пользователю давать указание о том, что предварительно должно быть загружено до восьми страниц. РЕ-формат, напротив, не имеет ничего подобного. Исходя из этого, приходится заключить, что Microsoft абсолютно уверена в исполнимости требований загрузки страниц для своих реализаций Win32.
В РЕ-формате также отсутствует таблица поиска промежуточных страниц. Эквивалент IMAGE_SECTION_HEADER в файловом формате OS/2 LX не указывает непосредственно, где находятся в файле данные и программный код секции. Вместо этого файл формата OS/2 LX содержит таблицу поиска страниц, определяющую атрибуты и расположение в файле определенных диапазонов страниц внутри секции. РЕ-формат обходится без всего этого и гарантирует, что данные из секции будут храниться непрерывно в файле. Сравнивая два формата, можно сказать, что LX-метод более гибок, тогда как стиль РЕ намного проще в работе. Имея опыт написания программ просмотра файлов и дизассемблеров для обоих форматов, я могу лично поручиться за это!
Другим благоприятным отличием РЕ-формата от более старого NE-формата является то, что расположения элементов хранятся в виде простых смещений типа DWORD. В NE-формате расположение практически любого элемента хранилось в виде величины сектора. Чтобы посчитать действительное файловое смещение, нужно было сначала найти размер выравнивания в заголовке NE-файла и перевести его в размер сектора (обычно 16 или 512 байт). Затем нужно было умножить размер сектора на указанное смещение сектора, чтобы получить действительное файловое смещение. Если что-нибудь по случайности не хранится в виде секторного смещения в NE-файле, оно, вероятно, хранится как смещение относительно заголовка NE-файла. Ввиду того, что заголовок NE-файла не находится в начале файла, пользователю приходится привлекать в свою программу файловое смещение заголовка NE-файла. В противоположность этому РЕ-файлы определяют положение различных элементов, используя простые смещения относительно того положения, в которое файл был отображен в памяти. В общем, с РЕ значительно проще работать, чем с форматами NE, LX или LE (при условии, что можно использовать отображаемые в память файлы).
Часто встречающиеся секции
После того как читатель получил общее представление о том, что такое секция и как секции размещаются, можно перейти к более глубокому изучению секций, часто встречающихся в ЕХЕ- и объектных файлах. И хотя этот перечень ни в коем случае нельзя назвать исчерпывающим, он, тем не менее, включает секции, с которыми пользователь сталкивается ежедневно (даже если он об этом и не подозревает). Секции представлены в порядке важности и в соответствии с вероятной частотой их появления.
Рис. 8.4. Вызов импортируемых функций из РЕ-файла
Секция DATA
Если вам хочется попытать счастья и вы надеетесь, что загрузчик всегда сможет загрузить отображение по указанному компоновщиком базовому адресу, используйте ключ /FIXED, чтобы компоновщик удалил эту информацию. Хотя это и сохраняет место в исполняемом файле, однако это же может сделать данный файл неработающим на других платформах Win32. Пусть, например, вы создали ЕХЕ-файл для NT и расположили его по адресу 0х10000. Если вы дали указание компоновщику удалить поправки, данный ЕХЕ-файл не будет работать в Windows 95, так как там адрес 0х10000 не доступен (наименьший адрес загрузки в Windows 95 — 0х400000, т.е. 4 Мбайт).
Необходимо отметить, что инструкции JMP и CALL, генерируемые компилятором, используют смещения относительно этих инструкций, а не действительные смещения в 32-разрядном сегменте. Если отображение необходимо загрузить по базовому адресу, отличному от указанного компоновщиком, не нужно изменять эти инструкции, поскольку они используют относительную адресацию. В результате поправок не так много, как могло бы показаться. Поправки обычно требуются только для инструкций, использующих 32-разрядные смещения для данных. Допустим, имеются следующие объявления глобальных переменных:
Если компоновщик указал в качестве базового адреса отображения 0х10000, адрес переменной i будет в итоге содержать что-нибудь наподобие 0х12004. В памяти, используемой под указатель ptr, компоновщик поместит значение 0х12004, поскольку это— адрес переменной i. Если загрузчик решит (по каким-либо причинам) загрузить файл по базовому адресу 0х70000, адресом переменной i будет в таком случае 0х72004. Однако значение переменной ptr перед инициализацией теперь будет неправильным, так как / сейчас находится на 0х60000 байт выше в памяти.
Локальную память потока можно представить как отдельный набор глобальных переменных для каждого потока. Это означает, что каждый поток может иметь свой набор величин статических данных, однако программный код использует эти данные, безотносительно к тому, какой поток исполняется. Рассмотрим программу, имеющую несколько потоков, которые работают над одной и той же задачей, т.е. исполняют один и тот же программный код. Если пользователь объявил переменную локального хранения потока, например:
то каждый поток будет иметь свою собственную копию переменной i.
Также возможен явный запрос на использование локальной памяти потока во время исполнения программы с помощью функций TlsAlloc, TlsSetValue и TlsGetValue. (В главе 3 подробно рассказано о функциях TlsXXX.) В большинстве случаев гораздо проще объявлять данные в программе с помощью __declspec (thread), чем распределять память для потока и запоминать указатель на нее в слоте, выделенном функцией TlsAlloc().
Рис. 8.5. Типичный каталог отладки
Разнообразные секции
Работая с программой PEDUMP, я время от времени встречал и другие секции. Например, Windows 95 GDI32.DLL содержит секцию данных под названием _GPFIX, назначение которой предположительно связано с обработкой ошибок GP.
Отсюда можно извлечь двойной урок. Не обязательно придерживаться использования только стандартных секций, производимых компилятором или ассемблером. Если вам необходима отдельная секция, не бойтесь использовать ее. При работе с компилятором Microsoft C/C++ пользуйтесь #pragma code_seg и #pragma data_seg. Пользователи компилятора Borland могут использовать #pragma codeseg и #pragma dataseg. В ассемблере можно просто создать 32-разрядный сегмент с именем, отличным от имен стандартных секций. Компоновщик TLINK32 объединяет сегменты программного кода одного класса, так что следует либо присваивать каждому сегменту программного кода свое уникальное имя класса, либо отключить упаковку сегментов программного кода. Другой урок: необычные имена секций часто позволяют глубже взглянуть на назначение и реализацию конкретного РЕ-файла.
Импортирование в РЕ-файлах
DWORD Characteristics/OriginalFirstThunk
В этом поле содержится смещение (RVA) массива двойных слов. Каждое из этих двойных слов в действительности является объединением IMAGE_THUNK_DATA. Каждое двойное слово IMAGE_THUNK_DATA соответствует одной функции, импортируемой данным ЕХЕ-файлом или DLL. Я опишу формат IMAGE_THUNK_DATA DWORD немного позже. Если используется утилита BIND, то этот массив двойных слов не изменяется, а модифицируется массив двойных слов FirstThunk (описан вкратце).
DWORD TimeDateStamp
Отметка о времени и дате, указывающая, когда был создан данный файл. Обычно это поле содержит 0. Тем не менее утилита Microsoft BIND обновляет это поле датой и временем из DLL, на которую указывает данный 1MAGE_IMPORT_DESCRIPTOR.
DWORD ForwarderChain
Это поле имеет отношение к передаче, когда одна DLL передает ссылку на какую-то свою функцию другой DLL. Например, в Windows NT KERNEL32.DLL посылает несколько своих экспортируемых функций NTDLL.DLL. Приложение может посчитать это вызовом функции в KERNEL32.DLL, но в итоге это будет вызов в NTDLL.DLL. Это поле содержит указатель в массив FirstThunk (описан вкратце). Функция, указанная этим полем, будет послана в другую DLL. К сожалению, формат посылки функции лишь вкратце описан в документации Microsoft. За дополнительной информацией о посылке обращайтесь к разделу «Передача экспорта» в этой главе.
DWORD Name
Это RVA строки символов ASCII, оканчивающейся нулем и содержащей имена импортируемых DLL (например, KERNEL32.DLL или USER32.DLL).
PIMAGE_THUNK_DATA FirstThunk;
RVA-смещение массива двойных слов IMAGE_THUNK_DATA. В большинстве случаев двойное слово рассматривается как указатель на структуру IMAGE_IMPORT_BY_NAME. Однако можно импортировать функцию также и по порядковому номеру.
Важными частями IMAGE_IMPORT_DESCRIPTOR являются имя импортируемой DLL и два массива элементов IMAGE_THUNK_DATA DWORD. Каждое двойное слово IMAGE_THUNK_DATA соответствует одной импортируемой функции. В ЕХЕ-файлах оба эти массива (на них указывают поля Characteristics и FirstThunk) идут параллельно друг другу и оканчиваются элементом-указателем NULL в конце каждого массива.
Зачем нужны два параллельных массива указателей на структуры IMAGE_THUNK_DATA? Первый массив (на него указывает поле Characteristics) оставляется неизменным. Иногда его называют таблицей имен-намеков (hint-name table). Второй массив, на который указывает поле FirstThunk в IMAGE_IMPORT_DESCRIPTOR, переписывается РЕ-загрузчиком. Загрузчик последовательно перебирает IMAGE_THUNK_DATA и находит адрес функции, на которую ссылается последний. Затем загрузчик записывает в двойное слово IMAGE_THUNK_DATA адрес импортируемой функции.
Раньше я упоминал о том, что вызовы функций DLL происходят через переходник «JMP DWORD PTR[CXXXXXXX]». [XXXXXXXX] в переходнике ссылается на один из элементов массива FirstThunk. Поскольку массив, состоящий из IMAGE_THUNK_DATA, переписывается загрузчиком и в итоге содержит адреса всех импортируемых функций, он называется «Таблица адресов импорта». Рис. 8.6 показывает оба этих массива.
Для пользователей Borland есть некоторая дополнительная тонкость в этом описании. В РЕ-файле, созданном TLINK32, отсутствует один из массивов. В таких файлах поле Characteristics в IMAGE_IMPORT_DESCRIPTOR (т.е. в массиве имен-намеков) равно нулю (очевидно, загрузчики Win32 не нуждаются в этом массиве). Таким образом, во всех РЕ-файлах вообще обязан быть только массив, на который указывает поле FirstThunk (таблица адресов импорта).
На этом можно было бы и закончить изложение, если бы при написании программы PEDUMP я не столкнулся с одной интересной проблемой. В постоянной погоне за оптимизацией Microsoft «оптимизировала» массивы IMAGE_THUNK_DATA в системных DLL под Windows NT (например, KERNEL32.DLL). После этой оптимизации IMAGE_THUNK_DATA не содержит информации, необходимой для нахождения импортируемой функции. Вместо этого двойные слова IMAGE_THUNK_DATA уже содержат адреса импортируемых функций. Другими словами, для загрузчика нет необходимости выискивать адреса функций и переписывать массив переходников с адресами импортируемых функций. Массив уже содержит адреса импортируемых функций еще до загрузки. (Утилита BIND из Win32 SDK осуществляет такую оптимизацию.) К сожалению, это вызывает трудности при работе программ просмотра, предполагающих, что массив содержит смещения RVA для элементов IMAGE_THUNK_DATA. Вы можете подумать: «А почему не использовать таблицу имен-намеков?» Это было бы идеальным решением, если бы таблица имен-намеков существовала в файлах Borland. Программа PEDUMP работает в обеих ситуациях, однако, по понятным причинам, результат получается несколько беспорядочным.
Рис. 8.6. Как РЕ-фаил импортирует функции
Поскольку таблица адресов импорта находится обычно в доступной для записи секции, то не представляет труда перехватить вызовы, сделанные ЕХЕ или DLL другой DLL. Для этого нужно просто «залатать» соответствующий элемент таблицы адресов импорта так, чтобы он указывал на желаемую функцию-перехватчик. Не нужно модифицировать код ни в вызывающей, ни в вызываемой функции. Эта возможность представляется очень полезной. В главе 10 я расскажу о программе-шпионе Win32 API, которая опирается на этот прием.
IMAGE_THUNK_DATA DWORD
Как я уже упоминал, каждое двойное слово IMAGE_THUNK_DATA соответствует импортируемой функции. Интерпретация двойного слова зависит от того, был ли файл уже загружен в память и была ли функция импортирована по имени или по номеру (импортирование по имени встречается чаще).
При импортировании функции по номеру (что бывает редко) старший бит (0х80000000) двойного слова IMAGE_THUNK_DATA данного ЕХЕ-файла устанавливается в 1. Например, рассмотрим IMAGE_THUNK_DATA со значением 0х80000112 в массиве GDI32.DLL. Этот IMAGE_THUNK_DATA импортирует 112-ю экспортируемую функцию из GDI32.DLL. Проблема при импортировании по номеру состоит в том, что фирма Microsoft не позаботилась, чтобы поддержать соответствие номеров экспорта функций Win32 API для Windows NT, Windows 95 и Win32s.
Если функция импортируется по имени, то ее двойное слово IMAGE_THUNK_DATA содержит RVA структуры IMAGE_IMPORT_BY_NAME. Это простая структура выглядит следующим образом.
WORD Hint
Наилучшая догадка о том, какой номер экспорта у импортируемой функции. В отличие от NE-файлов эта величина не обязана быть верной. Загрузчик использует ее в качестве начального предполагаемого значения для бинарного поиска экспортируемой функции.
Строка ASCIIZ с именем импортируемой функции. Окончательная интерпретация двойного слова IMAGE_THUNK_DATA происходит после того, как РЕ-файл загружен загрузчиком Win32. Загрузчик Win32 использует исходную информацию из двойного слова IMAGE_THUNK_DATA для поиска адреса импортируемой функции (либо по имени, либо по номеру). Затем загрузчик записывает в двойное слово IMAGE_THUNK_DATA адрес импортируемой функции.
Сравнение IMAGE_IMPORT_DESCRIPTOR и IMAGE_THUNK_DATA
Теперь, после того как вы увидели и структуру IMAGEJMPORTJDESCRIPTOR, и структуру IMAGE_THUNK_DATA, будет просто составить отчет обо всех импортируемых функциях, которые использует ЕХЕ-файл или DLL. Для этого нужно просто перебрать последовательно все элементы массива IMAGE_IMPORT_DESCRIPTOR (каждый из которых соответствует одной импортируемой DLL). Для каждого элемента IMAGE_IMPORT_DESCRIPTOR найдите массив двойных слов IMAGE_THUNK_DATA и интерпретируйте его соответствующим образом. Рис. 8.7 показывает вывод программы PEDUMP для этой операции. (Функции без имен импортируются по номеру.)
Рис. 8.7. Типичная таблица импорта в ЕХЕ-файле (EXPLORER.EXE)
Экспорт в РЕ-файлах
DWORD Characteristics
Это поле, по-видимому, никогда не используется и всегда устанавливается в 0.
DWORD TimeDateStamp
Отметка о времени и дате, указывающая время создания файла.
WORD MajorVersion;WORD MinorVersion
Эти поля, по-видимому, никогда не используются и всегда устанавливаются в 0.
DWORD Name
RVA строки ASCIIZ с именем этой DLL (например, MYDLL.DLL).
DWORD Base
Начальный номер экспорта для функций, экспортируемых данным модулем. Например, если номера экспортируемых функций 10, 11 и 12, то это поле будет содержать 10.
DWORD NumberOfFunctions
Количество элементов в массиве AddressOfFunctions. Это также число экспортируемых данным модулем функций. Обычно это значение такое же, как и в поле NumberOfNames (см. следующее описание), хотя они могут быть и различными.
DWORD NumberOfNames
Количество элементов в массиве AddressOfNames. Это значение соответствует количеству функций, экспортируемых по имени, которое обычно (хотя и не всегда) равно общему количеству экспортируемых функций.
PDWORD * AddressOfFunctions
Это поле является RVA и указывает на массив адресов функций. Адреса функций — это RVA точек входа для каждой экспортируемой модулем функции.
PDWORD * AddressOfNames
Это поле является RVA и указывает на массив указателей строки. Строки содержат имена функций, экспортируемых по имени из данного модуля.
PWORD * AddressOfNameOrdinals
Это поле является RVA и указывает на массив слов. По существу, в этих словах хранятся номера экспорта всех экспортируемых из данного модуля по имени функций. Однако не забудьте прибавить начальный номер экспорта, указанный в поле Base (описано выше).
Формат таблицы экспорта несколько странен. Как я упоминал раньше, обязательными при экспортировании функции являются адрес и номер экспорта. Если вы решили экспортировать функцию по имени, здесь будет имя функции. Можно было бы подумать, что разработчики РЕ-формата могли поместить все эти три компонента в одну структуру и после этого оперировать массивом таких структур. Вместо этого приходится выискивать различные части в трех различных массивах.
Наиболее важным из всех массивов, на которые указывает IMAGE_EXPORT_DIRECTORY, является массив, на который указывает поле AddressOfFunctions. Это массив двойных слов, в котором каждое двойное слово содержит RVA одной из экспортируемых функций. Номер экспорта каждой экспортируемой функции соответствует ее положению в массиве, Так, например, принимая, что номера экспорта начинаются с 1, адрес, по которому хранится адрес функции с номером экспорта, равным 1, содержится в первом элементе массива. Адрес, по которому хранится адрес функции с номером экспорта, равным 2, содержится во втором элементе массива и т.д.
Необходимо помнить о двух моментах относительно массива AddressOfFunctions. Во-первых, нельзя забывать о том, что отсчет номеров экспорта начинается с числа, содержащегося в поле Base структуры IMAGE_EXPORT_DIRECTORY. Так, если поле Base содержит 10, то первое двойное слово в массиве AddressOfFunctions соответствует номеру экспорта 10, второе — 11 и т.д. Во-вторых, следует иметь в виду, что номера экспортов могут иметь пропуски. Допустим, что явно экспортируются две функции с номерами 1 и 3. Несмотря на то, что экспортированы только две функции, массив AddressOfFunctions обязан содержать три элемента. Любые элементы массива, не отвечающие экспортируемым функциям, содержат 0.
Когда загрузчик Win32 связывает вызов функции, экспортируемой по номеру, он выполняет совсем незначительный объем работ. Он просто использует номер функции как индекс в массиве AddressOfFunctions модуля-цели. Конечно, загрузчик должен учесть, что наименьший номер экспорта может быть не равен 1, и должен поправить индексацию соответствующим образом.
Чаще всего ЕХЕ-файлы и DLL под Win32 импортируют функции по имени, а не по номеру. Здесь выходят на сцену два других массива, на которые указывает структура IMAGE_EXPORT_DIRECTORY. Массивы AddressOfNames и AddressOfNameOrdinals существуют для того, чтобы загрузчик мог быстро найти номер экспорта по заданному имени функции. Массивы AddressOfNames и AddressOfNameOrdinals содержат одинаковое количество элементов (заданное в поле NumberOfNames структуры IMAGE_EXPORT_DIRECTORY). Массив AddressOfNames — это массив указателей на имена функций, а массив AddressOfNameOrdinals — массив индексов для массива AddressOfFunctions.
Давайте посмотрим, как загрузчик Win32 обрабатывает вызов функции, импортируемой по имени. Сначала загрузчик будет искать строки, на которые указывает массив AddressOfNames. Допустим, он находит искомую строку в третьем элементе. Затем загрузчик использует найденный индекс для поиска соответствующего элемента в массиве AddressOfNameOrdinals (в данном случае это третий элемент). Последний массив — это просто набор слов, где каждое слово играет роль индекса в массиве AddressOfFunctions. Наконец последний шаг — взять значение в массиве AddressOfNameOrdinals и использовать его в качестве индекса для массива AddressOfFunctions.
В программе на С нахождение адреса функции, импортируемой по имени, будет выглядеть примерно следующим образом:
Рис. 8.8 демонстрирует формат секции экспорта и три ее массива.
Рис. 8.8. Типичная таблица экспорта из ЕХЕ-фаила
Рис. 8.9 показывает вывод программой PEDUMP секции экспорта в KERNEL32.DLL.
Рис. 8.9. Распечатка секции экспорта для библиотеки KERNEL32.DLL с помощью программы PEDUMP
Если вы просматриваете экспорт в системных DLL (например, KERNEL32.DLL или USER32.DLL), вы можете случайно обнаружить, что часто две функции отличаются только одним символом в конце имени, например CreateWindowExA и CreateWindowExW. Вот так «явно» осуществлена поддержка уникода (Unicode). Функции, оканчивающиеся на А, являются ASCII-совместимыми (или ANSI-) функциями. Функции, оканчивающиеся на W, — это Unicode-версии этих функций. Программируя, пользователь не указывает явно, какую функцию надо вызывать. Вместо этого соответствующая функция выбирается в WINDOWS.H с помощью директивы препроцессора #ifdefs. Это иллюстрируется следующим отрывком из NT WINDOWS.H:
Передача экспорта
Иногда в DLL полезно экспортировать функцию, а ее программный код иметь в другой DLL. При таком сценарии одна DLL может передавать функцию другой DLL. Если загрузчик Win32 встречает вызов передаваемой функции, он разрешает ссылку на функцию в ту DLL, которая содержит настоящий программный код.
Проиллюстрируем сказанное примером. Рассмотрим следующий отрывок из вывода программой PEDUMP Windows NT3.5 KERNEL32.DLL:
Каждая функция в этом выводе передается функции в NTDLL. Таким образом, программа, вызывающая функцию HeapAlloc, в действительности вызывает функцию RtlAllocateHeap из NTDLL.DLL. Аналогично, вызов HeapFree является в действительности вызовом функции RtlHeapFree из NTDLL.
Хотя передача экспорта кажется очень приятным свойством, Microsoft не дает описания того, как использовать передачу в пользовательских DLL. До сих пор я встречал использование передачи только одной DLL (вышеупомянутая Windows NT KERNEL32.DLL). Даже несмотря на то, что мне не встречались DLL с передачами экспорта под Windows 95, загрузчик Windows 95, тем не менее, поддерживает это свойство, о чем я рассказывал в главе 3.
Ресурсы РЕ-файла
Нахождение ресурсов в РЕ-файлах сложнее по сравнению с эквивалентными NE-файлами. Формат индивидуальных ресурсов (например, меню) существенно не изменился, но в РЕ-файлах приходится рыскать по сложной иерархии, чтобы найти их.
Перемещения по иерархии каталогов ресурсов похожи на перемещения по жесткому диску. Здесь есть главный каталог (корневой), имеющий свои подкаталоги. Подкаталоги имеют свои собственные подкаталоги. В этих подкаталогах находятся файлы. Файлы аналогичны исходным данным ресурсов, содержащим такие элементы, как диалоговые шаблоны. В РЕ-файлах как корневой каталог, так и его подкаталоги являются структурами типа IMAGE_RESOURCE_DIRECTORY. Структура IMAGE_RESOURCE_DIRECTORY имеет следующий формат.
DWORD Characteristics
Теоретически это поле может содержать флаги ресурсов, но, по-видимому, оно всегда равно 0.
DWORD TimeDateStamp
Отметка о времени и дате создания ресурса.
WORD MajorVersion; WORD MinorVersion
Теоретически эти поля могли бы содержать номер версии ресурса. По-видимому, они всегда равны 0.
WORD NumberOfNamedEntries
Количество элементов массива (описан позже), использующих имена и следующих за этой структурой. За дополнительной информацией обращайтесь к описанию поля DirectoryEntries.
WORD NumberOfIdEntries
Количество элементов массива, использующих целые ID и следующих за этой структурой и всеми поименованными элементами. За дополнительной информацией обращайтесь к описанию поля DirectoryEntries.
IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]
Это поле формально не является частью структуры IMAGE_RESOURCE_DIRECTORY. За этим полем сразу следует массив структур IMAGE_RESOURCE_DIRECTORY_ENTRY. Количество элементов в массиве равно сумме полей NumberOfNamedEntries и NumberOfIdEntries. Элементы каталога, имеющие идентификаторы-имена (а не целые ID), находятся в начале массива.
Элемент каталога может указывать либо на подкаталог (т.е, на другую IMAGE_RESOURCE_DIRECTORY), либо на IMAGE_RESOURCE_DATA_ENTRY, которая описывает, где в файле находятся исходные данные ресурсов. Как правило, необходимо пройти как минимум три уровня каталогов, перед тем как попасть на IMAGE_RESOURCE_DATA_ENTRY для данного ресурса. Каталог верхнего уровня (только один) всегда находится в начале секции ресурсов (.rsrc). Подкаталоги каталога верхнего уровня соответствуют различным типам ресурсов, находящихся в файле. Например, если РЕ-файл включает диалоги, таблицы строк и меню, этими тремя подкаталогами будут соответственно каталог диалогов, каталог таблицы строк и каталог меню. Каждый из этих «типов» подкаталогов будет в свою очередь иметь «ID»-подкаталоги. Для каждого образца заданного типа ресурса будет существовать один ID-подкаталог. Если в приведенном выше примере есть четыре диалоговых окна, каталог диалогов будет иметь четыре ID-подкаталога. Каждый ID-подкаталог будет иметь либо строковое имя (например, MyDialog), либо целый ID, используемый для идентификации ресурса в RC-файле. Рис. 8.10 наглядно иллюстрирует иерархию каталогов ресурсов.
Рис. 8.10. Иерархия ресурсов типичного РЕ-файла
Рис. 8.11 показывает вывод программой PEDUMP ресурсов файла CLOCK.EXE в Windows NT 3.5. На втором уровне отступов можно видеть пиктограммы, меню, диалоги, таблицы строк, пиктограммы групп и ресурсы версий. На третьем уровне — две пиктограммы (с ID 1 и 2), два меню (с именами CLOCK и GENERICMENU), два диалога (один с именем ABOUTBOX, а другой — с целым ID, равным 0х64) и т.д. На четвертом уровне отступов — данные для значка 1 с RVA 0х9754 длиной 0x130 байт. Аналогично данные для меню CLOCK имеют смещение Ох952С и занимают 0хЕА байт.
Рис. 8.11. Иерархия ресурсов для CLOCK.EXE
Чтобы продвинуться дальше в обсуждении форматов ресурсов, мне необходимо рассказать о формате индивидуальных типов ресурсов (диалоги, меню и т.д.). Этот рассказ занял бы целую главу. Но я решил сэкономить деревья, из которых делают бумагу. Если вам интересно, читайте файл RESFMT.TXT из Win32 SDK, в котором есть детальное описание всех типов ресурсов. Программа PEDUMP показывает иерархию ресурсов, но не затрагивает индивидуальные образцы ресурсов.
Базовые поправки РЕ-файла
В отличие от поправок в NE-файлах, базовые поправки РЕ-файлов чрезвычайно просты. Они не ссылаются на внешние DLL или на другие секции модуля. Вместо этого базовые поправки сводятся к перечню тех мест в отображении, где нужно прибавить некоторую величину.
Вот пример того, как работают базовые поправки. Предположим, что ЕХЕ-файл скомпонован в допущении, что базовый адрес равен 0х400000. Пусть указатель, содержащий адрес какой-либо строки, имеет смещение 0х2134 в отображении. Строка начинается с физического адреса 0х404002, так что указатель содержит это значение. В момент загрузки загрузчик решает, что модуль нужно отобразить в память, начиная с физического адреса 0х600000. Разность между предполагаемым компоновщиком базовым адресом и реальным адресом загрузки называется дельта. В нашем случае дельта равна 0х200000 (0х600000-0х400000). Поскольку все отображение оказывается на 0х200000 байт выше в памяти, адрес строки теперь 0х604002. Указатель на строку теперь содержит неверное значение. Чтобы исправить его, к нему необходимо прибавить дельту (в нашем случае 0х200000).
Чтобы загрузчик Windows сделал это исправление, исполняемый файл содержит базовую поправку для того места в памяти, в котором находится указатель (его смещение в отображении равно 0х2134). Чтобы разрешить базовую поправку, загрузчик добавляет дельту к исходному значению, находящемуся по адресу, указанному в базовой настройке. В нашем случае загрузчик должен прибавить 0х200000 к исходному значению указателя (0х404002) и поместить это значение (0х604002) обратно в указатель. Раз строка действительно находится по адресу 0х604002, все снова становится правильным. Рис. 8.12 показывает весь этот процесс.
Рис. 8.12. Базовые поправки РЕ-файла
Формирование данных базовых поправок выглядит несколько странно. Поправки упаковываются сериями смежных кусков различной длины. Каждый кусок описывает поправки для одной четырехкилобайтовой страницы отображения и начинается со структуры IMAGE_BASE_RELOCATION, которая выглядит следующим образом.
DWORD VirtualAddress
Это поле содержит стартовый RVA для данного куска поправок. Смещение каждой поправки, которая следует дальше, добавляется к этой величине для получения истинного RVA, к которому должна быть применена данная поправка.
DWORD SizeOfBlock
Размер данной структуры плюс все последующие поправки типа WORD. Чтобы определить количество поправок в данном блоке, нужно из значения этого поля вычесть размер IMAGE_BASE_RELOCATION (8 байт) и затем разделить на 2 (размер типа WORD). Например, если это поле содержит значение 44, то в блоке имеется 18 поправок:
WORD TypeOffset
На самом деле это не отдельное слово, а массив слов, количество элементов в котором вычисляется по формуле, приведенной в описании предыдущего двойного слова. Младших 12 разрядов каждого из этих слов представляют поправочное смещение, которое должно быть прибавлено к значению в поле VirtualAddress из заголовка данного блока поправок. Старших 4 разряда каждого слова являются типом поправки. Для РЕ-файлов, исполняемых на процессорах серии Intel, существуют только два типа поправок:
Есть также другие поправки, определенные в WINNT.H, большая часть которых рассчитана на архитектуры процессоров, отличные от i386.
Рис. 8.13 показывает некоторые базовые поправки, выведенные программой PEDUMP. Заметьте, что значения RVA, показанные на рисунке, были уже заранее определены полем VirtualAddress структуры IMAGE_BASE_RELOCATION.
Рис. 8.13. Базовые поправки в ЕХЕ-файле
COFF-таблица символов
Если вас интересуют только те части РЕ-файла, которые используются операционной системой, вы можете пропустить этот и следующий («COFF-отладочная информация») разделы и продолжить чтение с раздела «Различия между РЕ-файлами и объектными COFF-файлами».
Если при компиляции отладочная информация не включается, то в таблице символов объектного файла будет находится лишь небольшое количество символов. Если же включить отладочную информацию (с помощью /Zi), то компилятор добавит дополнительную информацию о начале, конце и длине каждой функции модуля. Если затем провести компоновку либо с /DEBUGTYPE:COFF, либо с /DEBUGTYPE:BOTH, компоновщик поместит в получившийся ЕХЕ-файл таблицу символов в COFF-стиле.
И в ЕХЕ-файлах, и в объектных файлах расположение и размер COFF-таблицы символов определены в структуре IMAGE_FILE_HEADER (см. раздел «Заголовок РЕ-файла» раньше в этой главе, чтобы освежить в памяти сведения об этой структуре). Таблица символов специально сделана простой и состоит из массива структур IMAGE_SYMBOL. Количество элементов в этом массиве задается значением поля NumberOfSymbols структуры IMAGE_FILE_HEADER. Рис. 8.14 показывает пример вывода символов программой PEDUMP.
Рис. 8.14. Типичная COFF-таблица символов
Каждая структура IMAGE_SYMBOL имеет следующий формат:
Давайте изучим каждое из этих полей детально.
union (Symbol name union)
DWORD Value
Это поле содержит значение, связанное с символом. Для нормальных символов и символов данных (т.е. функции и глобальные переменные) поле Value содержит RVA элемента, на который ссылается данный символ. Это значение интерпретируется иначе для некоторых других символов. В табл. 8.2 представлен краткий перечень некоторых назначений поля Value для специальных символов.
Таблица 8.2. Специальные символы в COFF-таблицах символов
SHORT SectionNumber
WORD Type
Тип символа. Файл WINNT.H определяет достаточно широкий спектр типов символов (int, struct, enum и т.д.). (См. полный перечень в директивах #defines IMAGE_SYM_TYPE_xxx.) К сожалению, средства Microsoft, по-видимому, не генерируют символов всех возможных типов. Вместо этого все глобальные переменные и функции имеют тип NULL или тип функции, возвращающей NULL.
BYTE StorageClass
Класс памяти символа. Как и для типов символов файл WINNT.H определяет достаточно широкий спектр классов памяти: automatic, static, register, label и т.д. (См. полный перечень в директивах #define IMAGE_SYM_CLASS_xxx.) Опять-таки, как и в случае типов, средства Microsoft создают только небольшое количество информации. Все глобальные переменные и функции имеют класс памяти внешний. По всей видимости, не существует способа создать символы для локальных переменных, регистровых переменных и т.д.
BYTE NumberOfAuxSymbols
К счастью, размер структуры IMAGE_AUX_SYMBOL такой же, как и у структуры IMAGE_SYMBOL, так что пользователь все же может рассматривать таблицу символов как массив структур IMAGE_SYMBOL. Запомните, что индекс символа должен рассматриваться как индекс массива, даже если некоторые элементы являются вспомогательными записями. Чтобы вычислить индекс следующего регулярного символа, нужно прибавить количество вспомогательных структур, используемых символом. Например, пусть символ имеет индекс 1. Если он использует три вспомогательных символа, то индекс следующего регулярного символа будет равен 4.
IMAGE_AUX SYMBOL представляет собой запутанное объединение полей. Чтобы определить, какие члены объединения использовать, необходимо знать тип регулярного символа, связанного с данным вспомогательным символом. И хотя я так и не понял, какие поля объединения должны быть использованы в каждом случае, я уяснил себе два следующих.
Символы, имеющие класс памяти IMAGE_SYM_CLASS_FILE, используют член объединения File в структуре IMAGE_AUX_SYMBOL.
Символы, имеющие класс памяти IMAGE_SYM_CLASS_STATIC, используют член объединения Section в структуре IMAGE_AUX_SYMBOL.
Все мои знания, касающиеся интерпретации вспомогательных символов, в совокупности содержатся в функции DumpAuxSymbols() в исходном файле COMMON.С в программе PEDUMP. Если вы самостоятельно обнаружите что-нибудь новенькое, смело вносите эти дополнения в эту функцию.
COFF-отладочная информация
Для среднего программиста термин отладочная информация включает как символьную информацию, так и информацию о номерах строк. В COFF-формате записи, относящиеся к символам и записи, относящиеся к номерам строк, находятся в разных областях файла. (В форматах фирмы Borland и в формате Code View для таблиц символов этих два вида информации поступают из одной и той же части файла.) Я обсудил вначале COFF-таблицу символов потому, что она имеется как в объектных, так и в ЕХЕ-файлах. К тому же очень рано в процессе изучения РЕ-формата приходится иметь дело с полем PointerToSymbolTable в структуре IMAGE_FILE_HEADER. По этим причинам я решил рассказать о таблице символов отдельно.
Вся COFF-таблица символов ЕХЕ-файла состоит из трех частей: заголовка, информации о номерах строк и таблицы символов. Они не обязательно расположены по соседству в памяти, но компоновщик Microsoft выстраивает их таким образом. Полная COFF-таблица символов выглядит так:
Структура IMAGE_COFF_SYMBOLS_HEADER рассчитана на то, чтобы помочь отладчикам быстро найти необходимую им информацию. Эта структура содержит указатели на таблицы номеров строк и символов, а также на информацию, находящуюся где-либо в другом месте в файле.
Рассмотрим подробнее поля структуры IMAGE_COFF_SYMBOLS_HEADER.
DWORD NumberOfSymbols
Количество символов в COFF-таблице символов. Данное поле содержит такое же значение, как и поле IMAGE_FILE_HEADER.NumberOfSymbols, что обсуждалось раньше в разделе «Заголовок РЕ-файла».
DWORD LvaToFirstSymbol
Байтовое смещение COFF-таблицы символов по отношению к началу рассматриваемой структуры. Прибавление этой величины к RVA структуры IMAGE_COFF_SYMBOLS_HEADER даст результат, совпадающий со значением поля IMAGE_FILE_HEADER.PointerToSymbolTable.
DWORD NumberOfLinenumbers
Количество элементов в таблице номеров строк (рис. 8.15).
Рис. 8.15. Типичный пример информации из таблицы номеров строк в ЕХЕ-фачле
DWORD LvaToFirstLinenumber
Байтовое смещение COFF-таблицы номеров строк по отношению к началу рассматриваемой структуры.
DWORD RvaToFirstByteOfCode
DWORD RvaToLastByteOfCode
RVA последнего байта исполняемого программного кода в отображении. Если есть только одна программная секция (.text), то данное поле будет равно RVA этой секции плюс размер ее исходных данных. Это значение также можно найти, просматривая таблицу секций исполняемого файла.
DWORD RvaToFirstByteOfData
DWORD RvaToLastByteOfData
COFF-таблица номеров строк
WORD Linenumber
Если необходим доступ только к номерам строк данной программной секции, то следует искать лишь соответствующий диапазон элементов, относящихся к номерам строк, в таблице секций. Структура IMAGE_SECTION_HEADER данной секции содержит файловое смещение и счетчик номеров своих строк внутри таблицы. Объектные файлы COFF-формата тоже содержат информацию о номерах строк в формате, который был только что описан. В объектных файлах отсутствует структура IMAGE_COFF_SYMBOLS_HEADER, поэтому пользователю придется искать записи номеров строк с помощью структур IMAGE_SECTION_HEADER.
Различия между РЕ-файлами и объектными COFF-файлами
Файлы COFF LIB
Все LIB-файлы начинаются с одной и той же восьмибайтовой сигнатуры. Эта сигнатура определена в файле WINNT.H:
Остальная часть файла представляет ряд записей переменной длины. Каждая запись начинается структурой IMAGE_ARCHIVE_MEMBER_HEADER:
Каждая структура IMAGE_ARCHIVE_MEMBER_HEADER отвечает либо объектному файлу внутри библиотеки, либо одной записи из небольшого набора специальных записей. Эти специальные записи находятся в начале библиотеки и существуют для того, чтобы компоновщик в дальнейшем мог быстро отыскивать объектные файлы в LIB-файле. Исходные данные для члена архива следуют сразу за структурой IMAGE_ARCHIVE_MEMBER_HEADER, с которой начинается каждая запись. Для большинства членов архива записей исходные данные точно такие же, как и в объектном файле. Действительно, когда программа PEDUMP проводит вывод LIB-файлов, она вызывает те же процедуры, что и при обработке объектного файла. Рис. 8.16 показывает формат LIB-файлов.
Рассмотрим поля структуры IMAGE_ARCHIVE_MEMBER_HEADER.
BYTE Name[16]
Имя члена архива. Если символ «/» появляется после ASCII-строки (например, FOO.OBJ/), то строка перед символом «/» представляет имя члена. Если имя начинается с символа «/», за которым следует десятичное число (например, /104), то число является смещением имени члена архива внутри члена Longnames LIB-файла. В предыдущем примере имя члена начинается со 104-го байта от начала области Longname.
Имеются также специальные имена для специальных членов архива:
Рис. 8.16. СОFF-формат LIB-файлов
Для объектных файлов внутри библиотеки импорта это поле представляет имя DLL, содержащей импортируемые функции.
BYTE Date[12]
Дата и время создания члена. Это число хранится в десятичном ASCII-виде.
BYTE UserID[6]
Десятичное ASCII-представление идентификатора пользователя. По-видимому, всегда является строкой NULL.
BYTE GroupID[6]
Десятичное ASCII-представление идентификатора группы. По-видимому, всегда является строкой NULL.
BYTE Mode[8]
Десятичное ASCII-представление файлового режима. По-видимому, всегда равно нулю.
BYTE Size[10]
Размер данных члена, представленный в десятичной ASCII-форме. Формат данных зависит от их типа (указан в уже описанном поле Name).
Члены компоновщика
Первый член компоновщика имеет следующий формат.
DWORD NumberOfSymbols
Число общедоступных символов в данной библиотеке. Это число представлено в формате big-endian (отражает наследие COFF-формата для машин, отличных от машин i386). Функция ConvertBigEndian в файле LIBDUMP.C программы PEDUMP осуществляет переключение из формата big-endian в формат little-endian, используемый i386.
DWORD Offsef[NumberOfSymbols]
BYTE StringTable[?]
Это неразрывная серия строк в стиле С в памяти.
По существу, каждый элемент массива Offset соответствует одному общедоступному символу, имя которого появляется в области StringTable. Например, третий элемент массива Offsets отвечает третьей строке в области StringTable. Вывод программы PEDUMP поясняет это:
Формат второго члена компоновщика запутаннее из-за добавления массива, необходимого для быстрого поиска символов. Второй член компоновщика имеет следующий формат.
DWORD NumberOfMembers
Это двойное слово содержит количество членов в архиве объектных модулей, следующих дальше в файле.
DWORD Offsets[NumberOfSymbols]
Массив файловых смещений других членов архива. В отличие от первого члена компоновщика эти смещения заданы в естественном формате машины (т.е. в формате little-endian для i386).
DWORD NumberOfSymbols
Количество общедоступных символов в массиве StringTable (следовательно, и количество общедоступных символов в библиотеке). Это поле к тому же содержит количество элементов в следующем далее массиве Indices.
WORD Indices[NumberOfSymbols]
Данный массив содержит индексы (отсчет начинается с 1) массива Offsets (описан на два поля раньше). Этот массив идет параллельно строкам массива StringTable.
BYTE SiringTable[NumberOfSymbols]
Это неразрывная серия строк в стиле С в памяти.
Для того чтобы отыскать объектный файл по его символу, используя второй член компоновщика, компоновщик сначала просматривает массив StringTable и вычисляет относительный индекс строки в массиве. Затем компоновщик использует этот индекс для поиска слова в массиве Indices. Наконец, компоновщик вычитает 1 из этого слова в массиве Indeces и использует результат как индекс массива Offsets. Найденное двойное слово в массиве Offsets как раз и будет смещением в объектном файле, содержащем общедоступный символ. Функция DumpSecondLinkerMember из файла LIBDUMP.C программы PEDUMP показывает этот процесс в действии.
Член Longnames
Резюме
Ценная часть как объектных, так и исполняемых файлов начинается со структуры IMAGE_FILE_HEADER. За этой структурой (и, возможно, еще одной дополнительной структурой) следует таблица секций. В таблице секций указаны местонахождение и атрибуты всех секций файла. Секцией называется совокупность логически связанных программного кода и данных. Чтобы обеспечить быстрое нахождение информации, РЕ-файл содержит каталог данных, указывающий на важные позиции в файле (например, расположение таблицы экспорта файла). Помимо заголовка (или заголовков), таблиц секций и исходных данных секций объектные COFF- и РЕ-файлы могут также содержать информацию о именах символов и номерах строк. Эта информация хранится в конце файла после всех заголовков и данных для секций.
Источник
This is no mere «How do I write a Windows 95 app?» manual. Windows 95 System Programming SECRETS reveals the hard-core technical information you need to know in order to tap the true power of 32-bit programming for Windows 95. Written for the programmer who’s done at least some Windows 3.x programming, this authoritative and comprehensive insider’s guide offers practical insight into why Windows 95 works as it does. Designing applications — no matter the type — will become second nature as you…
Discover what’s new in Windows 95 from a programming and architectural point of view Plunge into the specifics of its three core data structures — modules, processes, and threads — to understand issues such as local thread storage and structured exception handling Get nitty-gritty details about key 16- and 32-bit data structures, and tour the USER and GDI subsystems to see how windowing, messaging, and graphics work in Windows 95 Dissect the Windows 95 memory architecture: memory paging, selectors, virtual, and heap functions Uncover the relationships among KRNL386.EXE, KERNEL32.DLL, and VWIN32.VXD Scrutinize Windows 95’s Portable Executable formats to glean new insight into dynamic linking, loader behavior, and memory management Plus, on the bonus disk included with Windows 95 System Programming SECRETS, you get spy utilities and valuable source code, as well as author Matt Pietrek’s own special programs for spelunking Windows 95:
WIN32WLK.EXE Walk the Kernel32 Data Structures W32SVSPY.EXE Spy on Win32 VxD Service Calls VAR2MAP.EXE Build Your Own Symbol Tables SHOWWND.EXE View the Internals of Windows Classes PEDUMP.EXE Look at PE Files FSR32.EXE See Thunking without the Thunk Compiler