Maxim25012

Известный
Автор темы
531
263
LJEdit – это библиотека для языка программирования Lua, написанная на Lua, которая призвана помочь с чтением и редактированием байт-кода LuaJIT. Она не требует LuaJIT для работы, но его использование рекомендуется, особенно при работе с большими скриптами. Работает с байт-кодом LuaJIT версии 2.0 и 2.1. Работа с модификациями LuaJIT, которые затрагивают байт-код, не гарантируется.

1. Использование:
Чтобы начать её использование, необходимо её загрузить:
Пример:
local ljedit = require('ljedit')
Возвращённое значение будет представлять собой таблицу, в которой будут находиться следующие функции:
  1. (ljedit.edit or ljedit)(bcdump [, do_not_convert]) - главная функция библиотеки, которая читает данный ей в виде строки байт-код и возвращает скрипт в виде объекта, структура которого будет описана ниже. Второй необязательный аргумент заставит парсер пропустить автоматическую реструктуризацию объекта.
  2. ljedit.new.script([version, big_endian, stripped, has_ffi, fr2, chunkname]) - создаёт новый пустой скрипт в виде объекта.
  3. ljedit.new.proto() - создаёт новый пустой объект прототипа.
  4. ljedit.new.bcins(op, A, B, C, D) - создаёт новую инструкцию в виде объекта. В зависимости от того, указан операнд "B" или нет, будет создана инструкция вида ABC или AD.
  5. ljedit.new.kgc(tp [, string | table | lo, hi | lo0, hi0, lo1, hi1]) - создаёт новый объект комплексной константы указанного типа.
  6. ljedit.new.knum(lo, hi | num | int) - создаёт новую числовую константу. Если даны два целых 32-битных числа, то они будут собраны в u32. Если дано одно Lua-число, которое может быть больше максимального 32-битного значения или быть вещественным, то оно будет автоматически преобразовано в u32. Если же дано одно целое число в диапазоне знакового 32-битного числа, то оно вернётся в неизменном виде. u32 – объект, представляющий собой два целых 32-битных числа, склеенных в одно 64-битное.
  7. ljedit.new.uv(id, [, name]) - создаёт новый объект верхней переменной с заданным айди и названием (название будет сохранено только в том случае, если скрипт может содержать дебаг-информацию). Задавать название необязательно.
  8. ljedit.get_instruction_info(bcins | op [, version]) - возвращает таблицу, содержащую информацию про инструкцию по типу названия и типа операндов. Первый аргумент может быть объектом инструкции либо опкодом. Второй операнд является необязательным и задаёт версию байт-кода (по умолчанию используется версия 2, которая соответствует версии 2.1, даже если скрипт версии 1 (2.0)).
2. Объекты:
В объектах, указанных ранее, используются массивы, которые могут начинаться либо с 1 (array1), либо с 0 (array0), содержащие два метода: "insert" и "remove". Для каждого из двух видов массивов используются свои методы, которые учитывают их особенности.

Объект скрипта имеет следующую структуру:
Код:
{
  header = {
    version = int (1 or 2, not 2.0 or 2.1),
    flags = {
      big_endian = boolean,
      stripped = boolean,
      has_ffi = boolean,
      fr2 = boolean
    },
    chunkname = string
  },
  protos or main = array1 or proto
}
И три метода. Для примера предположим, что переменная "script" содержит объект скрипта, в таком случае:
script:proper() - изменяет структуру объекта таким образом, что она становится сходна со структурой прототипов в памяти LuaJIT, перемещая прототипы из массива прототипов в константы других прототипов, заменяя нули, служащие заглушкой, до тех пор, пока не останется только один – главный прототип. Может потребовать значительное количество времени.
script:literal() - раскладывает прототипы в один линейный массив. Автоматически вызывается компилятором, так как он умеет работать только с линейной структурой, что в разы медленнее, но заметно только при работе с большими скриптами.
script:compile() - компилирует объект скрипта обратно в строку, содержащую байт-код, и возвращает её. Сам объект не должен изменяться.
Предупреждение: объект скрипта может жрать в 17 раз больше оперативной памяти, чем обычная строка, содержащая тот же байт-код, что нехило, однако, и виноваты в этом, без всякого сомнения, Lua-таблицы, потому что они пиздец жирные. Фиг знает, почему так происходит.

Каждый объект прототипа имеет следующую структуру:
Код:
{
  flags = {
    has_children = boolean,
    is_vararg    = boolean,
    has_ffi      = boolean,
    no_jit       = boolean,
    iloop        = boolean
  },
  numparams = int,
  framesize = int,
  sizeuv = int,
  sizekgc = int,
  sizekn = int,
  sizebc = int,
  sizedbg = int,
  firstline = nil or int,
  numline = nil or int,
  bcins = array1,
  uv = array1,
  kgc = array0,
  knum = array0,
  lineinfo = nil or string,
  uvinfo = nil or string,
  varinfo = nil or string
}
Мета-таблицу, содержащую поле "__proto", которая равна "true" и нужна для определения прототипа среди прочих таблиц и объектов; и метод "get_varname(pt, pc, slot)", который получает название локальной переменной прототипа по позиции инструкции, которая работает с ней, и номеру слота.

Каждый объект инструкции на деле представляет собой таблицу, содержащую инструкцию в виде целого числа, чтобы уменьшить потребление памяти, а также мета-таблицу, которая позволяет индексировать её так, как если бы она представляла собой обычную таблицу с полями. Структура объекта инструкции выглядит следующим образом:
Код:
{
  [0] = int,
  A = byte,
  B = byte or nil,
  C = byte or nil,
  D = short or nil,
  li = int or nil
}
Также каждая инструкция имеет метод "info", который представляет собой вышеупомянутую "ljedit.get_instruction_info". Поле li может содержать номер строки в исходном коде скрипта, которая представляется инструкцией, и заполняется автоматически, если скрипт содержит дебаг-информацию. Оно игнорируется, если скрипт не должен содержать дебаг-информации.

Каждый объект верхней переменной содержит два поля: "id" и "name", где второе является необязательным и заполняется автоматически, если скрипт содержит дебаг-информацию. Поле "name" игнорируется, если скрипт не должен содержать дебаг-информацию.

Kgc-объекты бывают разными: чистыми строками, таблицами (они, почти как обычные Lua-таблицы, но имеют мета-таблицу с полем "__tab", чтобы его можно было отличить от других объектов), прототипами или нулями, в зависимости от структуры объекта скрипта, знаковыми 64-битными числами, беззнаковыми 64-битными числами и мнимыми числами. Последние три вида представляют собой таблицы с u32-объектами на 0 и 1 (*[0], *[1]). Два u32 могут быть только у мнимых чисел, а проверить наличие знака у 64-битных чисел можно, проверяя поле "signed".

Числовые константы могут быть либо u32, либо целым числом.

u32-объекты не могут быть созданы напрямую, так как это почти бесполезно, ибо функции, создающие kgc-объекты и числовые константы, уже это делают автоматически. Они имеют следующую структуру:
Код:
{
  u32 = {
    lo = lo,
    hi = hi
  }
}
И метод "double" который преобразовывает их обратно в Lua-числа. Не работает с kgc-объектами.
 

Вложения

  • ljedit.lua
    35.7 KB · Просмотры: 2