1##===-- sourcewin.py -----------------------------------------*- Python -*-===## 2## 3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4# See https://llvm.org/LICENSE.txt for license information. 5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6## 7##===----------------------------------------------------------------------===## 8 9import cui 10import curses 11import lldb 12import lldbutil 13import re 14import os 15 16 17class SourceWin(cui.TitledWin): 18 19 def __init__(self, driver, x, y, w, h): 20 super(SourceWin, self).__init__(x, y, w, h, "Source") 21 self.sourceman = driver.getSourceManager() 22 self.sources = {} 23 24 self.filename = None 25 self.pc_line = None 26 self.viewline = 0 27 28 self.breakpoints = {} 29 30 self.win.scrollok(1) 31 32 self.markerPC = ":) " 33 self.markerBP = "B> " 34 self.markerNone = " " 35 36 try: 37 from pygments.formatters import TerminalFormatter 38 self.formatter = TerminalFormatter() 39 except ImportError: 40 #self.win.addstr("\nWarning: no 'pygments' library found. Syntax highlighting is disabled.") 41 self.lexer = None 42 self.formatter = None 43 pass 44 45 # FIXME: syntax highlight broken 46 self.formatter = None 47 self.lexer = None 48 49 def handleEvent(self, event): 50 if isinstance(event, int): 51 self.handleKey(event) 52 return 53 54 if isinstance(event, lldb.SBEvent): 55 if lldb.SBBreakpoint.EventIsBreakpointEvent(event): 56 self.handleBPEvent(event) 57 58 if lldb.SBProcess.EventIsProcessEvent(event) and \ 59 not lldb.SBProcess.GetRestartedFromEvent(event): 60 process = lldb.SBProcess.GetProcessFromEvent(event) 61 if not process.IsValid(): 62 return 63 if process.GetState() == lldb.eStateStopped: 64 self.refreshSource(process) 65 elif process.GetState() == lldb.eStateExited: 66 self.notifyExited(process) 67 68 def notifyExited(self, process): 69 self.win.erase() 70 target = lldbutil.get_description(process.GetTarget()) 71 pid = process.GetProcessID() 72 ec = process.GetExitStatus() 73 self.win.addstr( 74 "\nProcess %s [%d] has exited with exit-code %d" % 75 (target, pid, ec)) 76 77 def pageUp(self): 78 if self.viewline > 0: 79 self.viewline = self.viewline - 1 80 self.refreshSource() 81 82 def pageDown(self): 83 if self.viewline < len(self.content) - self.height + 1: 84 self.viewline = self.viewline + 1 85 self.refreshSource() 86 pass 87 88 def handleKey(self, key): 89 if key == curses.KEY_DOWN: 90 self.pageDown() 91 elif key == curses.KEY_UP: 92 self.pageUp() 93 94 def updateViewline(self): 95 half = self.height / 2 96 if self.pc_line < half: 97 self.viewline = 0 98 else: 99 self.viewline = self.pc_line - half + 1 100 101 if self.viewline < 0: 102 raise Exception( 103 "negative viewline: pc=%d viewline=%d" % 104 (self.pc_line, self.viewline)) 105 106 def refreshSource(self, process=None): 107 (self.height, self.width) = self.win.getmaxyx() 108 109 if process is not None: 110 loc = process.GetSelectedThread().GetSelectedFrame().GetLineEntry() 111 f = loc.GetFileSpec() 112 self.pc_line = loc.GetLine() 113 114 if not f.IsValid(): 115 self.win.addstr(0, 0, "Invalid source file") 116 return 117 118 self.filename = f.GetFilename() 119 path = os.path.join(f.GetDirectory(), self.filename) 120 self.setTitle(path) 121 self.content = self.getContent(path) 122 self.updateViewline() 123 124 if self.filename is None: 125 return 126 127 if self.formatter is not None: 128 from pygments.lexers import get_lexer_for_filename 129 self.lexer = get_lexer_for_filename(self.filename) 130 131 bps = [] if not self.filename in self.breakpoints else self.breakpoints[self.filename] 132 self.win.erase() 133 if self.content: 134 self.formatContent(self.content, self.pc_line, bps) 135 136 def getContent(self, path): 137 content = [] 138 if path in self.sources: 139 content = self.sources[path] 140 else: 141 if os.path.exists(path): 142 with open(path) as x: 143 content = x.readlines() 144 self.sources[path] = content 145 return content 146 147 def formatContent(self, content, pc_line, breakpoints): 148 source = "" 149 count = 1 150 self.win.erase() 151 end = min(len(content), self.viewline + self.height) 152 for i in range(self.viewline, end): 153 line_num = i + 1 154 marker = self.markerNone 155 attr = curses.A_NORMAL 156 if line_num == pc_line: 157 attr = curses.A_REVERSE 158 if line_num in breakpoints: 159 marker = self.markerBP 160 line = "%s%3d %s" % (marker, line_num, self.highlight(content[i])) 161 if len(line) >= self.width: 162 line = line[0:self.width - 1] + "\n" 163 self.win.addstr(line, attr) 164 source += line 165 count = count + 1 166 return source 167 168 def highlight(self, source): 169 if self.lexer and self.formatter: 170 from pygments import highlight 171 return highlight(source, self.lexer, self.formatter) 172 else: 173 return source 174 175 def addBPLocations(self, locations): 176 for path in locations: 177 lines = locations[path] 178 if path in self.breakpoints: 179 self.breakpoints[path].update(lines) 180 else: 181 self.breakpoints[path] = lines 182 183 def removeBPLocations(self, locations): 184 for path in locations: 185 lines = locations[path] 186 if path in self.breakpoints: 187 self.breakpoints[path].difference_update(lines) 188 else: 189 raise "Removing locations that were never added...no good" 190 191 def handleBPEvent(self, event): 192 def getLocations(event): 193 locs = {} 194 195 bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) 196 197 if bp.IsInternal(): 198 # don't show anything for internal breakpoints 199 return 200 201 for location in bp: 202 # hack! getting the LineEntry via SBBreakpointLocation.GetAddress.GetLineEntry does not work good for 203 # inlined frames, so we get the description (which does take 204 # into account inlined functions) and parse it. 205 desc = lldbutil.get_description( 206 location, lldb.eDescriptionLevelFull) 207 match = re.search('at\ ([^:]+):([\d]+)', desc) 208 try: 209 path = match.group(1) 210 line = int(match.group(2).strip()) 211 except ValueError as e: 212 # bp loc unparsable 213 continue 214 215 if path in locs: 216 locs[path].add(line) 217 else: 218 locs[path] = set([line]) 219 return locs 220 221 event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) 222 if event_type == lldb.eBreakpointEventTypeEnabled \ 223 or event_type == lldb.eBreakpointEventTypeAdded \ 224 or event_type == lldb.eBreakpointEventTypeLocationsResolved \ 225 or event_type == lldb.eBreakpointEventTypeLocationsAdded: 226 self.addBPLocations(getLocations(event)) 227 elif event_type == lldb.eBreakpointEventTypeRemoved \ 228 or event_type == lldb.eBreakpointEventTypeLocationsRemoved \ 229 or event_type == lldb.eBreakpointEventTypeDisabled: 230 self.removeBPLocations(getLocations(event)) 231 elif event_type == lldb.eBreakpointEventTypeCommandChanged \ 232 or event_type == lldb.eBreakpointEventTypeConditionChanged \ 233 or event_type == lldb.eBreakpointEventTypeIgnoreChanged \ 234 or event_type == lldb.eBreakpointEventTypeThreadChanged \ 235 or event_type == lldb.eBreakpointEventTypeInvalidType: 236 # no-op 237 pass 238 self.refreshSource() 239