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