GDBDebugger.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Sun, 04 Feb 2018 21:18:15 +0000
changeset 105 1d4ca4370d05
parent 104 4add55336dfe
child 106 12c96f17fc53
permissions -rw-r--r--
Report gdb's exist status in `GDBExitEvent` in case `gdb` terminates prematurely ...for exampple, ig GDB segfaults due to a bug. Yes, that may happen, bugs are even in debuggers.

"
jv:libgdbs - GNU Debugger Interface Library
Copyright (C) 2015-now Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
"{ Package: 'jv:libgdbs' }"

"{ NameSpace: Smalltalk }"

Object subclass:#GDBDebugger
	instanceVariableNames:'connection commandSequenceNumber inferiorStateSequenceNumber
		inferiors breakpoints selectedInferior selectedThread
		selectedFrame prettyPrintingEnabled frameFiltersEnabled
		finalizationRegistry'
	classVariableNames:''
	poolDictionaries:'GDBCommandStatus'
	category:'GDB-Core'
!

!GDBDebugger class methodsFor:'documentation'!

copyright
"
jv:libgdbs - GNU Debugger Interface Library
Copyright (C) 2015-now Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
! !

!GDBDebugger class methodsFor:'instance creation'!

new    
    ^ self newWithProcess: GDBProcess new

    "Modified: / 12-12-2017 / 21:15:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

newWithProcess: aGDBProcess
    ^ self basicNew initializeWithProcess: aGDBProcess

    "Created: / 20-06-2014 / 21:44:34 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'accessing'!

announcer
    ^ connection eventAnnouncer.

    "Created: / 02-06-2014 / 23:06:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

breakpoints
    breakpoints isNil ifTrue:[ 
        breakpoints := List new.
    ].
    ^ breakpoints

    "Created: / 06-07-2017 / 16:06:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

consoleInput
    ^ connection consoleInput

    "Created: / 31-05-2017 / 23:20:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-06-2017 / 23:13:23 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

consoleOutput
    ^ connection consoleOutput

    "Created: / 31-05-2017 / 23:20:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-06-2017 / 23:13:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

inferiorForId: id
    ^ inferiors ? #() detect:[:e | e id = id ] ifNone:[ 
        self error: ('No inferior (thread group) with id ''%1'' found!!' bindWith: id)
    ].

    "Created: / 07-09-2014 / 21:22:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

inferiorStderr
    ^ connection inferiorPTY master

    "Created: / 09-06-2014 / 10:01:21 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 09-06-2014 / 18:26:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

inferiorStdin
    ^ connection inferiorPTY master

    "Created: / 09-06-2014 / 10:00:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 09-06-2014 / 18:27:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

inferiorStdout
    ^ connection inferiorPTY master

    "Created: / 09-06-2014 / 10:01:16 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 09-06-2014 / 18:27:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

inferiors
    ^ inferiors ? #()

    "Modified: / 06-07-2017 / 16:06:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

selectedInferior
    selectedInferior isNil ifTrue:[ 
        inferiors size == 1 ifTrue:[ 
            ^ inferiors anElement.
        ].
    ].
    ^ selectedInferior

    "Created: / 07-09-2014 / 23:02:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'accessing-private'!

currentInferiorStateSequnceNumber
    ^ inferiorStateSequenceNumber

    "Created: / 19-06-2014 / 22:22:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

finalizationRegistry
    ^ finalizationRegistry
!

nextCommandSequnceNumber
    commandSequenceNumber := commandSequenceNumber + 1.
    commandSequenceNumber == SmallInteger maxVal ifTrue:[ 
        commandSequenceNumber := 0.
    ].
    ^ commandSequenceNumber

    "Created: / 02-06-2014 / 23:48:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

nextInferiorStateSequnceNumber
    inferiorStateSequenceNumber := inferiorStateSequenceNumber + 1.
    inferiorStateSequenceNumber == SmallInteger maxVal ifTrue:[
        inferiorStateSequenceNumber := 0.
    ].
    ^ inferiorStateSequenceNumber

    "Created: / 02-06-2014 / 23:48:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'commands'!

send: command
    "Execute given `command` and wait until it finishes and return its result.
     `command` may be either a GDBCommand or string, in which case it will
     be parsed into a GDBCommand. 
   "
    ^ self send:command andWait:true.

    "Created: / 03-06-2014 / 00:10:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 08-03-2015 / 05:50:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

send:command andWait:wait 
    "Sends given `command` to GDB. If `wait` is true, wait for
     command to finish and return the command result.  
     Otherwise, return nil immediately.

     If `wait` is true and if command results in an error GDBError
     is thrown.

     `command` may be either a GDBCommand or string, in which case it will
     be parsed into a GDBCommand.           
    "
    
    | blocker result |

    self assert: self isConnected.
    ^ wait ifTrue:[
        blocker := Semaphore new.
        self send: command andWithResultDo: [ :r | 
            result := r.
            blocker signal
        ].
        blocker wait.
        result isError ifTrue:[
            GDBCommandFailedError raiseForResult: result.
        ].
        result.
    ]
    ifFalse:[
        | cmd |

        cmd := command.
        cmd isString ifTrue:[
            cmd := GDBCLICommand new value:cmd.
        ].    
        connection pushEvent:(GDBCommandEvent new command:cmd).
        nil.
    ]

    "Created: / 02-06-2014 / 23:45:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-02-2018 / 00:21:16 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

send:command andWaitFor:eventHandlers
    "Sends given `command` to GDB and then wait for events mathing 
     `eventHandlers`. 

     Params:
     `command`      may be either a GDBCommand or string, in which case it will
                    be parsed into a GDBCommand.
     `eventHandler` may be either nil or event class or one arg block or collection
                    of event classes or blocks.
                    - If nil then do not wait for anything (use nil for async send)
                    - If event class, then wait for an event of that class. Note, that
                      subclasses are handled too.
                    - If block, then wait for an event for which the block returns true.
                    - If collectio, then wait for a sequence of events, each matched as above.

     Returns:       a matching event or events (in case a collection of handlers has been passes)"

    ^ self send: command andWaitFor: eventHandlers withTimeoutMs: nil

    "Created: / 08-03-2015 / 06:03:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

send:command andWaitFor:eventHandlers withTimeoutMs:timeout 
    "Sends given `command` to GDB and then wait for events mathing 
     `eventHandlers`. Raise a TimeoutError if expected events don't arrive
     in specified time.

     Params:
     `command`      may be either a GDBCommand or string, in which case it will
                    be parsed into a GDBCommand.
     `eventHandler` may be either nil or event class or one arg block or collection
                    of event classes or blocks.
                    - If nil then do not wait for anything (use nil for async send)
                    - If event class, then wait for an event of that class. Note, that
                      subclasses are handled too.
                    - If block, then wait for an event for which the block returns true.
                    - If collectio, then wait for a sequence of events, each matched as above.
     `timeout`       wait at most that much milliseconds, throw TimeoutError otherwise. If `timeout` == mil
                    then wait for indefinitly

     Returns:       a matching event or events (in case a collection of handlers has been passes)"
    
    | commandObject |

    self assert: self isConnected.
    command isString ifTrue:[
        commandObject := (GDBMIParser on:command) parseCommand.
        commandObject token:self nextCommandSequnceNumber.
    ] ifFalse:[
        commandObject := command.
        commandObject token isNil ifTrue:[
            commandObject token:self nextCommandSequnceNumber.
        ].
    ].
    ^ self 
        do:[ connection pushEvent:(GDBCommandEvent new command: commandObject) ] 
        andWaitFor: eventHandlers 
        withTimeoutMs: timeout

    "Created: / 07-03-2015 / 11:38:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (format): / 04-02-2018 / 00:20:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

send:command andWithResultDo: block
    "Sends given `command` to GDB and return nil immediately. Once the command
     is processed and result returned, evaluate `block` passing the
     result (as instance of GDBCommandResult)

     The `command` may be either a GDBCommand or string, in which case it will
     be parsed into a GDBCommand.

     When calling this method from a UI code, i.e., from an UI even loop, keep
     in mind that the block will be evaluated from within GDB's internal event
     dispatch loop.

     It's is a gooc practice not to do anything real within the block but just
     grab the result and lt it processed within UI event loop, e.g.

         doSomeAction

             debugger send: (GDBMI_data_read_memory arguments:...) 
                      andWithResultDo:[ :result | self sensor pushUserEvent: #updateWithReslt: with: result ].
    "
    
    | cmd  token handler1  handler2  result |

    self assert: self isConnected.
    cmd := command.
    cmd isString ifTrue:[
        cmd := GDBCLICommand new value:cmd.
    ].
    token := self nextCommandSequnceNumber.
    cmd token:token.
    self assert:Processor activeProcess ~~ connection eventDispatchProcess
        message:'Cannot send commands from within event dispatching process. Would deadlock'.                
    handler1 := [:ev | 
        ev token == token ifTrue:[
            connection eventAnnouncer unsubscribe:handler1.
            result := ev result.
            connection eventAnnouncerInternal when:GDBEventSetProcessingFinished
                do:handler2.
        ]
    ].
    handler2 := [:ev | 
        connection eventAnnouncerInternal unsubscribe:handler2.
        block value: result.
    ].
    connection eventAnnouncer when:GDBCommandResultEvent do:handler1.
    connection pushEvent:(GDBCommandEvent new command:cmd).
    ^ nil

    "Created: / 26-01-2018 / 21:47:34 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (format): / 04-02-2018 / 00:20:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'commands - API'!

attach: aStringOrInteger
    "Attach to a running process.
     API equivalent to CLI command:

        (gdb) attach <aStringOrInteger>
    "
    self send:(GDBMI_target_attach arguments:(Array with: aStringOrInteger asString)).

    "Created: / 05-06-2017 / 17:08:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 25-01-2018 / 23:28:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

enableFrameFilters
    "Enables frame filters & frame decorators for MI interface. Once enabled,
     cannot be turned off.

     @see GDBMI_enable_frame_filters"

    | result |
    result := self send: GDBMI_enable_frame_filters new.
    result isDone ifTrue:[ 
        frameFiltersEnabled := true.
    ] ifFalse:[ 
        GDBError raiseErrorString: 'failed to enable frame filters'
    ].

    "Created: / 12-06-2017 / 09:29:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

enablePrettyPrinting
    "Enables pretty printing for MI interface. Once enabled,
     cannot be turned off.

     @see GDBMI_enable_pretty_printing"

    | result |
    result := self send: GDBMI_enable_pretty_printing new.
    result isDone ifTrue:[ 
        prettyPrintingEnabled := true.
    ] ifFalse:[ 
        GDBError raiseErrorString: 'failed to enable pretty printing'
    ].

    "Created: / 12-06-2017 / 09:29:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

executable: aStringOrFilename
    "Sets the executable to debug. 
     API equivalent to CLI command:

        (gdb) exec-file <aStringOrFilename>
    "
    self executable: aStringOrFilename arguments: nil

    "Created: / 28-02-2015 / 00:19:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 05-06-2017 / 17:06:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

executable: aStringOrFilename arguments: anArray"of String"
    "Sets the executable to debug and argument to pass to it.
     API equivalent to CLI command:

        (gdb) exec-file <aStringOrFilename>   
        (gdb) set args <anArray>
    "

    self send:(GDBMI_file_exec_and_symbols arguments: (Array with: aStringOrFilename asString)).
    anArray notEmptyOrNil ifTrue:[ 
        self send: (GDBMI_exec_arguments arguments: anArray).
    ].

    "Created: / 05-06-2017 / 17:05:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 25-01-2018 / 23:28:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

selectFrame: aGDBFrame
    "
    Set the context frame to given frame. This frame is then
    the contex frame for other commands, like finish, info frame
    and so on
      "
    self send:(GDBMI_thread_select new arguments:(Array with:aGDBFrame thread id))andWait:false.
    self send:(GDBMI_stack_select_frame new arguments:(Array with:aGDBFrame level)) andWait:false.

    "Created: / 01-02-2018 / 22:27:16 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

selectThread: aGDBThread
    "
    Set the context thread to given thread. This thread is then
    the contex thread for other commands (if no thread id is given)
    "
    self send:(GDBMI_thread_select new arguments:(Array with:aGDBThread id))andWait:false.

    "Created: / 01-02-2018 / 22:25:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'evaluating'!

do: block andWaitFor:eventHandlers
    "Evaluates a given block and then wait for events.
     `eventHandlers`. 
     Params:
     `block`        block to evaluate
     `eventHandler` may be either nil or event class or one arg block or collection
                    of event classes or blocks.
                    - If nil then do not wait for anything (use nil for async send)
                    - If event class, then wait for an event of that class. Note, that
                      subclasses are handled too.
                    - If block, then wait for an event for which the block returns true.
                    - If collection, then wait for a sequence of events, each matched as above.
     Returns:       a matching event or events (in case a collection of handlers has been passed)"

    ^ self do: block andWaitFor: eventHandlers withTimeoutMs: nil

    "Created: / 08-03-2015 / 07:30:36 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 12-06-2017 / 09:31:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

do: block andWaitFor:eventHandlers withTimeoutMs:timeout 
    "Evaluates a given block and then wait for events.
     `eventHandlers`. Raise a TimeoutError if expected events don't arrive
     in specified time.

     Params:
     `block`        block to evaluate
     `eventHandler` may be either nil or event class or one arg block or collection
                    of event classes or blocks.
                    - If nil then do not wait for anything (use nil for async send)
                    - If event class, then wait for an event of that class. Note, that
                      subclasses are handled too.
                    - If block, then wait for an event for which the block returns true.
                    - If collectio, then wait for a sequence of events, each matched as above.
     `timeout`       wait at most that much milliseconds, throw TimeoutError otherwise. If `timeout` == mil
                    then wait for indefinitly

     Returns:       a matching event or events (in case a collection of handlers has been passes)"
    
    | handlersArray handlerFinal eventsArray blocker |

    eventHandlers isNil ifTrue:[
        "/ Asynchronous send...
        self assert:timeout isNil.
        block value.
        ^ nil
    ].
    "/ Synchronous send...
    self assert:Processor activeProcess ~~ connection eventDispatchProcess
        message:'Cannot send commands from within event dispatching process. Would deadlock'.
    eventHandlers isCollection 
        ifTrue:[ handlersArray := eventHandlers ]
        ifFalse:[ handlersArray := Array with:eventHandlers ].
    eventsArray := Array new:handlersArray size.
    1 to:handlersArray size do:[:i | 
        | handler |

        handler := handlersArray at:i.
        handlersArray at:i
            put:[:event | 
                | matches |

                matches := handler isBlock ifTrue:[ handler value:event ] ifFalse:[ event isKindOf:handler ].
                (matches and:[ i == 1 or:[ (eventsArray at:i - 1) notNil ] ]) ifTrue:[
                    eventsArray at:i put:event.
                    self announcer unsubscribe:(handlersArray at:i).
                    i == handlersArray size ifTrue:[
                        connection eventAnnouncerInternal when: GDBEventSetProcessingFinished do: handlerFinal.
                    ].
                ].
            ].
        self announcer when:GDBEvent do:(handlersArray at:i).
    ].
    handlerFinal := [ :event | connection eventAnnouncerInternal unsubscribe: handlerFinal. blocker signal ].  
    blocker := Semaphore new.
    block value.
    [
        (blocker waitWithTimeoutMs:timeout) isNil ifTrue:[
            eventsArray := nil.
        ].
    ] ensure:[
        handlersArray do:[:handler | 
            handler notNil ifTrue:[
                self announcer unsubscribe:handler
            ]
        ].
    ].
    eventsArray isNil ifTrue:[
        (TimeoutError newException)
            parameter:timeout;
            raise.
    ].
    ^ eventHandlers isCollection ifTrue:[ eventsArray ] ifFalse:[ eventsArray first ]

    "Created: / 08-03-2015 / 07:28:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'event handling'!

onBreakpointCreatedEvent: aGDBBreakpointCreatedEvent

    breakpoints isNil ifTrue:[ 
        breakpoints := List new.
    ].
    aGDBBreakpointCreatedEvent breakpoints do:[:breakpoint |  
        breakpoint setDebugger: self.
        breakpoints add: breakpoint.
    ].

    "Created: / 06-07-2017 / 16:08:57 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-11-2017 / 20:17:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onBreakpointDeletedEvent: aGDBBreakpointDeletedEvent
    | breakpoint |

    breakpoint := breakpoints detect:[:e | e number == aGDBBreakpointDeletedEvent id ].
    breakpoint setDebugger: nil.
    breakpoints remove: breakpoint.

    "Created: / 06-07-2017 / 16:26:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 07-07-2017 / 12:39:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onBreakpointModifiedEvent: aGDBBreakpointModifiedEvent

    aGDBBreakpointModifiedEvent breakpoints do:[:new | 
        | old |    

        old := breakpoints detect:[:e | e number = new number ].
        old updateFrom: new.
    ].

    "Created: / 06-07-2017 / 16:28:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-11-2017 / 20:18:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 15-01-2018 / 23:11:52 / jv"
!

onCommandEvent:aGDBCommandEvent 
    connection send:aGDBCommandEvent command.

    "Created: / 02-06-2014 / 23:38:59 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 20-06-2014 / 22:09:57 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onExitEvent:aGDBExitEvent 
    self release.

    "Created: / 03-06-2014 / 00:36:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-06-2014 / 09:28:59 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onRunningEvent: aGDBRunningEvent
    | threads threadId |

    self nextInferiorStateSequnceNumber.        
    threadId := aGDBRunningEvent threadId.
    threadId = 'all' ifFalse:[ 
        threadId := threadId asInteger.
    ].
    threads := Set new.
    inferiors do:[:inferior | 
        inferior threads do:[:thread | 
            (threadId isString or:[thread id = threadId]) ifTrue:[ 
                threads add: thread.
            ].
        ].
    ].
    aGDBRunningEvent setThreads: threads.
    threads do:[:thread | 
        thread onRunningEvent: aGDBRunningEvent.
    ].

    "Created: / 07-09-2014 / 23:34:05 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-07-2017 / 13:48:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onStoppedEvent: aGDBStoppedEvent
    | threads threadIds |

    self nextInferiorStateSequnceNumber.  
    threadIds := aGDBStoppedEvent stoppedThreadIds.
    threadIds = 'all' ifFalse:[ 
        threadIds := threadIds collect:[:e | e asInteger ].
    ].
    threads := Set new.
    inferiors do:[:inferior | 
        inferior threads do:[:thread | 
            (threadIds isString or:[threadIds includes: thread id]) ifTrue:[ 
                threads add: thread.
            ].
        ].
    ].
    aGDBStoppedEvent setThreads: threads.
    threads do:[:thread | 
        thread onStoppedEvent: aGDBStoppedEvent.
    ].

    "Created: / 07-09-2014 / 23:34:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-07-2017 / 13:47:56 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onThreadCreatedEvent:aGDBThreadCreatedEvent 
    | inferior |

    inferior := self inferiorForId:aGDBThreadCreatedEvent threadGroupId.
    inferior onThreadCreatedEvent:aGDBThreadCreatedEvent.

    "Created: / 07-09-2014 / 21:20:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onThreadExitedEvent:aGDBThreadExitedEvent 
    | inferior |

    self nextInferiorStateSequnceNumber.  
    inferior := self inferiorForId:aGDBThreadExitedEvent threadGroupId.
    inferior onThreadExitedEvent:aGDBThreadExitedEvent.

    "Created: / 07-09-2014 / 21:20:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-03-2015 / 13:57:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onThreadGroupAddedEvent:aGDBThreadGroupAddedEvent 
    | inferior |

    inferiors isNil ifTrue:[
        inferiors := List new.
    ].
    inferior := GDBThreadGroup newWithDebugger:self
            id:aGDBThreadGroupAddedEvent threadGroupId.
    inferiors add:inferior.
    aGDBThreadGroupAddedEvent setThreadGroup:inferior

    "Modified: / 07-09-2014 / 21:18:54 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onThreadGroupExitedEvent:aGDBThreadGroupExitedEvent 
    | inferior |

    self nextInferiorStateSequnceNumber.        
    inferior := self inferiorForId:aGDBThreadGroupExitedEvent threadGroupId.
    inferior setExitCode:aGDBThreadGroupExitedEvent exitCode.
    aGDBThreadGroupExitedEvent setThreadGroup:inferior

    "Created: / 06-09-2014 / 02:37:04 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 18-03-2015 / 13:57:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

onThreadGroupStartedEvent:aGDBThreadGroupStartedEvent 
    | inferior |

    inferior := self inferiorForId:aGDBThreadGroupStartedEvent threadGroupId.
    inferior setPid:aGDBThreadGroupStartedEvent pid.
    aGDBThreadGroupStartedEvent setThreadGroup:inferior

    "Created: / 06-09-2014 / 02:37:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 07-09-2014 / 21:23:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'finalization'!

finalize
    self release.

    "Created: / 26-05-2014 / 21:23:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'initialize & release'!

initializeWithProcess: aGDBProcess
    self registerForFinalization.

    finalizationRegistry := Registry new.
    connection := GDBConnection newWithProcess: aGDBProcess.

    commandSequenceNumber := 0.
    inferiorStateSequenceNumber := 0.

    self subscribe.        

    connection eventPumpStart.
    connection eventDispatchStart.
    Delay waitForMilliseconds:100.  

    connection inferiorPTY notNil ifTrue:[
        self send: (GDBMI_inferior_tty_set arguments: (Array with: connection inferiorPTY name)).
    ].
    self send: (GDBMI_gdb_set arguments: #('target-async' 'on')).

    prettyPrintingEnabled := false.
    frameFiltersEnabled := false.

    "Created: / 20-06-2014 / 21:45:21 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-01-2018 / 00:13:09 / jv"
    "Modified: / 28-01-2018 / 23:19:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

release
    connection notNil ifTrue:[ 
        self unsubscribe.
        connection release.
        connection := nil.
    ].

    "Created: / 26-05-2014 / 21:24:04 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 20-06-2014 / 22:08:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

subscribe
    connection eventAnnouncerInternal
        when: GDBCommandEvent               send: #onCommandEvent:           to: self;
        when: GDBExitEvent                  send: #onExitEvent:              to: self;

        when: GDBThreadGroupAddedEvent      send: #onThreadGroupAddedEvent:  to: self;
        when: GDBThreadGroupStartedEvent    send: #onThreadGroupStartedEvent: to: self;
        when: GDBThreadGroupExitedEvent     send: #onThreadGroupExitedEvent: to: self;

        when: GDBThreadCreatedEvent         send: #onThreadCreatedEvent:      to: self;
        when: GDBThreadExitedEvent          send: #onThreadExitedEvent:       to: self;

        when: GDBRunningEvent               send: #onRunningEvent:            to: self;
        when: GDBStoppedEvent               send: #onStoppedEvent:            to: self;

        when: GDBBreakpointCreatedEvent     send: #onBreakpointCreatedEvent:  to: self;
        when: GDBBreakpointModifiedEvent    send: #onBreakpointModifiedEvent: to: self;
        when: GDBBreakpointDeletedEvent     send: #onBreakpointDeletedEvent:  to: self.

    "Created: / 20-06-2014 / 22:07:04 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 06-07-2017 / 16:08:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

unsubscribe
    connection eventAnnouncerInternal unsubscribe: self

    "Created: / 20-06-2014 / 22:07:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger methodsFor:'testing'!

isConnected
    ^ connection notNil

    "Created: / 20-06-2014 / 22:12:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isFrameFiltersEnabled
    ^ frameFiltersEnabled

    "Created: / 12-06-2017 / 09:41:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isPrettyPrintingEnabled
    ^ prettyPrintingEnabled

    "Created: / 12-06-2017 / 09:40:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBDebugger class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !