Исследование «промежуточного» кода на примере GP-кода языка NATURAL - Часть 2: Операнды (окончание)

Дата публикации 13 ноя 2006

Исследование «промежуточного» кода на примере GP-кода языка NATURAL - Часть 2: Операнды (окончание) — Архив WASM.RU

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

2.2 Параметры (Value table index =  6)

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

Параметры в NATURAL-подпрограммах объявляются одним из двух способов (либо их комбинацией):

  1. 1.      Явно определяются в блоке define data parameter. Например:

    Рис 2.6. Пример объявлений в Natural

  2. 2.      Объявляются в специальном программном модуле (PDA – Parameter Data Area), а в подпрограмме осуществляется ссылка на этот программный модуль (либо на несколько модулей, при необходимости). Например, так:

    Рис 2.7. Пример объявлений в Natural

    В этом случае модули PDA_1, LDA_1, LDA_2 должны быть заранее сформированы и скомпилированы. При компиляции целевой подпрограммы определение операндов копируется внутрь GP_кода. Для выполнения целевой подпрограммы модули объявлений операндов уже не нужны.

Важно! После компиляции в папке объектных кодов появятся файлы, содержащие компилированное описание операндов. В нашем случае это PDA_1.nga, LDA_1.ngl,  LDA_1.ngl. Гарантированно восстанавливать из них операнды – милое дело! Но об этом чуть ниже.

GP-код  не зависит от того, каким образом объявлены параметры (да и операнды вообще), 

Подробнее параметры мы будем обсуждать, когда доберемся до разбора механизма вызова внешних программных модулей (для знатоков - речь идет о call, callnat, fetch, perform), а сейчас нам важно запомнить три основных момента:

  • Если в Таблице операндов нам встретился Value table index =  6, перед нами параметр (но не все параметры имеют Value table index =  6 в Таблице операндов! См. ниже).
  • Параметрическая  область общая для всего программного модуля.
  • Формат и порядок следования параметров определен в таблице операндов, в Таблице же параметров (Parameter Table, таблица с седьмым индексом) содержатся дополнительные свойства параметров. Структура записи в таблице параметров и возможные значения полей приведены в Приложении 2 (Структура Таблицы параметров).

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

Задача

Группа программистов-энтузиастов после многодневных свирепых дебатов пришла к выводу, что для полного счастья (т.е. для завершения супер-пупер-мега-проги, умножающей сумму двух чисел на третье) им не хватает всего лишь одной процедурки. Причем процедурка существующая, но в виде исполняемого кода (аксакалы еще помнят людей, видевших исходник). Документация (если она и была) оказалась нещадно съедена гигантскими крысами-мутантами. А безумный программист, сваявший эту процедуру, был задавлен самосвалом при попытке перевести какую-то бабушку через дорогу (бабушка жива, но ничего не помнит). Программисты-натуралисты готовы отдать пачку нечитающихся пятидюймовых дискет и свою душу в придачу за восстановление параметров вызова этой процедуры.

А вот и GP-код этой процедуры:

Решение

Общий план восстановления параметров таков:  разбор заголовка GP-кода, разбор секции параметров, определение формата параметров по таблице операндов. Почему сначала секция параметров, потом таблица операндов, а не наоборот, увидим  чуть позже.

  1. Определение названия и типа программного модуля (условимся с версиями не заморачиваться)

    Название и тип модуля надежнее всего определять по фиксированной части заголовка. Это обсуждалось в первой части статьи. В приложении к ней же приведена структура фиксированной части заголовка. В результате получаем, что перед нами подпрограмма (Object type = N, что значит Subprogram) с именем Test10. Это вполне соответствует имени и расширению файла, содержащего GP-код (test10.ngn)

  2. Определение Таблицы Секций

    Как ее отыскать – см. в первой части статьи, соответствие индексов секций и их названий – см. Секции в GP-коде Приложения 2 (вторая часть статьи).

    Должны получить вот такую табличку секций

  3. Разбор Секции Параметров

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

    Начало секции 0x011A (см. Рис 2.7).

    Первые 4 байта – количество параметров. Далее следует таблица параметров (см. Секция параметров в Приложении 2) . Разбираем секцию:

    Итак, перед нами 4 параметра. За счет ссылок на операнды в таблице операндов становится возможным определить формат этих параметров.

  4. Определение формата параметров по Таблице Операндов

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

    Интерпретация длины формата N требует разъяснений. Здесь логика такая: пять бит отводится для хранения размера целой части операнда, а три бита – для хранения размера дробной части. Например, 0x4A в двоичном представлении 01001010, здесь 010 (3 dec) – точность после запятой, т.е. размер дробной части, а 01010 (10 dec) – размер до запятой, т.е. целой части операнда. Итого N10.2. Аналогично F4 (11110100) интерпретируется как N20.7 (111 = 7dec после запятой, 10100 = 20dec – до запятой).

    Важная особенность! Обратите внимание, после второго операнда, распознанного нами как параметр, следуют два, распознанные нами (согласно значению поля Value table index Таблицы операндов) как локальные переменные, а потом опять параметр. В NATURAL-программах такого не бывает! Операнды в таблицу операндов заносятся в порядке объявления, а объявлять сначала параметры, потом локальные переменные, и опять параметры не позволит синтаксический анализатор! Что-то тут не то!!!

    Собственно, чтобы избежать этой неопределенности мы и сделали таблицу параметров ведущей.

  5. Восстановление параметрической области

    Таблицы символов в пердставленном GP-коде нет, потому сами придумаем названия и восстановим объявление параметрической области. Например, так:

    Код (Text):
    1.  
    2.    define data parameter
    3.     1 parm1 (n10.2)
    4.     1 parm2 (f4) by value
    5.     1 parm3 (I4) by value result
    6.     1 parm4 (n20.7)
    7.    end-define

    И это работает!!!

Что ж, хороший повод поздравить себя с очередной маленькой победой!

2.3 Глобальные переменные (Value table index =  0x12)

Глобальная область данных (GDA – Global Data Area) представляет собой часть общей памяти, используемой Natural, отдельные элементы которой могут быть совместно использованы программными модулями Natural. Любое изменение элементов данных глобальной области сразу же становится доступным всем программным модулям Natural, ссылающимся на данную глобальную область.

В GP-коде о наличии глобальных переменных говорят поля Flags (значение 0x40 соответствует  GDA referenced) и GDA GP timestamp постоянной части заголовка (см. первую часть статьи). Описание формата глобальных переменных содержится в Таблице операндов и отличается от описания локальных переменных лишь значением поля Value table index (значение 0x12 соответствует GDA).

2.4 Независимые переменные (AIV, Value table index =  0x15)

AIV (Application-Independent Variables) – переменные, глобальные по своей сути. Принципиальное отличие от «нормальных» глобальных переменных, рассмотренных выше, заключается в том, что aiv-переменные объявляются непосредственно в программных модулях, а «нормальные» глобальные переменные объявляются только в специальном модуле, на который можно сослаться в целевой программе.

Обязательное условие объявления aiv-переменной – имя переменной должно начинаться с символа «+» (например, «+aiv_var_01»);

Структура записи в AIV-TABLE хитрая (см. п.4 AIV-Table Приложения 2). Она состоит из постоянной части  и переменной части. Если значение поля Flags отлично от 0x4000 (Redefine), то переменная часть содержит только имя переменной (без знака «+»). Если же значение поля Flags равно 0x4000 (Redefine), то переменная часть содержит параметры переопределения и имя переменной (без знака «+»).

После поля Variable name могут следовать «выравнивающие» нули (обычно значение Align для AIV-Table равно 4, но надежнее посмотреть в соответствующей записи Таблицы секций).

Все не так сложно и запутанно, как может показаться, и в этом мы убедимся на примере.

Предположим, нам посчастливилось встретиться с таким содержимым AIV-Table:

Наши действия?

Насвистывая бодрую и незатейливую мелодию (не забывая при этом поглядывать на значение поля Flags, чтобы правильно подобрать подходящий вариант структуры записи), получаем следующее:

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

Код (Text):
  1.  
  2. define data
  3.   independent
  4.    1 +aiv0(…)
  5.    1 +aiv1(…)
  6.    1 redefine +aiv1
  7.    2 filler 3x
  8.    2 aiv2(…)
  9.  сontext
  10.    1 cont1(…)
  11. end-define

Вот и все! Ничего сложного…

2.5 Массивы (Value table index =  0x08)

Для объявления массивов в Natural используется так называемая индексная нотация. По умолчанию, нижняя граница индекса равна 1. Фантазию программиста Natural ограничивает, максимум, трехмерным массивом.

Но это верхушка айсберга, а что у нас там, под водой? А под водой у нас замечательная секция Array Description Table!

Структура записи таблицы состоит из «описательной» части (см. Приложение 2 п.5) и данных.  «Описательная» часть значениями полей Dimension flags 1, Dimension flags 2, Dimension flags 3 определяет перечень параметров, описывающих соответствующие размерности массива. Данные – это 4-байтовые значения этих параметров.

Чтобы переварить этот словесный винегрет, разберем простой пример:

  1. Разбираем «описательную» часть (см. Приложение 2 п.5.1)

    По значению поля Dimension заключаем, что перед нами одномерный массив (косвенно на это же указывают нулевые значения полей Dimension flags 2 и Dimension flags 3).

  2. Определяем перечень параметров, описывающих размерности (см. Приложение 2 п.5.2). В нашем случае,  размерность одна. Значение поля Dimension flags 1, равное 10110000 bin, позволяет заключить, что параметров 3, и это:

    1. OPT offset lower;
    2. Occurences;
    3. Index factor;

Разбор описательной части завершен.

  • Определение значений параметров. Первые 4 байта, следующие за «описательной» частью, являются значением первого параметра, вторые 4 байты – значением второго параметра и т.д. Итого:

    Из предыдущих примеров мы уже знаем, что в Таблице операндов в самую первую запись (OPT offset = 0) Natural с завидным постоянством «кладет» константу со значением 1. А раз так, то мы можем смело заявить, что перед нами одномерный массив из 3 элементов, нижний индекс которого равен 1, соответственно верхний 3.

    2.6 Восстановление компилированных областей данных

    Объявлять из программы в программу повторяющиеся параметры и переменные – дело мучительное и неблагодарное. Особенно в мало-мальски серьезных проектах, в которых, как правило, повторяемость их использования довольно велика. Чтобы немного облегчить разработку и сопровождение прикладных программных продуктов, Natural предлагает использовать так называемые «области данных» (data area). Создав однажды некую область и скомпилировав ее, на нее можно ссылаться из множества программ.

    Важно: для исполнения целевой программы нужна скомпилированная область данных!

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

    Теперь ближе к теме.

    Самый простой способ восстановления – ручной. Для этого необходимо открыть файл скомпилированной области данных в редакторе и скопировать оттуда строки определений – благо они хранятся в объектном коде в явном виде.

    В этом примере нужно скопировать блок, начиная с «0010 DEFINE DATA PARAMETER» (смещение 0x48) и заканчивая «0060 END-DEFINE» (смещение 0xCF). Что делать дальше? Это дело вкуса, квалификации и наличия свободного времени!

    Такой вариант годится и для LDA, и для PDA, и для GDA, и может быть легко автоматизирован. Единственное, на что нужно обратить внимание, не добавил ли при компиляции Natural в объявления что-то от «себя» (например, GFID при компиляции LDA на основе DDM).

    Хорошо, с экстренным восстановлением, предположим, понятно, а что со структурой скомпилированных областей данных вообще?

    Структуры LDA и PDA идентичны. Вначале (вы не поверите!) идет уже полюбившийся нам и ставший родным заголовок (фиксированная часть плюс таблица секций, которые мы разбирали в первой части статьи), а далее – секция, содержащая «человеческие» объявления.

    В качестве упражнения вы можете разобрать заголовок предыдущего примера и убедиться, что вторая секция начинается как раз по смещению 0x48. И это вполне закономерно, т.к. размер фиксированной части заголовка 0x38 плюс 16 байт на Таблицу секций (8 байт – размер записи в таблице Х 2 секции) как раз дает 0x48.    

    Структура же GDA гораздо сложнее, по той простой причине, что фактически это – полноценная подпрограмма! Не верите? Смотрим пример!

    Разбор фиксированной части заголовка показывает, что перед нами действительно GDA (значение 0x47 по смещению 0x02), и нам следует морально подготовиться к 19 записям в Таблице секций (значение 0x13 по смещению 0x18). А что же самой Таблице секций?

    Однако! Как говорится, почувствуйте разницу! Две очевидные секции в скомпилированных LDA и PDA и «жалких» 10 (пустые секции не в счет) в GDA при их внешней «похожести»! Но давайте по порядку…

    Заголовок: с ним все понятно, без него – никуда. Найти там что-то новое, наверно, нереально.

    Секция кода: 40 байт кода – это серьезно. К сожалению, мы пока не в состоянии понять, что же там происходит (разбору секции кода будет посвящена следующая часть статьи). Но разглядеть и интерпретировать  последовательность 46 80 D0 FF нам должно быть уже вполне по силам – это оператор END в 0070 строке программы (см. первую часть статьи). Догадка перерастет в убеждение, если мы устремим свой взор на ascii-последовательность, начинающуюся с 0x0251.  Это, конечно, интересно, но ведь никакого оператора END (впрочем, как и 40 байт кода) мы не вводили при создании этой глобальной области! Т.е. это – ни что иное, как «отсебятина» Natural!

    Таблица операндов: «раскрутить» операнды мы уже вполне способны! Нет большого смысла повторяться, привожу конечный результат

    Обратите внимание на последние 3 операнда – это тоже «отсебятина» Natural! Ничего подобного мы не объявляли! Забегая вперед, скажу, что эти операнды непосредственно связаны с секцией кода. Параметром является некий флаг, который показывает интерпретатору, создавать и инициализировать эту глобальную область, или же она уже существует. А константы – возможные значения этого флага, с которыми сравнивается текущее значение флага. Но об этом – в следующей части статьи.

    Секция описания класса: причем здесь класс, не очень понятно. Тем более что именно в этой секции фактически хранится «человеческое» представление глобальной области.

    Область глобальных данных: в самом GP-коде этой секции нет. Почему это «source table» (а это SAG’овское оригинальное название секции) – тоже не очень понятно. Ну и бог с ним! Замечу, что размер секции – это количество байтов, которое необходимо отвести интерпретатору для размещения «глобалки» в памяти.

    Заключение

    Операнды в GP-коде – тема обширная. И я не ставил перед собой задачи рассказать обо всех возможных операндах в Natural. Что-то – слишком очевидно, что-то – слишком специфично, а чего-то я и сам до сих пор не знаю. Основной побудительный мотив, заставивший меня начать «бодягу» с операндами – идея, что перед тем, как ковырять секцию кода, необходимо хотя бы по-крупному разобраться с операндами. Думаю, это утверждение годится не только для Natural.


    Приложение 2

    3. Parameter Table

    Значения поля Flags

    4. AIV-Table

    Значения поля Flags

    5. Array Description Table

    «Заголовочная» запись массива

    Значения первого байта Dimension flags

    Значения поля Flags

    © Konstantin

  • 0 975
    archive

    archive
    New Member

    Регистрация:
    27 фев 2017
    Публикаций:
    532