Как узнать адрес начала и конца КОДА процедуры?

Адрес начала — Address(Proc).
Адрес конца — сомневаюсь. Если в модуле, где сидит данная прога есть инфа о длине проги, то тогда понятно, как определить адрес конца (проги). Хотя, сомневаюсь, что такое кто-нибудь делает.

Правда, исследуя недра Клариона, могу сказать, что в подавляющем большинстве процедур, есть только ОДИН выход через RET NEAR. И обычно всегда — в конце кода процедуры. По крайней мере, я иных не встречал. Хотя допускаю их наличие.
Так что, можно просканировать память с адреса начала процедуры и до нахождения кода оператора RET NEAR. Это и будет, с большой вероятностью, адрес конца кода данной процедуры.
Кстати, все процедуры в Кларионе завершаются именно оператором RET NEAR xx. Даже процедуры из внешних DLL-библиотек. Для вызова таких процедур Кларион эмулирует «дальний» вызов с помощью дополнительной локальной процедуры с одним оператором JMP _DllProc.

AA> 2. Являются ли они константами относительно Address(Proc), или при разных моделях компиляции или загрузки могут меняться?

Разные не только в зависимости от модели, но и в зависимости от самого вызываемого кода. Т.е., например, при одном запуске проги адрес будет один, достаточно добавить в прогу какой кусочек кода, адрес будет уже другой. Для LIB-компоновки адрес надо определять каждый раз в рантайме с помощью Address(Proc). Для DLL-компоновки адрес лучше всего определять через API-функции. Что-то типа этого:

  MAP
    MODULE('WinAPI')
      LoadLibrary(STRING _cLibName),LONG,RAW,PASCAL,NAME('LoadLibraryA')
      GetProcAddress(LONG _LibHandle,STRING _cProcName),LONG,RAW,PASCAL,NAME('GetProcAddress')
    END
    MODULE('RTL Clarion')
      RTL::GetBindVar(STRING _cVarName),*?,RAW,NAME('Cla$EvaluateVariable')
    END
  END

  Code
    OMIT('***',dll_mode)
  Proc_Addr# = Address(RTL::GetBindVar)
    ***
    COMPILE('***',dll_mode)
  LibHandle# = LoadLibrary('C5RUNX.DLL<0>')
  if ~LibHandle# then Proc_Addr# = 0 else
     Proc_Addr# = GetProcAddress(LibHandle#,'Cla$EvaluateVariable<0>')
  .
    ***

AA> 3. Информации, получаемой от отладчика, для ответа на второй вопрос достаточно?

Нет! Отладчик показывает адрес процедуры, который является правильным ТОЛЬКО в текущем сеансе!

AA> 4. Как узнать адрес метки В КОДЕ?

Если эта метка объявлена как PUBLIC, то обычным способом:

  MAP
    ExtLabel,NAME('_MyFunc_PublicLabel_')
  END
  Code
  Addr# = Address(ExtLabel)

Если эта метка еще и экспортирована в DLL-библиотеку, то тогда можно получить ее адрес и при DLL-линковке. Иначе — увы! Примером тому могут служить очень много полезных процедур и просто структур в ядре Клариона, которые можно «достать» при LIB-компоновке, но невозможно при DLL-компоновке!

AA> 5. Меняются ли ответы на эти вопросы от версии к версии клариона?

Нет. Меняться могут лишь имена процедур и public-меток. Хотя, нафиг кому это нужно! Еще может меняться ответ на вопрос об адресе конца кода процедуры.

Кстати!
Недавно, в одном своем письме, когда отвечал на вопрос о некоторых неочевидных аспектах при работе с разными типами полей групп через What(), я упоминал, что некоторые, описанные мною, методы не являются стабильными и могут меняться в зависимости от версии Клариона.

Так вот, приведенный мною способ определения факта, что поле группы является массивом, не будет работать в версии C55d. Там несколько другая схема работы внутреннего менеджера памяти. Соответственно, по другому распределяются блоки памяти. А некоторые, похоже, вообще отданы на откуп виндовому менеждеру глобальной «кучи». По крайней мере, в C55d блок памяти, выделенный для ANY-переменной, не имеет заголовка, как это было в C50. Хотя, наряду с такими блоками, есть и «стандартные» блоки памяти со «стандартными» заголовками.

Кроме того. Если в C50 адрес описания UFO-блока можно было получить оператором:

  Peek(Address(ANY_Var),UFO_Addr#),
то в C55d для этого достаточно выполнить:
  UFO_Addr# = Address(ANY_Var)

Вообщем, похоже, в C55d ребята порядочно «перекопали».
Да, и еще!
В С55d, наконец, исправили некоторые ошибки, о которых я недавно писал в рассылке:

   MAP
     Tst1(),*?
     Tst2(),*STRING,PROC
   END

В С50 из Tst1() нельзя было вернуть нулевой указатель, прога «падала» по GPF. В С55d исправлено!

В С50 из Tst2 нельзя было вернуть нулевой указатель простым оператором Return NULL. Надо было обязательно описывать в процедуре строковый реферал, присваивать ему NULL и уже его возвращать через Return SRef. Иначе — возвращался мусор. В С55d исправлено!

Также, в С50 нельзя было ставить для Tst2 атрибут PROC. В этом случае компилятор генерил неверный код после вызова такой процедуры как процедуры а не функции. Что, если помните, приводило к разрушению внутреннего строкового стека RTL-ядра Клариона. В С55d исправлено!

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

! AA>> 1. Как узнать адрес начала и конца КОДА процедуры?
OR>> Адрес начала — Address(Proc).
NT>> А разве нельзя длину кода процедуры определить из MAP файла?
AA> Ладно, попробуем.

Вообще-то я отвечал, предполагая что вся инфа должна получаться в рантайме. Надежнее как-то!:)
А так, конечно, все инфу можно вытащить из отчетов компилера, линкера, дебагера. Вот только, после первого же изменения/добавления в программе или библиотеке или при переходе на другую версию языка, придется опять «лопатить» все эти отчеты!

AA>> 3. Информации, получаемой от отладчика, для ответа на второй вопрос достаточно?

Нет! Отладчик показывает адрес процедуры, который является правильным ТОЛЬКО в текущем сеансе! Но с помощью отладчика можно посмотреть, где код начинается, где кончается. Вычесть одно от другого, и, при необходимости, прибавить к Address.

Опять-же, см. выше.

AA> Как говорится, каков вопрос, таков ответ. Хотелось узнать: — не имеет ли компилер привычки «рубить» код (или делать мусор, или хвосты), перемежая код одной процедуры с данными и/или кодом другой процедуры

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

AA> — менять размер процедуры после перезагрузки оверлеев/виртуальной памяти

Так-же не замечено.

AA>> 4. Как узнать адрес метки В КОДЕ?
AA> ! Если эта метка обьявлена как PUBLIC, то обычным способом:
AA> ! <SKIP>
AA> Sorry, имел ввиду
AA> Label: Loop 2 times ! Labelled loop
AA>            Loop While True
AA>             If … then Break Label.
AA>            End
AA>          End
AA> Как узнать адрес Label (метки в коде)?

Никак. Да и зачем? Если для того, чтобы потом на нее перейти, то есть оператор GOTO Label. Ну, а если очень сильно надо, то можно написать ма-а-аленькую процедурку на встроенном асме, которая будет возвращать адрес инструкции, с которой будет продолжено выполнение программы после возврата из этой процедурки. В этой процедуре надо просто взять значение из предыдущего DWord стека.
Или следующего — это как смотреть:)
Достаточно поставить вызов этой процедурки перед такой меткой и она (процедура) вернет адрес инструкции, соответствующий адресу заданной метки. И все дела!

AA> ! Так что, некоторые подвижки в лучшую сторону есть! AA> А это не очевидно. Сейчас в SoftVelocity господствуют не менеджеры, а AA> программисты. В Topspeed перед слиянием с Clarion так и было. И где сейчас AA> лучший компилер Topspeed С++/Modula/Pascal/Asm? Уже и названия не осталось. Я имел ввиду исправления ошибок, которые уже давно «тянуться хвостом» за Кларионом.

Кстати!
Тестируя тут примеры использования моей библиотеки dQueue, наткнулся еще на один баг. Актуален для C5eeb и C55d.

Обстановка:
Есть очередь с одним или несколькими OVER-полями. Ставим эту очередь в качестве рабочей для LIST-контрола. Уже на данном этапе имеем неприятный глюк: List-контрол «не видит» такие OVER-поля. Это еще полбеды! Основное, что при этом все последующие поля этот List-контрол нумерует так, как будто нет пропущенного OVER-поля, что, естественно, расходится с реальными номерами полей в структуре, с которыми работают, например, функции типа What()/Who()/Where().

Но и это еще не все!
Если несколько раз провести прямую/реверсивную сортировку по полям, которые идут после OVER-поля, то в дальнейшем следует ждать GPF или на операциях с этой очередью или, что практически 100%, при завершении процедуры/программы, если перед этим были удалены все записи. Т.е., если такая рабочая очередь была обьявлена в процедуре и перед выходом из процедуры были удалены все записи из этой очереди, то при выходе из процедуры получаем GPF.

Если кому интересно, то в аттаче небольшой пример. quebag.zip Последовательность тестирования:

Добавить в таблицу несколько записей (> 1).

Несколько раз посортировать список по колонкам ‘DESC’ или ‘PRICE’, нажимая мышкой на заголовки колонок. При этом, кстати, могут пропадать/появляться значения в колонке ‘PRICE’.

Удалить последовательно все записи. Уже на данном этапе возможен GPF.

Закрыть окно программы. Стабильный GPF.

Возможно есть и другие варианты достижения GPF.