GDBFrame.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Mon, 28 Jan 2019 14:56:14 +0000
changeset 173 02546d4fbe6d
parent 150 e208036a1953
child 177 e7bd05df3d6b
permissions -rw-r--r--
Fix frame of `GDBThreadSelectedEvent` if inferior is running When ifnferior is running at time we get `=thread-selected` event, we should at least make that frame kind of usable by fixing up it's debugger and thread. This allow clients to use (to some extent) event's frame without worring (too much) about these details.

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

GDBDebuggerObject subclass:#GDBFrame
	instanceVariableNames:'thread level addr func file fullname line arch from variables
		registers registersChanges'
	classVariableNames:''
	poolDictionaries:''
	category:'GDB-Core'
!

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

!GDBFrame class methodsFor:'accessing - GDB value descriptors'!

description
    ^ (super description)
        define:#level as:Integer;
        define:#func as:String;
        define:#file as:String;
        define:#fullname as:String;
        define:#line as:Integer;
        define:#from as:String;
        define:#addr as:Integer;
        yourself

    "Created: / 16-09-2014 / 23:59:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 15-02-2018 / 08:27:50 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'accessing'!

addr
    ^ addr
!

address
    ^ self addr

    "Created: / 03-07-2018 / 15:10:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

arch
    arch isNil ifTrue:[
        arch := GDBArchitecture named: 'unknown'.
    ].
    arch isString ifTrue:[ 
        arch := GDBArchitecture named: arch
    ].
    ^ arch

    "Created: / 16-08-2018 / 06:59:34 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 16-08-2018 / 09:04:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

disassemble
    "Return instructions for a function of this frame"

    | disassembly |

    (debugger hasFeature:'data-disassemble-a-option') ifTrue:[
        disassembly := debugger disassembleFunction: '0x', addr hexPrintString.  
    ] ifFalse:[ 
        disassembly := debugger disassembleFile: file  line: line count: nil.
    ].
    disassembly do:[ :each | each setArchitecture: self arch ].  
    ^ disassembly

    "Created: / 22-06-2018 / 12:47:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 16-08-2018 / 09:40:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

file
    "Return filename (path) containing frame's function source."

    | f |

    "/ GDB/MI provides two paths, `file` and `fullname`. 
    "/ 
    "/ However, sometimes GDB gets confused and does not return
    "/ anything directly useful, especially when debug info contains
    "/ relative paths with multiple segments. 
    "/ 
    "/ As a courtesy to the user, in that case try to resolve full
    "/ path here too. Hence the code below.
    "/
    "/ To avoid re-resolving of file each time this method is called,
    "/ cache resolved Filename in `fullname` instvar. 

    fullname isFilename ifTrue:[ 
        "/ Already resolved by the code below
        ^ fullname pathName
    ].

    f := fullname ? file.
    f isNil ifTrue:[ ^ nil ].
    f := f copyReplaceAll: $/ with: Filename separator.
    f := f asFilename.

    "/ check, if GDB returned correctly resolved filename...
    f exists ifTrue:[
        fullname := f.
        ^ fullname pathName
    ].

    "/ ...if not, try to look it up in source directories...
    self debugger directories do:[:d | 
        f := d asFilename / (fullname ? file).
        f exists ifTrue:[ 
            fullname := f.
            ^ fullname pathName.
        ].
    ].

    "/ ...if not found there...
    ^ nil

    "Modified: / 12-03-2018 / 10:32:26 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 22-03-2018 / 16:52:52 / jv"
!

from
    ^ from
!

func
    ^ func
!

level
    ^ level
!

line
    ^ line
!

registers
    "Return a list of mchine registers and their correcponsing
     values in this frame (as list of GDBRegisterWithValue)"

    registers isNil ifTrue:[
        | registersSet result |

        "/ First, retrieve a list of register available in this
        "/ frame.
        "/
        "/ To reduce MI communication overhead, registers are cached
        "/ in shared cache kept in process group (inferior). Caching is 
        "/ based on an assumption that while each frame may have different 
        "/ architecture, it is unlikely that  different frames with same 
        "/ architecture would have different set of registers.
        registersSet := self thread group registersMap at: self arch ifAbsentPut:[ 
            result := debugger send: (GDBMI_data_list_register_names arguments: (Array with: '--thread' with: self thread id with: '--frame' with: self level)).
            registersSet := Dictionary new.
            (result propertyAt: 'register-names') withIndexDo:[ :name :number | 
                name notEmpty ifTrue:[
                    "/ Note, that GDB register indices starts with 0 (zero) like in C!!
                    registersSet at: number - 1 put: (GDBRegister new setNumber: number - 1; setName: name)
                ].
            ].
            registersSet
        ].

        "/ Second, fetch values and populate a collection of registers (as `GDBRegisterWithValue`). This is done
        "/ onlt once, later on, the value of registers is updated automagically (see `GDBRegisterWithValue >> value`).
        result := debugger send: (GDBMI_data_list_register_values new arguments: (Array with: '--thread' with: thread id with: '--frame' with: level with: 'r')).
        registers := result propertyAt: #'register-values'.
        registers do:[:value | value setFrame: self; setRegisterFrom: registersSet ].
    ].
    ^ registers value

    "Created: / 26-09-2018 / 09:51:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 27-09-2018 / 11:17:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

thread
    ^ thread
!

variables
    self ensureIsValid.
    variables isNil ifTrue:[
        variables := GDBTransientDataHolder debugger: debugger factory:[ :old |
            | result new |

            result := debugger send: (GDBMI_stack_list_variables new arguments: (Array with: '--thread' with: thread id with: '--frame' with: level with: '--simple-values')).
            new := (result propertyAt: #variables) ? #().
            old notNil ifTrue:[ 
                old size == new size ifTrue:[
                    1 to: new size do:[:i | 
                        | oldVar newVar |

                        oldVar := old at: i.
                        newVar := new at: i.
                        newVar name = oldVar name ifTrue:[ 
                            new at: i put: (old at: i)
                        ].
                    ].
                ] ifFalse:[ 
                    "/ Sorry for this - but I'm not sure when this may happen
                    "/ so I would like get a debugger to investigate
                    self breakPoint: #jv.
                    new do:[:newVar | newVar setFrame: self ]
                ].
            ] ifFalse:[ 
                new do:[:newVar | newVar setFrame: self ]
            ].
            new
        ].
    ].
    ^ variables value

    "Created: / 27-02-2015 / 14:56:22 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 05-07-2018 / 11:10:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'initialization'!

setAddr: aString
    addr := aString

    "Created: / 31-01-2018 / 09:50:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setLevel: anInteger
    level := anInteger

    "Created: / 15-02-2018 / 08:34:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Created: / 02-02-2018 / 12:16:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setLine: anInteger
    line := anInteger

    "Created: / 01-02-2018 / 10:09:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

setThread: aGDBThread
    thread := aGDBThread

    "Created: / 30-01-2018 / 15:56:21 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'printing & storing'!

displayString
    ^ String streamContents: [ :aStream |
        level printOn:aStream base: 10 size: 2 fill: Character space.
        aStream nextPutAll:' 0x'.
        addr printOn:aStream base: 16 size: (self arch sizeofPointer * 2) fill: $0.
        aStream nextPutAll:' '.
        func notNil ifTrue:[
            func printOn:aStream.
        ] ifFalse:[ 
            aStream nextPutAll: '?'
        ].
        file notNil ifTrue:[
            aStream nextPutAll:' ('.
            aStream nextPutAll: file startingAt: (file lastIndexOf: Filename separator) + 1.
            line notNil ifTrue:[
                aStream nextPutAll:':'.
                line printOn:aStream.
            ].
            aStream nextPutAll:')'.
        ].
    ].

    "Created: / 27-02-2015 / 15:20:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 06-10-2018 / 08:57:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

printOn:aStream
    "append a printed representation if the receiver to the argument, aStream"

    super printOn: aStream.
    aStream nextPutAll:'('.
    level printOn:aStream base: 10 size: 2 fill: Character space.
    aStream nextPutAll:' '.
    addr printOn:aStream.
    aStream nextPutAll:' '.
    func printOn:aStream.
    aStream nextPutAll:' - '.
    file printOn:aStream.
    aStream nextPutAll:':'.
    line printOn:aStream.
    aStream nextPutAll:')'.

    "Modified: / 27-02-2015 / 15:21:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'private'!

registersChanges
    registersChanges isNil ifTrue:[ 
        self isValid ifTrue:[
            registersChanges := GDBTransientDataHolder debugger: debugger factory: [ :old |
                old isNil ifTrue:[ 
                    #()
                ] ifFalse:[
                    | result changed |

                    result := debugger send: (GDBMI_data_list_changed_registers new arguments: (Array with: '--thread' with: thread id with: '--frame' with: level)).
                    changed := result propertyAt: #'changed-registers'.

                    changed notEmptyOrNil ifTrue:[ 
                        result := debugger send: (GDBMI_data_list_register_values new arguments: (Array with: '--thread' with: thread id with: '--frame' with: level with: 'r') , changed).
                        (result propertyAt: #'register-values') asSet.
                    ] ifFalse:[ 
                        #()
                    ].
                ].
            ].
        ] ifFalse:[ 
            registersChanges := #().
        ].
    ].
    ^ registersChanges value

    "Created: / 26-09-2018 / 22:35:52 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 27-09-2018 / 10:18:46 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'queries'!

hasSource
    "Return `true` if a source file can is available,
     `false` otherwise."

    ^ self file notNil

    "Created: / 02-10-2018 / 10:28:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

hasSymbol
    "Return `true` if GDB find a symbol (function name) 
     for this frame's code, `false` otherwise."

    "/ Following test is rally, really stupid, but at
    "/ the moment there's no better way. We'd need to fix GDB/MI
    "/ to report frame type too. Sigh.
    ^ func notNil 
        and:[ func ~= '??' 
        and: [(func includesString: 'signal handler called') not]]

    "Created: / 02-10-2018 / 09:47:50 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 07-10-2018 / 08:22:50 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame methodsFor:'testing'!

isValid
    ^ thread isValid and:[addr notNil]

    "Modified: / 04-02-2018 / 21:30:07 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GDBFrame class methodsFor:'documentation'!

version_HG

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