unit disassemblerviewunit; { Disassemblerview is a component that displays the memory using the disassembler routines requirements: display disassembled lines split up into sections, resizable with a section bar selecting one or more lines keyboard navigation show function names before each function extra classes: Lines Lines contain he disassembled address and the description of that line } interface uses windows,forms, classes, controls, comctrls, stdctrls, extctrls, symbolhandler, cefuncproc, newkernelhandler, graphics, disassemblerviewlinesunit, disassembler, math, messages, menus, dissectcodethread; type TShowjumplineState=(jlsAll, jlsOnlyWithinRange); type TDisassemblerview=class(tpanel) private statusinfo: TPanel; statusinfolabel: TLabel; header: THeaderControl; previousCommentsTabWidth: integer; scrollbox: TScrollbox; //for the header verticalscrollbar: TScrollbar; disassembleDescription: TPanel; disCanvas: TPaintbox; isUpdating: boolean; fTotalvisibledisassemblerlines: integer; disassemblerlines: tlist; offscreenbitmap: TBitmap; fSelectedAddress: dword; //normal selected address fSelectedAddress2: dword; //secondary selected address (when using shift selecting) fTopAddress: dword; //address to start disassembling from fShowJumplines: boolean; //defines if it should draw jumplines or not fShowjumplineState: TShowjumplineState; fdissectCode: TDissectCodeThread; procedure updateScrollbox; procedure scrollboxResize(Sender: TObject); //-scrollbar- procedure scrollbarChange(Sender: TObject); procedure scrollbarKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure scrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); procedure headerSectionResize(HeaderControl: THeaderControl; Section: THeaderSection); procedure OnLostFocus(sender: TObject); procedure DisCanvasPaint(Sender: TObject); procedure DisCanvasMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure DisCanvasMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure SetPopupMenu(p: Tpopupmenu); function getPopupMenu: Tpopupmenu; procedure wmMouseWheel (var Msg : TWMMouseWheel); message wm_MouseWheel; procedure setSelectedAddress(address: dword); procedure setTopAddress(address: dword); function getOnDblClick: TNotifyEvent; procedure setOnDblClick(x: TNotifyEvent); procedure renderJumpLines; procedure setJumpLines(state: boolean); procedure setJumplineState(state: tshowjumplinestate); protected procedure HandleSpecialKey(key: word); procedure WndProc(var msg: TMessage); override; procedure DoEnter; override; procedure DoExit; override; published property OnKeyDown; property OnDblClick: TNotifyEvent read getOnDblClick write setOnDblClick; public procedure BeginUpdate; //stops painting until endupdate procedure EndUpdate; procedure Update; procedure setCommentsTab(state: boolean); function getheaderWidth(headerid: integer): integer; procedure setheaderWidth(headerid: integer; size: integer); property Totalvisibledisassemblerlines: integer read fTotalvisibledisassemblerlines; property PopupMenu: TPopupMenu read getPopupMenu write SetPopupMenu; property SelectedAddress: dword read fSelectedAddress write setSelectedAddress; property SelectedAddress2: dword read fSelectedAddress2; property TopAddress: dword read fTopAddress write setTopAddress; property ShowJumplines: boolean read fShowJumplines write setJumpLines; property ShowJumplineState: TShowJumplineState read fShowjumplinestate write setJumplineState; constructor create(AOwner: TComponent); override; end; implementation procedure TDisassemblerview.setJumplineState(state: tshowjumplinestate); begin fShowjumplineState:=state; update; end; procedure TDisassemblerview.setJumpLines(state: boolean); begin fShowJumplines:=state; update; end; function TDisassemblerview.getOnDblClick: TNotifyEvent; begin result:=disCanvas.OnDblClick; end; procedure TDisassemblerview.setOnDblClick(x: TNotifyEvent); begin disCanvas.OnDblClick:=x; end; function TDisassemblerview.getheaderWidth(headerid: integer): integer; begin result:=header.Sections[headerid].width; end; procedure TDisassemblerview.setheaderWidth(headerid: integer; size: integer); begin header.Sections[headerid].width:=size; end; procedure TDisassemblerview.setCommentsTab(state:boolean); begin if not state then begin previousCommentsTabWidth:=header.Sections[3].Width; header.Sections[3].MinWidth:=0; header.Sections[3].Width:=0; header.Sections[3].MaxWidth:=0; end else begin header.Sections[3].MaxWidth:=10000; if previousCommentsTabWidth>0 then header.Sections[3].Width:=previousCommentsTabWidth; header.Sections[3].MinWidth:=5; end; update; headerSectionResize(header, header.Sections[3]); end; procedure TDisassemblerview.setTopAddress(address: dword); begin fTopAddress:=address; fSelectedAddress:=address; fSelectedAddress2:=address; update; end; procedure TDisassemblerview.setSelectedAddress(address: dword); var i: integer; found: boolean; begin fSelectedAddress:=address; fSelectedAddress2:=address; if (fTotalvisibledisassemblerlines>0) and (InRange(fSelectedAddress, Tdisassemblerline(disassemblerlines[0]).address, Tdisassemblerline(disassemblerlines[fTotalvisibledisassemblerlines-1]).address)) then begin //in range found:=false; for i:=0 to fTotalvisibledisassemblerlines-1 do if address=Tdisassemblerline(disassemblerlines[i]).address then begin found:=true; break; end; //not in one of the current lines. Looks like the disassembler order is wrong. Fix it by setting it to the top address if not found then fTopAddress:=address; end else fTopAddress:=address; update; end; procedure TDisassemblerview.wmMouseWheel (var Msg : TWMMouseWheel); begin if msg.WheelDelta>0 then begin fTopAddress:=previousopcode(fTopAddress); //up end else begin disassemble(fTopAddress); //down end; update; end; function TDisassemblerview.getPopupMenu: Tpopupmenu; begin result:=discanvas.PopupMenu; end; procedure TDisassemblerview.SetPopupMenu(p: tpopupmenu); begin discanvas.PopupMenu:=p; end; procedure TDisassemblerview.HandleSpecialKey(key: word); var i: integer; shiftispressed: boolean; ctrlispressed: boolean; altispressed: boolean; begin beginupdate; shiftispressed:=GetBit(15, GetKeyState(VK_SHIFT))=1; ctrlispressed:=GetBit(15, GetKeyState(VK_CONTROL))=1; altispressed:=GetBit(15, GetKeyState(VK_MENU))=1; case key of VK_UP: begin fSelectedAddress:=previousopcode(fSelectedAddress); if (fSelectedAddress= TDisassemblerLine(disassemblerlines[fTotalvisibledisassemblerlines-1]).address) then begin disassemble(fTopAddress); update; fSelectedAddress:=TDisassemblerLine(disassemblerlines[fTotalvisibledisassemblerlines-1]).address; end; end; VK_LEFT: dec(fTopAddress); VK_RIGHT: inc(fTopAddress); vk_prior: begin if fSelectedAddress>TDisassemblerLine(disassemblerlines[0]).address then fSelectedAddress:=TDisassemblerLine(disassemblerlines[0]).address else begin for i:=0 to fTotalvisibledisassemblerlines-2 do fTopAddress:=previousopcode(fTopAddress); //go 'numberofaddresses'-1 times up fSelectedAddress:=0; update; fSelectedAddress:=TDisassemblerLine(disassemblerlines[0]).address; end; end; vk_next: begin if fSelectedAddressdiscanvas.Height) then exit; //moved out of range //find the selected line found:=false; for i:=0 to fTotalvisibledisassemblerlines-1 do begin line:=disassemblerlines[i]; if (y>=line.getTop) and (y<(line.getTop+line.getHeight)) then begin //found the selected line found:=true; break; end; end; if not found then exit; //not found, which is weird, but whatever... if line.address<>fSelectedAddress then begin //not the selected address fSelectedAddress:=line.address; //set the secondary address to the same as the first if shift isn't pressed if not (ssShift in shift) then fSelectedAddress2:=fSelectedAddress; end; //now update the RWAddress update; end; procedure TDisassemblerview.DisCanvasPaint(Sender: TObject); var cr: Trect; begin cr:=discanvas.Canvas.ClipRect; discanvas.Canvas.CopyRect(cr,offscreenbitmap.Canvas,cr); end; procedure TDisassemblerview.renderJumpLines; { will render the lines of visible jumps pre: must be called after the disassemblerlines have been created and configured } var currentline,linex: TDisassemblerLine; i,j: integer; address: dword; found: boolean; jumplineoffset: integer; begin jumplineoffset:=4; for i:=0 to fTotalvisibledisassemblerlines-1 do begin currentline:=disassemblerlines[i]; if currentline.isJumpOrCall(address) then begin for j:=0 to fTotalvisibledisassemblerlines-1 do begin linex:=disassemblerlines[j]; if linex.address=address then begin currentline.drawJumplineTo(linex.instructionCenter, jumplineoffset); found:=true; break; end else if (j>0) and (linex.address>address) and (TDisassemblerline(disassemblerlines[j-1]).addressaddress then begin //line to the top currentline.drawJumplineTo(0, jumplineoffset,false); end else begin //line to the bottom currentline.drawJumplineTo(offscreenbitmap.Height-1, jumplineoffset,false); end; end; end; inc(jumplineoffset,2); end; end; end; procedure TDisassemblerview.update; { fills in all the lines according to the current state and then renders the lines to the offscreen bitmap finally the bitmap is rendered by the onpaint event of the discanvas } var currenttop:integer; //determines the current position that lines are getting renderesd i: integer; currentline: TDisassemblerLine; currentAddress: dword; selstart, selstop: dword; description: string; x: dword; begin if (not symhandler.isloaded) and (not symhandler.haserror) then begin if processid>0 then statusinfolabel.Caption:='Symbols are being loaded' else statusinfolabel.Caption:='Please open a process first'; end else begin if symhandler.haserror then statusinfolabel.Font.Color:=clRed else statusinfolabel.Font.Color:=clWindowText; statusinfolabel.Caption:=symhandler.getnamefromaddress(TopAddress); end; //initialize bitmap dimensions if discanvas.width>scrollbox.HorzScrollBar.Range then offscreenbitmap.Width:=discanvas.width else offscreenbitmap.Width:=scrollbox.HorzScrollBar.Range; offscreenbitmap.Height:=discanvas.Height; //clear bitmap offscreenbitmap.Canvas.Brush.Color:=clBtnFace; offscreenbitmap.Canvas.FillRect(rect(0,0,offscreenbitmap.Width, offscreenbitmap.Height)); currenttop:=0; i:=0; currentAddress:=fTopAddress; selstart:=min(fSelectedAddress,fSelectedAddress2); selstop:=max(fSelectedAddress,fSelectedAddress2); while currenttop=disassemblerlines.Count then //add a new line disassemblerlines.Add(TDisassemblerLine.Create(offscreenbitmap, header.Sections)); currentline:=disassemblerlines[i]; currentline.renderLine(currentAddress,currenttop, inrange(currentAddress,selStart,selStop), currentAddress=fSelectedAddress); inc(currenttop, currentline.getHeight); inc(i); end; fTotalvisibledisassemblerlines:=i; x:=fSelectedAddress; disassemble(x,description); if disassembleDescription.caption<>description then disassembleDescription.caption:=description; if ShowJumplines then renderjumplines; if not isupdating then disCanvas.Repaint; end; procedure TDisassemblerview.BeginUpdate; begin isUpdating:=true; end; procedure TDisassemblerview.EndUpdate; begin isUpdating:=false; update; end; procedure TDisassemblerview.updateScrollbox; var x: integer; begin x:=(header.Sections[header.Sections.Count-1].Left+header.Sections[header.Sections.Count-1].Width); scrollbox.HorzScrollBar.Range:=x; update; end; procedure TDisassemblerView.scrollboxResize(Sender: TObject); begin updatescrollbox; end; //scrollbar procedure TDisassemblerView.scrollbarChange(Sender: TObject); begin update; end; procedure TDisassemblerView.scrollbarKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin GetFocus; end; procedure TDisassemblerView.scrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); var x: integer; found: boolean; temp: string; delta: integer; i: integer; begin beginupdate; if scrollcode=sctrack then begin delta:=scrollpos-50; if delta>0 then begin for i:=0 to delta do scrollBarScroll(Sender,scLineDown, scrollpos); end else begin for i:=delta to 0 do scrollBarScroll(Sender,scLineUp, scrollpos); end; end; case scrollcode of scLineUp: fTopAddress:=previousopcode(fTopAddress); scLineDown: begin found:=false; for x:=0 to fTotalvisibledisassemblerlines-1 do if fTopAddress<>TDisassemblerLine(disassemblerlines[x]).address then begin fTopAddress:=TDisassemblerLine(disassemblerlines[x]).address; found:=true; break; end; if not found then //disassemble disassemble(fTopAddress,temp); end; scPageDown: begin if TDisassemblerLine(disassemblerlines[fTotalvisibledisassemblerlines-1]).address=fTopAddress then begin for x:=0 to fTotalvisibledisassemblerlines-1 do disassemble(fTopAddress,temp); end else fTopAddress:=TDisassemblerLine(disassemblerlines[fTotalvisibledisassemblerlines-1]).address; end; scPageUp: begin for x:=0 to fTotalvisibledisassemblerlines-2 do fTopAddress:=previousopcode(fTopAddress); //go 'numberofaddresses'-1 times up end; end; scrollpos:=50; //i dont want the slider to work 100% endupdate; SetFocus; end; //header procedure TDisassemblerview.headerSectionResize(HeaderControl: THeaderControl; Section: THeaderSection); begin updatescrollbox; end; procedure TDisassemblerView.OnLostFocus(sender: TObject); begin self.SetFocus; end; constructor TDisassemblerview.create(AOwner: TComponent); begin inherited create(AOwner); tabstop:=true; DoubleBuffered:=true; statusinfo:=tpanel.Create(self); with statusinfo do begin align:=alTop; bevelInner:=bvLowered; height:=17; parent:=self; end; statusinfolabel:=TLabel.Create(self); with statusinfolabel do begin align:=alClient; Alignment:=taCenter; autosize:=false; transparent:=false; parent:=statusinfo; end; disassembleDescription:=Tpanel.Create(self); with disassembleDescription do begin align:=alBottom; height:=17; bevelInner:=bvLowered; bevelOuter:=bvLowered; Color:=clWhite; ParentFont:=false; Font.Charset:=DEFAULT_CHARSET; Font.Color:=clBtnText; Font.Height:=-11; Font.Name:='Courier'; Font.Style:=[]; parent:=self; end; verticalscrollbar:=TScrollBar.Create(self); with verticalscrollbar do begin align:=alright; kind:=sbVertical; pagesize:=2; position:=50; parent:=self; OnEnter:=OnLostFocus; OnChange:=scrollbarChange; OnKeyDown:=scrollbarKeyDown; OnScroll:=scrollBarScroll; end; scrollbox:=TScrollbox.Create(self); with scrollbox do begin DoubleBuffered:=true; align:=alClient; bevelInner:=bvNone; bevelOuter:=bvNone; Borderstyle:=bsNone; HorzScrollBar.Tracking:=true; VertScrollBar.Visible:=false; scrollbox.OnResize:=scrollboxResize; parent:=self; OnEnter:=OnLostFocus; end; header:=THeaderControl.Create(self); with header.Sections.Add do begin AllowClick:=false; ImageIndex:=-1; MinWidth:=50; Text:='Address'; Width:=75; end; with header.Sections.Add do begin AllowClick:=false; ImageIndex:=-1; MinWidth:=50; Text:='Bytes'; Width:=83; end; with header.Sections.Add do begin AllowClick:=false; ImageIndex:=-1; MinWidth:=50; Text:='Opcode'; Width:=200; end; with header.Sections.Add do begin AllowClick:=false; ImageIndex:=-1; MinWidth:=5; Text:='Comment'; Width:=97; end; with header do begin OnSectionResize:=headerSectionResize; parent:=scrollbox; onenter:=OnLostFocus; end; disCanvas:=TPaintbox.Create(self); with disCanvas do begin align:=alClient; ParentFont:=False; Font.Charset:=DEFAULT_CHARSET; Font.Color:=clwindow; Font.Height:=-11; Font.Name:='Courier'; Font.Style:=[]; parent:=scrollbox; OnPaint:=DisCanvasPaint; OnMouseDown:=DisCanvasMouseDown; OnMouseMove:=DisCanvasMouseMove; end; offscreenbitmap:=Tbitmap.Create; disassemblerlines:=TList.Create; fShowjumplineState:=jlsOnlyWithinRange; fShowJumplines:=true; end; end.