diff --git a/help/TDBHistoryExplorerHelp/structure.html b/help/TDBHistoryExplorerHelp/structure.html index 3565a315..4570f45e 100644 --- a/help/TDBHistoryExplorerHelp/structure.html +++ b/help/TDBHistoryExplorerHelp/structure.html @@ -1 +1 @@ -Structure

A history explorer consts of three regions:



In the title bar, the explored object instance is revealed.

The control bar on top of the window contains a code pane for the current expression. You can enter any Smalltalk expression here whose changes over the recorded time you are interested in. When you accept it (Cmd + S), the expression is evaluated against all historic versions of the explored object instance (that is mapped to the receiver, self). You can also print it, inspect it, explore it, or debug it (experimental) the expression from here through the yellow button menu or the usual shortcuts.

Next to the code pane, you can find two buttons for managing the appearance of the history:

Spawn brings up the currently viewed history in another history explorer, where you can ask follow-up questions about the result.
The representation button in the corner lets you choose an alternative representation mode for the history. Currently supported representations are a list of values (default) and a details view that shows an expanded form of the current selection. The latter can be used for viewing longer printStrings, images, or formatted texts.

The contents pane below the control bar displays the history of the expression evaluated against the explored object instance.

On the right side, you can see all results of the expression for all recorded points in time if you have chosen the list representation in the control bar, or a single result if you are in details mode.

In the yellow-button menu of the list, you can inspect or explore a snapshot of the selection or debug the snapshot to observe how it was created. You can also choose debug here to open a new trace debugger on the selected context causing the selected change.

On the left side, you can find a reduced context tree that only contains all contexts that are responsible for any change of the explored expression. By default, this tree is displayed in a compact form. You can alternate the tree display from the yellow-button menu under compact labels. Fiddling around with these options is a great way to understand the tree display.

Note that each inner node in the tree can be collapsed or expanded and only shows a value from the slice on its own when it is collapsed. When you select an expanded node while in details mode, the details pane displays all values from all leaves within the selected node.



A note on fray outs: Even if the results of a query contains of multiple values, it is possible that the representation (e.g., the printString) of some values varies within the associated time range of the value. In this case, the history explorer will display a fray out error for the relevant items. In the details mode, all frayed out elements will be displayed on top of each other. See exampleDrawing for an example. \ No newline at end of file +Structure

A history explorer consts of three regions:



In the title bar, the explored object instance is revealed.

The control bar on top of the window contains a code pane for the current expression. You can enter any Smalltalk expression here whose changes over the recorded time you are interested in. When you accept it (Cmd + S), the expression is evaluated against all historic versions of the explored object instance (that is mapped to the receiver, self). You can also print it, inspect it, explore it, or debug it (experimental) the expression from here through the yellow button menu or the usual shortcuts.

Next to the code pane, you can find two buttons for managing the appearance of the history:

Spawn brings up the currently viewed history in another history explorer, where you can ask follow-up questions about the result.
The representation button in the corner lets you choose an alternative representation mode for the history. Currently supported representations are a list of values (default) and a details view that shows an expanded form of the current selection. The latter can be used for viewing longer printStrings, images, or formatted texts.

The contents pane below the control bar displays the history of the expression evaluated against the explored object instance.

On the right side, you can see all results of the expression for all recorded points in time if you have chosen the list representation in the control bar, or a single result if you are in details mode.

In the yellow-button menu of the list, you can inspect or explore a snapshot of the selection or debug the snapshot to observe how it was created. You can also choose debug here to open a new trace debugger on the selected context causing the selected change.

On the left side, you can find a reduced context tree that only contains all contexts that are responsible for any change of the explored expression. By default, this tree is displayed in a compact form. You can alternate the tree display from the yellow-button menu under compact labels. Fiddling around with these options is a great way to understand the tree display.

Note that each inner node in the tree can be collapsed or expanded and only shows a value from the slice on its own when it is collapsed. When you select an expanded node while in details mode, the details pane displays all values from all leaves within the selected node.



A note on fray outs: Even if the results of a query contains of multiple values, it is possible that the representation (e.g., the printString) of some values varies within the associated time range of the value. In this case, the history explorer will display a fray out error for the relevant items. In the details mode, all frayed out elements will be displayed on top of each other. See exampleDrawing for an example. \ No newline at end of file diff --git a/help/basicNavigation.html b/help/basicNavigation.html index 5e74e681..4e121507 100644 --- a/help/basicNavigation.html +++ b/help/basicNavigation.html @@ -1 +1 @@ -Basic Navigation

You can use the stepping buttons to navigate through the program trace. The trace debugger uses lazy stepping, i.e., the program will be only executed up to the currently watched position. If you step forward, the program will be advanced accordingly (just like in the regular debugger). If you step back, the trace debugger displays the recorded historic state of the program execution. This includes the selected context and the selected expression, the variables in the inspectors, and even the result of print-its in the inspectors.

Close the debugger and proceed the program normally.
Rewind the selected context to its start. Other than in the normal debugger, the code will not be executed again, but the historic execution will be displayed again.
Step into the next message send.
Step over the next message send.
Step to the next expression in the same method, including expressions in blocks.
Step back the previous message send.
Select the expression that will be executed next.
Jump to the sender of the current context.

Alternatively to the stepping buttons, you can also navigate through the context tree: Red-click any context to jump to the time where it was active. Shift + red-click any context to select its sender at its activation time. \ No newline at end of file +Basic Navigation

You can use the stepping buttons to navigate through the program trace. The TraceDebugger uses lazy stepping, i.e., the program will be only executed up to the currently watched position. If you step forward, the program will be advanced accordingly (just like in the regular debugger). If you step back, the TraceDebugger displays the recorded historic state of the program execution. This includes the selected context and the selected expression, the variables in the inspectors, and even the result of print-its in the inspectors.

Close the debugger and proceed the program normally.
Rewind the selected context to its start. Other than in the normal debugger, the code will not be executed again, but the historic execution will be displayed again.
Step into the next message send.
Step over the next message send.
Step to the next expression in the same method, including expressions in blocks.
Step back the previous message send.
Select the expression that will be executed next.
Jump to the sender of the current context.

Alternatively to the stepping buttons, you can also navigate through the context tree: Red-click any context to jump to the time where it was active. Shift + red-click any context to select its sender at its activation time. \ No newline at end of file diff --git a/help/implementation.html b/help/implementation.html new file mode 100644 index 00000000..10253670 --- /dev/null +++ b/help/implementation.html @@ -0,0 +1 @@ +How does the TraceDebugger work?

For program tracing, the program is executed in a specialized code simulator that overrides instructions for sending messages (e.g., send, superSend) and for performing side-effects (e.g., popIntoRcvr, primitiveAtPut, push). All message sends are recorded in a tree and all changed object slots are stored in a sparse time-dependent memory structure before they are overwritten. For time-traveling, the tree is traversed using a cursor. For accessing historic objects, a proxy evaluates all messages sent to an object in another specialized simulator (retracing simulator) that emulates historic states for the requested point in time by forwarding read primitives (e.g., pushRcvr, primitiveAt) to the recorded memory. For gathering state changes in the History Explorer efficiently, the query is evaluated in a range retracing simulator with vectorization and fork semantics.

To dive into the implementation details, in addition to the package overview on GitHub, some good starting points might be the class comments in TraceDebugger and TDBCursor.

Current Limitations:
* High performance. While (sufficiently) fast enough for most small to medium workloads, tracing very compute- or mem-intensive operations may require more time (ex.: Compiler/decompiler invocation: <1s, HTTPS request: <10s, tool building: <5m, complex rendering: minutes up to hours).
* Not a dataflow analyzer: The TraceDebugger does not track dataflow events (e.g., argument passing) but only state changes.
* No tracing of external states/events for FFI/OSProcess or custom VM modules.
* No support for advanced language concepts such as identity forwarding/write barriers. \ No newline at end of file diff --git a/help/index.html b/help/index.html index 724ca096..dc40a37c 100644 --- a/help/index.html +++ b/help/index.html @@ -1 +1 @@ -Trace Debugger

- Welcome
- Entry points
- Structure
- Basic Navigation
- Advanced Navigation
- Finding contexts
- Context filters
- History Explorer
- Scripting APIs
- Learn more \ No newline at end of file +TraceDebugger

- Welcome
- Entry points
- Structure
- Basic Navigation
- Advanced Navigation
- Finding contexts
- Context filters
- History Explorer
- Scripting APIs
- Implementation
- Learn more \ No newline at end of file diff --git a/help/learnMore.html b/help/learnMore.html index ff645654..aede8ae5 100644 --- a/help/learnMore.html +++ b/help/learnMore.html @@ -1 +1 @@ -Learn more

To learn more about the project and further plans about the protoype, please visit the GitHub repository:

https://github.com/hpi-swa-lab/squeak-tracedebugger

In particular, there are many open issues for further ideas, bugs etc.:

https://github.com/hpi-swa-lab/squeak-tracedebugger/issues

To dive into the implementation details, in addition to the package overview on GitHub, some good starting points might be the class comments in TraceDebugger and TDBCursor.

If you have any feedback or ideas, please let me know and drop an issue! You can also do this directly via the window menu of the trace debugger. By the way, it is also possible from there to update your installed version or perform some special process-related operations. Last not least, you can also check out some experimental preferences from there.

\ No newline at end of file +Learn more

To learn more about the project and related academic publications, please visit our GitHub repository:

https://github.com/hpi-swa-lab/squeak-tracedebugger

In particular, there are many open issues for further ideas, bugs etc.:

https://github.com/hpi-swa-lab/squeak-tracedebugger/issues

To understand the implementation, please read the previous section of this chapter.

If you have any feedback or ideas, please let me know and drop an issue! You can also do this directly via the window menu of the TraceDebugger. By the way, it is also possible from there to update your installed version or perform some special process-related operations. Last not least, you can also check out some experimental preferences from there.

\ No newline at end of file diff --git a/help/scriptingAPIs.html b/help/scriptingAPIs.html index 62953efa..373af943 100644 --- a/help/scriptingAPIs.html +++ b/help/scriptingAPIs.html @@ -1 +1 @@ -Scripting APIs

If you are not happy with what the present tooling of the trace debugger offers, of course you are invited to open an issue in the repository! Apart from that, here is short summary of relevant interfaces that you might want to use to assemble some information about a program trace yourself. Hand me a workspace with all code snippets.

Recording a program trace
cursor := TDBCursor traceForBlock: ['\w+' asRegex].

Accessing the context tree (like the trace debugger does)
root := cursor context tdbFullStack first. "[] in UndefinedObject>>DoIt"
children := cursor childContextsFor: root.
firstChild := children first. "ByteString(String)>>asRegex"
someContext := cursor findContextSuchThat: [:ctx | ctx method == (PositionableStream class>>#on:)]. "ReadStream class(PositionableStream class)>>on:"
someContext sender. "RxParser>>parse:"
stream := cursor returnValueFor: someContext ifPresent: #yourself. "a ReadStream"

Do not manually access the trace instances. They are an implementation detail of the cursor and subjected to change. See the class comment for more information.

Stepping through a program trace (like the trace debugger does)
See the stepping protocol and TDBCursor exampleStepping.

For more information on the cursor interface, do this:
cursor exploreProtocol.

Accessing historic values (like the snapshot inspectors do)
All historic values are stored in the cursor's memory and can be conveniently accessed through transparent proxies that just behave like the requested object at a certain time. Proxies can be sent further messages to access their state, all relevant answers will be wrapped in further proxy instances.

(cursor object: stream atTime: 0) position. "nil"
(cursor object: stream atTime: (cursor maxTimeIndexFor: someContext)) position. "0"
(cursor object: stream atTime: (cursor maxTimeIndexFor: root)) position. "3"

Note that all side effects during a proxy access will be isolated, so for instance, sending a proxy the message #printOn: will not actually modify the passed stream instance.

(cursor object: stream atTime: (cursor maxTimeIndexFor: someContext)) next. "$\"

For more details, refer to the comment in TDBProxy. If you ever need to escape from proxy hell, send aProxy copy tdbproxyYourself. See flag #proxyHacks.

Accessing historic ranges (like the history explorer does)
cursor object: stream collect: #position. "a TDBMemorySlice(... (0 to: 28) -> nil (29 to: 55) -> 0 (56 to: 174) -> 1 (175 to: 299) -> 2 (300 to: 2339) -> 3) ...)"
cursor memory object: stream atTimes: ((cursor minTimeIndexFor: someContext) to: (cursor maxTimeIndexFor: someContext)) collect: #position. "a TDBMemorySlice(... (18 to: 28) -> nil (29 to: 36) -> 0) ...)"

The following example illustrates that historic range queries typically need to perform a lot of error handling and need to copy objects for later reuse (#proxyHacks):

cursor object: stream collect: [:ea | [(ea originalContents first: ea position) copy] on: Error do: #yourself]. "a TDBMemorySlice((0 to: 23) -> MessageNotUnderstood: UndefinedObject>>first: (24 to: 28) -> MessageNotUnderstood: UndefinedObject>>- (29 to: 55) -> '' (56 to: 174) -> '\' (175 to: 299) -> '\w' (300 to: 2339) -> '\w+')"

The resulting memory slices can be accessed and transformed. Noticeably, when you operate on the discretized values from a memory slice, your code will likely operate on transparent range proxies that represent a value in an entire time range. A range proxy will behave similar to a normal proxy, but if it is sent further messages that reveal any state that has changed within the time range, the proxy will signal a fray out error. For more details, please refer to the class comments in TDBMemorySlice and TDBRangeProxy. \ No newline at end of file +Scripting APIs

If you are not happy with what the present tooling of the TraceDebugger offers, of course you are invited to open an issue in the repository! Apart from that, here is short summary of relevant interfaces that you might want to use to assemble some information about a program trace yourself. Hand me a workspace with all code snippets.

Recording a program trace
cursor := TDBCursor traceForBlock: ['\w+' asRegex].

Accessing the context tree (like the TraceDebugger does)
root := cursor context tdbFullStack first. "[] in UndefinedObject>>DoIt"
children := cursor childContextsFor: root.
firstChild := children first. "ByteString(String)>>asRegex"
someContext := cursor findContextSuchThat: [:ctx | ctx method == (PositionableStream class>>#on:)]. "ReadStream class(PositionableStream class)>>on:"
someContext sender. "RxParser>>parse:"
stream := cursor returnValueFor: someContext ifPresent: #yourself. "a ReadStream"

Do not manually access the trace instances. They are an implementation detail of the cursor and subjected to change. See the class comment for more information.

Stepping through a program trace (like the TraceDebugger does)
See the stepping protocol and TDBCursor exampleStepping.

For more information on the cursor interface, do this:
cursor exploreProtocol.

Accessing historic values (like the snapshot inspectors do)
All historic values are stored in the cursor's memory and can be conveniently accessed through transparent proxies that just behave like the requested object at a certain time. Proxies can be sent further messages to access their state, all relevant answers will be wrapped in further proxy instances.

(cursor object: stream atTime: 0) position. "nil"
(cursor object: stream atTime: (cursor maxTimeIndexFor: someContext)) position. "0"
(cursor object: stream atTime: (cursor maxTimeIndexFor: root)) position. "3"

Note that all side effects during a proxy access will be isolated, so for instance, sending a proxy the message #printOn: will not actually modify the passed stream instance.

(cursor object: stream atTime: (cursor maxTimeIndexFor: someContext)) next. "$\"

For more details, refer to the comment in TDBProxy. If you ever need to escape from proxy hell, send aProxy copy tdbproxyYourself. See flag #proxyHacks.

Accessing historic ranges (like the history explorer does)
cursor object: stream collect: #position. "a TDBMemorySlice(... (0 to: 28) -> nil (29 to: 55) -> 0 (56 to: 174) -> 1 (175 to: 299) -> 2 (300 to: 2339) -> 3) ...)"
cursor memory object: stream atTimes: ((cursor minTimeIndexFor: someContext) to: (cursor maxTimeIndexFor: someContext)) collect: #position. "a TDBMemorySlice(... (18 to: 28) -> nil (29 to: 36) -> 0) ...)"

The following example illustrates that historic range queries typically need to perform a lot of error handling and need to copy objects for later reuse (#proxyHacks):

cursor object: stream collect: [:ea | [(ea originalContents first: ea position) copy] on: Error do: #yourself]. "a TDBMemorySlice((0 to: 23) -> MessageNotUnderstood: UndefinedObject>>first: (24 to: 28) -> MessageNotUnderstood: UndefinedObject>>- (29 to: 55) -> '' (56 to: 174) -> '\' (175 to: 299) -> '\w' (300 to: 2339) -> '\w+')"

The resulting memory slices can be accessed and transformed. Noticeably, when you operate on the discretized values from a memory slice, your code will likely operate on transparent range proxies that represent a value in an entire time range. A range proxy will behave similar to a normal proxy, but if it is sent further messages that reveal any state that has changed within the time range, the proxy will signal a fray out error. For more details, please refer to the class comments in TDBMemorySlice and TDBRangeProxy. \ No newline at end of file diff --git a/help/structure.html b/help/structure.html index afe4e055..de7217e9 100644 --- a/help/structure.html +++ b/help/structure.html @@ -1 +1 @@ -Structure of the TraceDebugger

The user interface of the Trace Debugger is largely inspired by Squeak's default debugger, so we will only describe the differences here:



In the title of the window, you can see the current time index. This is the point in time of the trace where the program has halted. As you step through the trace, this index will update.

On the top of the window, you can see the context tree. This tree displays all methods that are active or have been activated at an early point in the program trace. Red-click on any context to inspect its execution in the debugger. By hovering any context, you can also see a quick preview of its invocation. If you have enabled the "Balloon help in message lists" preference, you will also see a preview of the method source. By yellow-clicking on any context, you can also find many other commands which will be discussed in the following pages.



Below the context tree, there is the button bar which provides options to step through the program trace. If you have enabled the "Extra debugger buttons" preference, you will also find further buttons for browsing the code here.

The third view is the code pane which displays the source of the current activated method. The initial selection marks the expression that will executed next when you move forward the program.

The final view at the bottom is the inspectors which allow you to inspect the state of the receiver and the context interactively. Red-click any field to view its value, or yellow-click the field and choose inspect/explore to spawn a separate tool on the selected value. This tool displays a snapshot of the value that will not change when you continue the program.



You can also perform do-its and print-its in the inspector/explorer value panes. You can add a custom inspector field by typing and selecting an expression into the value pane, yellow-clicking it, and choosing "add as field":



As you continue stepping, the values in the inspector panes will be updated automatically. \ No newline at end of file +Structure of the TraceDebugger

The user interface of the TraceDebugger is largely inspired by Squeak's default debugger, so we will only describe the differences here:



In the title of the window, you can see the current time index. This is the point in time of the trace where the program has halted. As you step through the trace, this index will update.

On the top of the window, you can see the context tree. This tree displays all methods that are active or have been activated at an early point in the program trace. Red-click on any context to inspect its execution in the debugger. By hovering any context, you can also see a quick preview of its invocation. If you have enabled the "Balloon help in message lists" preference, you will also see a preview of the method source. By yellow-clicking on any context, you can also find many other commands which will be discussed in the following pages.



Below the context tree, there is the button bar which provides options to step through the program trace. If you have enabled the "Extra debugger buttons" preference, you will also find further buttons for browsing the code here.

The third view is the code pane which displays the source of the current activated method. The initial selection marks the expression that will executed next when you move forward the program.

The final view at the bottom is the inspectors which allow you to inspect the state of the receiver and the context interactively. Red-click any field to view its value, or yellow-click the field and choose inspect/explore to spawn a separate tool on the selected value. This tool displays a snapshot of the value that will not change when you continue the program.



You can also perform do-its and print-its in the inspector/explorer value panes. You can add a custom inspector field by typing and selecting an expression into the value pane, yellow-clicking it, and choosing "add as field":



As you continue stepping, the values in the inspector panes will be updated automatically. \ No newline at end of file diff --git a/screenshots/exampleMorphLayout.gif b/screenshots/exampleMorphLayout.gif index 6157a950..bf5ad037 100644 Binary files a/screenshots/exampleMorphLayout.gif and b/screenshots/exampleMorphLayout.gif differ