PDA

Просмотр полной версии : [C] Pawn 3.2 (+)



Daniel_Cortez
28.07.2017, 21:56
Основная цель данного проекта - создание улучшенной версии Pawn 3.2 с исправлениями и нововведениями как для компилятора, так и для библиотеки времени выполнения.

Кодовая база проекта базируется на форке Zeex'а (), направленном на исправление багов и улучшение компилятора для SA-MP.
Данный проект, напротив, сфокусирован на улучшении интерпретатора и модулей расширения AMX - часть изменений перенесена из более поздних версий Pawn (такие как, например, добавление функций frename(), fstat() и strcopy()), другая часть является оригинальной разработкой (верификация байткода при загрузке скрипта, модернизированное ядро интерпретатора).

Список изменений:
Бэкпортированы изменения из Pawn 3.3 и дальнейших предрелизных версий, вплоть до 4.0.
Среди перенесённых изменений можно отметить добавление новых нативных функций в модулях amxfile (frename, fstat, fattrib и filecrc), amxtime (settimestamp() и cvttimestamp()) и amxstring (strcopy).
Кроме новых функций есть исправления для уже существующих - valstr (устранён уход в бесконечный цикл при больших числах), printf (исправлена неправильная обработка комбинации "%%").
Перенести изменений из 4.0 и более поздних версий не представляется возможным, т.к. это повлечёт нарушение авторских прав - начиная с 4-й версии, основная часть кода Pawn была переведена под новую лицензию.

В корневой папке репозитория добавлен проект для CMake, служащий для построения и компилятора, и интерпретатора.
Также в скрипт для построения рантайма (в папке "source/amx") добавлены опции для построения с 32/64-битными ячейками, компиляции модулей расширения в виде статический библиотек (может пригодиться для встраивания модулей в исполняемый файл пользовательского приложения, как в сервере SA-MP), и включения/исключения отдельных модулей из билда.

Добавлено новое ядро интерпретатора (amx_Exec), основанное на двух других стандартных ядрах: обычном (ANSI C) и GCC-специфичном.
Этот шаг должен значительно упростить поддержку кода - сопровождать одну версию ядра куда проще, чем две и более.
Также в новое ядро добавлены новые проверки времени выполнения, исключающие множество недочётов и уязвимостей, присутствующих в оригинальном коде.
Например, в оригинале во многих опкодах, совершающих чтение или запись в секцию данных или стек, отсутствует проверка адреса доступа, благодаря чему атакующий может читать и записывать данные за пределами памяти скрипта.
Кроме того, у всех опкодов, изменяющих состояние стека, кроме опкода STACK, отсутствует проверка значение в регистре STK (указателе вершины стека) - можно с помощью SCTRL 4 заставить STK указывать за пределы памяти скрипта и выполнять чтение/запись данных с помощью опкодов PUSH*/POP*.
В новом ядре эти и многие другие виды уязвимостей устранены. Тем не менее, для совместимости оставлена возможность использования старых ядер, для этого следует отключить опцию PAWN_USE_NEW_AMXEXEC в проекте для CMake.

Кроме того, в новом ядре изменён принцип считывания аргументов в опкодах, интерпретатор избавлен от последовательного чтения. В оригинале чтобы считать 2-й аргумент, нужно сначала прочитать 1-й, а чтобы перейти к следующему опкоду, нужно считать все аргументы текущего опкода. Теперь же интерпретатор может считывать аргументы не по порядку и при возможности вообще пропускать их. Например, в инструкциях jump и j** интерпретатор не будет считывать адрес перехода, если условие перехода не выполняется. Кроме того, интерпретатор теперь может считывать два аргумента не друг за другом, а одновременно (при условии, что операции чтения из памяти распараллеливаются на целевом процессоре).
Сам процесс считывания аргумента теперь не модифицирует значение в регистре cip, поэтому в случае возникновения ошибки интерпретатор сможет выдать адрес именно инструкции, на которой возникла ошибка, а не адрес одного из аргументов инструкции (или даже следующей инструкции, если у текущей аргументов нет), как в оригинале.

Несмотря на добавление новых проверок времени выполнения, новое ядро обладает большей производительностью, чем у стандартных ядер.
Достигнуто это благодаря упрощению кода (см. пример выше с устранением последовательности чтения) и использованию нестандартных возможностей компиляторов C/C++ для статического предсказания переходов (__assume(0) в MSVC++, а также __builtin_unreachable() и __builtin_expect() в GCC и Clang).
На моём ПК прирост производительности при построении в VS 2010 составил 23%, VS 2017 - 75%, MinGW - 90%. Я также пробовал провести измерения на своём планшете (ARMv8) - на нём разница оказалась не сильно заметна: +10% при компиляции с GCC и -15% с Clang.
Если вы хотите проверить факт прироста производительности самостоятельно, постройте два экземпляра интерпретатора, с включенной и выключенной опцией PAWN_USE_NEW_AMXEXEC соответственно, затем скомпилируйте скрипт fib_bench.p (находится в папке "examples") и запустите его на каждом из них.

Помимо проверок времени выполнения добавлена верификация байткода при загрузке скрипта (в amx_Init).

Устранена зависимость размера ячейки Pawn от размера указателя на целевой платформе.
Теперь можно использовать интерпретатор с 32-битными ячейками под управлением 64-разрядной ОС.
В оригинальной версии Pawn при такой конфигурации работа интерпретатора невозможна, т.к. физические адреса нативных функций и хендлов модулей расширения AMX хранятся в ячейках, а размера ячейки (32 бита) с большой долей вероятности может не хватить для хранения 64-битного указателя.
В данном форке если размер указателя больше размера ячейки, то под физические адреса (указатели) выделяется отдельный блок памяти, в котором они помещаются полностью.

Файловые функции теперь оперируют над ID файлов (например, 1, 2, 3, ...) вместо указателей FILE *, приведённых к типу ячейки.
Это означает, что файловые функции не обрушат целое хост-приложение из-за нечаянного использования неправильного ID файла (привет, SA-MP) (например, при вызове функции fwrite() после закрытия файла с помощью fclose()), т.к. ID легко проверить на валидность.

В инструкциях доступа к секциям данных, в которых адрес доступа указывается в виде константы (LOAD.pri/alt, LREF.pri/alt, STOR.pri/alt и т.д.), реализована релокация адресов.
Теперь при загрузке скрипта виртуальные адреса в вышеупомянутых инструкциях заменяются физическими адресами для прямого доступа к данным, что должно повысить общую производительность. Релокация осуществляется только при использовании нового ядра интерпретатора (см. выше) и только если ячейка Pawn способна вместить физический адрес.



Исходный код: [url]https://github.com/Daniel-Cortez/pawn-3.2-plus (]Pawn 3.10[/url)
Статью подготовил: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)


Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Запрещается копирование содержимого данной статьи на других ресурсах без разрешения автора.

Daniel_Cortez
05.08.2017, 17:25
Обновление от 05.08.2017:
Удалена лишняя проверка времени выполнения в обработчике инструкции PUSH (адрес для чтения данных является постоянным и проверяется при загрузке скрипта).
В инструкциях доступа к секциям данных, в которых адрес доступа указывается в виде константы (LOAD.pri/alt, LREF.pri/alt, STOR.pri/alt и т.д.), реализована релокация адресов. Теперь при загрузке скрипта виртуальные адреса в вышеупомянутых инструкциях заменяются физическими адресами для прямого доступа к данным, что должно повысить общую производительность.

OLDBOY
08.08.2017, 19:10
А не подскажете как пользоваться вашим компилятором. Я что-то понять не могу, перешёл на github что мне надо скачать оттуда, как заменить стандартный компилятор на ваШ?

Daniel_Cortez
09.08.2017, 11:05
А не подскажете как пользоваться вашим компилятором. Я что-то понять не могу, перешёл на github что мне надо скачать оттуда, как заменить стандартный компилятор на ваШ?
*facepalm*

Данный проект, напротив, сфокусирован на улучшении интерпретатора и модулей расширения AMX
TL;DR: Это не "компилятор".

Daniel_Cortez
27.10.2017, 19:57
Обновление от 27.10.2017:
Кодовая база синхронизирована с компилятором от Zeex версии 3.10.3.
Убрана генерация архива с компилятором с помощью CPack, т.к. данный форк сфокусирован на развитии не только компилятора, но и рантайма.
Серия правок в реализации оператора emit/__emit (чистка кода, исправлена неправильная установка старшего бита на числах типа Float при 64-битном размере ячейки).

Daniel_Cortez
30.10.2017, 22:12
Обновление от 31.10.2017:
Кодовая база синхронизирована с форком Zeex.
Приняты изменения, направленные на улучшение скорости работы компилятора.
Исправлена проблема с исчезновением пробелов, возникавшая при переносе строк с помощью обратного слеша.


Серия изменений для оператора emit/__emit.
Исправлена неправильная генерация отрицательных чисел типа Float при размере ячейки в 64 бита (установка старшего бита работала правильно только при 32-битном размере ячейки).
Убрано излишнее копирование названия инструкции AMX (функция emit_findopcode()).
Использован чувствительный к регистру поиск имени инструкции (emit_findopcode()).


Исправлена сборка под Visual Studio 2010.

Также принята ещё одна порция изменений (https://github.com/Zeex/pawn/pull/201), направленных на повышение скорости записи файла *.amx.

Daniel_Cortez
04.03.2018, 21:13
Состояние на 04.03.2018:
В модуль amxFloat добавлена функция floatint() из Pawn 4. Также добавлена перегрузка оператора "=" для автоматического преобразования вещественных чисел в целые, которой в Pawn 4 нет.

native Float:floatint(Float:value);
native operator=(Float:oper) = floatint;

Что интересно, на самом деле аналогичная перегрузка "=" задумывалась к добавлению и в 4-й версии (судя по комментарию (https://github.com/pawn-lang/compiler/blob/6ee6a43dd7926cf6d309577e2b01aeae5f37a1d7/source/amx/amxfloat.c#L366) в коде), но по какой-то причине этого сделано не было. Кроме того, сама функция floatint() реализована в amxfloat.c, но в float.inc нет заголовка этой функции (т.е. в Pawn 4.0 она никак не используется).

В модуль amxFile добавлены функции fcopy() и fcreatedir() для копирования файлов и создания новых папок соответственно.
Поскольку изначально данные функции были реализованы в Pawn 4.0, после смены лицензии на код, для 3.2 их пришлось делать заново.
Новая реализация fcopy() не настолько проста, как оригинал в 4.0, но зато она не подвержена атаке по принципу инъекции кода (https://en.wikipedia.org/wiki/Code_injection) (в оригинале из Pawn 4 для копирования вызывается команда "cp <файл-источник> <файл-назначение>", однако имена файлов не экранируются, в результате чего в вызываемую команду можно подставить вредоносный код).

native bool: fcopy(const source[], const target[]);
native bool: fcreatedir(const name[]);


Функция fremove() наделена способностью удалять не только файлы, но и папки (логичный шаг после добавления функции для создания папок).

Исправлены баги в функции strins().
Функция была склонна к переполнению буфера, поскольку не учитывала размер массива под результирующую строку (параметр "maxlen").
В некоторых случаях функция не завершала строку нуль-символом (возможно, она полагалась на то, что массив под результирующую строку уже проинициализирован нулевыми значениями, что не совсем правильно).

Также добавлен тестовый скрипт (https://github.com/Daniel-Cortez/pawn-3.2-plus/blob/57a3e0b426b950dea85135df35ffd969adfba9eb/examples/test_strins.p) для проверки работы обновлённой реализации strins().
Интересный факт: оригинальная реализация strins() из Pawn 4.0 build 5514 (если адаптировать её к Pawn 3.2 путём замены вызовов amx_Address на amx_GetAddr), в которой также были предприняты меры по устранению переполнения буфера, всё ещё заваливает 9 из 23 тестовых кейсов из упомянутого скрипта - строки либо не завершаются нуль-символом, либо этот нуль-символ добавляется уже за пределами массива (т.е. выход за пределы буфера в 4.0 так и не устранён до конца).

Исправлен баг в функции fputchar().
Функция возвращала неправильные значения при параметре "utf8" равном "true".

Для конструкции switch реализован опциональный бинарный поиск среди значений case (активируется, когда в таблице более 30 значений, при меньшем количестве используется простой линейный поиск).


В ближайшее время планируется сделать исправления для некоторых уязвимостей из оригинала (3.2) опциональными для совместимости с кодом для SA-MP (к примеру, для таких библиотек как YSI и amx_assembly).

Daniel_Cortez
31.03.2018, 14:05
Состояние на 31.03.2018:
Бэкпортированы функции для работы с файлами конфигурации ("*.cfg", "*.ini").

native readcfg(const filename[]="", const section[]="", const key[], value[], size=sizeof value, const defvalue[]="", bool: pack=false);
native readcfgvalue(const filename[]="", const section[]="", const key[], defvalue=0);
native bool: writecfg(const filename[]="", const section[]="", const key[], const value[]);
native bool: writecfgvalue(const filename[]="", const section[]="", const key[], value);
native bool: deletecfg(const filename[]="", const section[]="", const key[]="");

Теперь файловые функции (fopen(), fclose(), fwrite(), fread(), ...) оперируют идентификаторами файлов (1, 2, ...), а не приводят указатель на файловый объект (FILE *) к типу ячейки Pawn и наоборот.
Недостаток предыдущего подхода с приведением типов был в том, что указатель не на всех платформах и конфигурациях может вместиться в ячейку (например, если собрать интерпретатор с 32-битными ячейками под 64-разрядную архитектуру, часть указателя при попытке уместить его в ячейку окажется "урезанной"). Кроме того, при прежнем подходе на некоторых платформах (в т.ч. и на Windows) файловые функции из стандартной библиотеки C не проверяли указатель на файловый объект, из-за чего было возможно падение интерпретатора при передаче неправильного указателя.
Таким образом, в рантайме устранена последняя зависимость размера ячейки от размера указателя.

Проведён рефакторинг кода нативных функций, теперь неудачное резервирование пространства в стеке с помощью функции alloca() и неправильные виртуальные адреса, передаваемые в нативные функции (посредством #emit или каким-либо другим способом) воспринимаются.
В стоковом Pawn 3.2 (да и Pawn 4 до сих пор) ошибки подобного типа не отлавливаются и могут привести к падению интерпретатора из-за разыменования нулевого указателя.

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

Исправлен баг в функции ispacked(): функция могла вернуть неправильное значение (0), если в первой ячейке упакованной строки самый старший бит установлен в 1 (например, с символами кириллицы).
Пример:

static const packed_str[] = !"абвг";
printf("ispacked(): %d\n", ispacked(packed_str)); // "ispacked(): 0"

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

static const str1[] = "1234567890";
static const str2[] = "abcdefghij";
#pragma unused str1
new result = strfind(str2, "5", false, -10);
printf("result: %d", result); // "result: -7"

В заголовке функции getchar() (файл console.inc) перед параметром "echo" добавлен недостающий тег "bool".
Данный недочёт был найден при сравнении заголовков функций в console.inc с комментариями в нативных функциях из amxconsole.c.
Заголовок getchar() в console.inc (перед исправлением):

native getchar(echo=true);

Комментарий (https://github.com/pawn-lang/compiler/blob/766b96bcf3ae07eee9f3553d1fbae6d070405ac3/source/amx/amxcons.c#L962) перед реализацией getchar() в amxconsole.c:

/* getchar(bool:echo=true) */

Заголовок getchar() после исправления:

native getchar(bool:echo=true);

Аналогичным образом в заголовке fstat() (amxfile.inc) добавлен недостающий квалификатор "const" перед параметром "name".
До:

native bool: fstat(name[], &size = 0, &timestamp = 0, &mode = 0, &inode = 0);

После:

native bool: fstat(const name[], &size = 0, &timestamp = 0, &mode = 0, &inode = 0);

Daniel_Cortez
09.09.2018, 22:13
Состояние на 09.09.2018:
Компилятор обновлён до версии 3.10.8.

Бэкпортированы функции urlencode() и urldecode().

native urlencode(dest[], const source[], maxlength=sizeof dest, bool:pack=false);
native urldecode(dest[], const source[], maxlength=sizeof dest, bool:pack=false);
Функции были доступны ещё в Pawn 3.3, но по неизвестной причине не были объявлены в string.inc.

Добавлен форматный спецификатор "%b" для функций printf() и strformat().

printf("%b", 23); // Вывод: "10111"

Исправлен баг с выводом cellmin в strformat(), printf() и valstr():

printf("%d", cellmin); // До исправления: "-(", после: "-2147483648"

Исправлен баг в функциях getarg() и setarg(): если индекс аргумента был отрицательным, функции всё ещё считали его правильным.

Исправлена неправильная работа функции uuencode().

Теперь при выгрузке скрипта интерпретатор удаляет не все свойства ("properties", создаются с помощью функции setproperty()), а только те, которые были созданы выгруженным скриптом.

Добавлена дополнительная валидация заголовка AMX и проверка адресов переменных и функций с атрибутом public при загрузке скрипта.

Daniel_Cortez
13.04.2019, 17:01
Состояние на 13.04.2019:
Компилятор обновлён до версии 3.10.9.

В модуле amxFloat добавлены обратные тригонометрические функции:

native Float:floatasin(Float:value, anglemode:mode=radian);
native Float:floatacos(Float:value, anglemode:mode=radian);
native Float:floatatan(Float:value, anglemode:mode=radian);
native Float:floatatan2(Float:y, Float:x, anglemode:mode=radian);

Исправлен баг в функции 'fwrite()', из-за которого в файл не записывался последний символ, если строка упакована.

Реализована защита от перекрытия кода.
С помощью инструкций перехода 'jump.pri', 'sctrl 6' или 'jrel' можно было заставить интерпретатор "перепрыгнуть" через следующий опкод, чтобы он принял за опкод один из аргументов инструкции и попытался выполнить его.
Пример:

emit jrel (cellbits / charbits); // "перепрыгнуть" через одну ячейку (опкод 'push2.c')
emit push2.c 12 1; // '12 1' ошибочно интерпретируется как 'const.alt 1'

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

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

Исправлено ложное срабатывание проверок безопасности в инструкциях 'push*', 'lref.pri/alt', 'sref.pri/alt', 'lref.s.pri/alt', 'sref.s.pri/alt'.

Исправлен неправильный вывод cellmin (-2147483648) в функциях 'printf()', 'strformat()' и 'valstr()' (проблема не проявлялась только под Windows).

Исправлено потенциальное состояние гонки в функции 'valstr()'.

Daniel_Cortez
02.01.2022, 01:12
Состояние на 02.01.2022:
Компилятор обновлён до версии 3.10.10.

Убрана неявная конверсия вещественных чисел в целые, т.к. нежелательные неявные конверсии могли вызвать потери в производительности (понятия не имею, зачем я её вообще добавил). Возможность явной конверсии с помощью функции floatint() оставлена без изменений.

Обновлён файл readme, добавлена более подробная информация по компиляции проекта и опциям в скрипте CMakeLists.txt. Список самых значимых изменений вынесен в отдельный файл changes.md.