Как стать автором
Обновить
37
1

Пользователь

Отправить сообщение

Поправлю некоторые опечатки, а то пост уже не отредактировать:


а сторона, инициировавшая загрузку PE-образа, не потребовала сделать загрузку по нестандартной базе — PE-образ (хоть EXE, хоть DLL) будет загружен, даже если он перемещаемый.

даже если он не перемещаемый.


Microsoft-овский линкер умееть делать и так так.

умеет делать и так, и так.


Но если вы собираетесь свой PE-файл запускать под Win32s (это 32-битная подсистема для 16-битных Windows 3.1/3.11).

Но если вы собираетесь свой PE-файл запускать под Win32s (это 32-битная подсистема для 16-битных Windows 3.1/3.11), образ обязательно должен быть перемещаемым и иметь релоки.

Среди игр под Win32 до 2000 года некоторая часть была без таблицы, т.к. ценился размер файла и лишний килобайт экономили.

Нет, не «так как ценился размер файла», а так как у Microsoft-овского линкера поведением по-умолчанию было подразумевать ключ /FIXED (означающий «сгенерировать неперемещаемый образ»), если генерируется EXE-файл, а не DLL.


Такая тактика даже закреплена в официальной MS-овской спецификации на форматы COFF и PE:
image


А вообще, читайте большой коммент. Ваш спор (zvszvs и Angeld) со стороны выглядит странно: вроде бы оба что-то знают на тему PE/COFF, но у обоих в голове каша — даже не только не уровне знаний, сколько на уровне подхода к самому спору.

Решение — указать в свойствах секции кода не только бит "разрешение записи", но и бит "грузить только по указанной базе".

Нет такого бита в «свойствах секции». Это бит relocs stripped поля Characteristics из NT-заголовка.


Дальнейшее ваше обсуждение с Angeld тоже расстраивает: оба не в состоянии разделить компилятор и линкер и спорите с изначально неправильным подходом к спору и к определению истины.


Ваш спор о том «по умолчанию имеют ли EXE фиксированную базу» лишён смысла в первую очередь потому, что он построен так, как будто существует какой-то общемировой догмат/стандарт/подход о способе формирования EXE, и в его рамках есть своё «по-умолчанию». А его нет.


Во-первых, будет PE-файл иметь возможность загружаться по произвольной базе или будет иметь фиксированную базу зависит не от компилятора, а от линкера. Линкер, а не компиятор формирует основообразующие структуры PE-файла. Линкер, а не компилятор, определяет, будет ли в поле Characteristics стоять флаг IMAGE_FILE_RELOCS_STRIPPED. Линкер, а не компилятор, формирует таблицу релокаций. Компилятор определяет только набор секций, и начинку и характеристики, а линкер склеивает начинку всех одноимённых секций всех входных объектных файлов, комбинирует характеристики секций, формирует окончательную таблицу секций, разрешает зависимости/проставляет адреса, формирует прочие структуры PE-файла и даёт на выходе готовый исполняемый файл. Поэтому нужно спорить не «имеют ли EXE-файлы релокации вообще в принципе» и не «делают ли компиляторы EXE-файлы перемещаемыми или нет», а нужно говорить о линкере.


Во-вторых, линкер — он не как бог в монотеических религиях. Нет единого истинного линкера со своим линкерским догматом, всё кроме которого считалось бы ересью. Очевидно, что различных линкеров существует очень большое число. Многие из них вообще не имеют никакого отношения к генерации PE-файлов и миру Windows соответственно: те же линкеры из *nix-мира, формирующие ELF-бинарники, или вообще линкеры, используемые при сборке прошивок для микроконтроллеров.


Но даже если взять из всего множества линкеров те, что заточены на формирование исполняемых бинарников под Windows в формате PE, то и таких линкеров много. Есть линкер от Microsoft, который идёт в составе Visual C++ и DDK/WDK/Platform SDK. Который на вход принимает объектные файлы формата COFF. Есть линкер от Borland, который шёл в составе Delphi и C++ Builder, который на вход принимает объектные файлы формата OMF, кстати говоря. Есть линкер ulink от Юрия Харона, который в своих статьях постоянно восхвалял Крис Касперски. Есть, надо полагать, ряд линкеров из никс-мира, способных при желании генерировать PE-бинарники из объектных файлов формата ELF — в рамках кросс-компиляции/кросс-сборки. Есть линкеры из продуктов менее известных, из какого-нибудь Lazarus-а, например. Есть, наконец, самодельные/экспериментальные линкеры.


И все эти линкеры ведут себя по разному. Более того: «линкер от Microsoft» (или любой другой из выше упомянутых) — это не один единственный инструмент из палаты мер и весов. Это целый зоопарк версий одного и того же инструмента. И одна версия может вести себя так, а другая версия уже иначе.


Так о чём вы спорите, господа?
image


Ваш спор имел бы какой-то смысл, если бы вы конкретно говорили: «линкер от Microsoft такой-то версии по-умолчанию делает EXE-файлы неперемещаемыми». Тогда был бы предмет спора, была бы правая и неправая сторона. Можно было бы поставить эксперимент, и можно было бы пойти и ткнуть кого-то носом в документацию. Или вообще, если говорить о способах получения EXE какими-то инструментами, вообще не предполагающими использование линкера (например каким-нибудь FASM-ом) — то так и нужно ставить вопрос «когда FASM генерирует EXE сразу из ассемблерного исходника, минуя стадию формирования объектных файлов и линковку, делает ли он образ перемещаемым по умолчанию».


Ещё раз: будет ли в PE-файле присутствовать таблица релокаций, или же её не будет, а зато будет установлен флаг IMAGE_FILE_RELOCS_STRIPPED — зависит от линкера. И в этом плане способности линкеров и тактика по умолчанию может отличаться от линкера к линкеру. Microsoft-овский линкер умееть делать и так так.


Ключ /FIXED заставить его не делать таблицу релокаций. Ключ /FIXED:NO заставит его сделать образ перемещаемым. Долгое время для Microsoft-овского линкера тактикой по умолчанию было подразумевание /FIXED для EXE, и /FIXED:NO для DLL/OCX/SYS и всего остального. Оно и сейчас позиционируется так же:
/FIXED:NO is the default setting for a DLL, and /FIXED is the default setting for any other project type.
В какой-то момент был добавлен ключик /DYNAMICBASE для ASLR, который подразумевает /FIXED:NO.


В третьих, вы может быть спорите не насчёт того, формируют ли инструменты сборки EXE-файл с или без таблицы релокаций, а спорите о том, требует ли Windows от EXE-файлов наличие перемещаемости? Опять же, вопрос не имеет смысла без уточнения версии ОС. Большинству версий ОС непосредственно на наличие перемещаемости наплевать: и не только для EXE, но и для DLL. Если участок виртуального адресного пространства процесса, в которое PE-образ хочет (в соответствии со своим полем ImageBase) быть спроецирован, свободен, а сторона, инициировавшая загрузку PE-образа, не потребовала сделать загрузку по нестандартной базе — PE-образ (хоть EXE, хоть DLL) будет загружен, даже если он перемещаемый. Если PE-образ не может быть загружен туда, куда он хочет быть загруженным, а должен быть загружен по непривычной для него базе — либо потому что «родному» базовому адресу он не может быть загружен, так как это участок виртуального АП чем-то занят, либо потому что сторона, инициировавшая загрузку, потребовала загрузить образ по указанному адресу (MapViewOfFileEx имеет такую опцию) — тогда, если он не перемещаемый, загрузка PE-файла не удастся (будь-то хоть EXE, хоть DLL).


Но если вы собираетесь свой PE-файл запускать под Win32s (это 32-битная подсистема для 16-битных Windows 3.1/3.11). Поэтому я не зря выше сказал, что нужно уточнять версию ОС. После появления технологии ASLR, очевидно, жёсткого требования, что все бинарники должны быть перемещаемыми, не появилось — это бы уничтожило обратную совместимость. Поэтому если ASLR включен, а PE-файл не предполагает перемещаемости, он будет загружен так, как если бы ASLR не действовало.


В-четвёртых, когда вы ниже говорите «компиляторы (не) генерирует релокации» и спорите на эту тему, это не просто маленькая оговорка, которую можно оправдать фразой «да ладно, все же прекрасно поняли, что мы имеем в виду линкеры, а не компиляторы». Это пускание рассуждений по ложному пути, потому что при желании к этим словам можно прицепиться.


Потому что на самом деле в рамках PE\COFF существует два разных типов сущностей, чуть-чуть близких друг к другу по назначению, но всё-таки сильно разных — и обе называются релоками/релокациями.


  • Есть концепция релоков в рамках COFF-файлов (объектных файлов, поступающих на вход линкеру).
  • Есть концепция релоков в рамках PE-файлов (исполняемых файлов, выходящих как продукт работы линкера).

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


Первое — это relocations в COFF-файлах (объектных файлах, OBJ-файлах). Этот тип информации не имеет никакого отношения к загрузке исполняемых файлов по нестандартному адресу. Как и PE-файл (который являет собой химеру из MZ- и COFF форматов), COFF-файл имеет внутри себя таблицу секций и сами секции. Это почти такие же секции, как в испоняемом PE-файла (как минимум таблица секций в COFF имеет абсолютно тот же формат/структуру записей — IMAGE_SECTION_HEADER), но это как правило большое количество довольно мелки секций, которые на этапе линковке будут перегруппированы, часть секций будет отброшено, а оставшиеся одноимённые секции будут склеены.


Так вот у каждой секции COFF-файла есть собственная таблица релокаций (на неё и её размер указывают поля PointerToRelocations и NumberOfRelocations структуры IMAGE_SECTION_HEADER). Записями/элементами этой таблицы являются тройки вида {адрес_в_рамках_секции; тип_релокации; индекс_элемента_таблицы_символов} — и эта таблица является ни чем иным, как таблицей ссылок на внешние сущности/символы, с той лишь оговоркой, что ссылки вполне себе могут быть и не на внешние символы. Это ключевая структура для осуществления процесса линковки: именно пользуясь таблицей релокаций (точнее таблицами релокаций — у каждой секции COFF-файла она собственная) и таблицей символов линкер занимается тем, что во всех местах, где в коде/данных результирующего файла должна быть ссылка (указатель, адрес, относительное смещение) на какую-то сущность из другого COFF-файла (или даже из того же самого), он правильным образом проставляет эти указатели/адреса/относительные смещения.


В этом ключе каждая «релокация» описывается структурой IMAGE_RELOCATION размером 10 байт (не кратно 4, да):


//
// Relocation format.
//

typedef struct _IMAGE_RELOCATION {
    union {
        DWORD   VirtualAddress;
        DWORD   RelocCount;             // Set to the real count when IMAGE_SCN_LNK_NRELOC_OVFL is set
    };
    DWORD   SymbolTableIndex;
    WORD    Type;
} IMAGE_RELOCATION;
typedef IMAGE_RELOCATION UNALIGNED *PIMAGE_RELOCATION;

Первое поле содержит смещение места (в рамках COFF-секции) от начала COFF-секции. Второе поле содержит ссылку (в виде индекса) на элемент таблицы COFF-символов, описывающий сущность, на которую в данном месте секции после окончания линковки должна оказаться корректная ссылка того или иного вида. Третье поле содержит тип релокаций — по скольку «ссылки», вшитые в код или данные, могут быть разного типа. На ум приходит, что как минимум это могут быть абсолютные и относительные адреса (то есть уже два типа ссылок), в реальности же форматом COFF предусмотрено гораздо боле вариантов, причём для каждой процессорной архитектуры (I386, MIPS, ALPHA, PowerPC, ARM, IA64) набор типов свой.


Показать типы COFF-релокаций

//
// I386 relocation types.
//
#define IMAGE_REL_I386_ABSOLUTE         0x0000  // Reference is absolute, no relocation is necessary
#define IMAGE_REL_I386_DIR16            0x0001  // Direct 16-bit reference to the symbols virtual address
#define IMAGE_REL_I386_REL16            0x0002  // PC-relative 16-bit reference to the symbols virtual address
#define IMAGE_REL_I386_DIR32            0x0006  // Direct 32-bit reference to the symbols virtual address
#define IMAGE_REL_I386_DIR32NB          0x0007  // Direct 32-bit reference to the symbols virtual address, base not included
#define IMAGE_REL_I386_SEG12            0x0009  // Direct 16-bit reference to the segment-selector bits of a 32-bit virtual address
#define IMAGE_REL_I386_SECTION          0x000A
#define IMAGE_REL_I386_SECREL           0x000B
#define IMAGE_REL_I386_REL32            0x0014  // PC-relative 32-bit reference to the symbols virtual address

//
// MIPS relocation types.
//

#define IMAGE_REL_MIPS_ABSOLUTE         0x0000  // Reference is absolute, no relocation is necessary
#define IMAGE_REL_MIPS_REFHALF          0x0001
#define IMAGE_REL_MIPS_REFWORD          0x0002
#define IMAGE_REL_MIPS_JMPADDR          0x0003
#define IMAGE_REL_MIPS_REFHI            0x0004
#define IMAGE_REL_MIPS_REFLO            0x0005
#define IMAGE_REL_MIPS_GPREL            0x0006
#define IMAGE_REL_MIPS_LITERAL          0x0007
#define IMAGE_REL_MIPS_SECTION          0x000A
#define IMAGE_REL_MIPS_SECREL           0x000B
#define IMAGE_REL_MIPS_SECRELLO         0x000C  // Low 16-bit section relative referemce (used for >32k TLS)
#define IMAGE_REL_MIPS_SECRELHI         0x000D  // High 16-bit section relative reference (used for >32k TLS)
#define IMAGE_REL_MIPS_JMPADDR16        0x0010
#define IMAGE_REL_MIPS_REFWORDNB        0x0022
#define IMAGE_REL_MIPS_PAIR             0x0025

//
// Alpha Relocation types.
//

#define IMAGE_REL_ALPHA_ABSOLUTE        0x0000
#define IMAGE_REL_ALPHA_REFLONG         0x0001
#define IMAGE_REL_ALPHA_REFQUAD         0x0002
#define IMAGE_REL_ALPHA_GPREL32         0x0003
#define IMAGE_REL_ALPHA_LITERAL         0x0004
#define IMAGE_REL_ALPHA_LITUSE          0x0005
#define IMAGE_REL_ALPHA_GPDISP          0x0006
#define IMAGE_REL_ALPHA_BRADDR          0x0007
#define IMAGE_REL_ALPHA_HINT            0x0008
#define IMAGE_REL_ALPHA_INLINE_REFLONG  0x0009
#define IMAGE_REL_ALPHA_REFHI           0x000A
#define IMAGE_REL_ALPHA_REFLO           0x000B
#define IMAGE_REL_ALPHA_PAIR            0x000C
#define IMAGE_REL_ALPHA_MATCH           0x000D
#define IMAGE_REL_ALPHA_SECTION         0x000E
#define IMAGE_REL_ALPHA_SECREL          0x000F
#define IMAGE_REL_ALPHA_REFLONGNB       0x0010
#define IMAGE_REL_ALPHA_SECRELLO        0x0011  // Low 16-bit section relative reference
#define IMAGE_REL_ALPHA_SECRELHI        0x0012  // High 16-bit section relative reference
#define IMAGE_REL_ALPHA_REFQ3           0x0013  // High 16 bits of 48 bit reference
#define IMAGE_REL_ALPHA_REFQ2           0x0014  // Middle 16 bits of 48 bit reference
#define IMAGE_REL_ALPHA_REFQ1           0x0015  // Low 16 bits of 48 bit reference
#define IMAGE_REL_ALPHA_GPRELLO         0x0016  // Low 16-bit GP relative reference
#define IMAGE_REL_ALPHA_GPRELHI         0x0017  // High 16-bit GP relative reference

//
// IBM PowerPC relocation types.
//

#define IMAGE_REL_PPC_ABSOLUTE          0x0000  // NOP
#define IMAGE_REL_PPC_ADDR64            0x0001  // 64-bit address
#define IMAGE_REL_PPC_ADDR32            0x0002  // 32-bit address
#define IMAGE_REL_PPC_ADDR24            0x0003  // 26-bit address, shifted left 2 (branch absolute)
#define IMAGE_REL_PPC_ADDR16            0x0004  // 16-bit address
#define IMAGE_REL_PPC_ADDR14            0x0005  // 16-bit address, shifted left 2 (load doubleword)
#define IMAGE_REL_PPC_REL24             0x0006  // 26-bit PC-relative offset, shifted left 2 (branch relative)
#define IMAGE_REL_PPC_REL14             0x0007  // 16-bit PC-relative offset, shifted left 2 (br cond relative)
#define IMAGE_REL_PPC_TOCREL16          0x0008  // 16-bit offset from TOC base
#define IMAGE_REL_PPC_TOCREL14          0x0009  // 16-bit offset from TOC base, shifted left 2 (load doubleword)

#define IMAGE_REL_PPC_ADDR32NB          0x000A  // 32-bit addr w/o image base
#define IMAGE_REL_PPC_SECREL            0x000B  // va of containing section (as in an image sectionhdr)
#define IMAGE_REL_PPC_SECTION           0x000C  // sectionheader number
#define IMAGE_REL_PPC_IFGLUE            0x000D  // substitute TOC restore instruction iff symbol is glue code
#define IMAGE_REL_PPC_IMGLUE            0x000E  // symbol is glue code; virtual address is TOC restore instruction
#define IMAGE_REL_PPC_SECREL16          0x000F  // va of containing section (limited to 16 bits)
#define IMAGE_REL_PPC_REFHI             0x0010
#define IMAGE_REL_PPC_REFLO             0x0011
#define IMAGE_REL_PPC_PAIR              0x0012
#define IMAGE_REL_PPC_SECRELLO          0x0013  // Low 16-bit section relative reference (used for >32k TLS)
#define IMAGE_REL_PPC_SECRELHI          0x0014  // High 16-bit section relative reference (used for >32k TLS)
#define IMAGE_REL_PPC_GPREL             0x0015

#define IMAGE_REL_PPC_TYPEMASK          0x00FF  // mask to isolate above values in IMAGE_RELOCATION.Type

// Flag bits in IMAGE_RELOCATION.TYPE

#define IMAGE_REL_PPC_NEG               0x0100  // subtract reloc value rather than adding it
#define IMAGE_REL_PPC_BRTAKEN           0x0200  // fix branch prediction bit to predict branch taken
#define IMAGE_REL_PPC_BRNTAKEN          0x0400  // fix branch prediction bit to predict branch not taken
#define IMAGE_REL_PPC_TOCDEFN           0x0800  // toc slot defined in file (or, data in toc)

//
// Hitachi SH3 relocation types.
//
#define IMAGE_REL_SH3_ABSOLUTE          0x0000  // No relocation
#define IMAGE_REL_SH3_DIRECT16          0x0001  // 16 bit direct
#define IMAGE_REL_SH3_DIRECT32          0x0002  // 32 bit direct
#define IMAGE_REL_SH3_DIRECT8           0x0003  // 8 bit direct, -128..255
#define IMAGE_REL_SH3_DIRECT8_WORD      0x0004  // 8 bit direct .W (0 ext.)
#define IMAGE_REL_SH3_DIRECT8_LONG      0x0005  // 8 bit direct .L (0 ext.)
#define IMAGE_REL_SH3_DIRECT4           0x0006  // 4 bit direct (0 ext.)
#define IMAGE_REL_SH3_DIRECT4_WORD      0x0007  // 4 bit direct .W (0 ext.)
#define IMAGE_REL_SH3_DIRECT4_LONG      0x0008  // 4 bit direct .L (0 ext.)
#define IMAGE_REL_SH3_PCREL8_WORD       0x0009  // 8 bit PC relative .W
#define IMAGE_REL_SH3_PCREL8_LONG       0x000A  // 8 bit PC relative .L
#define IMAGE_REL_SH3_PCREL12_WORD      0x000B  // 12 LSB PC relative .W
#define IMAGE_REL_SH3_STARTOF_SECTION   0x000C  // Start of EXE section
#define IMAGE_REL_SH3_SIZEOF_SECTION    0x000D  // Size of EXE section
#define IMAGE_REL_SH3_SECTION           0x000E  // Section table index
#define IMAGE_REL_SH3_SECREL            0x000F  // Offset within section
#define IMAGE_REL_SH3_DIRECT32_NB       0x0010  // 32 bit direct not based

#define IMAGE_REL_ARM_ABSOLUTE          0x0000  // No relocation required
#define IMAGE_REL_ARM_ADDR32            0x0001  // 32 bit address
#define IMAGE_REL_ARM_ADDR32NB          0x0002  // 32 bit address w/o image base
#define IMAGE_REL_ARM_BRANCH24          0x0003  // 24 bit offset << 2 & sign ext.
#define IMAGE_REL_ARM_BRANCH11          0x0004  // Thumb: 2 11 bit offsets
#define IMAGE_REL_ARM_SECTION           0x000E  // Section table index
#define IMAGE_REL_ARM_SECREL            0x000F  // Offset within section

//
// IA64 relocation types.
//

#define IMAGE_REL_IA64_ABSOLUTE         0x0000
#define IMAGE_REL_IA64_IMM14            0x0001
#define IMAGE_REL_IA64_IMM22            0x0002
#define IMAGE_REL_IA64_IMM64            0x0003
#define IMAGE_REL_IA64_DIR32            0x0004
#define IMAGE_REL_IA64_DIR64            0x0005
#define IMAGE_REL_IA64_PCREL21B         0x0006
#define IMAGE_REL_IA64_PCREL21M         0x0007
#define IMAGE_REL_IA64_PCREL21F         0x0008
#define IMAGE_REL_IA64_GPREL22          0x0009
#define IMAGE_REL_IA64_LTOFF22          0x000A
#define IMAGE_REL_IA64_SECTION          0x000B
#define IMAGE_REL_IA64_SECREL22         0x000C
#define IMAGE_REL_IA64_SECREL64I        0x000D
#define IMAGE_REL_IA64_SECREL32         0x000E
#define IMAGE_REL_IA64_LTOFF64          0x000F
#define IMAGE_REL_IA64_DIR32NB          0x0010
#define IMAGE_REL_IA64_RESERVED_11      0x0011
#define IMAGE_REL_IA64_RESERVED_12      0x0012
#define IMAGE_REL_IA64_RESERVED_13      0x0013
#define IMAGE_REL_IA64_RESERVED_14      0x0014
#define IMAGE_REL_IA64_RESERVED_15      0x0015
#define IMAGE_REL_IA64_RESERVED_16      0x0016
#define IMAGE_REL_IA64_ADDEND           0x001F

Этот тип релоков формируется компилятором и формируется ВСЕГДА. Поэтому постановка вопросов «формирует ли компилятор релоки» не такой уж невинный, и откупиться фразой «ой, да ладно, мы под компилятором имели в виду совокупность из компилятора и линкера вообще и линкер в частности» не получится.


Этот тип информации о релокациях используется при линковке (он является абсолютно ключевым для процесса линковки), но дальше процесса линковки он не идёт. В сформированном PE-файле в таблице секций поля PointerToRelocations и NumberOfRelocations всегда будут занулены — в PE-файла нет ни места таблицам релокаций, ни смысла для их существования.


Тем не менее, релокации этого рода влияют на формирование релокаций второго года.




Второй тип информации, который тоже называется релокациями — это информация совершенно иного рода и формата. Если первый тип присутствует только в объектных файлах, и никогда не присутствует в исполняемых файлах, то второй тип, наоборот, может присутствовать в исполняемых файлах, но никогда не присутствует в объектных файлах.


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


Если в COFF-файлах таблиц релокаций много (у каждой секции своя собственная — на неё указывается IMAGE_SECTION_HEADER::PointerToRelocations ), то в PE-файле таблица релокаций, если и есть, то одна — и указывает на неё директория релокаций (OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]).


Если в COFF-файлах таблица релокаций была гомогенной таблицей 10-байтовых записей с тремя полями (троек: {смещение; индекс_символа; тип_релока}), то в PE-файлах таблица релокаций представляет собой блочный список записей вида {тип_релока; смещение}: каждый блок описывает область образа размером 4 Кб и содержит переменное число двухбайтовых (а не 10-байтовых, как в COFF) структур вида {тип_релока; смещение} — смещение в данном случае отсчитывается от начала 4К-страницы, описываемой данным блоком, а в качестве типов релокаций используется совершенно другой (по сравнению с релокациями в COFF) набор констант:


Показать набор типов PE-релокаций
//
// Based relocation types.
//

#define IMAGE_REL_BASED_ABSOLUTE              0
#define IMAGE_REL_BASED_HIGH                  1
#define IMAGE_REL_BASED_LOW                   2
#define IMAGE_REL_BASED_HIGHLOW               3
#define IMAGE_REL_BASED_HIGHADJ               4
#define IMAGE_REL_BASED_MIPS_JMPADDR          5
#define IMAGE_REL_BASED_SECTION               6
#define IMAGE_REL_BASED_REL32                 7

#define IMAGE_REL_BASED_MIPS_JMPADDR16        9
#define IMAGE_REL_BASED_IA64_IMM64            9
#define IMAGE_REL_BASED_DIR64                 10
#define IMAGE_REL_BASED_HIGH3ADJ              11

И наконец третий тип информации, связанный с релокациями второго типа, который тоже формируется линкером и тоже на основе релокаций первого типа из COFF-файлов: это таблцица FIXUP-ов, которая попадает в debug-директорию PE-файла или в отдельный DBG-файл, если отладочная информация из PE-файла извлекается+изымается. В то время как в PE-формате есть таблица директорий, и одна из директорий типа IMAGE_DIRECTORY_ENTRY_DEBUG описывает блок отладочной информации, внутри отладочной информации есть своя таблица директорий — таблица отладочных директорий. В этой таблице отладочных директорий может присутствовать директория IMAGE_DEBUG_TYPE_FIXUP, описывающая таблицу фиксапов.


Фиксапы — это тройки вида {тип; rva_ссылки; rva_места_куда_ссылаются}. Эта таблица перечисляет все места в готовом исполняемом бинарнике, где применена абсолютная адресация. Как не трудно догадаться, она сформирована на основе COFF-релокаций из COFF-файлов (но не только на её основе!), подобно тому, как таблица base relocations в PE-файле сформирована линкером на основе COFF-релокаций


Она похожа таблицу COFF-релокаций в том плане, что это тоже гомогенный набор структур с тремя полями. Она похожа на таблицу base-релокаций PE-файла тем, что как и таблица base-релокаций, fixup-таблица одна на весь образ (а таблица COFF-релокаций у каждой секции своя). В отличие от COFF-релокаций, хоть и тут и там мы имеем набор структур с тремя полями, в fixup-таблице нет никаких ссылок на таблицу символов (хотя таблица символов является частью отладочной информации и таблица fixup-ов тоже является частью отладочной информации). В отличие от таблицы base-релокаций в таблице fixup-ов перечислены не только места, где применена абсолютная адресация и которые требуют корректировке при загрузке не по предпочтительной базе, но и места, где применена RVA-адресация (а она применена в таблицах импорта/экспорта/ресурсов). Места с RVA-адресацией не перечислены в base-релокациях PE-файла: при загрузке по нестандартной базе такие места корректировать не нужны, но таблица fixup-ов в составе отладочной информации решает немного другую задачу: обеспечение хот-патч-абильности образа, судя по всему. В отличие от таблицы COFF-релокаций, в таблице fixup-ов не перечислены места, где используется относительная адресация (call/jmp/jcc-инструкции). Тем не менее, как и таблица COFF-релокаций, таблица fixup-ов использует тот же набор констант для поля «тип».

Есть рассекреченные в рамках суда по делу Iowa vs. microsoft (или же слитые?) внутрикорпоративные переписки с участием Билла Гейтса и тимлидов проектов.


Читая, понимаешь, что они там усердно бились ради увеличения скорости работы/загрузки на каждую долю секунду. 1997 год. Как сейчас — большой вопрос.

Тем не менее, это более достойный и правильный путь противостоять тому, что творится с современной Windows, чем просто кричать «переходите на ллинукс».

Пишите на коленке утилиту, которая вычисляет ключ шифрования, который путём xor-ирования сохранённых данных из /dev/random «расшифровывает» их в текст конституции, например.

Когда я вижу или слышу «куар», я сатанею от раздражения. Господа хорошие. Есть латинская транскрипция букв латинского алфавита. Её вы слышали в школе на уроках геометрии и химии. Треугольник а—бэ—цэ, цэ-два—аш-пять—о-аш.


Есть английская традиции транслитерации: си-ди диск, ар-джи-би.


Я что-то никогда раньше не слышал про «треугольник «эй-бэ-цэ», про «цэ-ди диски или «си-дэ диски»», про «си два эйч пять оу аш». Вы либо используете одну форму транскрипции, и тогда у вас «ку эр», либо другую, и тогда у вас «кью ар».


У вас либо цэ-пэ-у, либо си-пи-ю. Так какого чёрта возник и в головах людей закрепился этот уродец «куар»?

Я вас умоляю. Дельфины это совершенно безобидная история. Тут у нас всем городом активно собирают 15 000 долларов на то, чтобы свозить ребёнка в Грузию, где ему должны вылечить аутизм путём пересадки клеток из бедра в головной мозг. Так прямо и написано в посте, который все без раздумий репостят, чтобы поскорее собрать всю необходимую сумму.

Эти люди, кажется, готовы собирать деньги на лечение аутизма с помощью гвоздей, выдержанных в моче африканского осла — плевать что ноль доказательств не только эффективности, но и безопасности. Зато на предложение вакцинировать детей они вас будут буквально порвут словно разъярённый зверь. Как вы смеете непроверенную вакцину... деткам... и вообще билгейц чипирует!

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

А можно выдержку из стандарта, где зафиксирована легитимность такого вывода?

Правило же должно звучать не как «не вызывать виртуальные функции», а «вызывая виртуальные функции, не мечтать о вызове непонятно чего, а отдавать себе отчёт, что может быть вызвано, а что нет».

Можете сами попробовать. Только для чистоты эксперимента без фреймворков, голый WinAPI.
Я и пишу на голом WinAPI на C/C++ без всяких фреймворков.
То, что вы описываете, для меня выглядит как нонсенс. Не наблюдал я такого в десятках и сотнях случаях, когда подобные циклы возникали.

Sleep(1) «помог бы» (по вашим словам), но Sleep(1) сделает так, что то, что мега-фукнция вычислила бы за секунду, она будет вычислять за полторы минуты.


Единственное вразумительное объяснение, которое я могу дать вашим наблюдением вот такое: у вас на машине, где вы это наблюдали, была катастрофическая ситуация с системой охлаждения. Высохшая/неправильно нанесённая термопаста, например, или что-то в таком духе.


Как только вы запускали поток с бесконечным циклом, он, в норме не страшный для процессора, вызывал его критический разогрев до точки, где начинал действовать throttling для предотвращения перегрева. Естественно, что throttling вызывал деградацию всей системы.


В норме того, что вы описываете, не происходит.

Речь шла совершенно о другом.
Недавно тут была статья «Как понять, чо вы пишите не на C++, а на Си с классами».


Так вот, если вы пореверсите продукты MS, написанные на C++, да хоть тот же обсуждаемый здесь VB6, то откроете для себя, что внутри MS для своих же продуктов С++ используется как «Си с классами».


Они не используют STL или сишный механизм исключений внутри своих продуктов. Не на уровне API ОС, не между разными DLL-библиотеками, а внутри одной программы/библиотеки они это обходят стороной.

Банальные утечки памяти или незакрытые handle. Один из сценариев когда goto мог создать проблемы несмотря на безопасную среду.

Ну так и файл, открытый родными средствами средствами VB, не будет закрыт при выходе из процедеры. И оконо, показанное из процедуры, тоже не будет закрыто.


А если обернуть в класс хоть получение хендла через WinAPI, хоть открытие файла родными средствами — тогда экземпляр класса будет правильным образом уничтожен (если других ссылок нет).


В общем, деланье чего-то через WinAPI тут приведено зря, потому что в связанных с этим проблемах оно ничем не отличается от октрытия файлов родными средствами.


Ну и к этому относится и использование "хаков" вроде прямых вызовов управляющих функций вирт. машины чтобы что-то там оптимизировать, все это один и тот же сценарий проблем с goto по сути.

Что за управляющие вызов виртуальной машины? Имплементации P-кодных инструкций не экспортируются — вызвать их нельзя. Да и функциями эти имплементации не являются.


Из управляющих можно назвать разве что ProcCallEngline/ProcMethEngline — эдакие гейты между native-кодом и P-кодом — позволяют вызывать P-код из native-среды.


Но не представляю, каким образом их использование совместно с goto может нести какие-то проблемы.

Кто говорил о кросс-процессности?

"нтерпретация байт-кода" для режима P-CODE, потому что в таком режиме библиотека машины все равно интерпретирует его по ходу выполнения


Тогда скомпилированные Java-программы работают путём интерпретации Java-байкода, дотнетовские программы работают путём интерпретации MSIL-байткода. Да и процессоры интерпретируют машинный код.


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

Можно было бы говорить «в отличие от режима компиляции в Native», если бы суть обработки P-кодных инструкций заключалась бы в том, что виртуальная машина P-код переписывала бы в Native-код (генерируя для каждой пи-кодной инструкции дюжину машинных), а потом отдавала бы сгенерированный Native-код на выполнению процессору.


Но она же не так делает. В том большом комменте описано, как она это делает.
Коротко и упрощённо говоря: для каждой P-кодной инструкции есть свой хендлер, опкод P-кодной инструкции является индексом в таблице указателей на соответствующие обработчики.


Приведённое выше академическое определение понятия «интерпретор» настаивает на том, что входными данными для него является исходный текст программы, а обработка происходит построчно.


В данном случае нет ни построчности, ни исходного текста в роли входной последовательности для виртуальной машины.


В теории можно было бы вообще создать архитектуру процессора, которая бы машинно умела исполнять VB-шный P-код. Ну какая же это интерпретация?

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

Да что же в этом дорогого? Это не было дорогим удовольствием 25 лет назад, а уж тем более сейчас.


Это не дороже, чем две лишних локальных переменных.

Я смотрю, пока я писал, вы перекроили свой коммент.


или например нативные вызовы winapi которые после перехода останутся незакрытыми.

Как понять «незакрытый вызов WinAPI»? Вызовы WinAPI были не хаком, а штатной возможностью, точнее даже были осуществимы через два разных механизма: через Declare Sub/Function и через TLB.


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

Процедуры генерировались в чистейший машинный код.


Давайте разделять понятия: виртуальная машина — это то, что, принимая на вход цепочку команд, каким-то образом выполняет их, предпринимая какие-то действия по поводу каждой команды и меняя своё внутреннее состояние.


Реализация виртуальной машины VB-шного P-кода жила в MSVBVMxx.DLL (что и давало название этой библиотеки), но помимо виртуальной машины значительную часть библиотеки составляло то, что можно назвать «стандартной библиотекой» по аналогии с языками Си/Си++. Т.е. это просто различные «встроенные функции», заявленные как «часть языка», а также служебные функции (в случае C++ примером такой функции может стать _purecall). Это обычные процедуры в виде машинного кода, они к виртуальной машине отношения не имеют.


Так вот, то, что генерировалось в режиме создания Native-кодных исполняемых файлов, представляло собой обычный машинный код x86 и не с вкраплениями P-кодных кусочков, а с вкраплениями вызовов служебны функций для выполнения тех задач, которые приходится очень часто решать и которые раздули бы объём кода, если бы эти задачи инлайнились.


Например — копирование структур. Копирование структур можно было бы «инлайнить», сгенерировав код для копирования каждого члена структуры (в общем случае структуру нельзя копировать банальными memcpy, ведь там могут быть указатели на строки или COM-интерфейсы, массивы — и копировать это надо по умному). Вместо генерировалась одна единственная call-инструкция, вызывающая функцию рантайма, которая брала на себя все заботы по правильному копированию структуры. Ей передавался некий дескриптор структуры, предопределявший правила копирования.


Или же после каждого вызова метода COM-интерфейса (читай «метода вызова любого VB-объекта или любого внешнего COM-объекта»), который мог выбросить ошибку путём возврата HRESULT, ставился вызов __vbaHresultCheck, который брал на себя всю работу по «транслированию» кода сбоя (HRESULT) в VB-ошибку (VB использовал SEH-исключения для работы своего механизма ошибок).

Ну а в том комментарии кстати кажется упущен еще 1 момент для полноты, а именно опция сбора итоговых ехешников не только в байткод (который был медленнее + на раз-два декомпилировался без защиты), но и в натив со своими особенностями.

Плохо читали — ничего там не упущено. Оно скрыто под спойлером «Немного подробностей о том, как происходит компиляция в Native-код», внутри которого есть свой под-спойлер «Интересный вопрос касательно IL на входе бэкенда C2».


Ну и поскольку она при работе интерпретирует байткод (который так вот круто сгенерирован IDE еще в процессе написания), то это все же интерпретатор.

В Википедии приводится вот такое определения термина «интерпретация» в контексте ЯП:
Интерпрета́ция — построчный анализ, обработка и выполнение исходного кода программы или запроса, в отличие от компиляции, где весь текст программы, перед запуском анализируется и транслируется в машинный или байт-код без её выполнения[4][5][6]


Источниками такого определения назван не Вася Пупкин, а литература, написанная авторами, заслуживающими уважения:


Источники
  • Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 1991. — 543 с. — 50 000 экз. — ISBN 5-279-00367-0.
  • Борковский А. Б. Англо-русский словарь по программированию и информатике (с толкованиями). — М.: Русский язык, 1990. — 335 с. — 50 050 (доп.) экз. — ISBN 5-200-01169-3.
  • Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — М.: Машиностроение, 1990. — 560 с. — 70 000 (доп.) экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания).

Так что в соответствтии с общеприянтым определением это не интерпретация, а компиляция и исполнение байт-кода виртуальной машины. Видимо многим просто нелегко принять факт, что презираемый очень многими инструмент с репутацией «для чайников в программирование» имел крутейший и непревзойдённый механизм патчинга скомпилированного байт-кода по живому. «Ну не может же такого быть, что оно вот так легко на лету перекомпилирует и перекраивает скомпилированную процедуру, в данный момент исполнявшуюся — значит там под капотом банальная интерпретация» — так видимо рассуждало большинство.

Windows: Structured Exception Handling. Очень вкусно именно в этом плане, но
1) Дороговато

SEH-фрейм занимает 8 байт 2 sizeof(void) на стеке — разве это дорого? В обходе односвязного списка и вызове обработчика по указанному адресу тоже вряд ли что-то дорогое можно узреть. Скорее всего по сравнению с тем, что обработчики будут делать, overhead от обхода цепочки вызовов будет незначительным.


SEH был дорогим по совсем другой причине. Вызов RaiseException приводит к переходу в режим ядра, а это довольно медлительная вещь. В этом смысле SEH-исключения не могли стать альтернативой для кодов возрата статуса и должны были использоваться только для реально исключительны ситуаций. Попытка переделать цикл, проходящийся по миллиону элементов и вызывающий для каждого элемента какую-нибудь функцию, которая с вероятностью 30...50% может сбойнуть на выкидывания исключений очень значительно замедлила бы работу такого цикла, потому что 500 000 раз выкидывать исключение — это 500 переходов в режим ядра и обратно, что на порядок или несколько порядков медленнее, чем если бы вызываемая функция просто возвращала код ошибки, при условии что в в вызываемой функции только какая-нибудь арифметика и манипуляция данными и нет системных вызовов.

Информация

В рейтинге
1 880-й
Откуда
Петропавловск, Северо-Казахстанская обл., Казахстан
Зарегистрирован
Активность

Специализация

Software Developer, Embedded Software Engineer
Pure C
Assembler
X86 asm
Win32 API
Visual Basic
MySQL
Git
OOP
Electronics Development
Reverse development