OberonCore
https://forum.oberoncore.ru/

Пошаговый отладчик? - Сделаем пошаговый просмотровщик...
https://forum.oberoncore.ru/viewtopic.php?f=1&t=404
Страница 1 из 2

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 00:22 ]
Заголовок сообщения:  Пошаговый отладчик? - Сделаем пошаговый просмотровщик...

rv82 писал(а):
Я уже неоднократно читал, что в системе нет пошагового отладчика. Это очень огорчает вот по какой причине. По долгу службы мне приходится иногда писать программы, в которых присутствует множество переменных, и на каждом шаге цикла
они меняются. Цикл содержит большое количество шагов (от нескольких тысяч, иногда о
нескольких миллионов). Удержать в голове все эти переменные практически невозможно из-за большого их количества. Так вот, не подскажет ли кто, может уже предпринимались попытки создать отладчик самостоятельно? Или может есть приемы для остановки цикла, контроля переменных, а затем для перехода на последующие шаги цикла?

Я тут пораскинул мозгами и состряпал тулзовину, которая поможет в этой ситуации.

Прошу любить и жаловать - модуль DevLocalWatch.
Пользоваться очень просто, в интересующей нас процедуре в начале пишем DevLocalWath.Begin (передавая либо пустую строку, если нас интересуют все локальные переменные, либо список интересующих нас переменных через пробел),
затем в тех местах процедуры, где нас интересуют "срезы" локальных переменных - DevLocalWatch.Snap, а в конце - DevLocalWatch.End.
В итоге, после вызова End откроется окошко, содержащее табулированную информации об истории изменения переменных.

Вот пример использования:

Код:
   PROCEDURE Test ;
      VAR x, y: INTEGER;
            z: REAL;
   BEGIN
      x := 10; y := 0; z := 1;
      DevLocalWatch.Begin("x y z");
      WHILE x > 0 DO
         DevLocalWatch.Snap;
         z := z * (x + y);
         DEC(x); INC(y)      
      END;
      DevLocalWatch.End
   END Test;


А вот сам код модуля DevLocalWatch:
Код:
MODULE DevLocalWatch;
(* (C) 2007 Ilya Ermakov
   http://oberoncore.ru *)

   IMPORT Mem, S := SYSTEM, Kernel, Strings, TextModels, TextMappers, TextViews, Views;

   CONST
      (* atomic types signatures *)
      boolean = 1X;
      shortchar = 2X;
      char = 3X;
      byte = 4X;
      shortint = 5X;
      integer = 6X;
      shortreal = 7X;
      real = 8X;
      set = 9X;
      longint = 0AX;
      
      regBP = 5;
      
   TYPE
      Variable = RECORD
         name: ARRAY 256 OF CHAR;
         type: SHORTCHAR;
         offset: INTEGER
      END;
      
      Value = RECORD [union]
         boolean: BOOLEAN;
         shortchar: SHORTCHAR;
         char: CHAR;
         byte: BYTE;
         shortint: SHORTINT;
         integer: INTEGER;
         shortreal: SHORTREAL;
         real: REAL
      END;
      PtrValue = POINTER TO Value;
      
      ValueBlock = POINTER TO RECORD
         next: ValueBlock;
         mem: ARRAY 1024 * SIZE(Value) OF BYTE;
         used: INTEGER
      END;
         
      Snapshot = POINTER TO RECORD
         next: Snapshot;
         values: POINTER TO ARRAY OF PtrValue
      END;
      
   VAR
      stack: INTEGER;                                 (* Stack frame pointer *)
      varMap: POINTER TO ARRAY OF Variable;   (* Map of local variables *)
      valHeap: ValueBlock;                           (* Heap for value elem allocation *)
      history, hisLast: Snapshot;                     (* Loop history *)

   PROCEDURE AddVarToMap (IN name: ARRAY OF CHAR; type: SHORTCHAR; offset: INTEGER);
      VAR n: INTEGER;
   BEGIN
      n := LEN(varMap);
      Mem.SetLength(varMap, n+1);
      varMap[n].name := name$;
      varMap[n].type := type;
      varMap[n].offset := offset
   END AddVarToMap;

   PROCEDURE Begin* (IN varToWatch: ARRAY OF CHAR);
      VAR retAdr: INTEGER;
            mod: Kernel.Module;
            ref, end: INTEGER;
            name: Kernel.Name;
            mode, form: SHORTCHAR;
            desc: Kernel.Type;
            offs, spos: INTEGER;
   BEGIN
      S.GETREG(regBP, stack); (* get frame of current proc *)
      S.GET(stack+4, retAdr);
      S.GET(stack, stack);            (* get frame of target proc *)
      NEW(varMap, 1);
      Mem.SetLength(varMap, 0);
      mod := Kernel.modList;
      WHILE (mod # NIL) & ((retAdr < mod.code) OR (retAdr >= mod.code + mod.csize)) DO
         mod := mod.next
      END;
      ASSERT(mod # NIL, 100);
      DEC(retAdr, mod.code);
      ref := mod.refs;
      REPEAT Kernel.GetRefProc(ref, end, name) UNTIL (end = 0) OR (retAdr < end);
      ASSERT(retAdr < end, 101);
      Kernel.GetRefVar(ref, mode, form, desc, offs, name);
      WHILE mode # 0X DO
         IF (mode = 1X) & (offs < 0) & (form >= boolean) & (form <= longint) THEN (* variable, non parameter, atomic *)
            IF varToWatch # "" THEN
               Strings.Find(varToWatch, name$, 0, spos)
            END;
            IF (varToWatch = "") OR (spos # -1) & ( (spos = 0) OR (varToWatch[spos-1] = " ") )
               & ((varToWatch[spos+LEN(name$)] = " ") OR (varToWatch[spos+LEN(name$)] = 0X)) THEN
               AddVarToMap(name$, form, offs)
            END
         END;
         Kernel.GetRefVar(ref, mode, form, desc, offs, name)
      END;
      NEW(valHeap)      
   END Begin;
   
   PROCEDURE NewVal (): PtrValue;
      VAR blk: ValueBlock;
   BEGIN
      IF valHeap.used = LEN(valHeap.mem) THEN
         NEW(blk);
         blk.next := valHeap;
         valHeap := blk
      END;
      INC(valHeap.used, SIZE(Value));
      RETURN S.VAL(PtrValue, S.ADR(valHeap.mem[valHeap.used - SIZE(Value)]))
   END NewVal;
   
   PROCEDURE Snap*;
      VAR s: Snapshot;
            fp, i: INTEGER;
            val: PtrValue;
   BEGIN
      ASSERT(varMap # NIL, 20); (* Begin has been called before *)
      S.GETREG(regBP, fp); S.GET(fp, fp);
      ASSERT(stack = fp, 21);    (* Begin & Loop called from same procedure *)
      NEW(s);
      NEW(s.values, LEN(varMap));
      FOR i := 0 TO LEN(varMap)-1 DO
         val := NewVal();
         CASE varMap[i].type OF
         | boolean: S.GET(fp + varMap[i].offset, val.boolean)
         | shortchar: S.GET(fp + varMap[i].offset, val.shortchar)
         | char: S.GET(fp + varMap[i].offset, val.char)
         | byte: S.GET(fp + varMap[i].offset, val.byte)
         | shortint: S.GET(fp + varMap[i].offset, val.shortint)
         | integer: S.GET(fp + varMap[i].offset, val.integer)
         | shortreal: S.GET(fp + varMap[i].offset, val.shortreal)
         | real: S.GET(fp + varMap[i].offset, val.real)
         END;
         s.values[i] := val
      END;
      IF hisLast # NIL THEN
         hisLast.next := s;
         hisLast := s
      ELSE
         history := s;
         hisLast := s
      END
   END Snap;
   
   PROCEDURE ^ WriteVal (type: SHORTCHAR; IN val: Value; VAR f: TextMappers.Formatter);
      
   PROCEDURE End*;
      VAR tm: TextModels.Model;
            f: TextMappers.Formatter;
            i: INTEGER;
            s: Snapshot;
            tv: TextViews.View;
   BEGIN
      tm := TextModels.dir.New();
      f.ConnectTo(tm);
      FOR i := 0 TO LEN(varMap) - 1 DO
         f.WriteTab;
         f.WriteString(varMap[i].name)
      END;
      f.WriteLn;
      s := history;
      WHILE s # NIL DO
         FOR i := 0 TO LEN(varMap) - 1 DO
            f.WriteTab;
            WriteVal(varMap[i].type, s.values[i], f)
         END;
         f.WriteLn;
         s := s.next
      END;
      tv := TextViews.dir.New(tm);
      Views.OpenView(tv);
      varMap := NIL;
      valHeap := NIL;
      history := NIL; hisLast := NIL;
      Kernel.Collect
   END End;
   
   PROCEDURE WriteVal (type: SHORTCHAR; IN val: Value; VAR f: TextMappers.Formatter);
   BEGIN
      CASE type OF
      | boolean: f.WriteBool(val.boolean)
      | shortchar: f.WriteChar(val.shortchar)
      | char: f.WriteChar(val.char)
      | byte: f.WriteInt(val.byte)
      | shortint: f.WriteInt(val.shortint)
      | integer: f.WriteInt(val.integer)
      | shortreal: f.WriteReal(val.shortreal)
      | real: f.WriteReal(val.real)
      END
   END WriteVal;

END DevLocalWatch.

Автор:  rv82 [ Четверг, 15 Март, 2007 06:35 ]
Заголовок сообщения: 

Небольшая проблемка возникла. При компиляции сообщается, что модуль Mem не найден.

Автор:  Vlad [ Четверг, 15 Март, 2007 12:18 ]
Заголовок сообщения:  Re: Пошаговый отладчик? - Сделаем пошаговый просмотровщик...

Илья Ермаков писал(а):
Я тут пораскинул мозгами и состряпал тулзовину, которая поможет в этой ситуации.


Чем данный подход отличается от обычного логирования?

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 13:02 ]
Заголовок сообщения: 

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

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 13:06 ]
Заголовок сообщения: 

Исправил небольшую ошибку в коде выше.

Модуль Mem введен в Service Pack 4 - скачивайте в разделе Дистрибутивы.

Автор:  Иван Горячев [ Четверг, 15 Март, 2007 13:14 ]
Заголовок сообщения: 

Я думаю таким же манером несложно написать процедуру DEBUG (cond : BOOLEAN), которая бадет выводить окно наподобие трапа и ждать нажатия кнопки "продолжить"

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 13:39 ]
Заголовок сообщения: 

В принципе, можно. Только окно потребуется использовать отдельное. Либо системный MessageBox, либо создавать средствами WinApi в отдельном потоке.

Автор:  Борис Рюмшин [ Четверг, 15 Март, 2007 13:51 ]
Заголовок сообщения: 

Что-то вы, товарищи, начинаете отклоняться от линии партии... :):):)

Автор:  Иван Горячев [ Четверг, 15 Март, 2007 13:56 ]
Заголовок сообщения: 

Илья Ермаков писал(а):
В принципе, можно. Только окно потребуется использовать отдельное. Либо системный MessageBox, либо создавать средствами WinApi в отдельном потоке.

На самом деле нужна штатная возможность создавать модальные диалоги. Например для настроек сонвертера при открытии документа.

Автор:  Александр Ильин [ Четверг, 15 Март, 2007 14:26 ]
Заголовок сообщения: 

Илья Ермаков писал(а):
В принципе, можно. Только окно потребуется использовать отдельное. Либо системный MessageBox, либо создавать средствами WinApi в отдельном потоке.

Тогда уж лучше сделать отдельное приложение - отладчик, а к основному присоединяться через TCP/IP. Там вызов DEBUG и ожидание продолжения - здесь дамп памяти и кнопка "Продолжить".
Ivor писал(а):
На самом деле нужна штатная возможность создавать модальные диалоги. Например для настроек конвертера при открытии документа.

А я считаю, что стандартные диалоги открытия, сохранения файла и печати нужно сделать немодальными. А еще - разрешить через стандартный Open-диалог открывать несколько документов сразу.
Для настроек конвертера модальность не нужна. Что мешает конвертеру отобразить окно настроек и отложить открытие файла до нажатия пользователем кнопки "Продолжить"?

Автор:  Иван Горячев [ Четверг, 15 Март, 2007 14:48 ]
Заголовок сообщения: 

Александр Ильин писал(а):
А я считаю, что стандартные диалоги открытия, сохранения файла и печати нужно сделать немодальными. А еще - разрешить через стандартный Open-диалог открывать несколько документов сразу.

Да, это было бы неплохо.
Цитата:
Для настроек конвертера модальность не нужна. Что мешает конвертеру отобразить окно настроек и отложить открытие файла до нажатия пользователем кнопки "Продолжить"?

Устройство каркаса ББ :) Если посмотреть на HostDialog.GetIntSpec можно заметить, что в случае открфтия файла с маской *.* модальность диалога выбора конвертера эмулируется - иначе не получается. Насколько я помню, проблема в кнопке "отмена" - отменить то мы отменили, а процесс то уже идёт

Автор:  Alexander Shiryaev [ Четверг, 15 Март, 2007 14:51 ]
Заголовок сообщения: 

Зачем всё это нужно, когда есть HALT/ASSERT ?

Автор:  Борис Рюмшин [ Четверг, 15 Март, 2007 15:02 ]
Заголовок сообщения: 

Жестоко.

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 15:06 ]
Заголовок сообщения: 

Alexander Shiryaev писал(а):
Зачем всё это нужно, когда есть HALT/ASSERT ?

HALT/ASSERT прерывает выполнение. А тут, как видите, цель - проследить все витки цикла...

Зачем? Ну, у меня необходимости никогда не возникало. Но вот человеку для специфических задач потребовалось :-)

Автор:  Vlad [ Четверг, 15 Март, 2007 15:49 ]
Заголовок сообщения: 

Илья Ермаков писал(а):
При "обычном логировании" нужно ручками что-то куда-то выводить.


Тут тоже ручками...

Илья Ермаков писал(а):
В итоге - лично я непредставляю - зачем еще нужно что-то по шагам гонять... Вся картинка перед глазами...


Чтобы не искать нужное место в мегабайтах логов.

Автор:  Илья Ермаков [ Четверг, 15 Март, 2007 16:44 ]
Заголовок сообщения: 

Если цикл настолько сложен, что требует отладки, может, стоит инвариант записать и формально вывести? :-)

Автор:  rv82 [ Пятница, 16 Март, 2007 05:27 ]
Заголовок сообщения: 

Илья Ермаков писал(а):
Исправил небольшую ошибку в коде выше.

Модуль Mem введен в Service Pack 4 - скачивайте в разделе Дистрибутивы.

Урррааааа! Зработалааааа!
Хорошая вещь! Значительно упрощает процесс поиска ошибок.

Автор:  Info21 [ Пятница, 16 Март, 2007 10:30 ]
Заголовок сообщения: 

Илья Ермаков писал(а):
Если цикл настолько сложен, что требует отладки, может, стоит инвариант записать и формально вывести? :-)


Однозначно стоит. Ясно, что что-то там недодумано...

Автор:  rv82 [ Пятница, 16 Март, 2007 10:46 ]
Заголовок сообщения: 

info21 писал(а):
Илья Ермаков писал(а):
Если цикл настолько сложен, что требует отладки, может, стоит инвариант записать и формально вывести? :-)


Однозначно стоит. Ясно, что что-то там недодумано...

Если честно, с трудом себе представляю, о чём идёт речь...
Ух очень я привык к Смолтоку. А с КП слишком мало "общался", так что некоторые вещи в нём пока непонятны.

Автор:  Александр Ильин [ Пятница, 16 Март, 2007 11:18 ]
Заголовок сообщения: 

rv82 писал(а):
Урррааааа! Зработалааааа!
Хорошая вещь! Значительно упрощает процесс поиска ошибок.

Надо теперь попросить Илью написать модуль DevLocalGuard, который бы "значительно усложнил процесс создания ошибок".
:D :D

Страница 1 из 2 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/