Пример #1
0
class DebugView(QWidget, View):
	class DebugViewHistoryEntry(HistoryEntry):
		def __init__(self, memory_addr, address, is_raw):
			HistoryEntry.__init__(self)

			self.memory_addr = memory_addr
			self.address = address
			self.is_raw = is_raw

		def __repr__(self):
			if self.is_raw:
				return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr)
			return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr)

	def __init__(self, parent, data):
		if not type(data) == BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data

		self.debug_state = binjaplug.get_state(data)
		memory_view = self.debug_state.memory_view
		self.debug_state.ui.debug_view = self

		QWidget.__init__(self, parent)
		self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state)
		View.__init__(self)

		self.setupView(self)

		self.current_offset = 0

		self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

		frame = ViewFrame.viewFrameForWidget(self)
		self.memory_editor = LinearView(memory_view, frame)
		self.binary_editor = DisassemblyContainer(frame, data, frame)

		self.binary_text = TokenizedTextView(self, memory_view)
		self.is_raw_disassembly = False
		self.raw_address = 0

		self.is_navigating_history = False
		self.memory_history_addr = 0

		# TODO: Handle these and change views accordingly
		# Currently they are just disabled as the DisassemblyContainer gets confused
		# about where to go and just shows a bad view
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction())

		self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Types View", UIAction())

		small_font = QApplication.font()
		small_font.setPointSize(11)

		bv_layout = QVBoxLayout()
		bv_layout.setSpacing(0)
		bv_layout.setContentsMargins(0, 0, 0, 0)

		bv_label = QLabel("Loaded File")
		bv_label.setFont(small_font)
		bv_layout.addWidget(bv_label)
		bv_layout.addWidget(self.binary_editor)

		self.bv_widget = QWidget()
		self.bv_widget.setLayout(bv_layout)

		disasm_layout = QVBoxLayout()
		disasm_layout.setSpacing(0)
		disasm_layout.setContentsMargins(0, 0, 0, 0)

		disasm_label = QLabel("Raw Disassembly at PC")
		disasm_label.setFont(small_font)
		disasm_layout.addWidget(disasm_label)
		disasm_layout.addWidget(self.binary_text)

		self.disasm_widget = QWidget()
		self.disasm_widget.setLayout(disasm_layout)

		memory_layout = QVBoxLayout()
		memory_layout.setSpacing(0)
		memory_layout.setContentsMargins(0, 0, 0, 0)

		memory_label = QLabel("Debugged Process")
		memory_label.setFont(small_font)
		memory_layout.addWidget(memory_label)
		memory_layout.addWidget(self.memory_editor)

		self.memory_widget = QWidget()
		self.memory_widget.setLayout(memory_layout)

		self.splitter.addWidget(self.bv_widget)
		self.splitter.addWidget(self.memory_widget)

		# Equally sized
		self.splitter.setSizes([0x7fffffff, 0x7fffffff])

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.controls)
		layout.addWidget(self.splitter, 100)
		self.setLayout(layout)

		self.needs_update = True
		self.update_timer = QTimer(self)
		self.update_timer.setInterval(200)
		self.update_timer.setSingleShot(False)
		self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

		self.add_scripting_ref()

	def add_scripting_ref(self):
		# Hack: The interpreter is just a thread, so look through all threads
		# and assign our state to the interpreter's locals
		for thread in threading.enumerate():
			if type(thread) == PythonScriptingInstance.InterpreterThread:
				thread.locals["dbg"] = self.debug_state

	def getData(self):
		return self.bv

	def getFont(self):
		return binaryninjaui.getMonospaceFont(self)

	def getCurrentOffset(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentOffset()
		return self.raw_address

	def getSelectionOffsets(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getSelectionOffsets()
		return (self.raw_address, self.raw_address)

	def getCurrentFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentFunction()
		return None

	def getCurrentBasicBlock(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentBasicBlock()
		return None

	def getCurrentArchitecture(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentArchitecture()
		return None

	def getCurrentLowLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction()
		return None

	def getCurrentMediumLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction()
		return None

	def getHistoryEntry(self):
		if self.is_navigating_history:
			return None
		memory_addr = self.memory_editor.getCurrentOffset()
		if memory_addr != self.memory_history_addr:
			self.memory_history_addr = memory_addr
		if self.is_raw_disassembly and self.debug_state.connected:
			rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address)
			return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True)
		else:
			address = self.binary_editor.getDisassembly().getCurrentOffset()
			return DebugView.DebugViewHistoryEntry(memory_addr, address, False)

	def navigateToHistoryEntry(self, entry):
		self.is_navigating_history = True
		if hasattr(entry, 'is_raw'):
			self.memory_editor.navigate(entry.memory_addr)
			if entry.is_raw:
				if self.debug_state.connected:
					address = self.debug_state.modules.relative_addr_to_absolute(entry.address)
					self.navigate_raw(address)
			else:
				self.navigate_live(entry.address)

		View.navigateToHistoryEntry(self, entry)
		self.is_navigating_history = False

	def navigate(self, addr):
		# If we're not connected we cannot even check if the address is remote
		if not self.debug_state.connected:
			return self.navigate_live(addr)

		if self.debug_state.memory_view.is_local_addr(addr):
			local_addr = self.debug_state.memory_view.remote_addr_to_local(addr)
			if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0:
				return self.navigate_live(local_addr)

		# This runs into conflicts if some other address space is mapped over
		# where the local BV is currently loaded, but this is was less likely
		# than the user navigating to a function from the UI
		if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0:
			return self.navigate_live(addr)

		return self.navigate_raw(addr)

	def navigate_live(self, addr):
		self.show_raw_disassembly(False)
		return self.binary_editor.getDisassembly().navigate(addr)

	def navigate_raw(self, addr):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return False
		self.raw_address = addr
		self.show_raw_disassembly(True)
		self.load_raw_disassembly(addr)
		return True

	def notifyMemoryChanged(self):
		self.needs_update = True

	def updateTimerEvent(self):
		if self.needs_update:
			self.needs_update = False

			# Refresh the editor
			if not self.debug_state.connected:
				self.memory_editor.navigate(0)
				return

			# self.memory_editor.navigate(self.debug_state.stack_pointer)

	def showEvent(self, event):
		if not event.spontaneous():
			self.update_timer.start()
			self.add_scripting_ref()

	def hideEvent(self, event):
		if not event.spontaneous():
			self.update_timer.stop()

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True

	def load_raw_disassembly(self, start_ip):
		# Read a few instructions from rip and disassemble them
		inst_count = 50

		arch_dis = self.debug_state.remote_arch
		rip = self.debug_state.ip

		# Assume the worst, just in case
		read_length = arch_dis.max_instr_length * inst_count
		data = self.debug_state.memory_view.read(start_ip, read_length)

		lines = []

		# Append header line
		tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")]
		contents = DisassemblyTextLine(tokens, start_ip)
		if (major == 2):
			line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents)
		else:
			line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, 0, contents)
		lines.append(line)

		total_read = 0
		for i in range(inst_count):
			line_addr = start_ip + total_read
			(insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr)

			if insn_tokens is None:
				insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")]
				length = arch_dis.instr_alignment
				if length == 0:
					length = 1

			tokens = []
			color = HighlightStandardColor.NoHighlightColor
			if line_addr == rip:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint & pc
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# PC
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> "))
					color = HighlightStandardColor.BlueHighlightColor
			else:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# Regular line
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "     "))
			# Address
			tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr))
			tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "  "))
			tokens.extend(insn_tokens)

			# Convert to linear disassembly line
			contents = DisassemblyTextLine(tokens, line_addr, color=color)
			if (major == 2):
				line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents)
			else:
				line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, 0, contents)
			lines.append(line)

			total_read += length

		# terrible workaround for libshiboken conversion issue
		for line in lines:
			# line is LinearDisassemblyLine
			last_tok = line.contents.tokens[-1]
			#if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue
			#if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0")
			if last_tok.size != 8: continue
			#print('fixing: %s' % line)
			last_tok.value &= 0x7FFFFFFFFFFFFFFF

		self.binary_text.setLines(lines)

	def show_raw_disassembly(self, raw):
		if raw != self.is_raw_disassembly:
			self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget)
			self.is_raw_disassembly = raw

	def refresh_raw_disassembly(self):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return

		if self.is_raw_disassembly:
			self.load_raw_disassembly(self.getCurrentOffset())
Пример #2
0
class TimeSeriesPlot(DataView):
    def __init__(self, workbench: WorkbenchModel, parent: QWidget = None):
        super().__init__(workbench, parent)

        self.settingsPanel = _SettingsPanel(self)
        self.chartView = InteractiveChartView(parent=self, setInWindow=False)
        p: QSizePolicy = self.chartView.sizePolicy()
        p.setHorizontalStretch(20)
        self.chartView.setSizePolicy(p)
        self.searchableIndexTableModel: QSortFilterProxyModel = QSortFilterProxyModel(
            self)

        self.splitter = QSplitter(Qt.Horizontal, self)
        self.splitter.addWidget(self.chartView)
        self.splitter.addWidget(self.settingsPanel)
        # Adjust size policies
        policy = self.settingsPanel.sizePolicy()
        policy.setHorizontalStretch(2)
        self.settingsPanel.setSizePolicy(policy)
        # Add splitter to main layout
        layout = QHBoxLayout(self)
        layout.addWidget(self.splitter)

        self.settingsPanel.createButton.clicked.connect(self.createChart)
        self.settingsPanel.timeAxisFormatCB.currentTextChanged.connect(
            self.changeTimeFormat)

    @Slot(str)
    def changeTimeFormat(self, timeFormat: str) -> None:
        """ Changes the datetime format displayed on the X axis of the plot """
        chart = self.chartView.chart()
        if chart:
            axis = chart.axisX()
            if axis and axis.type() == QtCharts.QAbstractAxis.AxisTypeDateTime:
                axis.setFormat(timeFormat)
                self.chartView.setBestTickCount(chart.size())

    def __createTimeAxis(self, timeSeries: pd.Series,
                         timeType: Type) -> QtCharts.QAbstractAxis:
        """ Creates a time axis showing 'timeSeries' values of specified type (Ordinal or Datetime) """
        if timeType == Types.Datetime:
            # Time axis is Datetime
            xAxis = QtCharts.QDateTimeAxis()
            xAxis.setFormat(self.settingsPanel.timeAxisFormatCB.currentText())
        else:
            # Time axis is Ordinal (time are str labels)
            xAxis = QtCharts.QCategoryAxis()
            for cat, code in zip(timeSeries, timeSeries.cat.codes):
                xAxis.append(cat, code)
            xAxis.setStartValue(0)
            xAxis.setLabelsPosition(
                QtCharts.QCategoryAxis.AxisLabelsPositionOnValue)
        xAxis.setTitleText('Time')
        return xAxis

    @staticmethod
    def __createSeriesForAttributes(dataframe: pd.DataFrame, timeIndex: int, timeIndexType: Type) \
            -> Tuple[List[QtCharts.QLineSeries], float, float]:
        """ Creates a QLineSeries for every column in the dataframe. 'timeIndex' column is used for
        xAxis

        :return: tuple as (list of series, yMin, yMax)
        """
        timeIndexName: str = dataframe.columns[timeIndex]

        # Convert time values to their numerical equivalent
        if timeIndexType == Types.Datetime:
            # Time axis is Datetime, so convert every date into the number of ms from 01/01/1970
            # dataframe[timeIndexName]: pd.Series[pd.Timestamp]
            # This may not be super accurate
            dataframe.loc[:, timeIndexName] = pd.to_numeric(
                dataframe[timeIndexName], downcast='integer',
                errors='coerce').values / (10**6)
            # dataframe[timeIndexName] \
            #    .map(lambda timestamp: int(timestamp.to_pydatetime().timestamp() * 1000) if )
        else:
            # Types.Ordinal
            # dataframe[timeIndexName]: pd.Series[pd.Categorical]
            dataframe.loc[:, timeIndexName] = dataframe[
                timeIndexName].cat.codes.to_list()

        timeValues: pd.Series = dataframe[timeIndexName].astype(float)
        # Remove time column since we already used it to create the time points
        dataframe = dataframe.drop(timeIndexName, axis=1)

        # Create series for every column (excluding time)
        allSeries: List[QtCharts.QLineSeries] = list()
        # Also keep track of the range the y axis should have
        yMin: float = None
        yMax: float = None
        for colName, valueSeries in dataframe.items():
            valueSeries = pd.Series(valueSeries)
            if pd.api.types.is_categorical(valueSeries):
                # makes sure this is a series of floats
                valueSeries = valueSeries.cat.codes.astype(float)
            # Compute minimum and maximum of series and update global range
            smin = valueSeries.min()
            smax = valueSeries.max()
            yMin = smin if (yMin is None or yMin > smin) else yMin
            yMax = smax if (yMax is None or yMax < smax) else yMax
            # Create series
            qSeries = QtCharts.QLineSeries()
            points: List[QPointF] = list(
                map(lambda t: QPointF(*t), zip(timeValues, valueSeries)))
            qSeries.append(points)
            qSeries.setName(colName)
            qSeries.setUseOpenGL(True)
            qSeries.setPointsVisible(
                True)  # This is ignored with OpenGL enabled
            allSeries.append(qSeries)
        return allSeries, yMin, yMax

    def __createChartWithValues(self, dataframe: pd.DataFrame,
                                attributes: Set[int], timeIndex: int,
                                timeIndexType: Type) -> QtCharts.QChart:
        chart = QtCharts.QChart()
        # Sort by time
        timeIndexName: str = dataframe.columns[timeIndex]
        filteredDf = dataframe.iloc[:, [timeIndex, *attributes]].sort_values(
            by=timeIndexName, axis=0, ascending=True)
        # filteredDf has timeIndex at position 0, attributes following
        # Drop nan labels
        filteredDf.dropna(axis=0, inplace=True, subset=[timeIndexName])

        # Create X axis
        timeSeries: pd.Series = filteredDf.iloc[:, 0]
        xAxis = self.__createTimeAxis(timeSeries, timeIndexType)
        chart.addAxis(xAxis, Qt.AlignBottom)

        # Create the Y axis
        yAxis = QtCharts.QValueAxis(chart)
        yAxis.setTitleText('Values')
        chart.addAxis(yAxis, Qt.AlignLeft)

        series: List[QtCharts.QLineSeries]
        series, yMin, yMax = self.__createSeriesForAttributes(
            filteredDf, timeIndex=0, timeIndexType=timeIndexType)
        # Set range to show every point in chart
        yAxis.setRange(yMin, yMax)
        for s in series:
            chart.addSeries(s)
            s.attachAxis(xAxis)
            s.attachAxis(yAxis)
        return chart

    def __createChartWithIndexes(self,
                                 dataframe: pd.DataFrame,
                                 attributes: Set[int],
                                 indexes: List[Any],
                                 timeIndex: int,
                                 timeIndexType: Type,
                                 indexMean: bool = False) -> QtCharts.QChart:
        """ Creates a chart with a series for every 'index' in 'dataframe' showing only column
        specified in 'attributes'

        :param attributes: the position of the columns to consider to create series
        :param indexes: the indexes of the dataframe to use
        :param timeIndex: the index of the column in 'dataframe' which should be considered the time axis
        :param timeIndexType: the type of column specified in 'timeIndex'. Can be Ordinal or Datetime
        :param indexMean: ignored for now

        """
        columns = dataframe.columns
        timeIndexName: str = columns[timeIndex]
        # Get the subset of attribute columns and selected indexes
        filteredDf = dataframe.loc[indexes, columns[[timeIndex, *attributes]]] \
            .dropna(axis=0, subset=[timeIndexName])
        filteredDf = filteredDf.sort_values(by=timeIndexName,
                                            axis=0,
                                            ascending=True)

        # Group rows by their index attribute. Every index has a distinct list of values
        dfByIndex = filteredDf.groupby(filteredDf.index)
        timeAxisColumn: pd.Series = list(dfByIndex)[0][1][timeIndexName]

        chart = QtCharts.QChart()
        # There will be 1 time axis for all the series, so it is created based on the first series
        # This function is passed the original time label (either Ordinal or Datetime)
        xAxis = self.__createTimeAxis(timeAxisColumn, timeIndexType)
        chart.addAxis(xAxis, Qt.AlignBottom)

        # Create the Y axis
        yAxis = QtCharts.QValueAxis()
        yAxis.setTitleText('Values')
        # Add axis Y to chart
        chart.addAxis(yAxis, Qt.AlignLeft)

        # Add every index series
        groupedValues: pd.DataFrame  # timeValue, timeAttribute, *seriesValue
        for group, groupedValues in dfByIndex:
            # Create a series for this 'index'
            groupName: str = str(group)
            allSeries: List[QtCharts.QLineSeries]
            allSeries, yMin, yMax = self.__createSeriesForAttributes(
                groupedValues, 0, timeIndexType=timeIndexType)
            yAxis.setRange(yMin, yMax)
            for series in allSeries:
                chart.addSeries(series)
                series.attachAxis(xAxis)
                series.attachAxis(yAxis)
            if len(allSeries) == 1:
                # Only 1 attribute was selected, so assume we have multiple indexes (groups)
                allSeries[0].setName(groupName)
        return chart

    @Slot()
    def createChart(self) -> None:
        # Get options
        timeAxis: str = self.settingsPanel.timeAxisAttributeCB.currentText()
        attributes: Set[int] = self.settingsPanel.valuesTable.model().checked
        indexes: List[Any] = self.settingsPanel.indexTable.model().sourceModel(
        ).checked
        # indexMean: bool = self.settingsPanel.meanCB.isChecked()

        # Possibilities:
        # 1) 0 indexes and 1+ attributes
        # 2) 1+ indexes and 1 attribute
        # 3) 1 index and 1+ attributes

        # Validation
        errors: str = ''
        if not timeAxis:
            errors += 'Error: no time axis is selected\n'
        if not attributes:
            errors += 'Error: at least one attribute to show must be selected\n'
        if len(attributes) > 1 and len(indexes) > 1:
            errors += 'Error: select either 0/1 index and 1 or more attributes or 1 attribute and 1 '
            'or more indexes\n'
        if errors:
            errors = errors.strip('\n')
            self.settingsPanel.errorLabel.setText(errors)
            self.settingsPanel.errorLabel.setStyleSheet('color: red')
            self.settingsPanel.errorLabel.show()
            return  # stop
        else:
            self.settingsPanel.errorLabel.hide()

        # Get the integer index of the time attribute
        timeIndexModel: QAbstractItemModel = self.settingsPanel.timeAxisAttributeCB.model(
        )
        i = self.settingsPanel.timeAxisAttributeCB.currentIndex()
        timeIndex: int = timeIndexModel.mapToSource(
            timeIndexModel.index(i, 0, QModelIndex())).row()
        # Get the type of time attribute
        timeType: Type = self.settingsPanel.valuesTable.model().frameModel(
        ).shape.colTypes[timeIndex]
        # Get the pandas dataframe
        dataframe: pd.DataFrame = self.settingsPanel.valuesTable.model(
        ).frameModel().frame.getRawFrame()

        if len(attributes) >= 1 and len(indexes) == 0:
            # Create line plot with different attributes as series
            chart = self.__createChartWithValues(dataframe, attributes,
                                                 timeIndex, timeType)
        elif (len(attributes) == 1 and len(indexes) >= 1) or \
                (len(attributes) >= 1 and len(indexes) == 1):
            # Create chart with 1 attribute and many indexes, or with many attributes and 1 index
            chart = self.__createChartWithIndexes(dataframe, attributes,
                                                  indexes, timeIndex, timeType)
        else:
            raise NotImplementedError('Invalid chart parameters')

        chart.setDropShadowEnabled(False)
        chart.setAnimationOptions(QtCharts.QChart.NoAnimation)
        chart.legend().setVisible(True)
        # Set font size for axis
        font: QFont = chart.axisX().labelsFont()
        font.setPointSize(9)
        chart.axisX().setLabelsFont(font)
        chart.axisY().setLabelsFont(font)
        chart.setMargins(QMargins(5, 5, 5, 30))
        chart.layout().setContentsMargins(2, 2, 2, 2)
        # Set new chart and delete previous one
        self.__setChart(chart)

    def __setChart(self, chart: QtCharts.QChart) -> None:
        """ Creates a new view and set the provided chart in it """
        self.createChartView()
        self.chartView.setChart(chart)
        self.chartView.setBestTickCount(chart.size())

    @Slot(str, str)
    def onFrameSelectionChanged(self, name: str, *_) -> None:
        # Reset the ChartView
        self.createChartView()
        if not name:
            return
        # Set attribute table
        frameModel = self._workbench.getDataframeModelByName(name)
        self.settingsPanel.valuesTable.setSourceFrameModel(frameModel)
        # Set up combo box for time axis
        timeAxisModel = AttributeTableModel(self, False, False, False)
        timeAxisModel.setFrameModel(frameModel)
        filteredModel = AttributeProxyModel(
            filterTypes=[Types.Datetime, Types.Ordinal], parent=self)
        filteredModel.setSourceModel(timeAxisModel)
        timeAxisModel.setParent(filteredModel)
        m = self.settingsPanel.timeAxisAttributeCB.model()
        self.settingsPanel.timeAxisAttributeCB.setModel(filteredModel)
        safeDelete(m)
        # Set up index table
        if self.searchableIndexTableModel.sourceModel():
            indexTableModel: IndexTableModel = self.searchableIndexTableModel.sourceModel(
            )
            indexTableModel.setFrameModel(frameModel)
        else:
            # Searchable model has not a source model
            indexTableModel: IndexTableModel = IndexTableModel(
                self.searchableIndexTableModel)
            indexTableModel.setFrameModel(frameModel)
            # Set up proxy model
            self.searchableIndexTableModel.setSourceModel(indexTableModel)
            self.searchableIndexTableModel.setFilterKeyColumn(1)
            # Connect view to proxy model
            self.settingsPanel.indexTable.setModel(
                self.searchableIndexTableModel)
            self.settingsPanel.indexTable.searchBar.textEdited.connect(
                self.searchableIndexTableModel.setFilterRegularExpression)
            self.settingsPanel.indexTable.tableView.horizontalHeader(
            ).sectionClicked.connect(indexTableModel.onHeaderClicked)
        # Must be connected because Qt slot is not virtual
        indexTableModel.headerDataChanged.connect(
            self.settingsPanel.indexTable.tableView.horizontalHeader(
            ).headerDataChanged)

    def createChartView(self) -> None:
        """ Creates a new chart view """
        # Creating a new view, instead of deleting chart, avoids many problems
        self.chartView = InteractiveChartView(parent=self, setInWindow=False)
        oldView = self.splitter.replaceWidget(0, self.chartView)
        self.chartView.setSizePolicy(oldView.sizePolicy())
        self.chartView.setRenderHint(
            QPainter.Antialiasing)  # For better looking charts
        self.chartView.show()
        safeDelete(oldView)
Пример #3
0
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.workbenchModel = WorkbenchModel(self)
        self.graph = flow.dag.OperationDag()
        self.operationMenu = OperationMenu()
        self.frameInfoPanel = FramePanel(parent=self,
                                         w=self.workbenchModel,
                                         opModel=self.operationMenu.model())
        self.workbenchView = WorkbenchView()
        self.workbenchView.setModel(self.workbenchModel)
        self.workbenchModel.emptyRowInserted.connect(
            self.workbenchView.startEditNoSelection)

        tabs = QTabWidget(self)

        attributeTab = AttributePanel(self.workbenchModel, self)
        chartsTab = ViewPanel(self.workbenchModel, self)
        self.graphScene = GraphScene(self)
        self._flowView = GraphView(self.graphScene, self)
        self.controller = GraphController(self.graph, self.graphScene,
                                          self._flowView, self.workbenchModel,
                                          self)

        tabs.addTab(attributeTab, '&Attribute')
        tabs.addTab(chartsTab, '&Visualise')
        tabs.addTab(self._flowView, 'F&low')
        self.__curr_tab = tabs.currentIndex()

        self.__leftSide = QSplitter(Qt.Vertical)
        self.__leftSide.addWidget(self.frameInfoPanel)
        self.__leftSide.addWidget(self.workbenchView)

        # layout = QHBoxLayout()
        # layout.addWidget(leftSplit, 2)
        # layout.addWidget(tabs, 8)
        splitter = QSplitter(Qt.Horizontal, self)
        splitter.addWidget(self.__leftSide)
        splitter.addWidget(tabs)
        layout = QHBoxLayout(self)
        layout.addWidget(splitter)

        tabs.currentChanged.connect(self.changeTabsContext)
        self.workbenchView.selectedRowChanged[str, str].connect(
            attributeTab.onFrameSelectionChanged)
        self.workbenchView.selectedRowChanged[str, str].connect(
            chartsTab.onFrameSelectionChanged)
        self.workbenchView.selectedRowChanged[str, str].connect(
            self.frameInfoPanel.onFrameSelectionChanged)
        self.workbenchView.rightClick.connect(self.createWorkbenchPopupMenu)

    @Slot(int)
    def changeTabsContext(self, tab_index: int) -> None:
        if tab_index == 2:
            self.__leftSide.replaceWidget(0, self.operationMenu)
            self.frameInfoPanel.hide()
            self.operationMenu.show()
            self.__curr_tab = 2
        elif self.__curr_tab == 2 and tab_index != 2:
            self.__leftSide.replaceWidget(0, self.frameInfoPanel)
            self.operationMenu.hide()
            self.frameInfoPanel.show()
            self.__curr_tab = tab_index

    @Slot(QModelIndex)
    def createWorkbenchPopupMenu(self, index: QModelIndex) -> None:
        # Create a popup menu when workbench is right-clicked over a valid frame name
        # Menu display delete and remove options
        frameName: str = index.data(Qt.DisplayRole)
        pMenu = QMenu(self)
        # Reuse MainWindow actions
        csvAction = self.parentWidget().aWriteCsv
        pickleAction = self.parentWidget().aWritePickle
        # Set correct args for the clicked row
        csvAction.setOperationArgs(w=self.workbenchModel, frameName=frameName)
        pickleAction.setOperationArgs(w=self.workbenchModel,
                                      frameName=frameName)
        deleteAction = QAction('Remove', pMenu)
        deleteAction.triggered.connect(
            lambda: self.workbenchModel.removeRow(index.row()))
        pMenu.addActions([csvAction, pickleAction, deleteAction])
        pMenu.popup(QtGui.QCursor.pos())

    def createNewFlow(self, graph: flow.dag.OperationDag) -> None:
        self.graph = graph
        oldScene = self._flowView.scene()
        self.graphScene = GraphScene(self)
        self._flowView.setScene(self.graphScene)
        oldScene.deleteLater()
        self.controller.deleteLater()
        self.controller = GraphController(self.graph, self.graphScene,
                                          self._flowView, self.workbenchModel,
                                          self)
Пример #4
0
class DebugView(QWidget, View):
    def __init__(self, parent, data):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data

        self.debug_state = binjaplug.get_state(data)
        memory_view = self.debug_state.memory_view
        self.debug_state.ui.debug_view = self

        QWidget.__init__(self, parent)
        self.controls = ControlsWidget.DebugControlsWidget(
            self, "Controls", data, self.debug_state)
        View.__init__(self)

        self.setupView(self)

        self.current_offset = 0

        self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

        frame = ViewFrame.viewFrameForWidget(self)
        self.memory_editor = LinearView(memory_view, frame)
        self.binary_editor = DisassemblyContainer(frame, data, frame)

        self.binary_text = TokenizedTextView(self, memory_view)
        self.is_raw_disassembly = False

        # TODO: Handle these and change views accordingly
        # Currently they are just disabled as the DisassemblyContainer gets confused
        # about where to go and just shows a bad view
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Hex Editor", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Linear Disassembly", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Types View", UIAction())

        self.memory_editor.actionHandler().bindAction("View in Hex Editor",
                                                      UIAction())
        self.memory_editor.actionHandler().bindAction(
            "View in Disassembly Graph", UIAction())
        self.memory_editor.actionHandler().bindAction("View in Types View",
                                                      UIAction())

        small_font = QApplication.font()
        small_font.setPointSize(11)

        bv_layout = QVBoxLayout()
        bv_layout.setSpacing(0)
        bv_layout.setContentsMargins(0, 0, 0, 0)

        bv_label = QLabel("Loaded File")
        bv_label.setFont(small_font)
        bv_layout.addWidget(bv_label)
        bv_layout.addWidget(self.binary_editor)

        self.bv_widget = QWidget()
        self.bv_widget.setLayout(bv_layout)

        disasm_layout = QVBoxLayout()
        disasm_layout.setSpacing(0)
        disasm_layout.setContentsMargins(0, 0, 0, 0)

        disasm_label = QLabel("Raw Disassembly at PC")
        disasm_label.setFont(small_font)
        disasm_layout.addWidget(disasm_label)
        disasm_layout.addWidget(self.binary_text)

        self.disasm_widget = QWidget()
        self.disasm_widget.setLayout(disasm_layout)

        memory_layout = QVBoxLayout()
        memory_layout.setSpacing(0)
        memory_layout.setContentsMargins(0, 0, 0, 0)

        memory_label = QLabel("Debugged Process")
        memory_label.setFont(small_font)
        memory_layout.addWidget(memory_label)
        memory_layout.addWidget(self.memory_editor)

        self.memory_widget = QWidget()
        self.memory_widget.setLayout(memory_layout)

        self.splitter.addWidget(self.bv_widget)
        self.splitter.addWidget(self.memory_widget)

        # Equally sized
        self.splitter.setSizes([0x7fffffff, 0x7fffffff])

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.controls)
        layout.addWidget(self.splitter, 100)
        self.setLayout(layout)

        self.needs_update = True
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.setSingleShot(False)
        self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

        self.add_scripting_ref()

    def add_scripting_ref(self):
        # Hack: The interpreter is just a thread, so look through all threads
        # and assign our state to the interpreter's locals
        for thread in threading.enumerate():
            if type(thread) == PythonScriptingInstance.InterpreterThread:
                thread.locals["dbg"] = self.debug_state

    def getData(self):
        return self.bv

    def getCurrentOffset(self):
        return self.binary_editor.getDisassembly().getCurrentOffset()

    def getFont(self):
        return binaryninjaui.getMonospaceFont(self)

    def navigate(self, addr):
        return self.binary_editor.getDisassembly().navigate(addr)

    def notifyMemoryChanged(self):
        self.needs_update = True

    def updateTimerEvent(self):
        if self.needs_update:
            self.needs_update = False

            # Refresh the editor
            if not self.debug_state.connected:
                self.memory_editor.navigate(0)
                return

            self.memory_editor.navigate(self.debug_state.stack_pointer)

    def showEvent(self, event):
        if not event.spontaneous():
            self.update_timer.start()
            self.add_scripting_ref()

    def hideEvent(self, event):
        if not event.spontaneous():
            self.update_timer.stop()

    def shouldBeVisible(self, view_frame):
        if view_frame is None:
            return False
        else:
            return True

    def setRawDisassembly(self, raw=False, lines=[]):
        if raw != self.is_raw_disassembly:
            self.splitter.replaceWidget(
                0, self.disasm_widget if raw else self.bv_widget)
            self.is_raw_disassembly = raw

        # terrible workaround for libshiboken conversion issue
        for line in lines:
            # line is LinearDisassemblyLine
            last_tok = line.contents.tokens[-1]
            #if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue
            #if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0")
            if last_tok.size != 8: continue
            #print('fixing: %s' % line)
            last_tok.value &= 0x7FFFFFFFFFFFFFFF

        self.binary_text.setLines(lines)
Пример #5
0
class WTreeEdit(QWidget):
    """TreeEdit widget is to show and edit all of the pyleecan objects data."""

    # Signals
    dataChanged = Signal()

    def __init__(self, obj, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)

        self.class_dict = ClassInfo().get_dict()
        self.treeDict = None  # helper to track changes
        self.obj = obj  # the object
        self.is_save_needed = False

        self.model = TreeEditModel(obj)

        self.setupUi()

        # === Signals ===
        self.selectionModel.selectionChanged.connect(self.onSelectionChanged)
        self.treeView.collapsed.connect(self.onItemCollapse)
        self.treeView.expanded.connect(self.onItemExpand)
        self.treeView.customContextMenuRequested.connect(self.openContextMenu)
        self.model.dataChanged.connect(self.onDataChanged)
        self.dataChanged.connect(self.setSaveNeeded)

        # === Finalize ===
        # set 'root' the selected item and resize columns
        self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
        self.treeView.resizeColumnToContents(0)

    def setupUi(self):
        """Setup the UI"""
        # === Widgets ===
        # TreeView
        self.treeView = QTreeView()
        # self.treeView.rootNode = model.invisibleRootItem()
        self.treeView.setModel(self.model)
        self.treeView.setAlternatingRowColors(False)

        # self.treeView.setColumnWidth(0, 150)
        self.treeView.setMinimumWidth(100)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.selectionModel = self.treeView.selectionModel()

        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.statusBar.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Maximum)
        self.statusBar.setStyleSheet(
            "QStatusBar {border: 1px solid rgb(200, 200, 200)}")
        self.saveLabel = QLabel("unsaved")
        self.saveLabel.setVisible(False)
        self.statusBar.addPermanentWidget(self.saveLabel)

        # Splitters
        self.leftSplitter = QSplitter()
        self.leftSplitter.setStretchFactor(0, 0)
        self.leftSplitter.setStretchFactor(1, 1)

        # === Layout ===
        # Horizontal Div.
        self.hLayout = QVBoxLayout()
        self.hLayout.setContentsMargins(0, 0, 0, 0)
        self.hLayout.setSpacing(0)

        # add widgets to layout
        self.hLayout.addWidget(self.leftSplitter)
        self.hLayout.addWidget(self.statusBar)

        # add widgets
        self.leftSplitter.addWidget(self.treeView)

        self.setLayout(self.hLayout)

    def update(self, obj):
        """Check if object has changed and update tree in case."""
        if not obj is self.obj:
            self.obj = obj
            self.model = TreeEditModel(obj)
            self.treeView.setModel(self.model)
            self.model.dataChanged.connect(self.onDataChanged)
            self.selectionModel = self.treeView.selectionModel()
            self.selectionModel.selectionChanged.connect(
                self.onSelectionChanged)
            self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
            self.setSaveNeeded(True)

    def setSaveNeeded(self, state=True):
        self.is_save_needed = state
        self.saveLabel.setVisible(state)

    def openContextMenu(self, point):
        """Generate and open context the menu at the given point position."""
        index = self.treeView.indexAt(point)
        pos = QtGui.QCursor.pos()

        if not index.isValid():
            return

        # get the data
        item = self.model.item(index)
        obj_info = self.model.get_obj_info(item)

        # init the menu
        menu = TreeEditContextMenu(obj_dict=obj_info, parent=self)
        menu.exec_(pos)

        self.onSelectionChanged(self.selectionModel.selection())

    def onItemCollapse(self, index):
        """Slot for item collapsed"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onItemExpand(self, index):
        """Slot for item expand"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onDataChanged(self, first=None, last=None):
        """Slot for changed data"""
        self.dataChanged.emit()
        self.onSelectionChanged(self.selectionModel.selection())

    def onSelectionChanged(self, itemSelection):
        """Slot for changed item selection"""
        # get the index
        if itemSelection.indexes():
            index = itemSelection.indexes()[0]
        else:
            index = self.treeView.model().index(0, 0)
            self.treeView.setCurrentIndex(index)
            return

        # get the data
        item = self.model.item(index)
        obj = item.object()
        typ = type(obj).__name__
        obj_info = self.model.get_obj_info(item)
        ref_typ = obj_info["ref_typ"] if obj_info else None

        # set statusbar information on class typ
        msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}"
        self.statusBar.showMessage(msg)

        # --- choose the respective widget by class type ---
        # numpy array -> table editor
        if typ == "ndarray":
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        elif typ == "MeshSolution":
            widget = WMeshSolution(obj)  # only a view (not editable)

        # list (no pyleecan type, non empty) -> table editor
        # TODO add another widget for lists of non 'primitive' types (e.g. DataND)
        elif isinstance(obj, list) and not self.isListType(ref_typ) and obj:
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        # generic editor
        else:
            # widget = SimpleInputWidget().generate(obj)
            widget = WTableParameterEdit(obj)
            widget.dataChanged.connect(self.dataChanged.emit)

        # show the widget
        if self.leftSplitter.widget(1) is None:
            self.leftSplitter.addWidget(widget)
        else:
            self.leftSplitter.replaceWidget(1, widget)
            widget.setParent(
                self.leftSplitter)  # workaround for PySide2 replace bug
            widget.show()
        pass

    def isListType(self, typ):
        if not typ:
            return False
        return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict

    def isDictType(self, typ):
        if not typ:
            return False
        return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
Пример #6
0
class ScatterPlotMatrix(DataView):
    def __init__(self, workbench: WorkbenchModel, parent=None):
        super().__init__(workbench, parent)
        self.__frameModel: FrameModel = None

        # Create widget for the two tables
        sideLayout = QVBoxLayout()
        self.__matrixAttributes = SearchableAttributeTableWidget(
            self, True, False, False, [Types.Numeric, Types.Ordinal])
        matrixLabel = QLabel(
            'Select at least two numeric attributes and press \'Create chart\' to plot'
        )
        matrixLabel.setWordWrap(True)
        self.__createButton = QPushButton('Create chart', self)
        self.__colorByBox = QComboBox(self)
        self.__autoDownsample = QCheckBox('Auto downsample', self)
        self.__useOpenGL = QCheckBox('Use OpenGL', self)
        self.__autoDownsample.setToolTip(
            'If too many points are to be rendered, this will try\n'
            'to plot only a subsample, improving performance with\n'
            'zooming and panning, but increasing rendering time')
        self.__useOpenGL.setToolTip(
            'Enforce usage of GPU acceleration to render charts.\n'
            'It is still an experimental feature but should speed\n'
            'up rendering with huge set of points')

        # Layout for checkboxes
        optionsLayout = QHBoxLayout()
        optionsLayout.addWidget(self.__autoDownsample, 0, Qt.AlignRight)
        optionsLayout.addWidget(self.__useOpenGL, 0, Qt.AlignRight)

        sideLayout.addWidget(matrixLabel)
        sideLayout.addWidget(self.__matrixAttributes)
        sideLayout.addLayout(optionsLayout)
        sideLayout.addWidget(self.__colorByBox, 0, Qt.AlignBottom)
        sideLayout.addWidget(self.__createButton, 0, Qt.AlignBottom)
        self.__matrixLayout: pg.GraphicsLayoutWidget = pg.GraphicsLayoutWidget(
        )
        self.__layout = QHBoxLayout(self)
        self.__comboModel = AttributeProxyModel(
            [Types.String, Types.Ordinal, Types.Nominal], self)

        # Error label to signal errors
        self.errorLabel = QLabel(self)
        self.errorLabel.setWordWrap(True)
        sideLayout.addWidget(self.errorLabel)
        self.errorLabel.hide()

        self.__splitter = QSplitter(self)
        sideWidget = QWidget(self)
        sideWidget.setLayout(sideLayout)
        # chartWidget.setMinimumWidth(300)
        self.__splitter.addWidget(self.__matrixLayout)
        self.__splitter.addWidget(sideWidget)
        self.__splitter.setSizes([600, 300])
        self.__layout.addWidget(self.__splitter)
        self.__splitter.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        # Connect
        self.__createButton.clicked.connect(self.showScatterPlots)

        self.spinner = QtWaitingSpinner(self.__matrixLayout)

    @Slot()
    def showScatterPlots(self) -> None:
        self.__createButton.setDisabled(True)
        # Create plot with selected attributes
        attributes: Set[int] = self.__matrixAttributes.model().checked
        if len(attributes) < 2:
            self.errorLabel.setText('Select at least 2 attributes')
            self.errorLabel.setStyleSheet('color: red')
            self.errorLabel.show()
            return  # stop
        elif self.errorLabel.isVisible():
            self.errorLabel.hide()
        # Get index of groupBy Attribute
        group: int = None
        selectedIndex = self.__colorByBox.currentIndex()
        if self.__comboModel.rowCount() > 0 and selectedIndex != -1:
            index: QModelIndex = self.__comboModel.mapToSource(
                self.__comboModel.index(selectedIndex, 0, QModelIndex()))
            group = index.row() if index.isValid() else None

        # Create a new matrix layout and delete the old one
        matrix = GraphicsPlotLayout(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.useOpenGL(self.__useOpenGL.isChecked())
        matrix.show()

        # Get attributes of interest
        toKeep: List[int] = list(attributes) if group is None else [
            group, *attributes
        ]
        filterDf = self.__frameModel.frame.getRawFrame().iloc[:, toKeep]
        # Create a worker to create scatter-plots on different thread
        worker = Worker(ProcessDataframe(), (filterDf, group, attributes))

        worker.signals.result.connect(self.__createPlots)
        # No need to deal with error/finished signals since there is nothing to do
        worker.setAutoDelete(True)
        self.spinner.start()
        QThreadPool.globalInstance().start(worker)

    def resetScatterPlotMatrix(self) -> None:
        # Create a new matrix layout
        matrix = pg.GraphicsLayoutWidget(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.show()

    @Slot(object, object)
    def __createPlots(
            self, _, result: Tuple[pd.DataFrame, List[str], List[int],
                                   bool]) -> None:
        """ Create plots and render all graphic items """
        # Unpack results
        df, names, attributes, grouped = result

        # Populate the matrix
        for r in range(len(attributes)):
            for c in range(len(attributes)):
                if r == c:
                    name: str = names[r]
                    self.__matrixLayout.addLabel(row=r, col=c, text=name)
                else:
                    xColName: str = names[c]
                    yColName: str = names[r]
                    seriesList = self.__createScatterSeries(
                        df=df,
                        xCol=xColName,
                        yCol=yColName,
                        groupBy=grouped,
                        ds=self.__autoDownsample.isChecked())
                    plot = self.__matrixLayout.addPlot(row=r, col=c)
                    for series in seriesList:
                        plot.addItem(series)
                    # Coordinates and data for later use
                    plot.row = r
                    plot.col = c
                    plot.xName = xColName
                    plot.yName = yColName
        # When all plot are created stop spinner and re-enable button
        self.spinner.stop()
        self.__createButton.setEnabled(True)

    @staticmethod
    def __createScatterSeries(df: Union[pd.DataFrame,
                                        pd.core.groupby.DataFrameGroupBy],
                              xCol: str, yCol: str, groupBy: bool,
                              ds: bool) -> List[pg.PlotDataItem]:
        """
        Creates a list of series of points to be plotted in the scatterplot

        :param df: the input dataframe
        :param xCol: name of the feature to use as x-axis
        :param yCol: name of the feature to use as y-axis
        :param groupBy: whether the feature dataframe is grouped by some attribute
        :param ds: whether to auto downsample the set of points during rendering

        :return:

        """
        allSeries = list()
        if groupBy:
            df: pd.core.groupby.DataFrameGroupBy
            colours = randomColors(len(df))
            i = 0
            for groupName, groupedDf in df:
                # Remove any row with nan values
                gdf = groupedDf.dropna()
                qSeries1 = pg.PlotDataItem(x=gdf[xCol],
                                           y=gdf[yCol],
                                           autoDownsample=ds,
                                           name=str(groupName),
                                           symbolBrush=pg.mkBrush(colours[i]),
                                           symbol='o',
                                           pen=None)
                allSeries.append(qSeries1)
                i += 1
        else:
            df: pd.DataFrame
            # Remove any row with nan values
            df = df.dropna()
            series = pg.PlotDataItem(x=df[xCol],
                                     y=df[yCol],
                                     autoDownsample=ds,
                                     symbol='o',
                                     pen=None)
            allSeries.append(series)
        return allSeries

    @Slot(str, str)
    def onFrameSelectionChanged(self, frameName: str, *_) -> None:
        if not frameName:
            return
        self.__frameModel = self._workbench.getDataframeModelByName(frameName)
        self.__matrixAttributes.setSourceFrameModel(self.__frameModel)
        # Combo box
        attributes = AttributeTableModel(self, False, False, False)
        attributes.setFrameModel(self.__frameModel)
        oldModel = self.__comboModel.sourceModel()
        self.__comboModel.setSourceModel(attributes)
        if oldModel:
            oldModel.deleteLater()
        self.__colorByBox.setModel(self.__comboModel)
        # Reset attribute panel
        self.resetScatterPlotMatrix()