////////////////////////////////////////////////////////////////////// // // // observerCSource.pas: Source level debugger // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM User Interface, released // // April 1st, 2003. The Initial Developer of the Original Code is // // Bottled Light, Inc. Portions created by Bottled Light, Inc. are // // Copyright (C) 2001-2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // IN PROGRESS, NEEDS WORK // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit observerCSource; //////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, Menus, SynEditHighlighter, SynHighlighterCpp, SynEdit, SynMemo, cpuObservers, console, dwarfUtils, ImgList, nexus, ExtCtrls, ToolWin, AddressSpace, StdCtrls, Math; ////////////////////////////////////////////////////////////////////// type TDebuggerLineInfo = (dlCurrentLine, dlBreakpointLine, dlExecutableLine); TDebuggerLineInfos = set of TDebuggerLineInfo; TjdevSourceViewer = class(TCpuObserver) cSyntaxHighlighter: TSynCppSyn; status: TStatusBar; openDialog: TOpenDialog; imglActions: TImageList; imglGutterGlyphs: TImageList; ToolBar1: TToolBar; ToolButton1: TToolButton; ToolButton2: TToolButton; ToolButton3: TToolButton; ToolButton4: TToolButton; ToolButton5: TToolButton; ToolButton6: TToolButton; ToolButton7: TToolButton; contextMenu: TPopupMenu; mToggleBreakpoint: TMenuItem; N1: TMenuItem; mChangeFont: TMenuItem; fontDialog: TFontDialog; mainMenu: TMainMenu; topHorizSplitter: TSplitter; editorHolder: TPanel; tabs: TTabControl; classviewHolder: TPanel; classFileToggle: TPageControl; tsClassView: TTabSheet; tsFileView: TTabSheet; fileView: TTreeView; bottomHolder: TPanel; verticalSplitter: TSplitter; memo: TSynMemo; logHolder: TPanel; log: TListBox; variableHolder: TPanel; variableViewTabs: TTabControl; variableView: TListView; bottomHorizSplitter: TSplitter; Run1: TMenuItem; mStepOver: TMenuItem; mTraceInto: TMenuItem; mTraceToNextSourceLine: TMenuItem; mRunToCursor: TMenuItem; mStepOut: TMenuItem; N2: TMenuItem; mShowExecutionPoint: TMenuItem; mInspect: TMenuItem; N3: TMenuItem; classView: TTreeView; treeIcons: TImageList; procedure memoStatusChange(Sender: TObject; Changes: TSynStatusChanges); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure tabsChanging(Sender: TObject; var AllowChange: Boolean); procedure tabsChange(Sender: TObject); procedure goToLineNumber(Sender: TObject); procedure FindPC1Click(Sender: TObject); procedure memoGutterClick(Sender: TObject; X, Y, Line: Integer; mark: TSynEditMark); procedure memoSpecialLineColors(Sender: TObject; Line: Integer; var Special: Boolean; var FG, BG: TColor); procedure mStepClick(Sender: TObject); procedure mToggleBreakpointClick(Sender: TObject); procedure ChangeFont(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure fileViewGetImageIndex(Sender: TObject; Node: TTreeNode); procedure fileViewClick(Sender: TObject); procedure classViewClick(Sender: TObject); private { Private declarations } targetFile: string; currentUnit: TCompilationUnit; amUpdating: boolean; fourNodes: array[0..3] of TTreeNode; threeNodes: array[0..2] of TTreeNode; procedure PaintGutterGlyphs(ACanvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); public procedure UpdateObserver; override; class function OCaption: string; override; function addTab(filename: string): integer; procedure focusFileTab(index: integer); procedure SetupFileView; procedure AddToFileView(filename: string; tab: integer); procedure BuildClassView; procedure UpdateLog; procedure WalkCVNode(node: PDwarfNode; dnode: TTreeNode); procedure AttributeCV(node: PDwarfNode; var dnode: TTreeNode); end; ////////////////////////////////////////////////////////////////////// TDebugSupportPlugin = class(TSynEditPlugin) protected FForm: TjdevSourceViewer; procedure AfterPaint(ACanvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); override; procedure LinesInserted(FirstLine, Count: integer); override; procedure LinesDeleted(FirstLine, Count: integer); override; public constructor Create(AForm: TjdevSourceViewer); end; ////////////////////////////////////////////////////////////////////// //function focusSourceFile(filename: string; train: boolean): TStringList; function loadSourceFile(filename: string): TStringList; function focusFile(filename: string): TStringList; function findFile(filename: string): TStringList; procedure closeSourceFiles; ////////////////////////////////////////////////////////////////////// var jdevSourceViewer: TjdevSourceViewer; fileList: TStringList; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// {$R *.DFM} ////////////////////////////////////////////////////////////////////// // TGutterMarkDrawPlugin ///////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// constructor TDebugSupportPlugin.Create(AForm: TjdevSourceViewer); begin inherited Create(AForm.memo); FForm := AForm; end; ////////////////////////////////////////////////////////////////////// procedure TDebugSupportPlugin.AfterPaint(ACanvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); begin FForm.PaintGutterGlyphs(ACanvas, AClip, FirstLine, LastLine); end; ////////////////////////////////////////////////////////////////////// procedure TDebugSupportPlugin.LinesDeleted(FirstLine, Count: integer); begin end; ////////////////////////////////////////////////////////////////////// procedure TDebugSupportPlugin.LinesInserted(FirstLine, Count: integer); begin end; ////////////////////////////////////////////////////////////////////// // TjdevSourceViewer ///////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// class function TjdevSourceViewer.OCaption: string; begin Result := 'Source Debugger'; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.UpdateObserver; var compUnit: TCompilationUnit; i, k: integer; v: TDwarfVariable; inReg, notOptimized: boolean; addr: uint32; node: TListItem; begin memo.Refresh; if not assigned(dwarf) then Exit; with variableView.Items do begin BeginUpdate; Clear; // res.Add('Valid variables at PC = ' + IntToHex(vmCurrentPC, 8)); for k := 0 to dwarf.compUnits.Count - 1 do begin compUnit := TCompilationUnit(dwarf.compUnits[k]); if (vmCurrentPC >= compUnit.lowPC) and (vmCurrentPC <= compUnit.highPC) then begin for i := 0 to compUnit.variables.Count - 1 do begin v := TDwarfVariable(compUnit.variables.Objects[i]); // attrib := FindAttribute(v.node, DW_AT_location); if Assigned(v.loc) and (v.startScope <= vmCurrentPC) then begin addr := LocationSM(v.loc.block, inReg, notOptimized); node := Add; node.Caption := v.name; if inReg then begin addr := addr and 15; node.SubItems.Add('0x' + IntToHex(vmGetRegister(addr), 8)); end else begin node.SubItems.Add('0x' + IntToHex(vmReadWord(addr), 8)); end; end; end; end; end; EndUpdate; end; UpdateLog; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.memoStatusChange(Sender: TObject; Changes: TSynStatusChanges); var st: string; begin // Update the 1st panel to display the cursor position if (scCaretX in Changes) or (scCaretY in Changes) then begin st := Format('%9d: %-8d', [memo.CaretY, memo.CaretX]); status.Panels[0].Text := st; end; // Update the 2nd panel to display the state of the file (modified or not) if scModified in Changes then begin if memo.Modified then st := 'Modified' else st := ''; status.Panels[1].Text := st; end; // Update the 3rd panel to display the edit mode if (scInsertMode in Changes) or (scReadOnly in Changes) then begin if memo.ReadOnly then st := 'Read only' else begin if memo.InsertMode then st := 'Insert' else st := 'Overwrite'; end; status.Panels[2].Text := st; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.FormCreate(Sender: TObject); var index, temp: integer; begin // Muck with the gutter stuff TDebugSupportPlugin.Create(Self); // Set up a few things jdevSourceViewer := self; cpuSourceDebug := false; cpuSourceDebug := true; vmSoftBreakpoints(cpuSourceDebug); amUpdating := false; // Start a null hierarchy for the file viewer SetupFileView; // Add the tabs and build the file viewer for index := 0 to fileList.count - 1 do begin temp := addTab(fileList.Strings[index]); AddToFileView(fileList.Strings[index], temp); end; // Make things double buffered fileView.DoubleBuffered := true; classView.DoubleBuffered := true; log.DoubleBuffered := true; variableView.DoubleBuffered := true; // Set up help HelpContext := LinkHelp('source_debugger.html'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.FormDestroy(Sender: TObject); begin jdevSourceViewer := nil; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.tabsChanging(Sender: TObject; var AllowChange: Boolean); begin if tabs.tabIndex > -1 then (fileList.Objects[tabs.TabIndex] as TStringList).Assign(memo.Lines); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.tabsChange(Sender: TObject); var i: integer; begin if tabs.tabIndex > -1 then begin if tabs.Tabs[tabs.tabIndex] = '!' then begin i := 0; while i < tabs.Tabs.Count do begin if tabs.Tabs[i] <> '!' then begin tabs.TabIndex := i; Exit; end; Inc(i); end; end; memo.Lines.Assign(fileList.Objects[tabs.TabIndex] as TStringList); BuildClassView; end {else Close;} end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.goToLineNumber(Sender: TObject); begin memo.TopLine := StrToIntDef(InputBox('Go to Line Number', 'Enter new line number:', IntToStr(memo.TopLine)), memo.TopLine); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.FindPC1Click(Sender: TObject); var hit: TLineHit; begin if FindLineInP(vmCurrentPC, hit) then begin logWriteLn(Format('line: %d, column: %d, iss: %d, bb: %d, file: %s', [hit.line, hit.column, ord(hit.isStatement), ord(hit.basicBlock), hit.filename])); if Assigned(jdevSourceViewer) then begin focusFile(hit.filename); memo.CaretX := hit.column; memo.CaretY := hit.line; if (hit.line < uint32(memo.TopLine)) or (uint32(memo.LinesInWindow + memo.TopLine) > hit.line) then memo.TopLine := hit.line; end; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.memoGutterClick(Sender: TObject; X, Y, Line: Integer; mark: TSynEditMark); var addr: uint32; st: TStringList; begin if tabs.TabIndex > -1 then begin st := fileList.Objects[tabs.TabIndex] as TStringList; addr := uint32(st.Objects[line-1]); if addr > 0 then begin if bpmHard in vmIsBreakpoint(addr) then vmRemoveBreakpoint(addr, [bpmHard]) else vmAddBreakpoint(addr, false); end; memo.Refresh; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.memoSpecialLineColors(Sender: TObject; Line: Integer; var Special: Boolean; var FG, BG: TColor); var addr: uint32; st: TStringList; begin if tabs.TabIndex > -1 then begin st := fileList.Objects[tabs.TabIndex] as TStringList; addr := uint32(st.Objects[line-1]); if addr = 0 then Exit; // Color the current line if addr = vmCurrentPC then begin Special := true; BG := memo.SelectedColor.Background; FG := memo.SelectedColor.Foreground; end; // Color a breakpoint if bpmHard in vmIsBreakpoint(addr) then begin Special := true; BG := clRed; FG := clWhite; end; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.PaintGutterGlyphs(ACanvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); function GetLineInfo(line: integer): TDebuggerLineInfos; var addr: uint32; st: TStringList; begin Result := []; if tabs.TabIndex > -1 then begin st := fileList.Objects[tabs.TabIndex] as TStringList; addr := uint32(st.Objects[line-1]); if addr > 0 then begin Result := Result + [dlExecutableLine]; if bpmHard in vmIsBreakpoint(addr) then Result := Result + [dlBreakpointLine]; if vmCurrentPC = addr then Result := Result + [dlCurrentLine]; end; end; end; var LH, X, Y: integer; LI: TDebuggerLineInfos; ImgIndex: integer; begin X := 4; LH := memo.LineHeight; Y := (LH - imglGutterGlyphs.Height) div 2 + LH * (FirstLine - memo.TopLine); while FirstLine <= LastLine do begin LI := GetLineInfo(FirstLine); if dlCurrentLine in LI then begin if dlBreakpointLine in LI then ImgIndex := 2 else ImgIndex := 1; end else if dlExecutableLine in LI then begin if dlBreakpointLine in LI then ImgIndex := 3 else ImgIndex := 0; end else begin if dlBreakpointLine in LI then ImgIndex := 4 else ImgIndex := -1; end; if ImgIndex >= 0 then imglGutterGlyphs.Draw(ACanvas, X, Y, ImgIndex); Inc(FirstLine); Inc(Y, LH); end; end; ////////////////////////////////////////////////////////////////////// procedure FreeFiles; var index: integer; begin for index := 0 to fileList.Count - 1 do (fileList.Objects[index] as TStringList).Free; fileList.Clear; if jdevSourceViewer <> nil then begin jdevSourceViewer.tabs.Tabs.Clear; jdevSourceViewer.tabsChange(nil); jdevSourceViewer.SetupFileView; end; end; ////////////////////////////////////////////////////////////////////// procedure FixCyg(var s: string); var i: integer; begin if Pos('cygdrive', s) > 0 then begin Delete(s, 1, 10); if Length(s) > 1 then s[2] := ':'; Insert('\', s, 3); end; i := Pos('/', s); while i > 0 do begin s[i] := '\'; i := Pos('/', s); end; end; ////////////////////////////////////////////////////////////////////// function loadSourceFile(filename: string): TStringList; var i, temp: integer; backup: string; begin backup := filename; if Pos('/', filename) > 0 then FixCyg(filename); Result := nil; // Look for the file in the file list i := fileList.IndexOf(filename); if i > -1 then Result := TStringList(fileList.Objects[i]) else begin // The file isn't already loaded, so do so if FileExists(filename) then begin Result := TStringList.Create; Result.LoadFromFile(filename); fileList.AddObject(filename, Result); // Add a tab to the source viewer if its open if Assigned(jdevSourceViewer) then begin jdevSourceViewer.targetFile := backup; temp := jdevSourceViewer.addTab(filename); jdevSourceViewer.AddToFileView(filename, temp); end; end {else fileList.AddObject(filename, nil)}; end; end; ////////////////////////////////////////////////////////////////////// function TjdevSourceViewer.addTab(filename: string): integer; var st: string; begin st := ExtractFilename(filename); if not FileExists(filename) then st := '!'; tabs.TabIndex := tabs.Tabs.Add(st); Result := tabs.tabIndex; tabsChange(nil); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.focusFileTab(index: integer); begin tabs.TabIndex := index; end; ////////////////////////////////////////////////////////////////////// function focusFile(filename: string): TStringList; var index: integer; begin index := fileList.IndexOf(filename); if index > -1 then begin if jdevSourceViewer <> nil then jdevSourceViewer.focusFileTab(index); Result := fileList.Objects[index] as TStringList; end else Result := nil; end; ////////////////////////////////////////////////////////////////////// function findFile(filename: string): TStringList; var index: integer; begin if Pos('/', filename) > 0 then FixCyg(filename); index := fileList.IndexOf(filename); if index > -1 then Result := TStringList(fileList.Objects[index]) else Result := nil; end; ////////////////////////////////////////////////////////////////////// procedure closeSourceFiles; begin FreeFiles; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.mStepClick(Sender: TObject); begin // cpuActive := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.mToggleBreakpointClick(Sender: TObject); begin memoGutterClick(Sender, 0, 0, memo.CaretY, nil); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.ChangeFont(Sender: TObject); begin // fontDialog.Font.Assign(sourceDebuggerFont); // if fontDialog.Execute then sourceDebuggerFont.Assign(fontDialog.Font); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.FormClose(Sender: TObject; var Action: TCloseAction); begin cpuSourceDebug := false; vmSoftBreakpoints(cpuSourceDebug); end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.SetupFileView; var i: integer; begin with fileView.Items do begin BeginUpdate; Clear; fourNodes[0] := Add(nil, 'Source Files'); fourNodes[1] := Add(nil, 'Assembler Files'); fourNodes[2] := Add(nil, 'Header Files'); fourNodes[3] := Add(nil, 'Resources'); for i := 0 to 3 do fourNodes[i].data := pointer(-1); EndUpdate; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.AddToFileView(filename: string; tab: integer); var ext: string; j: integer; begin with fileView do begin ext := Uppercase(ExtractFileExt(filename)); if (ext = '.C') or (ext = '.CPP') then j := 0 else if (ext = '.S') or (ext = '.ASM') then j := 1 else if (ext = '.H') or (ext = '.HPP') then j := 2 else j := 3; with Items.AddChild(fourNodes[j], ExtractFileName(filename)) do begin Data := pointer(tab); end; end; end; (* case attrib^.format of DW_FORM_addr: st := 'address $' + IntToHex(attrib^.address, 8); DW_FORM_block, DW_FORM_block1, DW_FORM_block2, DW_FORM_block4: st := 'block of size ' + IntToStr(attrib^.block.size); DW_FORM_sdata, DW_FORM_udata, DW_FORM_data1, DW_FORM_data2, DW_FORM_data4, DW_FORM_data8: st := 'data $' + IntToHex(attrib^.data, 8); DW_FORM_string, DW_FORM_strp: st := attrib^.st; DW_FORM_flag: if attrib^.flag then st := 'TRUE' else st := 'FALSE'; DW_FORM_ref_addr: st := 'offset FINDME $' + IntToHex(attrib^.offset, 8); DW_FORM_ref_udata, DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4, DW_FORM_ref8: st := 'offset $' + IntToHex(attrib^.offset, 8); end; tree.Items.AddChild(dnode, Format('%-30s', [DwarfAttributeToString(attrib^.name)]) + ' ' + st);*) ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.AttributeCV(node: PDwarfNode; var dnode: TTreeNode); var attrib: PDwarfAttribute; begin attrib := node^.attribs; while attrib <> nil do begin case attrib^.name of DW_AT_name: dnode.Text := attrib^.st; DW_AT_artificial: if attrib^.flag then begin classView.Items.Delete(dnode); dnode := nil; Exit; end; DW_AT_decl_file: begin if (integer(attrib^.data) <= currentUnit.files.Count) and (attrib^.data > 0) then begin if currentUnit.files.strings[attrib^.data - 1] <> targetFile then begin classView.Items.Delete(dnode); dnode := nil; Exit; end; end; end; end; attrib := attrib^.next; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.WalkCVNode(node: PDwarfNode; dnode: TTreeNode); var temp: TTreeNode; doKids: boolean; begin while node <> nil do begin temp := nil; doKids := false; case node^.tag of DW_TAG_variable: begin temp := classView.Items.AddChild(threeNodes[2], ''); temp.Data := node; AttributeCV(node, temp); end; DW_TAG_member: begin temp := classView.Items.AddChild(dnode, ''); temp.Data := node; AttributeCV(node, temp); end; DW_TAG_structure_type, DW_TAG_class_type: begin temp := classView.Items.AddChild(nil, ''); temp.Data := node; AttributeCV(node, temp); doKids := true; end; DW_TAG_compile_unit: doKids := true; { DW_TAG_array_type, DW_TAG_enumeration_type, DW_TAG_pointer_type, DW_TAG_reference_type, DW_TAG_string_type, DW_TAG_subroutine_type, DW_TAG_typedef, DW_TAG_union_type, DW_TAG_ptr_to_member_type, DW_TAG_set_type, DW_TAG_subrange_type, DW_TAG_base_type, DW_TAG_const_type, DW_TAG_packed_type, DW_TAG_volatile_type: begin temp := classView.Items.AddChild(threeNodes[1], ''); temp.Data := node; AttributeCV(node, temp); end; } DW_TAG_subprogram: begin if dnode = nil then temp := classView.Items.AddChild(threeNodes[0], '') else temp := classView.Items.AddChild(dnode, ''); temp.Data := node; AttributeCV(node, temp); end; end; if temp = nil then temp := dnode; if node.hasChildren and doKids then WalkCVNode(node^.kids, temp); node := node^.next; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.BuildClassView; var i: integer; begin Exit; // fixme, findme: this is just too expensive if dwarf = nil then Exit;// targetFile := tab // targetFile := 2; with classView.Items do begin BeginUpdate; Clear; threeNodes[0] := Add(nil, 'Functions'); threeNodes[1] := Add(nil, 'Types'); threeNodes[2] := Add(nil, 'Globals'); for i := 0 to 2 do threeNodes[i].data := pointer(-1); for i := 0 to dwarf.CompUnits.Count - 1 do begin currentUnit := TCompilationUnit(dwarf.CompUnits.Items[i]); WalkCVNode(currentUnit.rootNode, nil); end; EndUpdate; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.fileViewGetImageIndex(Sender: TObject; Node: TTreeNode); begin if integer(Node.Data) < 0 then Node.ImageIndex := Ord(node.Expanded) else Node.ImageIndex := 2; Node.SelectedIndex := Node.ImageIndex; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.fileViewClick(Sender: TObject); var i: integer; begin if fileView.Selected <> nil then begin i := integer(fileView.Selected.Data); if i > -1 then begin tabs.tabIndex := i; tabsChange(nil); end; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.UpdateLog; var i: integer; begin if not amUpdating then begin amUpdating := true; i := Max(1, log.Height div log.ItemHeight - 1); log.Items.BeginUpdate; log.Items.Clear; logGetEntries(logNumEntries, i, log.Items); log.Items.EndUpdate; amUpdating := false; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevSourceViewer.classViewClick(Sender: TObject); var node: PDwarfNode; a: PDwarfAttribute; begin if classView.Selected <> nil then begin node := classView.Selected.Data; if (node <> nil) and (integer(node) <> -1) then begin a := FindAttribute(node, DW_AT_decl_line); if a <> nil then begin memo.TopLine := a^.data; end; end; end; end; ////////////////////////////////////////////////////////////////////// initialization jdevSourceViewer := nil; fileList := TStringList.Create; RegisterViewer(TjdevSourceViewer); finalization FreeFiles; fileList.Free; end. //////////////////////////////////////////////////////////////////////