@@ -58,7 +58,8 @@ int FrameItem::row() const
5858}
5959
6060
61- ThreadFrameModel::ThreadFrameModel (QObject* parent, DebuggerControllerRef controller) : QAbstractItemModel(parent), m_controller(controller)
61+ ThreadFrameModel::ThreadFrameModel (QObject* parent, DebuggerControllerRef controller) :
62+ QAbstractItemModel(parent), m_controller(controller)
6263{
6364 rootItem = new FrameItem ();
6465}
@@ -109,7 +110,8 @@ QVariant ThreadFrameModel::data(const QModelIndex& index, int role) const
109110
110111 auto isActiveThread = m_controller->GetActiveThread ().m_tid == item->tid ();
111112
112- QString text = QString::asprintf (" %s0x%x @ 0x%" PRIx64, isActiveThread ? " (*) " : " " , item->tid (), item->threadPc ());
113+ QString text =
114+ QString::asprintf (" %s0x%x @ 0x%" PRIx64, isActiveThread ? " (*) " : " " , item->tid (), item->threadPc ());
113115 if (role == Qt::SizeHintRole)
114116 return QVariant ((qulonglong)text.size ());
115117
@@ -182,20 +184,22 @@ void ThreadFrameModel::updateRows(DebuggerController* controller)
182184 parents << rootItem;
183185
184186 std::vector<DebugThread> threads = controller->GetThreads ();
185-
187+
186188 // Sort threads so that the active thread appears first
187189 uint32_t activeThreadId = controller->GetActiveThread ().m_tid ;
188190 std::sort (threads.begin (), threads.end (), [activeThreadId](const DebugThread& a, const DebugThread& b) {
189191 // Active thread comes first, then sort by thread ID for consistent ordering
190192 bool aIsActive = (a.m_tid == activeThreadId);
191193 bool bIsActive = (b.m_tid == activeThreadId);
192-
193- if (aIsActive && !bIsActive) return true ;
194- if (!aIsActive && bIsActive) return false ;
195-
194+
195+ if (aIsActive && !bIsActive)
196+ return true ;
197+ if (!aIsActive && bIsActive)
198+ return false ;
199+
196200 return a.m_tid < b.m_tid ;
197201 });
198-
202+
199203 for (const DebugThread& thread : threads)
200204 {
201205 parents.last ()->appendChild (new FrameItem (thread, parents.last ()));
@@ -550,6 +554,92 @@ void ThreadFramesWidget::copy()
550554}
551555
552556
557+ void ThreadFramesWidget::copyCurrentFrame ()
558+ {
559+ QModelIndexList sel = selectionModel ()->selectedIndexes ();
560+ if (sel.empty ())
561+ return ;
562+
563+ QString text;
564+ QSet<int > processedRows;
565+
566+ for (const QModelIndex& index : sel)
567+ {
568+ if (!index.isValid ())
569+ continue ;
570+
571+ int row = index.row ();
572+ if (processedRows.contains (row))
573+ continue ;
574+
575+ processedRows.insert (row);
576+
577+ FrameItem* item = static_cast <FrameItem*>(index.internalPointer ());
578+ if (!item || !item->isFrame ())
579+ continue ;
580+
581+ if (!text.isEmpty ())
582+ text += " \n " ;
583+
584+ // Format: FrameIndex Module Function PC SP FP
585+ text += QString::asprintf (" %lu %s %s 0x%" PRIx64 " 0x%" PRIx64 " 0x%" PRIx64, item->frameIndex (),
586+ item->module ().c_str (), item->function ().c_str (), item->framePc (), item->sp (), item->fp ());
587+ }
588+
589+ if (text.isEmpty ())
590+ return ;
591+
592+ auto * clipboard = QGuiApplication::clipboard ();
593+ clipboard->clear ();
594+ auto * mime = new QMimeData ();
595+ mime->setText (text);
596+ clipboard->setMimeData (mime);
597+ }
598+
599+
600+ void ThreadFramesWidget::copyAllFrames ()
601+ {
602+ QString text;
603+
604+ // Iterate through all top-level items (threads)
605+ for (int i = 0 ; i < m_model->rowCount (); i++)
606+ {
607+ QModelIndex threadIndex = m_model->index (i, 0 );
608+ if (!threadIndex.isValid ())
609+ continue ;
610+
611+ FrameItem* threadItem = static_cast <FrameItem*>(threadIndex.internalPointer ());
612+ if (!threadItem)
613+ continue ;
614+
615+ // Iterate through all frames in this thread
616+ for (int j = 0 ; j < threadItem->childCount (); j++)
617+ {
618+ FrameItem* frameItem = threadItem->child (j);
619+ if (!frameItem || !frameItem->isFrame ())
620+ continue ;
621+
622+ if (!text.isEmpty ())
623+ text += " \n " ;
624+
625+ // Format: FrameIndex Module Function PC SP FP
626+ text += QString::asprintf (" %lu %s %s 0x%" PRIx64 " 0x%" PRIx64 " 0x%" PRIx64, frameItem->frameIndex (),
627+ frameItem->module ().c_str (), frameItem->function ().c_str (), frameItem->framePc (), frameItem->sp (),
628+ frameItem->fp ());
629+ }
630+ }
631+
632+ if (text.isEmpty ())
633+ return ;
634+
635+ auto * clipboard = QGuiApplication::clipboard ();
636+ clipboard->clear ();
637+ auto * mime = new QMimeData ();
638+ mime->setText (text);
639+ clipboard->setMimeData (mime);
640+ }
641+
642+
553643ThreadFramesWidget::ThreadFramesWidget (QWidget* parent, ViewFrame* frame, BinaryViewRef data) :
554644 QTreeView(parent), m_view(frame)
555645{
@@ -585,7 +675,8 @@ ThreadFramesWidget::ThreadFramesWidget(QWidget* parent, ViewFrame* frame, Binary
585675 actionName = QString::fromStdString (" Resume Thread" );
586676 UIAction::registerAction (actionName);
587677 m_menu->addAction (actionName, " Options" , MENU_ORDER_FIRST);
588- m_actionHandler.bindAction (actionName, UIAction ([this ]() { resumeThread (); }, [this ]() { return canSuspendOrResume (); }));
678+ m_actionHandler.bindAction (
679+ actionName, UIAction ([this ]() { resumeThread (); }, [this ]() { return canSuspendOrResume (); }));
589680
590681 actionName = QString::fromStdString (" Make It Solo Thread" );
591682 UIAction::registerAction (actionName);
@@ -623,16 +714,24 @@ ThreadFramesWidget::ThreadFramesWidget(QWidget* parent, ViewFrame* frame, Binary
623714 }
624715 });
625716
717+ actionName = QString::fromStdString (" Copy Current Stack Trace" );
718+ UIAction::registerAction (actionName);
719+ m_menu->addAction (actionName, " Options" , MENU_ORDER_NORMAL);
720+ m_actionHandler.bindAction (
721+ actionName, UIAction ([this ]() { copyCurrentFrame (); }, [this ]() { return selectionNotEmpty (); }));
722+
723+ actionName = QString::fromStdString (" Copy All Stack Traces" );
724+ UIAction::registerAction (actionName);
725+ m_menu->addAction (actionName, " Options" , MENU_ORDER_NORMAL);
726+ m_actionHandler.bindAction (actionName, UIAction ([this ]() { copyAllFrames (); }));
727+
626728 // TODO: set as active thread action?
627729
628730 connect (this , &QTreeView::doubleClicked, this , &ThreadFramesWidget::onDoubleClicked);
629731 connect (this , &ThreadFramesWidget::debuggerEvent, this , &ThreadFramesWidget::onDebuggerEvent);
630732
631733 m_debuggerEventCallback = m_debugger->RegisterEventCallback (
632- [&](const DebuggerEvent& event) {
633- emit debuggerEvent (event);
634- },
635- " Thread Frame" );
734+ [&](const DebuggerEvent& event) { emit debuggerEvent (event); }, " Thread Frame" );
636735
637736 updateContent ();
638737}
@@ -721,7 +820,7 @@ void ThreadFramesWidget::onDoubleClicked()
721820 {
722821 uint32_t tid = frameItem->tid ();
723822 uint32_t currentTid = m_debugger->GetActiveThread ().m_tid ;
724-
823+
725824 if (tid != currentTid && !m_debugger->IsRunning ())
726825 m_debugger->SetActiveThread (tid);
727826
0 commit comments