Coder Social home page Coder Social logo

seekerdebugger's Introduction

SeekerDebugger

Baseline

Seeker: Prototype Scriptable Time-Traveling Queryable Debugger. Compatible with Pharo 9.0, Moose Suite 9.0, Pharo 10, Moose 10 and Pharo 11 (tested on 2022-10-28). Working in Pharo 12.0 (tested on 2023-04-26).

Do this:

Metacello new
    baseline: 'Seeker';
    repository: 'github://maxwills/SeekerDebugger:main';
    load.

The rest of the readme is not up do date. The debugger is currently under constant changes. The readme will be updated when reaching a stable point.

Requires the following projects (installed automatically with this Seeker Baseline):

The baseline will:

  • Enable the debugger extension in the StDebugger UI.
  • Change the StDebugger debuggerActionModel default class to SeekerStDebuggerActionModel. This will make the StDebugger to need Seeker to be enabled to work. (You can still debug normaly without Seeker, but it will be shown at the right, even if it is not used). Don't use this if you rely on your own modifications of StDebuggerActionModel. If you unload Seeker, you will need to manually restore StDebugger>>#debuggerActionModel.
  • Add a line to HandMorph>>handleEvent: to capture the pressed state of modifier keys.

Headless mode

|myProgramBlock|
myProgramBlock := [|a| 
  a:=1 .
  a:= a+a.
  a:= a+a.
  a].

"use this if your program correspond to the code of a block"
SeekerDebugger headlessDebugBlock: myProgramBlock.

"Try to avoid using auxiliary blocks (ie, avoid doing the following):"
SeekerDebugger headlessDebugBlock: [ anObject theProgramMethod].
"For that case, it is better to use the following initialization."
"Use this if your program corresponds to a call of a method:"
 SeekerDebugger headlessDebugFor: receiver selector: selector withArgs: argsArray
"This way, the time-traveling step 1 corresponds to the first
 instruction of the method instead of the auxiliary block"

"Optionally, although not recommended, unless you know what you do"
SeekerDebugger headlessDebug: aProcess

"API main methods"
|sk| sk := SeekerDebugger headlessDebugBlock: myProgramBlock.
sk restart. "reverts execution to step 1"
sk timeTravelTo: stepNumber. "Advances or reverses the execution until reaching the step number"
sk resume. "ends the time-traveling debugging session by resuming the debugged process"
sk terminate. "terminates the debugged process"
sk step. "advances debugged execution by one bytecode"
sk stepNumber. "returns the current step number."
sk stepToEnd. "executes every bytecode of the program, until it cannot advance anymore"
sk programStates. "An iterable object representing
 all the states of the debugged program. Used mostly for queries"

Time-Traveling Queries Usage / Quick reference:

The Quick Reference pdf document is included in the repository, and can be accessed here.

User Defined Queries

Developers can use the scripting area to write their own program queries.

The Query Notation

The Query notation is a general purpose notation to write queries over collections (Any collection, not just the ones related to executions). It uses standandar selection and collection semantics, however, the only difference is that selection and collection are lazily evaluated (This should be of no concern when writing the queries.

***This is just a regular query, and not a Time-Traveling Query (TTQ). ***

Example

"In the scripting presenter, paste the following code:"

"This query obtains an OrderedCollection containing the list of all the methods
 of any step that corresponds to a message send to any method with the selector #add:".

(Query from: seeker programStates "or just use the workspace variable: programStates"
    select: [ :state | state isMessageSend and: [ state node selector = #add: ] ]
    collect: [ :state | state methodAboutToExecute ]) asOrderedCollection.
    
"Reuse predefined queries (subclasses of UserTTQ) using the #queryFrom: method
 instead of #from:. This way, the selection is applied additively instead of overridding the predefined one.
For example: "
((UTTQAllReadings queryFrom: programStates) select: [ :state| state node variable name = #each ]) asOrderedCollection.

Then, select all the code, and inspect it (right click, and select Inspect it from the context menu, or simply press cmd+i). You should see an inspector with the collection of the results.

User Time-Traveling Query.

Time-Traveling Queries are just a specific type of Query. To explain how to write one, we will start from the more generic Query form (as described in the previous point).

To transform a Query into a Time-Traveling Query (with integration in the UI)

  1. Use UserTTQ instead of the Query class.
  2. Use Autotype for collected items.
  3. Include the #bytecodeIndex key as in this example:
"In the scripting presenter, paste the following code:"
| autoResultType |
    autoResultType := AutoType new.
    (UserTTQ from: seeker programStates
        select: [ :state | state isMessageSend and: [ state node selector = #add: ] ]
        collect: [ :state | 
            autoResultType newWith
            bytecodeIndex: state bytecodeIndex;
            methodClass: state methodAboutToExecute methodClass name;
            messageSelector: state methodAboutToExecute selector;
            newColumnASD: 123; "you can add any column you want like this"
            endWith ]) showInSeeker

Then select all the code, and do it (right click, and select Do it from the context menu, or simply press cmd+d). The query should be displayed in the query tab of Seeker (you need to manually change to the tab at the moment).

UPDATE APRIL 2023

New collection style - #collectAs:

This new collection style hides AutoType, allowing a cleaner query definition. For example, the same code of the previous example, if using the new #collectAs: method, is writen as follows:

(UserTTQ from: seeker programStates
  select: [ :state | state isMessageSend and: [ state node selector = #add: ] ]
  collectAs: [ :state :res| 
    res bytecodeIndex: state bytecodeIndex;
        methodClass: state methodAboutToExecute methodClass name;
        messageSelector: state methodAboutToExecute selector;
        newColumnASD: 123 ]) showInSeeker

The Autotype instantiation and management is performed by the #collectAs: method. The two collection styles are equivalent.

Queries (and TTQ) Composition.

Queries and TTQs can be composed. Ie, they can be used as a data source for other queries.

| query1 query2 |
   query1 := (Query from: seeker programStates "or just use the workspace variable #programStates"
    select: [ :state | state isMessageSend and: [ state node selector = #add: ] ]
    collect: [ :state | state methodAboutToExecute ]).
    
    "Can be used to compose:"
    query2 := Query from: query1 select: [:state| state messageReceiver class = OrderedCollection]. 
    "Which is equivalent to:"
    query2 := query1 select: [:state| state messageReceiver class = OrderedCollection]. 
    "Finally, to trigger the query evaluation, do"
    query2 asOrderedCollection

In both examples, the selection conditions are applied in order, from the innermost ones (query1 selection predicate is applied first) to the outermost ones (query2 selection predicate is applied last). The same applies to Time-Traveling Queries (TTQs). The methods #select: and #collect: of Queries returns new Queries objects (not the results of the query).

Time-Traveling Queries Notes:

  • The Query object instantiation doesn't trigger the production of results.
  • The field bytecodeIndex is mandatory. Include it like in the example.
  • AutoType automatically creates a class (and instances. The class is not registered in the system) that serves the collection function. To make time traveling queries, it is mandatory to include the bytecodeIndex field.

Running TTQs in headless mode.

It can be done in several ways:

A. Straightforward

Use this if you already have a headless mode instance of SeekerDebugger.

myBlock := [|a|
  a:=1.
  a:= a+a ].
seeker := SeekerDebugger headlessDebugBlock: myBlock.
"Or if you want to query directly inside a method do as follows:"
seeker := SeekerDebugger headlessDebugFor: receiver selector: #aSelector withArgs:{}.

"Then just create a query, passing as argument the programStates object"
(TTQAllAssignments queryFrom: seeker programStates) asOrderedCollection inspect 

B. Scoped querying (Directly using a block's programStates)

This is recommended if you only intend to query a program contained in a block, and do not intent to perform time-travels or extra tasks, as this hides the need to initialize the debugging session. Using this will terminate the underlaying debugged process once the execution exits the scope of the programStates. Just note that the effects of the block are always reverted at the end.

myBlock := [|a|
  a:=1.
  a:= a+a ].
myBlock programStates: [ :programStates |
  |queryRes|
  queryRes := (TTQAllMessageSends queryFrom: programStates) asOrderedCollection.
  "You could run other queries if needed"
  queryRes inspect ].
  "Once the programStates scope is escaped, the underlying time-traveling
   debugging session is automatically terminated."

C. Alternative (Also directly using a block's programStates)

This is similar to the previous one in the sense that it hides the debugger, and it is simpler, but the problem is that the debugged process is not terminated. If you use it, remember to terminate the debugger.

myBlock:= [|a|
  a:=1.
  a:= a+a ].
programStates := myBlock programStates.
[(TTQAllAssignments queryFrom: programStates) inspect]
  ensure: [programStates terminate]

Other debugger usage ideas

Reversible execute block

Execute any block of your program with reversible capabilities, with a simple api.

| b |
  b := 1.
  [ b := 3 ] asReversibleDo: [ :program |
      self assert: b equals: 1.
      program runReversibly.
      self assert: b equals: 3.
      program revert.
      self assert: b equals: 1.
      program runReversibly.
      self assert: b equals: 3.
      program revert ].
      self assert: b equals: 1

See SeekerReversibleValueTest>>#testAsReversibleDo for more info.

Scoped queries

Documentation in progress.
These are queries that can be run outside a time-traveling debugging session. Just select some code, rightclick to open the context menu, and chose a scoped query. They can be use to quickly get execution (dynamic) program data from code. For example, the scoped query AllBreakpointAndHalts will list all the breakpoints hits and halts that the selected code would hit if normally executed.

What happens behind courtains is that a Seeker headless debug session is created with the selected code, and a time-traveling query is run on that program. After collecting the queries result, Seeker reverts the effects of the executed code, as if nothing had been executed. (Although, current Seeker reverse mechanism doesn't cover yet changes in the filesystem or other external resources).

Limitations and known issues.

  • Supports "Debug it" and TestCases when launched from the corresponding seeker menu entry. No support for non intentional debugging.
  • Can be launched from the Playground, and SystemBrowser (for testCase methods), and from withing any instance of the StDebugger (Including other Seeker Debugging sessions) form the Right-Click Context Menu. The user MUST choose the "Debug it with Seeker Option" in order to enable a Time-Traveling Debugging session. Otherwise, a normal StDebugger debugging session will be started (even if Seeker is displayed in the Extensions panel).
  • No complete support for test clean up at the moment.
  • Single thread executions only.
  • No UI executions support.
  • (*) The WorldMenu >> Library >> SeekerDev >> OpenSeekerDebugger Config option opens a UI with the configuration of some parameters of the debugger. Not Documented at the moment.
  • The execution reversal mechanism can only undo changes that originates from within an execution (the debugged execution call tree). Changes made from outside the execution could affect the deterministic replay of an execution.
  • Performance: Executing code with Seeker is slow. The emergency stop (STOP button in the toolbar) might be useful if a query is started and takes too long to finish. Consider closing it by force if necessary.
  • No support yet for "Debug Drive development". Modifying the debugged code during a debug session might produce problems with time-indices.
  • Not fully compatible with instrumentation:
    • Executing Seeker will remove all breakpoints in the system.
    • If Breakpoints are added later will result in undefined behavior (don't add breakpoints while using the debugger).
    • Not tested yet with metalinks.
    • Code instrumented with method proxies works, but introduces several extra instructions, making Seeker and Queries slower.
  • The "execution interpretation and reversal mechanisms" are known to have problems with:
    • Executions that perform calls on and/or modify global state UI related objects (HandMorphs, for example), which is sadly a big part of Pharo.
    • Executions that performs class installations, and removal form the system.
    • Executions that compile code (adding methods to objects and classes).
    • Explicit garbage collection calls (Although not tested).
  • ObservableSlots "extra behavior" is suspected to not be monitored by the debugger, and therefore it might be left out of the Queries and from the execution reversal mechanism. This hasn't been tested yet.

Troubleshooting

Problem: I can't open a debugger anymore. Any time I try to debug something, the Emergency debugger is shown instead of the StDebugger, even if I am not using "debug it with Seeker". What can I do?

Answer: Since Seeker performs several initalization logic (which might fail), when a failure is detected during this phase, a flag is set to prevent any opening of the StDebugger. The reason for this is that once a failure is detected, Pharo will try to open a debugger to debug the failure, which result in the image being locked. If you didn't modify Seeker or StDebugger code, then the most likely cause of the failure is a Breakpoint or a halt in your domain code (that is being executed during the initialization of the Time-Traveling mechanism). To fix this:

  1. Open the Seeker Config UI (as described in Limitations and known issues, point (*)), and click in the Reset emergency deactivation button.

This should enable the normal debugger, and with the Breakpoints and halts removed, it should bring back Seeker.

seekerdebugger's People

Contributors

maxwills avatar stevencostiou avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

seekerdebugger's Issues

Feature: add tabs for more queries

The idea is that the developer don't need to re-run a query if it was already done.
A new TTQ will create a new tab. and it will be always accessible during the session, unless manually closed.

Feature: Enable TimeTravelingSession

During a normal debugging session, have a command to turn it into a seeker debugging session.
The recording starts in the current context.
Mid context support not available yet. (although it might be possible if a context shallow copy is stored to recover the literals at the target PC). Would also require to set an offset for the PC (Or just set it directly after restarting the context without undoing).

Inlined IfNil node issues

Two bytecodes correspond to that node, the first one has the tested value in the context top, and the next one has the test result (true or false).
The problem is that currently, only the second one is considered a message send (because of "it will jump"), but the receiver of the message at that moment is wrong (cause it should be the tested value and not true or false).
TODO: Consider the message send to be the first bytecode for inlined ifNil (and similar) so the message receiver can be retrieved.
The next bytecode could be considered the return value of the ifNil message send (which never happens in application level, because of optimizations)

SeekerCurrentInterface>>methodAboutToExecute fail case

When called on some (if not all) inlined message sends, the message receiver is not properly calculated, and therefore, the methodAboutToExecute calculation is wrong.

A possible solution might be to check the AST in inlined sends, and if the message receiver of the message node is a block, then at least we can retrieve the correct method about to execute (even though we cant actually retrieve the message receiver if it was supposedly a block (prior to be inlined))

About multithreading and synchronization

To step an execution, Executor now should create an extra thread per tcu. So the TCUs are advanced by each thread, however, they synchronize with executor to know which one advances.
In theory, the threads themselves can define the execution order, which is closer to the actual scheduling of Pharo. I should record such order, and for replay, no extra threads are required, one the ecu stepping each tcu according to the records, while ignoring waits and signals?

NDS issues

While running the test ComputedSlotTest>>#testWriteComputedSlotCompiled a problematic scenario was encountered.
Screen Shot 2023-03-23 at 15 37 40
The calls shown in the image execute a non deterministic number of steps (previously risking breaking the image (freezing) on time-travels, but It has been fixed by some VM Update. Tested in Pharo12 on 2023-04-25).

Suspects:

  • A buggy UndoBlock used almost exclusively in these methods (check for objectAt:put: and related writing primitives and their undoBlock creation). Might not be raising an exception and could be silently corrupting Dictionaries or other objects during reversal. (might still be worth to check this interaction)
  • Could be related to the usage of ASTCache in the code. The concern is that Seeker could be modifying the ASTCache (during stepping) and at the same time, the debugged program modifies it. On a reversal, only the debugged program performs the undo and Seeker changes remain, which introduces an NDS.
  • Something related to announcements? I don't think it is the current problem but it is still a concern: an announcment of method compilation is sent to all suscribers in the system. If the number of suscribers change from one replay to another, an NDS is introduced.

NDS

Just a reminder of possible NDS:

  • Methods that read from file system (for example, to iterate over the files of a folder).
  • SystemOrganizer? Or any methods that read from the class of the system.
  • calls of allInstances, all subinstances.
  • Should the debugger record all external variables readings? for replay? every external method return? (as extra measure of enforcing determinism?). Same for external methods? (One or the other or both?). Make it configurable.

Add feature: Clear or keep program record.

The current version always recreate records on replay (Except OIDs, although not 100% sure).
Have a configurable flag to control this behavior: a boolean that determines if the records are recreated on each replay or only during first.
Also, add an API to clear the record. The goal is to be able to make DDD seamlessly within Seeker, if possible.

Just a random idea (Need to be refined): Additionally, add an option to change the value of variable in the current context of the debugged program (or in case of message sends, to change the sender or the arguments about to be sent. (no need to modify the return of a method), and to override the returned value of a return expression (when inside the returning method)

Feature: Navigation Bookmark

Add a system to create bookmarks for current step.
And maybe add the capacity to edit a label
step| label (defaults to node source)| stack depth| stack path? |
Should store a list that owned by the session.
In the future, it might persist but (or remembered some how), but need to think how to migrate it to the new and potentially different execution.

[Bug] Error when try to debug advanced unit test in my project

I'm trying to debug a problem with seeker in my project Molecule.
I'm debugging a test with Seeker and there are a lot of problem seems to be complicated :)

The test is MolComponentFactoryTest>>testComponentSubclassesTraitsUses

image

This video show the problem :

SeekerMolecule.mp4

After there is a loop with all messages and this is not possible to continue without stop the image.

My config :

  • Pharo-11.0.0+build.688 64 bits
  • Windows 11 Pro

StepOver is broken

A change broke the stepOver, and now the program steps to the end in some circumstances.
TODO: Fix stepping in general

Feature: Extensible list of UserSystemCalls

Imagine having a class named UserSystemCall.
Each subclass targets a method (lazily, so it is Class+ selector rather than a CompiledMethod), an action to use as replacement (or not), a rule for enable or disable, and a groupName.
Add a new UI (like SeekerConfig) to enable or disable these methods.
SystemCalls are not undoable, and the debugger will not query inside them. In turn, they act as if it is only one instruction, and they are interpreted faster. It could be useful to enable
Possible use cases:

  • Treat announcements as system calls.
  • Ignore drawing or layout calls in general (there are a lot!).

StepOver and inlined blocks

Performing StepOver operations when debugging with Seeker doesn't have the same behavior as when the same action is performed using the StDebugger only (without Seeker) in case of inlined loops blocks.

Symptoms

For example, consider the code:

1 to: 10: do: [:i|
    Transcript show: i asString; cr.
 ]

If using the StDebugging only (No Seeker), performing stepOver actions until the end of the execution would make the developer to go through the 10 iterations of the block.
If Seeker is enabled, the developer would only go through the first iteration of the block, and then continue with the rest of the program.

Reason

Since Seeker tries to keep count of every executed bytecode of the execution, the StepOver operation was rewritten and "simulated" using the basic bytecode stepping of Seeker. The implemented heuristic relies on part on information of the ast and the activated contexts to know when to stop executing bytecodes to perform a stepOver.
The problem is that certain loops are inlined, which means that while the AST corresponds to a block, the bytecode is executed in the same context of the definition method (and not in the context that would normally introduced by a non inlined block), which introduces a special case for the heuristic that is currently not handled.

Possible fixes.

  • Implement a copy of the original StepOver mechanism performed by the StDebugger, but in Seeker terms (Might be an overkill solution, since there is a lot of methods called that would need to be converted to Seeker terms (including methods from Context).
  • Implement the heuristic correction to properly handle these inlined loop cases.

Feature: SeekerPlayground

  • Opens a playground with some bindings:
    seeker (or seekerDebugger?)
    programStates

  • Has an example script. (Similar to the scripting panel).

The only problem is that bindings are worthless after debugger is closed.
Im not sure if realding a Playground page will try to restore old bindings or it is just the text. (later case works well for us)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.