atlas
3rd April 2019, 19:29
the project is called Vivisect and it has been using PyQt5. Vivisect is a binary analysis framework (eg. a disassembler)
this is a relatively stable project, but while recently attempting to add in filtering capabilities to various QTreeView widgets using QSortFilterProxyModel, i'm getting somewhat inconsistent segfaults, with very little context (ie. none other than printed debug statements).
i've looked through this thread but i haven't been able to stop the segfaulting completely: https://www.qtcentre.org/threads/69208-PyQt5-QSortFilterProxyModel-index-from-wrong-model-passed-to-mapFromSource-segfalt
when i start up analysis, it always crashes (it populates the various widgets):
$ vivbin /bin/chown
Loaded (0.0685 sec) /bin/chown
Beginning analysis...
...analyzing exports.
0x0200ad31: Emulation Found 0x0200afd0 (from func: 0x0200ad20) via call 0x0200afd0
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
...
Dynamic Branch found at 0x2001d48 call rax
Segmentation fault (core dumped)
here's the relative code snippits (links to full code listed below):
class VivFilterModel(QSortFilterProxyModel):
def __init__(self, parent=None):
QSortFilterProxyModel.__init__(self, parent=parent)
self.setDynamicSortFilter(True)
self.setFilterKeyColumn(-1)
def __getattr__(self, name): # the existing "navModel" includes a couple functions
return getattr(self.sourceModel(), name)
class VivFilterView(QWidget):
'''
This is the primary window for the VQViv*Views if they want to include filtering
'''
window_title = '__undefined__'
view_type = None
def __init__(self, vw, vwqgui, *args, **kwargs):
QWidget.__init__(self)
self.view = self.view_type(vw, vwqgui, *args, **kwargs)
self.ffilt = VQFilterWidget(self)
layout = vq_basics.VBox(self.view, self.ffilt)
self.setLayout(layout)
self.ffilt.filterChanged.connect(self.textFilterCh anged)
self.setWindowTitle(self.view.window_title)
def textFilterChanged(self):
regExp = QtCore.QRegExp(self.ffilt.text(),
self.ffilt.caseSensitivity(),
self.ffilt.patternSyntax())
self.view.filterModel.setFilterRegExp(regExp)
class VQTreeModel(QtCore.QAbstractItemModel):
'''
A QT tree model that uses the tree API from visgraph
to hold the data...
'''
columns = ( 'A first column!', 'The Second Column!')
editable = None
dragable = False
def __init__(self, parent=None, columns=None):
if columns != None:
self.columns = columns
QtCore.QAbstractItemModel.__init__(self, parent=parent)
self.rootnode = VQTreeItem((), None)
if self.editable == None:
self.editable = [False,] * len(self.columns)
def vqEdited(self, pnode, col, value):
return value
def append(self, rowdata, parent=None):
if parent == None:
parent = self.rootnode
pidx = self.createIndex(parent.row(), 0, parent)
i = len(parent.children)
self.beginInsertRows(pidx, i, i)
node = parent.append(rowdata)
self.endInsertRows()
self.layoutChanged.emit()
return node
...
def sort(self, colnum, order=0):
cmpf = VQTreeSorter(colnum, order)
self.layoutAboutToBeChanged.emit()
self.rootnode.children.sort(cmp=cmpf)
self.layoutChanged.emit()
def flags(self, index):
if not index.isValid():
return 0
flags = QtCore.QAbstractItemModel.flags(self, index)
col = index.column()
if self.editable[col]:
flags |= QtCore.Qt.ItemIsEditable
if self.dragable:
flags |= QtCore.Qt.ItemIsDragEnabled
return flags
def columnCount(self, parent=None):
return len(self.columns)
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
return item
return None
def setData(self, index, value, role=QtCore.Qt.EditRole):
node = index.internalPointer()
if not node:
return False
# If this is the edit role, fire the vqEdited thing
if role == QtCore.Qt.EditRole:
value = self.vqEdited(node, index.column(), value)
if value == None:
return False
node.rowdata[index.column()] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, column, orientation, role):
if ( orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
return self.columns[column]
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
pitem = parent.internalPointer()
if not pitem:
pitem = self.rootnode
item = pitem.child(row)
if not item:
return QtCore.QModelIndex()
return self.createIndex(row, column, item)
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
if not item:
return QtCore.QModelIndex()
pitem = item.parent
if pitem == self.rootnode:
return QtCore.QModelIndex()
if pitem == None:
return QtCore.QModelIndex()
return self.createIndex(pitem.row(), 0, pitem)
...
the working pull request is here: https://github.com/vivisect/vivisect/pull/237/files
the full branch is here: https://github.com/atlas0fd00m/vivisect/tree/filtered_views
i expect the bug is some simple thing i'm doing (eg. not providing a parent, or maybe connecting signals in incompatible ways, etc...)
but i'm at a complete loss. i don't understand why adding in a QSortFilterProxyModel would cause the entire python interpreter to segfault.
thank you in advance for any help and guidance you can provide? i'm relatively new to pyqt and qt in general, so don't worry about offending me.
let me know if you need any other context (or clarification)
@
Added after 9 minutes:
i ran out of room for these bits of relevant code.
class VQVivFunctionsViewPart(VQVivTreeView):
_viv_navcol = 0
window_title = 'Functions'
columns = ('Name','Address', 'Size', 'Ref Count')
def __init__(self, vw, vwqgui):
VQVivTreeView.__init__(self, vw, vwqgui, withfilter=True)
self.navModel = VivNavModel(self._viv_navcol, self, columns=self.columns)
self.filterModel = VivFilterModel()
self.filterModel.setSourceModel(self.navModel)
self.setModel(self.filterModel)
self.vqLoad()
self.vqSizeColumns()
class VQVivFunctionsView(VivFilterView):
view_type = VQVivFunctionsViewPart
class VQTreeView(QTreeView):
def __init__(self, parent=None, cols=None, **kwargs):
QTreeView.__init__(self, parent=parent, **kwargs)
self.setSortingEnabled(True)
self.setAlternatingRowColors(True)
if cols != None:
model = VQTreeModel(parent=self, columns=cols)
self.setModel( model )
def vqSizeColumns(self):
c = self.model().columnCount()
for i in xrange(c):
self.resizeColumnToContents(i)
def setModel(self, model):
model.dataChanged.connect( self.dataChanged )
model.rowsInserted.connect( self.rowsInserted )
return QTreeView.setModel(self, model)
class EnviNavModel(vq_tree.VQTreeModel):
dragable = True
def __init__(self, navcol, parent=None, columns=None):
vq_tree.VQTreeModel.__init__(self, parent=parent, columns=columns)
self.navcol = navcol
def mimeData(self, idx):
pnode = idx[0].internalPointer()
expr = pnode.rowdata[self.navcol]
mdata = QtCore.QMimeData()
mdata.setData('envi/expression',str(expr))
return mdata
if you are looking through the codebase, the PyQt5 files of interest should be:
vivisect/qt/views.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vivisect/qt/views.py)
vqt/tree.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vqt/tree.py)
envi/qt/memory.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/envi/qt/memory.py)
vqt/common.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vqt/common.py)
vivisect/qt/main.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vivisect/qt/main.py)
i realize it's a little complex, but i appreciate the help, and i'm happy to provide whatever context may make it easier to troubleshoot.
thanks!
@
this is a relatively stable project, but while recently attempting to add in filtering capabilities to various QTreeView widgets using QSortFilterProxyModel, i'm getting somewhat inconsistent segfaults, with very little context (ie. none other than printed debug statements).
i've looked through this thread but i haven't been able to stop the segfaulting completely: https://www.qtcentre.org/threads/69208-PyQt5-QSortFilterProxyModel-index-from-wrong-model-passed-to-mapFromSource-segfalt
when i start up analysis, it always crashes (it populates the various widgets):
$ vivbin /bin/chown
Loaded (0.0685 sec) /bin/chown
Beginning analysis...
...analyzing exports.
0x0200ad31: Emulation Found 0x0200afd0 (from func: 0x0200ad20) via call 0x0200afd0
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
...
Dynamic Branch found at 0x2001d48 call rax
Segmentation fault (core dumped)
here's the relative code snippits (links to full code listed below):
class VivFilterModel(QSortFilterProxyModel):
def __init__(self, parent=None):
QSortFilterProxyModel.__init__(self, parent=parent)
self.setDynamicSortFilter(True)
self.setFilterKeyColumn(-1)
def __getattr__(self, name): # the existing "navModel" includes a couple functions
return getattr(self.sourceModel(), name)
class VivFilterView(QWidget):
'''
This is the primary window for the VQViv*Views if they want to include filtering
'''
window_title = '__undefined__'
view_type = None
def __init__(self, vw, vwqgui, *args, **kwargs):
QWidget.__init__(self)
self.view = self.view_type(vw, vwqgui, *args, **kwargs)
self.ffilt = VQFilterWidget(self)
layout = vq_basics.VBox(self.view, self.ffilt)
self.setLayout(layout)
self.ffilt.filterChanged.connect(self.textFilterCh anged)
self.setWindowTitle(self.view.window_title)
def textFilterChanged(self):
regExp = QtCore.QRegExp(self.ffilt.text(),
self.ffilt.caseSensitivity(),
self.ffilt.patternSyntax())
self.view.filterModel.setFilterRegExp(regExp)
class VQTreeModel(QtCore.QAbstractItemModel):
'''
A QT tree model that uses the tree API from visgraph
to hold the data...
'''
columns = ( 'A first column!', 'The Second Column!')
editable = None
dragable = False
def __init__(self, parent=None, columns=None):
if columns != None:
self.columns = columns
QtCore.QAbstractItemModel.__init__(self, parent=parent)
self.rootnode = VQTreeItem((), None)
if self.editable == None:
self.editable = [False,] * len(self.columns)
def vqEdited(self, pnode, col, value):
return value
def append(self, rowdata, parent=None):
if parent == None:
parent = self.rootnode
pidx = self.createIndex(parent.row(), 0, parent)
i = len(parent.children)
self.beginInsertRows(pidx, i, i)
node = parent.append(rowdata)
self.endInsertRows()
self.layoutChanged.emit()
return node
...
def sort(self, colnum, order=0):
cmpf = VQTreeSorter(colnum, order)
self.layoutAboutToBeChanged.emit()
self.rootnode.children.sort(cmp=cmpf)
self.layoutChanged.emit()
def flags(self, index):
if not index.isValid():
return 0
flags = QtCore.QAbstractItemModel.flags(self, index)
col = index.column()
if self.editable[col]:
flags |= QtCore.Qt.ItemIsEditable
if self.dragable:
flags |= QtCore.Qt.ItemIsDragEnabled
return flags
def columnCount(self, parent=None):
return len(self.columns)
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
return item
return None
def setData(self, index, value, role=QtCore.Qt.EditRole):
node = index.internalPointer()
if not node:
return False
# If this is the edit role, fire the vqEdited thing
if role == QtCore.Qt.EditRole:
value = self.vqEdited(node, index.column(), value)
if value == None:
return False
node.rowdata[index.column()] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, column, orientation, role):
if ( orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
return self.columns[column]
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
pitem = parent.internalPointer()
if not pitem:
pitem = self.rootnode
item = pitem.child(row)
if not item:
return QtCore.QModelIndex()
return self.createIndex(row, column, item)
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
if not item:
return QtCore.QModelIndex()
pitem = item.parent
if pitem == self.rootnode:
return QtCore.QModelIndex()
if pitem == None:
return QtCore.QModelIndex()
return self.createIndex(pitem.row(), 0, pitem)
...
the working pull request is here: https://github.com/vivisect/vivisect/pull/237/files
the full branch is here: https://github.com/atlas0fd00m/vivisect/tree/filtered_views
i expect the bug is some simple thing i'm doing (eg. not providing a parent, or maybe connecting signals in incompatible ways, etc...)
but i'm at a complete loss. i don't understand why adding in a QSortFilterProxyModel would cause the entire python interpreter to segfault.
thank you in advance for any help and guidance you can provide? i'm relatively new to pyqt and qt in general, so don't worry about offending me.
let me know if you need any other context (or clarification)
@
Added after 9 minutes:
i ran out of room for these bits of relevant code.
class VQVivFunctionsViewPart(VQVivTreeView):
_viv_navcol = 0
window_title = 'Functions'
columns = ('Name','Address', 'Size', 'Ref Count')
def __init__(self, vw, vwqgui):
VQVivTreeView.__init__(self, vw, vwqgui, withfilter=True)
self.navModel = VivNavModel(self._viv_navcol, self, columns=self.columns)
self.filterModel = VivFilterModel()
self.filterModel.setSourceModel(self.navModel)
self.setModel(self.filterModel)
self.vqLoad()
self.vqSizeColumns()
class VQVivFunctionsView(VivFilterView):
view_type = VQVivFunctionsViewPart
class VQTreeView(QTreeView):
def __init__(self, parent=None, cols=None, **kwargs):
QTreeView.__init__(self, parent=parent, **kwargs)
self.setSortingEnabled(True)
self.setAlternatingRowColors(True)
if cols != None:
model = VQTreeModel(parent=self, columns=cols)
self.setModel( model )
def vqSizeColumns(self):
c = self.model().columnCount()
for i in xrange(c):
self.resizeColumnToContents(i)
def setModel(self, model):
model.dataChanged.connect( self.dataChanged )
model.rowsInserted.connect( self.rowsInserted )
return QTreeView.setModel(self, model)
class EnviNavModel(vq_tree.VQTreeModel):
dragable = True
def __init__(self, navcol, parent=None, columns=None):
vq_tree.VQTreeModel.__init__(self, parent=parent, columns=columns)
self.navcol = navcol
def mimeData(self, idx):
pnode = idx[0].internalPointer()
expr = pnode.rowdata[self.navcol]
mdata = QtCore.QMimeData()
mdata.setData('envi/expression',str(expr))
return mdata
if you are looking through the codebase, the PyQt5 files of interest should be:
vivisect/qt/views.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vivisect/qt/views.py)
vqt/tree.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vqt/tree.py)
envi/qt/memory.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/envi/qt/memory.py)
vqt/common.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vqt/common.py)
vivisect/qt/main.py (https://github.com/atlas0fd00m/vivisect/blob/filtered_views/vivisect/qt/main.py)
i realize it's a little complex, but i appreciate the help, and i'm happy to provide whatever context may make it easier to troubleshoot.
thanks!
@