Web-разработка, иностранные языки, Eclipse и разные разности

Zygo Profile

Санскрит - словоформы

Verbos Irregulares

3. Рассказ об отладчиках, процессах и потоках.

02.11.2015

Краткое объяснение сути отладочного фреймворка



Поскольку все отладчики отличаются друг от друга, отладочный фреймворк (Debug Framework) использует обобщенную модель взаимодействия с отладчиком. Главная точка входа в эту модель - DebugTarget. Она содержит одиночный процесс (Process), который может включать от одного до нескольких потоков. Так же она может быть реальной (напр., в случае внешнего отладчика) или виртуальной реализацией, если наш отладчик запущен в среде Эклипс.

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



Поскольку отладка вызывается событиями UI (resume, step into, ...), все взаимодействия между фреймворком и моделью следует обрабатывать в различных событиях обработки потоков. Эклипс в данном случае использует установку «запустили и забыли» («fire and forget»). Например, когда кто-то нажимает кнопку "возобновить", вызывается IThread.resume(), который должен немедленно вернуть результат. В фоновом режиме обработчик событий должен иметь возможность проинформировать отладчик о запросе на возобновление. В случае, если отладчик изменяет свое состояние на "возобновленный" ("resumed"), обработчик событий проинформирует нашу модель, которая обновит свое внутреннее представление и затем отправит событие во фреймворк.

Раздельное использование событий немного сложно в применении, но необходимо для того, чтобы иметь возможность получать ответ от UI.

Разделенное взаимодействие между моделью и отладчиком можно избежать, если отладчик запущен в среде Эклипс. Поскольку часто это не так, мы будем использовать события. Типичные существующие реализации могут использовать TCP-сообщение с внешним процессом.


Шаг1. Добавляем поддержку отладки для лаунчеров


Когда мы создали расширения для запуска, мы разрешили только метод run. Для того, чтобы добавить цели запуска отладки откройте com.codeandme.textinterpreter.ui/plugin.xml и перейдите на вкладку "Расширения" (Extensions).

  • Вначале перейдите в launchConfigurationType и установите методы - "run,debug"
  • Затем откройте наш launchConfigurationTabGroup и добавьте новый метод запуска (launchMode). Здесь установите метод (mode) - "debug" и перспективу (perspective) - "org.eclipse.debug.ui.DebugPerspective".
  • В конце откройте launchShortcut и установите методы (modes) - "run,debug".


Шаг 2. Добавление реализации запуска


Откройте TextLaunchDelegate.java и замените метод launch следующим кодом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void launch(final IFile file, final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IProgressMonitor monitor) {  
 // create new interpreter  
 TextInterpreter interpreter = new TextInterpreter();  
  
 try {  
  if (ILaunchManager.DEBUG_MODE.equals(mode)) {  
  
   // create debugger  
   TextDebugger debugger = new TextDebugger(interpreter);  
   interpreter.setDebugger(debugger);  
  
   // create debugTarget  
   TextDebugTarget debugTarget = new TextDebugTarget(launch, file);  
  
   // create & launch dispatcher  
   EventDispatchJob dispatcher = new EventDispatchJob(debugTarget, debugger);  
   dispatcher.schedule();  
  
   // attach dispatcher to debugger & debugTarget  
   debugger.setEventDispatcher(dispatcher);  
   debugTarget.setEventDispatcher(dispatcher);  
  
   // add debug target to launch  
   launch.addDebugTarget(debugTarget);  
  }  
  
  interpreter.setCode(toString(file));  
  interpreter.schedule();  
  
 } catch (Exception e) {  
  // TODO handle this exception (but for now, at least know it  
  // happened)  
  throw new RuntimeException(e);  
 }  
}  

Основные компоненты, которые нам нужны:

  • Реализация отладчика (строки 9-10)

Отладчик привязан к нашему интерпретатору и управляет всеми отладочными взаимодействиями на клиентской стороне.

  • DebugTarget (строка 13)

DebugTarget является представителем нашего отладчика на стороне Эклипса. Когда отладочному фреймворку эклипса необходимо взаимодействовать с нашим отладчиком, он вызывает DebugTarget для обработки запроса.

  • Диспетчер асинхронных событий (16-17)

Поскольку запросы, осуществляемые отладчиком Эклипса (такие как resume, step over, ...) происходят из UI, они должны обрабатываться асинхронно. Таким образом, диспетчер событий обрабатывает эти запросы в их собственных потоках.

Шаг 3. Отладчик


Для всего, что непосредственно касается отладки мы будем использовать новый плагин, называемый com.codeandme.textinterpreter.debugger.

TextDebugger осуществляет интерфейс IDebugger из нашего интерпретатора и интерфейс IEventProcessor, чтобы обрабатывать события, полученные из EventDispatcher. Реализация обозначена с левой стороны блок-схемы, которая представлена выше. Давайте посмотрим на важные части кода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.codeandme.textinterpreter.debugger;  
  
public class TextDebugger implements IDebugger, IEventProcessor {  
  
 // FIXME for full source code see http://codeandme.googlecode.com/svn/trunk/blog/debugger/03/com.codeandme.textinterpreter.debugger/src/com/codeandme/textinterpreter/debugger/TextDebugger.java  
  
 private EventDispatchJob mDispatcher;  
 private final TextInterpreter mInterpreter;  
  
 @Override  
 public void loaded() {  
  fireEvent(new DebuggerStartedEvent());  
 }  
  
 @Override  
 public void resumed() {  
  fireEvent(new ResumedEvent());  
 }  
  
 @Override  
 public void terminated() {  
  fireEvent(new TerminatedEvent());  
 }  
  
 @Override  
 public void handleEvent(final IDebugEvent event) {  
  System.out.println("Debugger.handleEvent() " + event);  
  
  if (event instanceof ResumeEvent)  
   mInterpreter.resume();  
 }  
  
 private void fireEvent(final IDebugEvent event) {  
  System.out.println("Debugger.fireEvent() " + event);  
  
  mDispatcher.addEvent(event);  
 }  
}  

Мы можем видеть, что события срабатывают, когда наш интерпретатор изменяет свое состояние (loaded(), resumed(), terminated()). Отладчик всего лишь передает их диспетчеру. Событие loaded это особый случай события resumed: оно сообщает нашей модели, что установлен новый интерпретатор и что исходник уже загружен для обработки. Это позволяет модели устанавливать точки останова до того, как интерпретатор начитает выполнять код.

Когда модель посылает события отладчику, диспетчер вызывает handleEvent(). В нашем случае, мы всего лишь разрешаем возобновление (что необходимо после события loaded, которое автоматически приостанавливает интерпретатор)

Шаг 4. Модель


Давайте начнем с класса TextDebugTarget, поскольку это наша главная точка входа в модель. Снова рассмотрим только наиболее важные части.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.codeandme.textinterpreter.debugger.model;  
  
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {  
  
 // FIXME for full source code see http://codeandme.googlecode.com/svn/trunk/blog/debugger/03/com.codeandme.textinterpreter.debugger/src/com/codeandme/textinterpreter/debugger/model/TextDebugTarget.java   
  
 public enum State {  
  NOT_STARTED, SUSPENDED, RESUMED, TERMINATED, DISCONNECTED  
 };  
  
 private EventDispatchJob mDispatcher;  
  
 private State mState = State.NOT_STARTED;  
  
 private final IFile mFile;  
  
 public TextDebugTarget(final ILaunch launch, IFile file) {  
  super(null);  
  
  fireCreationEvent();  
  
  // create a process  
  mProcess = new TextProcess(this);  
  mProcess.fireCreationEvent();  
 }  
  
 void fireModelEvent(final IDebugEvent event) {  
  System.out.println("Target.fireEvent() " + event);  
  
  mDispatcher.addEvent(event);  
 }  
  
 @Override  
 public void handleEvent(final IDebugEvent event) {  
  
  if (!isDisconnected()) {  
   System.out.println("Target.handleEvent() " + event);  
  
   if (event instanceof DebuggerStartedEvent) {  
    // create debug thread  
    TextThread thread = new TextThread(this);  
    mThreads.add(thread);  
    thread.fireCreationEvent();  
  
    // debugger got started and waits in suspended mode  
    mState = State.SUSPENDED;  
  
    // inform eclipse of suspended state  
    fireSuspendEvent(DebugEvent.CLIENT_REQUEST);  
  
   } else if (event instanceof ResumedEvent) {  
    mState = State.RESUMED;  
  
    // inform eclipse of resumed state  
    fireResumeEvent(DebugEvent.UNSPECIFIED);  
  
   } else if (event instanceof TerminatedEvent) {  
    // debugger is terminated  
    mState = State.TERMINATED;  
  
    // we do not need our dispatcher anymore  
    mDispatcher.terminate();  
  
    // inform eclipse of terminated state  
    fireTerminateEvent();  
   }  
  }  
 }  
  
 @Override  
 public String getName() {  
  return "Text DebugTarget";  
 }  
  
 // ************************************************************  
 // ISuspendResume  
 // ************************************************************  
  
 @Override  
 public boolean canResume() {  
  return isSuspended();  
 }  
  
 @Override  
 public boolean isSuspended() {  
  return (mState == State.SUSPENDED);  
 }  
  
 @Override  
 public void resume() {  
  // resume request from eclipse  
  
  // send resume request to debugger  
  fireModelEvent(new ResumeRequest());  
 }  
}  

Эклипс предоставляет прекрасный базовый класс для всех частей модели: DebugElement. Мы добавляем наш собственный базовый класс TextDebugElement. Прямо сейчас он ничего не делает, но он нам понадобится в следующей части руководства.

Первое, что мы видим, это то, что модели необходимо отслеживание состояния отладчика (строка 13, 46, 52, ...). Это необходимо, чтобы разрешить/запретить опции отладки на панели инструментов.

В конструкторе мы запускаем событие создания (строка 20). Это событие отправляется отладочному фреймворку Эклипса и может запустить UI взаимодействия (такое как переключение в отладочную перспективу). После этого мы создаем процесс и снова запускаем для этого событие создания. Мы будем видеть эту структуру все время, пока взаимодействуем с фреймворком. Когда нам кажется, что изменилось что-то важное, нам нужно поставить в известность фреймворк о произошедшем изменении посредством отправки события. Других способов взаимодействия с нашей моделью нет.

handleEvent() это аналог TextDebugger.handleEvent(). В событии DebuggerStartedEvent мы создаем новый поток, настраиваем нашу модель состояний и сообщаем Эклипсу, что отладчик находится в приостановленном состоянии. Тогда Эклипс активирует для нас кнопку панели инструментов "resume" (возобновление). Когда наступает событие TerminateEvent, нам необходимо отключить планировщик, иначе он будет работать все время.

До того, как Эклипс активирует кнопку возобновления (как описано выше), он запрашивает нашу модель, посылая canResume(). Мы только позволяем Эклипсу возобновление, когда отладчик временно приостановлен. Затем resume() запускает новое событие, которое отправляется диспетчеру и в конечном итоге обрабатывается отладчиком.

Шаг 5. Запуск отладчика


Я покажу часть кода системного вывода, чтобы проследить все события, которые передаются между моделью и отладчиком. Когда вы запускаете отладчик, вы должны увидеть следующий вывод:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Debugger.fireEvent() DebuggerStartedEvent  
Target.handleEvent() DebuggerStartedEvent  
Target.fireEvent() ResumeRequest  
Debugger.handleEvent() ResumeRequest  
Debugger.fireEvent() ResumedEvent  
Target.handleEvent() ResumedEvent  
>>> hello world <<<  
>>> first name = Christian <<<  
>>> we just defined a variable <<<  
>>>  <<<  
>>> counter = 23 <<<  
>>>  <<<  
>>> we are running <<<  
>>> our interpreter <<<  
Debugger.fireEvent() TerminatedEvent  
Target.handleEvent() TerminatedEvent  

После того, как мы получили событие DebuggerStartedEvent, наш отладчик приостанавливается и ожидает событие возобновления, вызванное Эклипсом. Чтобы в этом убедиться, нажмите на кнопку возобновления, чтобы продолжить исполнение программы.
Облако тегов
Меню
Архив
© Psytronica.ru. Блог существа SherZa. 2015-2017 Наверх