GDBConnection.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Thu, 24 Jan 2019 21:59:23 +0000
changeset 172 836209352efb
parent 170 6cf990ac2cad
child 185 4e1be69b39ce
permissions -rw-r--r--
Update target features from `=target-connected event` ...so feature list is up-to-date

"
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:#GDBConnection
	instanceVariableNames:'process inferiorPTY eventAnnouncer eventAnnouncerInternal
		eventQueue eventQueueLock eventDispatchNotifier
		eventDispatchProcess eventDispatchProcessStopping
		eventPumpProcess outstandingCommands recorder'
	classVariableNames:''
	poolDictionaries:'GDBDebugFlags'
	category:'GDB-Private'
!

!GDBConnection 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
"
! !

!GDBConnection class methodsFor:'instance creation'!

new
    ^ self shouldNotImplement.

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

newWithProcess: aGDBProcess
    ^ self basicNew initializeWithProcess: aGDBProcess

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

!GDBConnection methodsFor:'accessing'!

consoleInput
    ^ process consoleInput

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

consoleOutput
    ^ process consoleOutput

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

eventAnnouncer
    ^ eventAnnouncer
!

eventAnnouncerInternal
    ^ eventAnnouncerInternal

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

inferiorPTY
    ^ inferiorPTY
!

nativeTargetFeatures
    ^ process nativeTargetFeatures

    "Created: / 09-04-2018 / 15:40:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

recorder
    ^ recorder
!

recorder:aGDBSessionRecorder
    recorder := aGDBSessionRecorder.
!

trace
    "Returns a GDB.MI record (trrace). Used for debugging GDB/MI communication."
    
    ^ self recorder trace

    "Created: / 09-03-2018 / 09:49:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection methodsFor:'commands'!

send: command
    | commandString |

    commandString := command asString.

    outstandingCommands add: command.
    recorder notNil ifTrue:[ 
        recorder recordCommand: commandString
    ].  
    process debuggerInput nextPutLine: commandString.

    "Created: / 20-06-2014 / 22:09:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 11-07-2017 / 22:39:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection methodsFor:'event dispatching'!

eventDispatchLoop
    "raise an error: this method should be implemented (TODO)"

    [  
        | eventQueueEmpty |

        eventQueueEmpty := false.
        [ eventQueueEmpty ] whileFalse:[
            | event |

            event := nil.
            eventQueueLock critical:[ 
                eventQueueEmpty := eventQueue isEmpty.
                eventQueueEmpty ifFalse:[ 
                    event := eventQueue removeFirst.
                ]
            ].
            eventQueueEmpty ifFalse:[
                (AbortOperationRequest , AbortAllOperationRequest) ignoreIn:[
                    self eventDispatchSingle: event.
                ]
            ].
        ].
        eventDispatchProcessStopping == true ifTrue:[ 
            ^ self.
        ].
        eventDispatchNotifier wait.
    ] loop.

    "Created: / 02-06-2014 / 22:51:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 14-01-2018 / 21:54:32 / jv"
    "Modified: / 02-10-2018 / 13:35:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

eventDispatchProcess
    ^ eventDispatchProcess
!

eventDispatchSingle: aGDBEvent
    TraceEvents ifTrue:[ 
        Logger log: ('event loop: broadcasting %1 (%2)' bindWith: aGDBEvent class name with: aGDBEvent token) severity: #trace facility: 'GDB' originator: self attachment: aGDBEvent
    ].
    eventAnnouncerInternal announce: aGDBEvent.
    eventAnnouncer announce: aGDBEvent

    "Created: / 02-06-2014 / 22:58:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 27-02-2015 / 09:49:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

eventDispatchStart
    eventDispatchProcess isNil ifTrue:[
        eventDispatchProcessStopping := false.
        eventDispatchProcess := [
                TraceEvents ifTrue:[
                    Logger log: 'event loop: starting' severity: #trace facility: 'GDB'
                ].
                self eventDispatchLoop.
            ] newProcess.
        eventDispatchProcess name:('GDB Event dispatcher (%1)' bindWith:process id).
        eventDispatchProcess priority:Processor userBackgroundPriority.
        eventDispatchProcess addExitAction:[ 
            eventDispatchProcess := nil. 
            eventDispatchProcessStopping := nil.
            TraceEvents ifTrue:[
                Logger log: 'event loop: terminated' severity: #trace facility: 'GDB'
            ].
        ].
        eventDispatchProcess resume.
    ].

    "Created: / 02-06-2014 / 22:51:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 19-10-2018 / 10:11:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

eventDispatchStop            
    eventDispatchProcessStopping := true.
    eventDispatchNotifier signal.

    "Created: / 02-06-2014 / 22:52:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:00:47 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection methodsFor:'event pump'!

eventPumpLoop
    | parser done |

    parser := GDBMIParser on:process debuggerOutput.
    parser 
        token2CommandMappingBlock:[:token | 
            | command |

            command := outstandingCommands 
                    detect:[:cmd | cmd token == token ]
                    ifNone:[ nil ].
            command notNil ifTrue:[
                outstandingCommands remove:command
            ].
            command
        ].
    parser recorder:recorder.
    parser parsePostStartHeader.
    done := false.
    [
        [
            done or:[ process debuggerOutput atEnd ]
        ] on: TerminateProcessRequest do:[:request| 
            done := true.    
        ].
    ] whileFalse:[
        | eventset |

        [
            [
                eventset := parser parseOutput.
                (eventset contains:[:each | each isCommandResultEvent and:[ each status == #exit] ]) ifTrue:[ 
                    done := true.
                ].
            ] on:StreamNotOpenError do:[ ^ self. ].
            self pushEventSet:eventset.
        ] on:AbortOperationRequest do:[
            | terminator  i  c |

            terminator := '(gdb)'.
            i := 1.
            process debuggerOutput notNil ifTrue:[
                [
                    process debuggerOutput atEnd not and:[ i <= terminator size ]
                ] whileTrue:[
                    c := process debuggerOutput next.
                    c == (terminator at:i) ifTrue:[
                        i := i + 1.
                    ] ifFalse:[ i := 1. ].
                ].
                process debuggerOutput next.
                "/ read nl.
            ] ifFalse:[ ^ self. ].
        ]
    ]

    "Created: / 02-06-2014 / 22:38:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 01-06-2017 / 22:22:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

eventPumpStart
    eventPumpProcess isNil ifTrue:[
        eventPumpProcess := [
                TraceEvents ifTrue:[
                    Logger log: 'event pump: starting' severity: #trace facility: 'GDB'
                ].
                self eventPumpLoop
            ] newProcess.
        eventPumpProcess name:('GDB Event pump (%1)' bindWith:process id).
        eventPumpProcess priority:Processor userBackgroundPriority.
        eventPumpProcess addExitAction:[ 
            TraceEvents ifTrue:[
                Logger log: 'event pump: terminated' severity: #trace facility: 'GDB'
            ].
            eventPumpProcess := nil. 
        ].
        eventPumpProcess resume.
    ].

    "Created: / 02-06-2014 / 22:38:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 19-10-2018 / 10:07:23 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

eventPumpStop
    | t |

    t := eventPumpProcess.
    (t notNil and:[ t isDead not]) ifTrue:[ 
        eventPumpProcess := nil.
        t terminate.
         "/ raise its prio to make it terminate quickly
        t priority:(Processor userSchedulingPriority + 1)                       
    ].

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

!GDBConnection methodsFor:'events'!

pushEvent: aGDBEvent
    eventQueueLock critical:[
        eventQueue add: aGDBEvent.
        eventDispatchNotifier signal.
    ].

    "Created: / 02-06-2014 / 22:49:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:00:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

pushEventSet: aGDBEventSet
    eventQueueLock critical:[
        eventQueue add: (GDBEventSetProcessingStarted new setEventSet: aGDBEventSet).  
        eventQueue addAll: aGDBEventSet.
        eventQueue add: (GDBEventSetProcessingFinished new setEventSet: aGDBEventSet).
        eventDispatchNotifier signal.
    ].

    "Created: / 02-06-2014 / 22:42:54 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:00:33 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection methodsFor:'initialize & release'!

initializeWithProcess:aGDBProcess 
    process := aGDBProcess.
    (OperatingSystem isUNIXlike and:[aGDBProcess canUsePTY]) ifTrue:[
        inferiorPTY := GDBPTY new.
    ].
    eventQueue := OrderedCollection new.
    eventQueueLock := RecursionLock new.
    eventDispatchNotifier := Semaphore new.
    eventAnnouncer := Announcer new.
    eventAnnouncer subscriptionRegistry 
        subscriptionClass:GDBEventSubscription.
    eventAnnouncerInternal := Announcer new.
    outstandingCommands := Set new.
    recorder := GDBMITracer new.
    aGDBProcess connection:self.

    "Created: / 20-06-2014 / 21:40:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-01-2018 / 00:12:25 / jv"
    "Modified: / 16-01-2019 / 23:02:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

release
    process release

    "Created: / 26-05-2014 / 21:30:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 12-01-2018 / 15:09:00 / jv"
    "Modified: / 20-10-2018 / 07:10:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

released: status
    self pushEvent: (GDBExitEvent new setStatus: status; setTrace: recorder; yourself).  
    status success ifFalse:[ 
        Logger log: ('gdb process: exited with status %1 code %2' bindWith: status status with: status code)  severity: #error facility: 'GDB'.
    ].
    TraceProcesses ifTrue:[ 
        Logger log: ('gdb process: exited') severity: #trace facility: 'GDB'.
        Logger log: 'gdb process: waiting for event pump to finish' severity: #trace facility: 'GDB'.
    ].
    self eventPumpStop.
    self eventDispatchStop.
    process release.
    inferiorPTY release.

    "Created: / 26-05-2014 / 21:31:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 13:37:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection methodsFor:'queries'!

hasPendingCommands
    | anyCommandEventPending |

    "/ This is more tricky. There are pending commands if:
    "/ 
    "/  * there's at least one GDBCommandEvent in event queue
    "/    (command it sent on API level but not yet sent to GDB), or
    "/  * there's at least one outstanding command (command has been
    "/    sent to GDB but response not yet arrived), or
    "/  * there's at least one GDBCommandResultEvent (command response
    "/    was received but not yet dispatched and handled.
    "/ 
    "/ 
    outstandingCommands notEmpty ifTrue:[ ^ true ].

    "/ Here, synchronization is required since we iterate over
    "/ collection...
    eventQueueLock critical:[
        anyCommandEventPending := eventQueue anySatisfy:[:e|e class == GDBCommandEvent or:[e class == GDBCommandResultEvent ] ]
    ].
    ^ anyCommandEventPending

    "Created: / 23-01-2019 / 20:50:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hasPendingEvents

    "/ Note that there's no obligation for this information to
    "/ be 100% accurate, so no need for symchronization here.
    ^ eventQueue notEmpty.

    "Created: / 23-01-2019 / 20:41:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBConnection class methodsFor:'documentation'!

version_HG

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