Анализ Marshal.dump в Ruby

Чтобы проанализировать, что и как записывается с помощью метода dump модуля Marshal, я буду писать небольшой код для примера. Если вы хотите читать результаты самостоятельно и пробовать по ходу дела, выводите их в консоль или в текстовый файл, открывать который следует специализированным редактором, поддерживающим отображение служебных символов. К таким редакторам относятся Notepad++, Sublime Text и другие. Пост пишется на за один вечер и будет обновляться впоследствии.

Заголовок файла

Как я успел выяснить за время тестов, заголовки делятся на несколько видов: модуль, класс и объект, в зависимости от того, что было передано в качестве аргумента методу dump.

Заголовок модуля
Создадим пустой модуль.
/
/

Запишем результат дампа в строку:
/
/
В строке 6 я декодирую каждый символ отдельно, потому что иначе консоль вывода Sublime Text выдает ошибку. Декодирование можно опустить и сразу вывести результат, используя puts string.

Выведем результат в консоль:

Руби записывает любой дамп сразу в кодировке Unicode. Первые два символа присутствуют в любом дампе и соответствуют символам под номерами 0004 (End of transmission) и 0008 (Backspace) в кодировке Unicode. Эти два символа обозначают версию модуля Marshal - 4.8, который поддерживается Ruby начиная с версии 1.8. Они здесь для того, чтобы предотвратить чтение дампа более ранними версиями, потому что это может привести к ошибкам в чтении и интерпретации данных дампа.

Третий символ - маленькая латинская буква "m" (первая буква слова "module").
Четвертый символ будет рассмотрен ниже.

Заголовок класса
Создадим пустой класс
/
/

Запишем его результат и выведем в консоль по аналогии с выводом дампа модуля:
/
/

Результат:
Третий символ в данном случае - маленькая латинская буква "c" (первая буква слова "class").
Четвертый символ будет рассмотрен ниже.

Заголовок объекта
Добавление инициализации в класс, код которого приведен выше, не обязательно, поэтому сразу перейдем к коду вывода дампа:
/
/
Результат:

Как мы видим, есть два отличия от прошлого результата:

  • символ под номером 003А (двоеточие) в кодировке Unicode. Возможно, указывает на то, что данный дамп имеет содержимое.
  • символ NUL в конце, означающий, очевидно, пустое содержимое объекта. Символ соответствует номеру 0000 (Null) в кодировке Unicode.

Подробнее о четвертом символе
В результатах дампа неинициализированного класса и модуля в качестве четвертого символа мы получали символы SI (Shift in) и SO (Shift out). В результате дама объекта мы видели SO на пятой позиции от начала файла. Откуда взялись эти символы?

Дело в том, что название класса вносится в дамп примерно тем же способом, что и другие переменные. Символ меняется в зависимости от длины названия класса объекта. Номер символа в кодировке высчитывается по формуле: длина названия + смещение в 5 единиц. Подробнее о смещении описано в разделе "Целое число".

На этом различия в записи различных типов объектов исчерпываются. Перейдем к записи каждого типа данных в отдельности.

Целое число

Здесь и далее мы будем работать над экземпляром класса SomeClass, дополняя его для каждого типа данных отдельно. Вот такое значение
/
/
Рассмотрим дамп экземпляра этого класса:
Обратимся к таблице, чтобы понять, какие данные записались у нас в разделе содержимого (после строки  ACK: ):

  1. BEL - это длина имени переменной, включая символ "@" + некоторое смещение (о нем я расскажу далее)
  2. Символ, указывающий на то, что переменная локальна для экземпляра
  3. Имя переменной, состоящее из одного символа
  4. Буква "i", обозначающая тип переменной (первая буква от "integer")
  5. ACK - значение переменной + смещение
Подробнее о смещении
По некоторым причинам (неизвестным мне, к сожалению), запись числовых данных (длины имени переменной, значения переменной) происходит со смещением, равным пяти.

Таким образом, переменная (например, abcde) имя которой состоит из пяти символов будет иметь перед своим названием символ под номером, вычисляемым следующим образом:
5 (смещение) + 1 (@) + 5 (длина имени) = VT (одиннадцатый символ в кодировке Unicode).

Стоит при этом заметить, что данное число записывается по тем же правилам, по которым записывается значение переменной, о чем смотри ниже.

Разберем значение переменной
Следует заметить, что данное смещение действует только для чисел до 122 включительно. Соответственно, если задать переменной "a" значение 122, то мы получим символ DEL, а если 123 - то уже некое SOH{


SOH, согласно официальной документации Ruby, - это указание на количество байт в числе. В данном случае для хранения целочисленного значения в памяти используется два байта. Чтобы не возникло недопонимания в этом моменте, сразу скажу, что числа до 122 включительно также являются двухбайтовыми, однако, SOH при их записи опущен для экономии памяти.

Согласно все той же таблице символов кодировки Unicode, символ открывающей фигурной скобки "{" равен числу 123, тут никаких неожиданной нас не ждет. Кроме одной - здесь нет смещения, о котором я писал выше.

Отдельно про количество байт
  • SOH - 2 байта
  • STX - 3 байта
  • ETX - 4 байта
  • EOT - 5 байт
Отрицательные числа
У отрицательных чисел смещение идет в противоположном порядке, начиная с символа под номером 00FA, означающего -1.
Подобное смещение идет только от -1 и до -123 включительно.
Для чисел ниже -123 вместо 00FA подставляется символ 00FF. И вот где-то там начинаются самые большие дебри, которые я пока не смог разобрать.


Дробное число


Булево значение

Переменные классы TrueClass и FalseClass записываются в дамп проще, чем все остальные типа данных. Вот порядок записи:
  1. Длина имени переменной, включая символ "@" + смещение (подробнее об этом сказано в разделе "Целое число")
  2. Символ "@", указывающий на то, что переменная локальна для экземпляра
  3. Имя переменной
  4. T для true и F для false
Код класса:
/
/

Результат:
Как вы можете заметить, здесь изменился символ, указанный сразу после имени класса. Почему? Подробнее об этом в разделе "Запись нескольких переменных".

Строковое значение


Символьное значение


Массив


Хэш-таблица


Экземпляр класса


Запись нескольких переменных

Приведу два результата дампа для сравнения.
Здесь всего одна переменная
А здесь их уже две
Разница в том, что после названия класса записываются разные значения, означающие разное количество переменных, входящих в содержимое дампа. Данное значение высчитывается так:
Количество переменных + смещение в 5 единиц. Значение записывается по тем же правилам, что и значение целочисленной переменной, подробнее об этом - в раздел "Целое число".
Все переменные со значениями разделяются символом двоеточия ":".

Альтернативный код записи дампа

Данный пост составлялся как раз по мере написания кода. Но его я представлю позже, уже в готовом виде.

Источники

Для написания данного поста использовалась информация из следующих источников:

  1. http://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF
  2. http://www.aivosto.com/vbtips/control-characters.html
  3. http://stackoverflow.com/questions/18492664/ruby-output-unicode-character
  4. А также была взята информация из официальной документации по Ruby 2.1.5, комплект Language Reference.

Комментарии

  1. Дядя Ёлфь, опередил, сделал тоже самое(

    ОтветитьУдалить
    Ответы
    1. Да я еще не доделал, так как сильно устал от смещений и некоторых сложностей с ними при написании кода альтернативного дампа

      Удалить
    2. Кстати, копни исходники, может так понятее будет.
      http://cache.ruby-lang.org/pub/ruby/1.9/

      Удалить

Отправить комментарий

Популярные сообщения из этого блога

Генератор названий оружия

Создание компонента Delphi

Идеи: Генератор сказки