GDBConnection.st
author Jan Vrany <jan.vrany@labware.com>
Fri, 08 Sep 2023 12:40:22 +0100
changeset 317 7f63737e0374
parent 293 d1422e1ee1bd
permissions -rw-r--r--
Fix `GDBMIDebugger` after rename of `GDBStXUnixProcess` to `GDBUnixProcess` ...in commit d1422e1ee.

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

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"
"{ Package: 'jv:libgdbs' }"

"{ NameSpace: Smalltalk }"

Object subclass:#GDBConnection
	instanceVariableNames:'process inferiorPTY eventAnnouncer eventAnnouncerInternal
		eventDispatcher eventPumpProcess outstandingCommands recorder'
	classVariableNames:''
	poolDictionaries:'GDBDebugFlags'
	category:'GDB-Internal'
!

!GDBConnection class methodsFor:'documentation'!

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

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"
! !

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

eventDispatchProcess
    ^ eventDispatcher process

    "Created: / 09-03-2021 / 20:51:54 / Jan Vrany <jan.vrany@labware.com>"
!

inferiorPTY
    ^ inferiorPTY
!

nativeTargetFeatures
    ^ process nativeTargetFeatures

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

process
    ^ process

    "Created: / 27-03-2019 / 09:20:34 / 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 nextPutAll: commandString.
    OperatingSystem isMSWINDOWSlike ifTrue: [ 
        process debuggerInput nextPut: Character cr.
    ].
    process debuggerInput nextPut: Character lf.
    process debuggerInput flush.

    "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>"
    "Modified: / 14-07-2023 / 17:04:05 / Jan Vrany <jan.vrany@labware.com>"
! !

!GDBConnection methodsFor:'event dispatching'!

eventDispatchRestartIfCalledFromDispatcherProcess

    "Created: / 27-03-2021 / 08:28:02 / Jan Vrany <jan.vrany@labware.com>"
    "Modified: / 22-02-2022 / 13:18:10 / Jan Vrany <jan.vrany@labware.com>"
!

eventDispatchStart
    eventDispatcher start

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

eventDispatchStop
    eventDispatcher wait

    "Created: / 02-06-2014 / 22:52:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:28:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 26-03-2021 / 14:14:23 / Jan Vrany <jan.vrany@labware.com>"
! !

!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
                ] ensure: [ 
                    TraceEvents ifTrue:[
                        Logger log: 'event pump: terminated' severity: #trace facility: 'GDB'
                    ].
                    eventPumpProcess := nil. 
                ].
            ] newProcess.
        eventPumpProcess name:('GDB Event pump (%1)' bindWith:process id).
        eventPumpProcess priority:Processor userBackgroundPriority.
        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>"
    "Modified: / 14-07-2023 / 09:59:29 / Jan Vrany <jan.vrany@labware.com>"
!

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>"
    "Modified (comment): / 26-03-2021 / 14:17:29 / Jan Vrany <jan.vrany@labware.com>"
! !

!GDBConnection methodsFor:'events'!

pushCommand: aGDBCommand
    "/ Restart event dispatcher if command is sent from within
    "/ event dispatch loop (for example, as part of handling
    "/ an event.
    Processor activeProcess == eventDispatcher process ifTrue:[
        eventDispatcher restart
    ].
    self pushEvent: (GDBCommandEvent new command: aGDBCommand)

    "Created: / 22-02-2022 / 13:06:51 / Jan Vrany <jan.vrany@labware.com>"
!

pushEvent: aGDBEvent
    eventDispatcher pushEvent: aGDBEvent

    "Created: / 02-06-2014 / 22:49:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:30:04 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 17-03-2021 / 12:33:41 / Jan Vrany <jan.vrany@labware.com>"
!

pushEventSet: aCollection
    eventDispatcher pushEventSet: aCollection

    "Created: / 02-06-2014 / 22:42:54 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-10-2018 / 14:30:09 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 17-03-2021 / 12:34:11 / Jan Vrany <jan.vrany@labware.com>"
    "Modified (comment): / 19-09-2022 / 23:16:13 / Jan Vrany <jan.vrany@labware.com>"
! !

!GDBConnection methodsFor:'initialize & release'!

initializeWithProcess:aGDBProcess 
    process := aGDBProcess.
    (OperatingSystem isUNIXlike and:[aGDBProcess canUsePTY]) ifTrue:[
        inferiorPTY := GDBPortlib current newPTY.
    ].
    eventAnnouncerInternal := Announcer new.
    eventAnnouncer := Announcer new.

    "/ On Smalltalk/X we use a custom event subscription
    "/ in order to trace (if needed) event delivery. Other
    "/ Smalltalk dialect may not (do not) provide this feature.
    Smalltalk isSmalltalkX ifTrue: [
        eventAnnouncer subscriptionRegistry subscriptionClass:GDBEventSubscription.
    ].

    eventDispatcher := GDBEventDispatcher new
                        setAnnouncer1: eventAnnouncerInternal;
                        setAnnouncer2: eventAnnouncer;
                        yourself.
    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>"
    "Modified: / 24-07-2023 / 22:44:01 / Jan Vrany <jan.vrany@labware.com>"
!

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.
    TraceProcesses ifTrue:[ 
        Logger log: 'gdb process: waiting for event loop to finish' severity: #trace facility: 'GDB'.
    ].
    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>"
    "Modified: / 26-03-2021 / 14:14:59 / Jan Vrany <jan.vrany@labware.com>"
!

setDebugger: aGDBDebugger
    eventDispatcher setDebugger: aGDBDebugger

    "Created: / 09-03-2021 / 11:11:47 / Jan Vrany <jan.vrany@labware.com>"
    "Modified: / 09-03-2021 / 21:03:36 / Jan Vrany <jan.vrany@labware.com>"
! !

!GDBConnection methodsFor:'queries'!

hasPendingCommands

    "/ 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 ].
    ^ eventDispatcher hasPendingEventsMatching: [:e|e class == GDBCommandEvent or:[e class == GDBCommandResultEvent ] ]

    "Created: / 23-01-2019 / 20:50:12 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 17-03-2021 / 12:38:43 / Jan Vrany <jan.vrany@labware.com>"
!

hasPendingEvents
    ^ eventDispatcher hasPendingEvents

    "Created: / 23-01-2019 / 20:41:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 17-03-2021 / 12:41:20 / Jan Vrany <jan.vrany@labware.com>"
! !

!GDBConnection class methodsFor:'documentation'!

version_HG

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