.gdbinit 92 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383
  1. python
  2. # GDB dashboard - Modular visual interface for GDB in Python.
  3. #
  4. # https://github.com/cyrus-and/gdb-dashboard
  5. # License ----------------------------------------------------------------------
  6. # Copyright (c) 2015-2023 Andrea Cardaci <cyrus.and@gmail.com>
  7. #
  8. # Permission is hereby granted, free of charge, to any person obtaining a copy
  9. # of this software and associated documentation files (the "Software"), to deal
  10. # in the Software without restriction, including without limitation the rights
  11. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. # copies of the Software, and to permit persons to whom the Software is
  13. # furnished to do so, subject to the following conditions:
  14. #
  15. # The above copyright notice and this permission notice shall be included in all
  16. # copies or substantial portions of the Software.
  17. #
  18. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  24. # SOFTWARE.
  25. # Imports ----------------------------------------------------------------------
  26. import ast
  27. import io
  28. import itertools
  29. import math
  30. import os
  31. import re
  32. import struct
  33. import traceback
  34. # Common attributes ------------------------------------------------------------
  35. class R():
  36. @staticmethod
  37. def attributes():
  38. return {
  39. # miscellaneous
  40. 'ansi': {
  41. 'doc': 'Control the ANSI output of the dashboard.',
  42. 'default': True,
  43. 'type': bool
  44. },
  45. 'syntax_highlighting': {
  46. 'doc': '''Pygments style to use for syntax highlighting.
  47. Using an empty string (or a name not in the list) disables this feature. The
  48. list of all the available styles can be obtained with (from GDB itself):
  49. python from pygments.styles import *
  50. python for style in get_all_styles(): print(style)''',
  51. 'default': 'monokai'
  52. },
  53. 'discard_scrollback': {
  54. 'doc': '''Discard the scrollback buffer at each redraw.
  55. This makes scrolling less confusing by discarding the previously printed
  56. dashboards but only works with certain terminals.''',
  57. 'default': True,
  58. 'type': bool
  59. },
  60. # values formatting
  61. 'compact_values': {
  62. 'doc': 'Display complex objects in a single line.',
  63. 'default': True,
  64. 'type': bool
  65. },
  66. 'max_value_length': {
  67. 'doc': 'Maximum length of displayed values before truncation.',
  68. 'default': 100,
  69. 'type': int
  70. },
  71. 'value_truncation_string': {
  72. 'doc': 'String to use to mark value truncation.',
  73. 'default': '…',
  74. },
  75. 'dereference': {
  76. 'doc': 'Annotate pointers with the pointed value.',
  77. 'default': True,
  78. 'type': bool
  79. },
  80. # prompt
  81. 'prompt': {
  82. 'doc': '''GDB prompt.
  83. This value is used as a Python format string where `{status}` is expanded with
  84. the substitution of either `prompt_running` or `prompt_not_running` attributes,
  85. according to the target program status. The resulting string must be a valid GDB
  86. prompt, see the command `python print(gdb.prompt.prompt_help())`''',
  87. 'default': '{status}'
  88. },
  89. 'prompt_running': {
  90. 'doc': '''Define the value of `{status}` when the target program is running.
  91. See the `prompt` attribute. This value is used as a Python format string where
  92. `{pid}` is expanded with the process identifier of the target program.''',
  93. 'default': '\[\e[1;35m\]>>>\[\e[0m\]'
  94. },
  95. 'prompt_not_running': {
  96. 'doc': '''Define the value of `{status}` when the target program is running.
  97. See the `prompt` attribute. This value is used as a Python format string.''',
  98. 'default': '\[\e[90m\]>>>\[\e[0m\]'
  99. },
  100. # divider
  101. 'omit_divider': {
  102. 'doc': 'Omit the divider in external outputs when only one module is displayed.',
  103. 'default': False,
  104. 'type': bool
  105. },
  106. 'divider_fill_char_primary': {
  107. 'doc': 'Filler around the label for primary dividers',
  108. 'default': '─'
  109. },
  110. 'divider_fill_char_secondary': {
  111. 'doc': 'Filler around the label for secondary dividers',
  112. 'default': '─'
  113. },
  114. 'divider_fill_style_primary': {
  115. 'doc': 'Style for `divider_fill_char_primary`',
  116. 'default': '36'
  117. },
  118. 'divider_fill_style_secondary': {
  119. 'doc': 'Style for `divider_fill_char_secondary`',
  120. 'default': '90'
  121. },
  122. 'divider_label_style_on_primary': {
  123. 'doc': 'Label style for non-empty primary dividers',
  124. 'default': '1;33'
  125. },
  126. 'divider_label_style_on_secondary': {
  127. 'doc': 'Label style for non-empty secondary dividers',
  128. 'default': '1;37'
  129. },
  130. 'divider_label_style_off_primary': {
  131. 'doc': 'Label style for empty primary dividers',
  132. 'default': '33'
  133. },
  134. 'divider_label_style_off_secondary': {
  135. 'doc': 'Label style for empty secondary dividers',
  136. 'default': '90'
  137. },
  138. 'divider_label_skip': {
  139. 'doc': 'Gap between the aligning border and the label.',
  140. 'default': 3,
  141. 'type': int,
  142. 'check': check_ge_zero
  143. },
  144. 'divider_label_margin': {
  145. 'doc': 'Number of spaces around the label.',
  146. 'default': 1,
  147. 'type': int,
  148. 'check': check_ge_zero
  149. },
  150. 'divider_label_align_right': {
  151. 'doc': 'Label alignment flag.',
  152. 'default': False,
  153. 'type': bool
  154. },
  155. # common styles
  156. 'style_selected_1': {
  157. 'default': '1;32'
  158. },
  159. 'style_selected_2': {
  160. 'default': '32'
  161. },
  162. 'style_low': {
  163. 'default': '90'
  164. },
  165. 'style_high': {
  166. 'default': '1;37'
  167. },
  168. 'style_error': {
  169. 'default': '31'
  170. },
  171. 'style_critical': {
  172. 'default': '0;41'
  173. }
  174. }
  175. # Common -----------------------------------------------------------------------
  176. class Beautifier():
  177. def __init__(self, hint, tab_size=4):
  178. self.tab_spaces = ' ' * tab_size if tab_size else None
  179. self.active = False
  180. if not R.ansi or not R.syntax_highlighting:
  181. return
  182. # attempt to set up Pygments
  183. try:
  184. import pygments
  185. from pygments.lexers import GasLexer, NasmLexer
  186. from pygments.formatters import Terminal256Formatter
  187. if hint == 'att':
  188. self.lexer = GasLexer()
  189. elif hint == 'intel':
  190. self.lexer = NasmLexer()
  191. else:
  192. from pygments.lexers import get_lexer_for_filename
  193. self.lexer = get_lexer_for_filename(hint, stripnl=False)
  194. self.formatter = Terminal256Formatter(style=R.syntax_highlighting)
  195. self.active = True
  196. except ImportError:
  197. # Pygments not available
  198. pass
  199. except pygments.util.ClassNotFound:
  200. # no lexer for this file or invalid style
  201. pass
  202. def process(self, source):
  203. # convert tabs if requested
  204. if self.tab_spaces:
  205. source = source.replace('\t', self.tab_spaces)
  206. if self.active:
  207. import pygments
  208. source = pygments.highlight(source, self.lexer, self.formatter)
  209. return source.rstrip('\n')
  210. def run(command):
  211. return gdb.execute(command, to_string=True)
  212. def ansi(string, style):
  213. if R.ansi:
  214. return '\x1b[{}m{}\x1b[0m'.format(style, string)
  215. else:
  216. return string
  217. def divider(width, label='', primary=False, active=True):
  218. if primary:
  219. divider_fill_style = R.divider_fill_style_primary
  220. divider_fill_char = R.divider_fill_char_primary
  221. divider_label_style_on = R.divider_label_style_on_primary
  222. divider_label_style_off = R.divider_label_style_off_primary
  223. else:
  224. divider_fill_style = R.divider_fill_style_secondary
  225. divider_fill_char = R.divider_fill_char_secondary
  226. divider_label_style_on = R.divider_label_style_on_secondary
  227. divider_label_style_off = R.divider_label_style_off_secondary
  228. if label:
  229. if active:
  230. divider_label_style = divider_label_style_on
  231. else:
  232. divider_label_style = divider_label_style_off
  233. skip = R.divider_label_skip
  234. margin = R.divider_label_margin
  235. before = ansi(divider_fill_char * skip, divider_fill_style)
  236. middle = ansi(label, divider_label_style)
  237. after_length = width - len(label) - skip - 2 * margin
  238. after = ansi(divider_fill_char * after_length, divider_fill_style)
  239. if R.divider_label_align_right:
  240. before, after = after, before
  241. return ''.join([before, ' ' * margin, middle, ' ' * margin, after])
  242. else:
  243. return ansi(divider_fill_char * width, divider_fill_style)
  244. def check_gt_zero(x):
  245. return x > 0
  246. def check_ge_zero(x):
  247. return x >= 0
  248. def to_unsigned(value, size=8):
  249. # values from GDB can be used transparently but are not suitable for
  250. # being printed as unsigned integers, so a conversion is needed
  251. mask = (2 ** (size * 8)) - 1
  252. return int(value.cast(gdb.Value(mask).type)) & mask
  253. def to_string(value):
  254. # attempt to convert an inferior value to string; OK when (Python 3 ||
  255. # simple ASCII); otherwise (Python 2.7 && not ASCII) encode the string as
  256. # utf8
  257. try:
  258. value_string = str(value)
  259. except UnicodeEncodeError:
  260. value_string = unicode(value).encode('utf8')
  261. except gdb.error as e:
  262. value_string = ansi(e, R.style_error)
  263. return value_string
  264. def format_address(address):
  265. pointer_size = gdb.parse_and_eval('$pc').type.sizeof
  266. return ('0x{{:0{}x}}').format(pointer_size * 2).format(address)
  267. def format_value(value, compact=None):
  268. # format references as referenced values
  269. # (TYPE_CODE_RVALUE_REF is not supported by old GDB)
  270. if value.type.code in (getattr(gdb, 'TYPE_CODE_REF', None),
  271. getattr(gdb, 'TYPE_CODE_RVALUE_REF', None)):
  272. try:
  273. value = value.referenced_value()
  274. except gdb.error as e:
  275. return ansi(e, R.style_error)
  276. # format the value
  277. out = to_string(value)
  278. # dereference up to the actual value if requested
  279. if R.dereference and value.type.code == gdb.TYPE_CODE_PTR:
  280. while value.type.code == gdb.TYPE_CODE_PTR:
  281. try:
  282. value = value.dereference()
  283. except gdb.error as e:
  284. break
  285. else:
  286. formatted = to_string(value)
  287. out += '{} {}'.format(ansi(':', R.style_low), formatted)
  288. # compact the value
  289. if compact is not None and compact or R.compact_values:
  290. out = re.sub(r'$\s*', '', out, flags=re.MULTILINE)
  291. # truncate the value
  292. if R.max_value_length > 0 and len(out) > R.max_value_length:
  293. out = out[0:R.max_value_length] + ansi(R.value_truncation_string, R.style_critical)
  294. return out
  295. # XXX parsing the output of `info breakpoints` is apparently the best option
  296. # right now, see: https://sourceware.org/bugzilla/show_bug.cgi?id=18385
  297. # XXX GDB version 7.11 (quire recent) does not have the pending field, so
  298. # fall back to the parsed information
  299. def fetch_breakpoints(watchpoints=False, pending=False):
  300. # fetch breakpoints addresses
  301. parsed_breakpoints = dict()
  302. catch_what_regex = re.compile(r'([^,]+".*")?[^,]*')
  303. for line in run('info breakpoints').split('\n'):
  304. # just keep numbered lines
  305. if not line or not line[0].isdigit():
  306. continue
  307. # extract breakpoint number, address and pending status
  308. fields = line.split()
  309. number = int(fields[0].split('.')[0])
  310. try:
  311. if len(fields) >= 5 and fields[1] == 'breakpoint':
  312. # multiple breakpoints have no address yet
  313. is_pending = fields[4] == '<PENDING>'
  314. is_multiple = fields[4] == '<MULTIPLE>'
  315. address = None if is_multiple or is_pending else int(fields[4], 16)
  316. is_enabled = fields[3] == 'y'
  317. address_info = address, is_enabled
  318. parsed_breakpoints[number] = [address_info], is_pending, ''
  319. elif len(fields) >= 5 and fields[1] == 'catchpoint':
  320. # only take before comma, but ignore commas in quotes
  321. what = catch_what_regex.search(' '.join(fields[4:]))[0].strip()
  322. parsed_breakpoints[number] = [], False, what
  323. elif len(fields) >= 3 and number in parsed_breakpoints:
  324. # add this address to the list of multiple locations
  325. address = int(fields[2], 16)
  326. is_enabled = fields[1] == 'y'
  327. address_info = address, is_enabled
  328. parsed_breakpoints[number][0].append(address_info)
  329. else:
  330. # watchpoints
  331. parsed_breakpoints[number] = [], False, ''
  332. except ValueError:
  333. pass
  334. # fetch breakpoints from the API and complement with address and source
  335. # information
  336. breakpoints = []
  337. # XXX in older versions gdb.breakpoints() returns None
  338. for gdb_breakpoint in gdb.breakpoints() or []:
  339. # skip internal breakpoints
  340. if gdb_breakpoint.number < 0:
  341. continue
  342. addresses, is_pending, what = parsed_breakpoints[gdb_breakpoint.number]
  343. is_pending = getattr(gdb_breakpoint, 'pending', is_pending)
  344. if not pending and is_pending:
  345. continue
  346. if not watchpoints and gdb_breakpoint.type != gdb.BP_BREAKPOINT:
  347. continue
  348. # add useful fields to the object
  349. breakpoint = dict()
  350. breakpoint['number'] = gdb_breakpoint.number
  351. breakpoint['type'] = gdb_breakpoint.type
  352. breakpoint['enabled'] = gdb_breakpoint.enabled
  353. breakpoint['location'] = gdb_breakpoint.location
  354. breakpoint['expression'] = gdb_breakpoint.expression
  355. breakpoint['condition'] = gdb_breakpoint.condition
  356. breakpoint['temporary'] = gdb_breakpoint.temporary
  357. breakpoint['hit_count'] = gdb_breakpoint.hit_count
  358. breakpoint['pending'] = is_pending
  359. breakpoint['what'] = what
  360. # add addresses and source information
  361. breakpoint['addresses'] = []
  362. for address, is_enabled in addresses:
  363. if address:
  364. sal = gdb.find_pc_line(address)
  365. breakpoint['addresses'].append({
  366. 'address': address,
  367. 'enabled': is_enabled,
  368. 'file_name': sal.symtab.filename if address and sal.symtab else None,
  369. 'file_line': sal.line if address else None
  370. })
  371. breakpoints.append(breakpoint)
  372. return breakpoints
  373. # Dashboard --------------------------------------------------------------------
  374. class Dashboard(gdb.Command):
  375. '''Redisplay the dashboard.'''
  376. def __init__(self):
  377. gdb.Command.__init__(self, 'dashboard', gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
  378. # setup subcommands
  379. Dashboard.ConfigurationCommand(self)
  380. Dashboard.OutputCommand(self)
  381. Dashboard.EnabledCommand(self)
  382. Dashboard.LayoutCommand(self)
  383. # setup style commands
  384. Dashboard.StyleCommand(self, 'dashboard', R, R.attributes())
  385. # main terminal
  386. self.output = None
  387. # used to inhibit redisplays during init parsing
  388. self.inhibited = None
  389. # enabled by default
  390. self.enabled = None
  391. self.enable()
  392. def on_continue(self, _):
  393. # try to contain the GDB messages in a specified area unless the
  394. # dashboard is printed to a separate file (dashboard -output ...)
  395. # or there are no modules to display in the main terminal
  396. enabled_modules = list(filter(lambda m: not m.output and m.enabled, self.modules))
  397. if self.is_running() and not self.output and len(enabled_modules) > 0:
  398. width, _ = Dashboard.get_term_size()
  399. gdb.write(Dashboard.clear_screen())
  400. gdb.write(divider(width, 'Output/messages', True))
  401. gdb.write('\n')
  402. gdb.flush()
  403. def on_stop(self, _):
  404. if self.is_running():
  405. self.render(clear_screen=False)
  406. def on_exit(self, _):
  407. if not self.is_running():
  408. return
  409. # collect all the outputs
  410. outputs = set()
  411. outputs.add(self.output)
  412. outputs.update(module.output for module in self.modules)
  413. outputs.remove(None)
  414. # reset the terminal status
  415. for output in outputs:
  416. try:
  417. with open(output, 'w') as fs:
  418. fs.write(Dashboard.reset_terminal())
  419. except:
  420. # skip cleanup for invalid outputs
  421. pass
  422. def enable(self):
  423. if self.enabled:
  424. return
  425. self.enabled = True
  426. # setup events
  427. gdb.events.cont.connect(self.on_continue)
  428. gdb.events.stop.connect(self.on_stop)
  429. gdb.events.exited.connect(self.on_exit)
  430. def disable(self):
  431. if not self.enabled:
  432. return
  433. self.enabled = False
  434. # setup events
  435. gdb.events.cont.disconnect(self.on_continue)
  436. gdb.events.stop.disconnect(self.on_stop)
  437. gdb.events.exited.disconnect(self.on_exit)
  438. def load_modules(self, modules):
  439. self.modules = []
  440. for module in modules:
  441. info = Dashboard.ModuleInfo(self, module)
  442. self.modules.append(info)
  443. def redisplay(self, style_changed=False):
  444. # manually redisplay the dashboard
  445. if self.is_running() and not self.inhibited:
  446. self.render(True, style_changed)
  447. def inferior_pid(self):
  448. return gdb.selected_inferior().pid
  449. def is_running(self):
  450. return self.inferior_pid() != 0
  451. def render(self, clear_screen, style_changed=False):
  452. # fetch module content and info
  453. all_disabled = True
  454. display_map = dict()
  455. for module in self.modules:
  456. # fall back to the global value
  457. output = module.output or self.output
  458. # add the instance or None if disabled
  459. if module.enabled:
  460. all_disabled = False
  461. instance = module.instance
  462. else:
  463. instance = None
  464. display_map.setdefault(output, []).append(instance)
  465. # process each display info
  466. for output, instances in display_map.items():
  467. try:
  468. buf = ''
  469. # use GDB stream by default
  470. fs = None
  471. if output:
  472. fs = open(output, 'w')
  473. fd = fs.fileno()
  474. fs.write(Dashboard.setup_terminal())
  475. else:
  476. fs = gdb
  477. fd = 1 # stdout
  478. # get the terminal size (default main terminal if either the
  479. # output is not a file)
  480. try:
  481. width, height = Dashboard.get_term_size(fd)
  482. except:
  483. width, height = Dashboard.get_term_size()
  484. # clear the "screen" if requested for the main terminal,
  485. # auxiliary terminals are always cleared
  486. if fs is not gdb or clear_screen:
  487. buf += Dashboard.clear_screen()
  488. # show message if all the modules in this output are disabled
  489. if not any(instances):
  490. # skip the main terminal
  491. if fs is gdb:
  492. continue
  493. # write the error message
  494. buf += divider(width, 'Warning', True)
  495. buf += '\n'
  496. if self.modules:
  497. buf += 'No module to display (see `dashboard -layout`)'
  498. else:
  499. buf += 'No module loaded'
  500. buf += '\n'
  501. fs.write(buf)
  502. continue
  503. # process all the modules for that output
  504. for n, instance in enumerate(instances, 1):
  505. # skip disabled modules
  506. if not instance:
  507. continue
  508. try:
  509. # ask the module to generate the content
  510. lines = instance.lines(width, height, style_changed)
  511. except Exception as e:
  512. # allow to continue on exceptions in modules
  513. stacktrace = traceback.format_exc().strip()
  514. lines = [ansi(stacktrace, R.style_error)]
  515. # create the divider if needed
  516. div = []
  517. if not R.omit_divider or len(instances) > 1 or fs is gdb:
  518. div = [divider(width, instance.label(), True, lines)]
  519. # write the data
  520. buf += '\n'.join(div + lines)
  521. # write the newline for all but last unless main terminal
  522. if n != len(instances) or fs is gdb:
  523. buf += '\n'
  524. # write the final newline and the terminator only if it is the
  525. # main terminal to allow the prompt to display correctly (unless
  526. # there are no modules to display)
  527. if fs is gdb and not all_disabled:
  528. buf += divider(width, primary=True)
  529. buf += '\n'
  530. fs.write(buf)
  531. except Exception as e:
  532. cause = traceback.format_exc().strip()
  533. Dashboard.err('Cannot write the dashboard\n{}'.format(cause))
  534. finally:
  535. # don't close gdb stream
  536. if fs and fs is not gdb:
  537. fs.close()
  538. # Utility methods --------------------------------------------------------------
  539. @staticmethod
  540. def start():
  541. # save the instance for customization convenience
  542. global dashboard
  543. # initialize the dashboard
  544. dashboard = Dashboard()
  545. Dashboard.set_custom_prompt(dashboard)
  546. # parse Python inits, load modules then parse GDB inits
  547. dashboard.inhibited = True
  548. Dashboard.parse_inits(True)
  549. modules = Dashboard.get_modules()
  550. dashboard.load_modules(modules)
  551. Dashboard.parse_inits(False)
  552. dashboard.inhibited = False
  553. # GDB overrides
  554. run('set pagination off')
  555. # display if possible (program running and not explicitly disabled by
  556. # some configuration file)
  557. if dashboard.enabled:
  558. dashboard.redisplay()
  559. @staticmethod
  560. def get_term_size(fd=1): # defaults to the main terminal
  561. try:
  562. if sys.platform == 'win32':
  563. import curses
  564. # XXX always neglects the fd parameter
  565. height, width = curses.initscr().getmaxyx()
  566. curses.endwin()
  567. return int(width), int(height)
  568. else:
  569. import termios
  570. import fcntl
  571. # first 2 shorts (4 byte) of struct winsize
  572. raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4)
  573. height, width = struct.unpack('hh', raw)
  574. return int(width), int(height)
  575. except (ImportError, OSError):
  576. # this happens when no curses library is found on windows or when
  577. # the terminal is not properly configured
  578. return 80, 24 # hardcoded fallback value
  579. @staticmethod
  580. def set_custom_prompt(dashboard):
  581. def custom_prompt(_):
  582. # render thread status indicator
  583. if dashboard.is_running():
  584. pid = dashboard.inferior_pid()
  585. status = R.prompt_running.format(pid=pid)
  586. else:
  587. status = R.prompt_not_running
  588. # build prompt
  589. prompt = R.prompt.format(status=status)
  590. prompt = gdb.prompt.substitute_prompt(prompt)
  591. return prompt + ' ' # force trailing space
  592. gdb.prompt_hook = custom_prompt
  593. @staticmethod
  594. def parse_inits(python):
  595. # paths where the .gdbinit.d directory might be
  596. search_paths = [
  597. '/etc/gdb-dashboard',
  598. '{}/gdb-dashboard'.format(os.getenv('XDG_CONFIG_HOME', '~/.config')),
  599. '~/Library/Preferences/gdb-dashboard',
  600. '~/.gdbinit.d'
  601. ]
  602. # expand the tilde and walk the paths
  603. inits_dirs = (os.walk(os.path.expanduser(path)) for path in search_paths)
  604. # process all the init files in order
  605. for root, dirs, files in itertools.chain.from_iterable(inits_dirs):
  606. dirs.sort()
  607. # skipping dotfiles
  608. for init in sorted(file for file in files if not file.startswith('.')):
  609. path = os.path.join(root, init)
  610. _, ext = os.path.splitext(path)
  611. # either load Python files or GDB
  612. if python == (ext == '.py'):
  613. gdb.execute('source ' + path)
  614. @staticmethod
  615. def get_modules():
  616. # scan the scope for modules
  617. modules = []
  618. for name in globals():
  619. obj = globals()[name]
  620. try:
  621. if issubclass(obj, Dashboard.Module):
  622. modules.append(obj)
  623. except TypeError:
  624. continue
  625. # sort modules alphabetically
  626. modules.sort(key=lambda x: x.__name__)
  627. return modules
  628. @staticmethod
  629. def create_command(name, invoke, doc, is_prefix, complete=None):
  630. if callable(complete):
  631. Class = type('', (gdb.Command,), {
  632. '__doc__': doc,
  633. 'invoke': invoke,
  634. 'complete': complete
  635. })
  636. Class(name, gdb.COMMAND_USER, prefix=is_prefix)
  637. else:
  638. Class = type('', (gdb.Command,), {
  639. '__doc__': doc,
  640. 'invoke': invoke
  641. })
  642. Class(name, gdb.COMMAND_USER, complete or gdb.COMPLETE_NONE, is_prefix)
  643. @staticmethod
  644. def err(string):
  645. print(ansi(string, R.style_error))
  646. @staticmethod
  647. def complete(word, candidates):
  648. return filter(lambda candidate: candidate.startswith(word), candidates)
  649. @staticmethod
  650. def parse_arg(arg):
  651. # encode unicode GDB command arguments as utf8 in Python 2.7
  652. if type(arg) is not str:
  653. arg = arg.encode('utf8')
  654. return arg
  655. @staticmethod
  656. def clear_screen():
  657. # ANSI: move the cursor to top-left corner and clear the screen
  658. # (optionally also clear the scrollback buffer if supported by the
  659. # terminal)
  660. return '\x1b[H\x1b[2J' + ('\x1b[3J' if R.discard_scrollback else '')
  661. @staticmethod
  662. def setup_terminal():
  663. # ANSI: enable alternative screen buffer and hide cursor
  664. return '\x1b[?1049h\x1b[?25l'
  665. @staticmethod
  666. def reset_terminal():
  667. # ANSI: disable alternative screen buffer and show cursor
  668. return '\x1b[?1049l\x1b[?25h'
  669. # Module descriptor ------------------------------------------------------------
  670. class ModuleInfo:
  671. def __init__(self, dashboard, module):
  672. self.name = module.__name__.lower() # from class to module name
  673. self.enabled = True
  674. self.output = None # value from the dashboard by default
  675. self.instance = module()
  676. self.doc = self.instance.__doc__ or '(no documentation)'
  677. self.prefix = 'dashboard {}'.format(self.name)
  678. # add GDB commands
  679. self.add_main_command(dashboard)
  680. self.add_output_command(dashboard)
  681. self.add_style_command(dashboard)
  682. self.add_subcommands(dashboard)
  683. def add_main_command(self, dashboard):
  684. module = self
  685. def invoke(self, arg, from_tty, info=self):
  686. arg = Dashboard.parse_arg(arg)
  687. if arg == '':
  688. info.enabled ^= True
  689. if dashboard.is_running():
  690. dashboard.redisplay()
  691. else:
  692. status = 'enabled' if info.enabled else 'disabled'
  693. print('{} module {}'.format(module.name, status))
  694. else:
  695. Dashboard.err('Wrong argument "{}"'.format(arg))
  696. doc_brief = 'Configure the {} module, with no arguments toggles its visibility.'.format(self.name)
  697. doc = '{}\n\n{}'.format(doc_brief, self.doc)
  698. Dashboard.create_command(self.prefix, invoke, doc, True)
  699. def add_output_command(self, dashboard):
  700. Dashboard.OutputCommand(dashboard, self.prefix, self)
  701. def add_style_command(self, dashboard):
  702. Dashboard.StyleCommand(dashboard, self.prefix, self.instance, self.instance.attributes())
  703. def add_subcommands(self, dashboard):
  704. for name, command in self.instance.commands().items():
  705. self.add_subcommand(dashboard, name, command)
  706. def add_subcommand(self, dashboard, name, command):
  707. action = command['action']
  708. doc = command['doc']
  709. complete = command.get('complete')
  710. def invoke(self, arg, from_tty, info=self):
  711. arg = Dashboard.parse_arg(arg)
  712. if info.enabled:
  713. try:
  714. action(arg)
  715. except Exception as e:
  716. Dashboard.err(e)
  717. return
  718. # don't catch redisplay errors
  719. dashboard.redisplay()
  720. else:
  721. Dashboard.err('Module disabled')
  722. prefix = '{} {}'.format(self.prefix, name)
  723. Dashboard.create_command(prefix, invoke, doc, False, complete)
  724. # GDB commands -----------------------------------------------------------------
  725. # handler for the `dashboard` command itself
  726. def invoke(self, arg, from_tty):
  727. arg = Dashboard.parse_arg(arg)
  728. # show messages for checks in redisplay
  729. if arg != '':
  730. Dashboard.err('Wrong argument "{}"'.format(arg))
  731. elif not self.is_running():
  732. Dashboard.err('Is the target program running?')
  733. else:
  734. self.redisplay()
  735. class ConfigurationCommand(gdb.Command):
  736. '''Dump or save the dashboard configuration.
  737. With an optional argument the configuration will be written to the specified
  738. file.
  739. This command allows to configure the dashboard live then make the changes
  740. permanent, for example:
  741. dashboard -configuration ~/.gdbinit.d/init
  742. At startup the `~/.gdbinit.d/` directory tree is walked and files are evaluated
  743. in alphabetical order but giving priority to Python files. This is where user
  744. configuration files must be placed.'''
  745. def __init__(self, dashboard):
  746. gdb.Command.__init__(self, 'dashboard -configuration',
  747. gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
  748. self.dashboard = dashboard
  749. def invoke(self, arg, from_tty):
  750. arg = Dashboard.parse_arg(arg)
  751. if arg:
  752. with open(os.path.expanduser(arg), 'w') as fs:
  753. fs.write('# auto generated by GDB dashboard\n\n')
  754. self.dump(fs)
  755. self.dump(gdb)
  756. def dump(self, fs):
  757. # dump layout
  758. self.dump_layout(fs)
  759. # dump styles
  760. self.dump_style(fs, R)
  761. for module in self.dashboard.modules:
  762. self.dump_style(fs, module.instance, module.prefix)
  763. # dump outputs
  764. self.dump_output(fs, self.dashboard)
  765. for module in self.dashboard.modules:
  766. self.dump_output(fs, module, module.prefix)
  767. def dump_layout(self, fs):
  768. layout = ['dashboard -layout']
  769. for module in self.dashboard.modules:
  770. mark = '' if module.enabled else '!'
  771. layout.append('{}{}'.format(mark, module.name))
  772. fs.write(' '.join(layout))
  773. fs.write('\n')
  774. def dump_style(self, fs, obj, prefix='dashboard'):
  775. attributes = getattr(obj, 'attributes', lambda: dict())()
  776. for name, attribute in attributes.items():
  777. real_name = attribute.get('name', name)
  778. default = attribute.get('default')
  779. value = getattr(obj, real_name)
  780. if value != default:
  781. fs.write('{} -style {} {!r}\n'.format(prefix, name, value))
  782. def dump_output(self, fs, obj, prefix='dashboard'):
  783. output = getattr(obj, 'output')
  784. if output:
  785. fs.write('{} -output {}\n'.format(prefix, output))
  786. class OutputCommand(gdb.Command):
  787. '''Set the output file/TTY for the whole dashboard or single modules.
  788. The dashboard/module will be written to the specified file, which will be
  789. created if it does not exist. If the specified file identifies a terminal then
  790. its geometry will be used, otherwise it falls back to the geometry of the main
  791. GDB terminal.
  792. When invoked without argument on the dashboard, the output/messages and modules
  793. which do not specify an output themselves will be printed on standard output
  794. (default).
  795. When invoked without argument on a module, it will be printed where the
  796. dashboard will be printed.
  797. An overview of all the outputs can be obtained with the `dashboard -layout`
  798. command.'''
  799. def __init__(self, dashboard, prefix=None, obj=None):
  800. if not prefix:
  801. prefix = 'dashboard'
  802. if not obj:
  803. obj = dashboard
  804. prefix = prefix + ' -output'
  805. gdb.Command.__init__(self, prefix, gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
  806. self.dashboard = dashboard
  807. self.obj = obj # None means the dashboard itself
  808. def invoke(self, arg, from_tty):
  809. arg = Dashboard.parse_arg(arg)
  810. # reset the terminal status
  811. if self.obj.output:
  812. try:
  813. with open(self.obj.output, 'w') as fs:
  814. fs.write(Dashboard.reset_terminal())
  815. except:
  816. # just do nothing if the file is not writable
  817. pass
  818. # set or open the output file
  819. if arg == '':
  820. self.obj.output = None
  821. else:
  822. self.obj.output = arg
  823. # redisplay the dashboard in the new output
  824. self.dashboard.redisplay()
  825. class EnabledCommand(gdb.Command):
  826. '''Enable or disable the dashboard.
  827. The current status is printed if no argument is present.'''
  828. def __init__(self, dashboard):
  829. gdb.Command.__init__(self, 'dashboard -enabled', gdb.COMMAND_USER)
  830. self.dashboard = dashboard
  831. def invoke(self, arg, from_tty):
  832. arg = Dashboard.parse_arg(arg)
  833. if arg == '':
  834. status = 'enabled' if self.dashboard.enabled else 'disabled'
  835. print('The dashboard is {}'.format(status))
  836. elif arg == 'on':
  837. self.dashboard.enable()
  838. self.dashboard.redisplay()
  839. elif arg == 'off':
  840. self.dashboard.disable()
  841. else:
  842. msg = 'Wrong argument "{}"; expecting "on" or "off"'
  843. Dashboard.err(msg.format(arg))
  844. def complete(self, text, word):
  845. return Dashboard.complete(word, ['on', 'off'])
  846. class LayoutCommand(gdb.Command):
  847. '''Set or show the dashboard layout.
  848. Accepts a space-separated list of directive. Each directive is in the form
  849. "[!]<module>". Modules in the list are placed in the dashboard in the same order
  850. as they appear and those prefixed by "!" are disabled by default. Omitted
  851. modules are hidden and placed at the bottom in alphabetical order.
  852. Without arguments the current layout is shown where the first line uses the same
  853. form expected by the input while the remaining depict the current status of
  854. output files.
  855. Passing `!` as a single argument resets the dashboard original layout.'''
  856. def __init__(self, dashboard):
  857. gdb.Command.__init__(self, 'dashboard -layout', gdb.COMMAND_USER)
  858. self.dashboard = dashboard
  859. def invoke(self, arg, from_tty):
  860. arg = Dashboard.parse_arg(arg)
  861. directives = str(arg).split()
  862. if directives:
  863. # apply the layout
  864. if directives == ['!']:
  865. self.reset()
  866. else:
  867. if not self.layout(directives):
  868. return # in case of errors
  869. # redisplay or otherwise notify
  870. if from_tty:
  871. if self.dashboard.is_running():
  872. self.dashboard.redisplay()
  873. else:
  874. self.show()
  875. else:
  876. self.show()
  877. def reset(self):
  878. modules = self.dashboard.modules
  879. modules.sort(key=lambda module: module.name)
  880. for module in modules:
  881. module.enabled = True
  882. def show(self):
  883. global_str = 'Dashboard'
  884. default = '(default TTY)'
  885. max_name_len = max(len(module.name) for module in self.dashboard.modules)
  886. max_name_len = max(max_name_len, len(global_str))
  887. fmt = '{{}}{{:{}s}}{{}}'.format(max_name_len + 2)
  888. print((fmt + '\n').format(' ', global_str, self.dashboard.output or default))
  889. for module in self.dashboard.modules:
  890. mark = ' ' if module.enabled else '!'
  891. style = R.style_high if module.enabled else R.style_low
  892. line = fmt.format(mark, module.name, module.output or default)
  893. print(ansi(line, style))
  894. def layout(self, directives):
  895. modules = self.dashboard.modules
  896. # parse and check directives
  897. parsed_directives = []
  898. selected_modules = set()
  899. for directive in directives:
  900. enabled = (directive[0] != '!')
  901. name = directive[not enabled:]
  902. if name in selected_modules:
  903. Dashboard.err('Module "{}" already set'.format(name))
  904. return False
  905. if next((False for module in modules if module.name == name), True):
  906. Dashboard.err('Cannot find module "{}"'.format(name))
  907. return False
  908. parsed_directives.append((name, enabled))
  909. selected_modules.add(name)
  910. # reset visibility
  911. for module in modules:
  912. module.enabled = False
  913. # move and enable the selected modules on top
  914. last = 0
  915. for name, enabled in parsed_directives:
  916. todo = enumerate(modules[last:], start=last)
  917. index = next(index for index, module in todo if name == module.name)
  918. modules[index].enabled = enabled
  919. modules.insert(last, modules.pop(index))
  920. last += 1
  921. return True
  922. def complete(self, text, word):
  923. all_modules = (m.name for m in self.dashboard.modules)
  924. return Dashboard.complete(word, all_modules)
  925. class StyleCommand(gdb.Command):
  926. '''Access the stylable attributes.
  927. Without arguments print all the stylable attributes.
  928. When only the name is specified show the current value.
  929. With name and value set the stylable attribute. Values are parsed as Python
  930. literals and converted to the proper type. '''
  931. def __init__(self, dashboard, prefix, obj, attributes):
  932. self.prefix = prefix + ' -style'
  933. gdb.Command.__init__(self, self.prefix, gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
  934. self.dashboard = dashboard
  935. self.obj = obj
  936. self.attributes = attributes
  937. self.add_styles()
  938. def add_styles(self):
  939. this = self
  940. for name, attribute in self.attributes.items():
  941. # fetch fields
  942. attr_name = attribute.get('name', name)
  943. attr_type = attribute.get('type', str)
  944. attr_check = attribute.get('check', lambda _: True)
  945. attr_default = attribute['default']
  946. # set the default value (coerced to the type)
  947. value = attr_type(attr_default)
  948. setattr(self.obj, attr_name, value)
  949. # create the command
  950. def invoke(self, arg, from_tty,
  951. name=name,
  952. attr_name=attr_name,
  953. attr_type=attr_type,
  954. attr_check=attr_check):
  955. new_value = Dashboard.parse_arg(arg)
  956. if new_value == '':
  957. # print the current value
  958. value = getattr(this.obj, attr_name)
  959. print('{} = {!r}'.format(name, value))
  960. else:
  961. try:
  962. # convert and check the new value
  963. parsed = ast.literal_eval(new_value)
  964. value = attr_type(parsed)
  965. if not attr_check(value):
  966. msg = 'Invalid value "{}" for "{}"'
  967. raise Exception(msg.format(new_value, name))
  968. except Exception as e:
  969. Dashboard.err(e)
  970. else:
  971. # set and redisplay
  972. setattr(this.obj, attr_name, value)
  973. this.dashboard.redisplay(True)
  974. prefix = self.prefix + ' ' + name
  975. doc = attribute.get('doc', 'This style is self-documenting')
  976. Dashboard.create_command(prefix, invoke, doc, False)
  977. def invoke(self, arg, from_tty):
  978. # an argument here means that the provided attribute is invalid
  979. if arg:
  980. Dashboard.err('Invalid argument "{}"'.format(arg))
  981. return
  982. # print all the pairs
  983. for name, attribute in self.attributes.items():
  984. attr_name = attribute.get('name', name)
  985. value = getattr(self.obj, attr_name)
  986. print('{} = {!r}'.format(name, value))
  987. # Base module ------------------------------------------------------------------
  988. # just a tag
  989. class Module():
  990. '''Base class for GDB dashboard modules.
  991. Modules are instantiated once at initialization time and kept during the
  992. whole the GDB session.
  993. The name of a module is automatically obtained by the class name.
  994. Optionally, a module may include a description which will appear in the
  995. GDB help system by specifying a Python docstring for the class. By
  996. convention the first line should contain a brief description.'''
  997. def label(self):
  998. '''Return the module label which will appear in the divider.'''
  999. pass
  1000. def lines(self, term_width, term_height, style_changed):
  1001. '''Return a list of strings which will form the module content.
  1002. When a module is temporarily unable to produce its content, it
  1003. should return an empty list; its divider will then use the styles
  1004. with the "off" qualifier.
  1005. term_width and term_height are the dimension of the terminal where
  1006. this module will be displayed. If `style_changed` is `True` then
  1007. some attributes have changed since the last time so the
  1008. implementation may want to update its status.'''
  1009. pass
  1010. def attributes(self):
  1011. '''Return the dictionary of available attributes.
  1012. The key is the attribute name and the value is another dictionary
  1013. with items:
  1014. - `default` is the initial value for this attribute;
  1015. - `doc` is the optional documentation of this attribute which will
  1016. appear in the GDB help system;
  1017. - `name` is the name of the attribute of the Python object (defaults
  1018. to the key value);
  1019. - `type` is the Python type of this attribute defaulting to the
  1020. `str` type, it is used to coerce the value passed as an argument
  1021. to the proper type, or raise an exception;
  1022. - `check` is an optional control callback which accept the coerced
  1023. value and returns `True` if the value satisfies the constraint and
  1024. `False` otherwise.
  1025. Those attributes can be accessed from the implementation using
  1026. instance variables named `name`.'''
  1027. return {}
  1028. def commands(self):
  1029. '''Return the dictionary of available commands.
  1030. The key is the attribute name and the value is another dictionary
  1031. with items:
  1032. - `action` is the callback to be executed which accepts the raw
  1033. input string from the GDB prompt, exceptions in these functions
  1034. will be shown automatically to the user;
  1035. - `doc` is the documentation of this command which will appear in
  1036. the GDB help system;
  1037. - `completion` is the optional completion policy, one of the
  1038. `gdb.COMPLETE_*` constants defined in the GDB reference manual
  1039. (https://sourceware.org/gdb/onlinedocs/gdb/Commands-In-Python.html).'''
  1040. return {}
  1041. # Default modules --------------------------------------------------------------
  1042. class Source(Dashboard.Module):
  1043. '''Show the program source code, if available.'''
  1044. def __init__(self):
  1045. self.file_name = None
  1046. self.source_lines = []
  1047. self.ts = None
  1048. self.highlighted = False
  1049. self.offset = 0
  1050. def label(self):
  1051. label = 'Source'
  1052. if self.show_path and self.file_name:
  1053. label += ': {}'.format(self.file_name)
  1054. return label
  1055. def lines(self, term_width, term_height, style_changed):
  1056. # skip if the current thread is not stopped
  1057. if not gdb.selected_thread().is_stopped():
  1058. return []
  1059. # try to fetch the current line (skip if no line information)
  1060. sal = gdb.selected_frame().find_sal()
  1061. current_line = sal.line
  1062. if current_line == 0:
  1063. self.file_name = None
  1064. return []
  1065. # try to lookup the source file
  1066. candidates = [
  1067. sal.symtab.fullname(),
  1068. sal.symtab.filename,
  1069. # XXX GDB also uses absolute filename but it is harder to implement
  1070. # properly and IMHO useless
  1071. os.path.basename(sal.symtab.filename)]
  1072. for candidate in candidates:
  1073. file_name = candidate
  1074. ts = None
  1075. try:
  1076. ts = os.path.getmtime(file_name)
  1077. break
  1078. except:
  1079. # try another or delay error check to open()
  1080. continue
  1081. # style changed, different file name or file modified in the meanwhile
  1082. if style_changed or file_name != self.file_name or ts and ts > self.ts:
  1083. try:
  1084. # reload the source file if changed
  1085. with io.open(file_name, errors='replace') as source_file:
  1086. highlighter = Beautifier(file_name, self.tab_size)
  1087. self.highlighted = highlighter.active
  1088. source = highlighter.process(source_file.read())
  1089. self.source_lines = source.split('\n')
  1090. # store file name and timestamp only if success to have
  1091. # persistent errors
  1092. self.file_name = file_name
  1093. self.ts = ts
  1094. except IOError as e:
  1095. msg = 'Cannot display "{}"'.format(file_name)
  1096. return [ansi(msg, R.style_error)]
  1097. # compute the line range
  1098. height = self.height or (term_height - 1)
  1099. start = current_line - 1 - int(height / 2) + self.offset
  1100. end = start + height
  1101. # extra at start
  1102. extra_start = 0
  1103. if start < 0:
  1104. extra_start = min(-start, height)
  1105. start = 0
  1106. # extra at end
  1107. extra_end = 0
  1108. if end > len(self.source_lines):
  1109. extra_end = min(end - len(self.source_lines), height)
  1110. end = len(self.source_lines)
  1111. else:
  1112. end = max(end, 0)
  1113. # return the source code listing
  1114. breakpoints = fetch_breakpoints()
  1115. out = []
  1116. number_format = '{{:>{}}}'.format(len(str(end)))
  1117. for number, line in enumerate(self.source_lines[start:end], start + 1):
  1118. # properly handle UTF-8 source files
  1119. line = to_string(line)
  1120. if int(number) == current_line:
  1121. # the current line has a different style without ANSI
  1122. if R.ansi:
  1123. if self.highlighted and not self.highlight_line:
  1124. line_format = '{}' + ansi(number_format, R.style_selected_1) + ' {}'
  1125. else:
  1126. line_format = '{}' + ansi(number_format + ' {}', R.style_selected_1)
  1127. else:
  1128. # just show a plain text indicator
  1129. line_format = '{}' + number_format + '> {}'
  1130. else:
  1131. line_format = '{}' + ansi(number_format, R.style_low) + ' {}'
  1132. # check for breakpoint presence
  1133. enabled = None
  1134. for breakpoint in breakpoints:
  1135. addresses = breakpoint['addresses']
  1136. is_root_enabled = addresses[0]['enabled']
  1137. for address in addresses:
  1138. # note, despite the lookup path always use the relative
  1139. # (sal.symtab.filename) file name to match source files with
  1140. # breakpoints
  1141. if address['file_line'] == number and address['file_name'] == sal.symtab.filename:
  1142. enabled = enabled or (address['enabled'] and is_root_enabled)
  1143. if enabled is None:
  1144. breakpoint = ' '
  1145. else:
  1146. breakpoint = ansi('!', R.style_critical) if enabled else ansi('-', R.style_low)
  1147. out.append(line_format.format(breakpoint, number, line.rstrip('\n')))
  1148. # return the output along with scroll indicators
  1149. if len(out) <= height:
  1150. extra = [ansi('~', R.style_low)]
  1151. return extra_start * extra + out + extra_end * extra
  1152. else:
  1153. return out
  1154. def commands(self):
  1155. return {
  1156. 'scroll': {
  1157. 'action': self.scroll,
  1158. 'doc': 'Scroll by relative steps or reset if invoked without argument.'
  1159. }
  1160. }
  1161. def attributes(self):
  1162. return {
  1163. 'height': {
  1164. 'doc': '''Height of the module.
  1165. A value of 0 uses the whole height.''',
  1166. 'default': 10,
  1167. 'type': int,
  1168. 'check': check_ge_zero
  1169. },
  1170. 'tab-size': {
  1171. 'doc': 'Number of spaces used to display the tab character.',
  1172. 'default': 4,
  1173. 'name': 'tab_size',
  1174. 'type': int,
  1175. 'check': check_gt_zero
  1176. },
  1177. 'path': {
  1178. 'doc': 'Path visibility flag in the module label.',
  1179. 'default': False,
  1180. 'name': 'show_path',
  1181. 'type': bool
  1182. },
  1183. 'highlight-line': {
  1184. 'doc': 'Decide whether the whole current line should be highlighted.',
  1185. 'default': False,
  1186. 'name': 'highlight_line',
  1187. 'type': bool
  1188. }
  1189. }
  1190. def scroll(self, arg):
  1191. if arg:
  1192. self.offset += int(arg)
  1193. else:
  1194. self.offset = 0
  1195. class Assembly(Dashboard.Module):
  1196. '''Show the disassembled code surrounding the program counter.
  1197. The instructions constituting the current statement are marked, if available.'''
  1198. def __init__(self):
  1199. self.offset = 0
  1200. self.cache_key = None
  1201. self.cache_asm = None
  1202. def label(self):
  1203. return 'Assembly'
  1204. def lines(self, term_width, term_height, style_changed):
  1205. # skip if the current thread is not stopped
  1206. if not gdb.selected_thread().is_stopped():
  1207. return []
  1208. # flush the cache if the style is changed
  1209. if style_changed:
  1210. self.cache_key = None
  1211. # prepare the highlighter
  1212. try:
  1213. flavor = gdb.parameter('disassembly-flavor')
  1214. except:
  1215. flavor = 'att' # not always defined (see #36)
  1216. highlighter = Beautifier(flavor, tab_size=None)
  1217. # fetch the assembly code
  1218. line_info = None
  1219. frame = gdb.selected_frame() # PC is here
  1220. height = self.height or (term_height - 1)
  1221. try:
  1222. # disassemble the current block
  1223. asm_start, asm_end = self.fetch_function_boundaries()
  1224. asm = self.fetch_asm(asm_start, asm_end, False, highlighter)
  1225. # find the location of the PC
  1226. pc_index = next(index for index, instr in enumerate(asm)
  1227. if instr['addr'] == frame.pc())
  1228. # compute the instruction range
  1229. start = pc_index - int(height / 2) + self.offset
  1230. end = start + height
  1231. # extra at start
  1232. extra_start = 0
  1233. if start < 0:
  1234. extra_start = min(-start, height)
  1235. start = 0
  1236. # extra at end
  1237. extra_end = 0
  1238. if end > len(asm):
  1239. extra_end = min(end - len(asm), height)
  1240. end = len(asm)
  1241. else:
  1242. end = max(end, 0)
  1243. # fetch actual interval
  1244. asm = asm[start:end]
  1245. # if there are line information then use it, it may be that
  1246. # line_info is not None but line_info.last is None
  1247. line_info = gdb.find_pc_line(frame.pc())
  1248. line_info = line_info if line_info.last else None
  1249. except (gdb.error, RuntimeError, StopIteration):
  1250. # if it is not possible (stripped binary or the PC is not present in
  1251. # the output of `disassemble` as per issue #31) start from PC
  1252. try:
  1253. extra_start = 0
  1254. extra_end = 0
  1255. # allow to scroll down nevertheless
  1256. clamped_offset = min(self.offset, 0)
  1257. asm = self.fetch_asm(frame.pc(), height - clamped_offset, True, highlighter)
  1258. asm = asm[-clamped_offset:]
  1259. except gdb.error as e:
  1260. msg = '{}'.format(e)
  1261. return [ansi(msg, R.style_error)]
  1262. # fetch function start if available (e.g., not with @plt)
  1263. func_start = None
  1264. if self.show_function and frame.function():
  1265. func_start = to_unsigned(frame.function().value())
  1266. # compute the maximum offset size
  1267. if asm and func_start:
  1268. max_offset = max(len(str(abs(asm[0]['addr'] - func_start))),
  1269. len(str(abs(asm[-1]['addr'] - func_start))))
  1270. # return the machine code
  1271. breakpoints = fetch_breakpoints()
  1272. max_length = max(instr['length'] for instr in asm) if asm else 0
  1273. inferior = gdb.selected_inferior()
  1274. out = []
  1275. for index, instr in enumerate(asm):
  1276. addr = instr['addr']
  1277. length = instr['length']
  1278. text = instr['asm']
  1279. addr_str = format_address(addr)
  1280. if self.show_opcodes:
  1281. # fetch and format opcode
  1282. region = inferior.read_memory(addr, length)
  1283. opcodes = (' '.join('{:02x}'.format(ord(byte)) for byte in region))
  1284. opcodes += (max_length - len(region)) * 3 * ' ' + ' '
  1285. else:
  1286. opcodes = ''
  1287. # compute the offset if available
  1288. if self.show_function:
  1289. if func_start:
  1290. offset = '{:+d}'.format(addr - func_start)
  1291. offset = offset.ljust(max_offset + 1) # sign
  1292. func_info = '{}{}'.format(frame.function(), offset)
  1293. else:
  1294. func_info = '?'
  1295. else:
  1296. func_info = ''
  1297. format_string = '{}{}{}{}{}{}'
  1298. indicator = ' '
  1299. text = ' ' + text
  1300. if addr == frame.pc():
  1301. if not R.ansi:
  1302. indicator = '> '
  1303. addr_str = ansi(addr_str, R.style_selected_1)
  1304. indicator = ansi(indicator, R.style_selected_1)
  1305. opcodes = ansi(opcodes, R.style_selected_1)
  1306. func_info = ansi(func_info, R.style_selected_1)
  1307. if not highlighter.active or self.highlight_line:
  1308. text = ansi(text, R.style_selected_1)
  1309. elif line_info and line_info.pc <= addr < line_info.last:
  1310. if not R.ansi:
  1311. indicator = ': '
  1312. addr_str = ansi(addr_str, R.style_selected_2)
  1313. indicator = ansi(indicator, R.style_selected_2)
  1314. opcodes = ansi(opcodes, R.style_selected_2)
  1315. func_info = ansi(func_info, R.style_selected_2)
  1316. if not highlighter.active or self.highlight_line:
  1317. text = ansi(text, R.style_selected_2)
  1318. else:
  1319. addr_str = ansi(addr_str, R.style_low)
  1320. func_info = ansi(func_info, R.style_low)
  1321. # check for breakpoint presence
  1322. enabled = None
  1323. for breakpoint in breakpoints:
  1324. addresses = breakpoint['addresses']
  1325. is_root_enabled = addresses[0]['enabled']
  1326. for address in addresses:
  1327. if address['address'] == addr:
  1328. enabled = enabled or (address['enabled'] and is_root_enabled)
  1329. if enabled is None:
  1330. breakpoint = ' '
  1331. else:
  1332. breakpoint = ansi('!', R.style_critical) if enabled else ansi('-', R.style_low)
  1333. out.append(format_string.format(breakpoint, addr_str, indicator, opcodes, func_info, text))
  1334. # return the output along with scroll indicators
  1335. if len(out) <= height:
  1336. extra = [ansi('~', R.style_low)]
  1337. return extra_start * extra + out + extra_end * extra
  1338. else:
  1339. return out
  1340. def commands(self):
  1341. return {
  1342. 'scroll': {
  1343. 'action': self.scroll,
  1344. 'doc': 'Scroll by relative steps or reset if invoked without argument.'
  1345. }
  1346. }
  1347. def attributes(self):
  1348. return {
  1349. 'height': {
  1350. 'doc': '''Height of the module.
  1351. A value of 0 uses the whole height.''',
  1352. 'default': 10,
  1353. 'type': int,
  1354. 'check': check_ge_zero
  1355. },
  1356. 'opcodes': {
  1357. 'doc': 'Opcodes visibility flag.',
  1358. 'default': False,
  1359. 'name': 'show_opcodes',
  1360. 'type': bool
  1361. },
  1362. 'function': {
  1363. 'doc': 'Function information visibility flag.',
  1364. 'default': True,
  1365. 'name': 'show_function',
  1366. 'type': bool
  1367. },
  1368. 'highlight-line': {
  1369. 'doc': 'Decide whether the whole current line should be highlighted.',
  1370. 'default': False,
  1371. 'name': 'highlight_line',
  1372. 'type': bool
  1373. }
  1374. }
  1375. def scroll(self, arg):
  1376. if arg:
  1377. self.offset += int(arg)
  1378. else:
  1379. self.offset = 0
  1380. def fetch_function_boundaries(self):
  1381. frame = gdb.selected_frame()
  1382. # parse the output of the disassemble GDB command to find the function
  1383. # boundaries, this should handle cases in which a function spans
  1384. # multiple discontinuous blocks
  1385. disassemble = run('disassemble')
  1386. for block_start, block_end in re.findall(r'Address range 0x([0-9a-f]+) to 0x([0-9a-f]+):', disassemble):
  1387. block_start = int(block_start, 16)
  1388. block_end = int(block_end, 16)
  1389. if block_start <= frame.pc() < block_end:
  1390. return block_start, block_end - 1 # need to be inclusive
  1391. # if function information is available then try to obtain the
  1392. # boundaries by looking at the superblocks
  1393. block = frame.block()
  1394. if frame.function():
  1395. while block and (not block.function or block.function.name != frame.function().name):
  1396. block = block.superblock
  1397. block = block or frame.block()
  1398. return block.start, block.end - 1
  1399. def fetch_asm(self, start, end_or_count, relative, highlighter):
  1400. # fetch asm from cache or disassemble
  1401. if self.cache_key == (start, end_or_count):
  1402. asm = self.cache_asm
  1403. else:
  1404. kwargs = {
  1405. 'start_pc': start,
  1406. 'count' if relative else 'end_pc': end_or_count
  1407. }
  1408. asm = gdb.selected_frame().architecture().disassemble(**kwargs)
  1409. self.cache_key = (start, end_or_count)
  1410. self.cache_asm = asm
  1411. # syntax highlight the cached entry
  1412. for instr in asm:
  1413. instr['asm'] = highlighter.process(instr['asm'])
  1414. return asm
  1415. class Variables(Dashboard.Module):
  1416. '''Show arguments and locals of the selected frame.'''
  1417. def label(self):
  1418. return 'Variables'
  1419. def lines(self, term_width, term_height, style_changed):
  1420. return Variables.format_frame(
  1421. gdb.selected_frame(), self.show_arguments, self.show_locals, self.compact, self.align, self.sort)
  1422. def attributes(self):
  1423. return {
  1424. 'arguments': {
  1425. 'doc': 'Frame arguments visibility flag.',
  1426. 'default': True,
  1427. 'name': 'show_arguments',
  1428. 'type': bool
  1429. },
  1430. 'locals': {
  1431. 'doc': 'Frame locals visibility flag.',
  1432. 'default': True,
  1433. 'name': 'show_locals',
  1434. 'type': bool
  1435. },
  1436. 'compact': {
  1437. 'doc': 'Single-line display flag.',
  1438. 'default': True,
  1439. 'type': bool
  1440. },
  1441. 'align': {
  1442. 'doc': 'Align variables in column flag (only if not compact).',
  1443. 'default': False,
  1444. 'type': bool
  1445. },
  1446. 'sort': {
  1447. 'doc': 'Sort variables by name.',
  1448. 'default': False,
  1449. 'type': bool
  1450. }
  1451. }
  1452. @staticmethod
  1453. def format_frame(frame, show_arguments, show_locals, compact, align, sort):
  1454. out = []
  1455. # fetch frame arguments and locals
  1456. decorator = gdb.FrameDecorator.FrameDecorator(frame)
  1457. separator = ansi(', ', R.style_low)
  1458. if show_arguments:
  1459. def prefix(line):
  1460. return Stack.format_line('arg', line)
  1461. frame_args = decorator.frame_args()
  1462. args_lines = Variables.fetch(frame, frame_args, compact, align, sort)
  1463. if args_lines:
  1464. if compact:
  1465. args_line = separator.join(args_lines)
  1466. single_line = prefix(args_line)
  1467. out.append(single_line)
  1468. else:
  1469. out.extend(map(prefix, args_lines))
  1470. if show_locals:
  1471. def prefix(line):
  1472. return Stack.format_line('loc', line)
  1473. frame_locals = decorator.frame_locals()
  1474. locals_lines = Variables.fetch(frame, frame_locals, compact, align, sort)
  1475. if locals_lines:
  1476. if compact:
  1477. locals_line = separator.join(locals_lines)
  1478. single_line = prefix(locals_line)
  1479. out.append(single_line)
  1480. else:
  1481. out.extend(map(prefix, locals_lines))
  1482. return out
  1483. @staticmethod
  1484. def fetch(frame, data, compact, align, sort):
  1485. lines = []
  1486. name_width = 0
  1487. if align and not compact:
  1488. name_width = max(len(str(elem.sym)) for elem in data) if data else 0
  1489. for elem in data or []:
  1490. name = ansi(elem.sym, R.style_high) + ' ' * (name_width - len(str(elem.sym)))
  1491. equal = ansi('=', R.style_low)
  1492. value = format_value(elem.sym.value(frame), compact)
  1493. lines.append('{} {} {}'.format(name, equal, value))
  1494. if sort:
  1495. lines.sort()
  1496. return lines
  1497. class Stack(Dashboard.Module):
  1498. '''Show the current stack trace including the function name and the file location, if available.
  1499. Optionally list the frame arguments and locals too.'''
  1500. def label(self):
  1501. return 'Stack'
  1502. def lines(self, term_width, term_height, style_changed):
  1503. # skip if the current thread is not stopped
  1504. if not gdb.selected_thread().is_stopped():
  1505. return []
  1506. # find the selected frame level (XXX Frame.level() is a recent addition)
  1507. start_level = 0
  1508. frame = gdb.newest_frame()
  1509. while frame:
  1510. if frame == gdb.selected_frame():
  1511. break
  1512. frame = frame.older()
  1513. start_level += 1
  1514. # gather the frames
  1515. more = False
  1516. frames = [gdb.selected_frame()]
  1517. going_down = True
  1518. while True:
  1519. # stack frames limit reached
  1520. if len(frames) == self.limit:
  1521. more = True
  1522. break
  1523. # zigzag the frames starting from the selected one
  1524. if going_down:
  1525. frame = frames[-1].older()
  1526. if frame:
  1527. frames.append(frame)
  1528. else:
  1529. frame = frames[0].newer()
  1530. if frame:
  1531. frames.insert(0, frame)
  1532. start_level -= 1
  1533. else:
  1534. break
  1535. else:
  1536. frame = frames[0].newer()
  1537. if frame:
  1538. frames.insert(0, frame)
  1539. start_level -= 1
  1540. else:
  1541. frame = frames[-1].older()
  1542. if frame:
  1543. frames.append(frame)
  1544. else:
  1545. break
  1546. # switch direction
  1547. going_down = not going_down
  1548. # format the output
  1549. lines = []
  1550. for number, frame in enumerate(frames, start=start_level):
  1551. selected = frame == gdb.selected_frame()
  1552. lines.extend(self.get_frame_lines(number, frame, selected))
  1553. # add the placeholder
  1554. if more:
  1555. lines.append('[{}]'.format(ansi('+', R.style_selected_2)))
  1556. return lines
  1557. def attributes(self):
  1558. return {
  1559. 'limit': {
  1560. 'doc': 'Maximum number of displayed frames (0 means no limit).',
  1561. 'default': 10,
  1562. 'type': int,
  1563. 'check': check_ge_zero
  1564. },
  1565. 'arguments': {
  1566. 'doc': 'Frame arguments visibility flag.',
  1567. 'default': False,
  1568. 'name': 'show_arguments',
  1569. 'type': bool
  1570. },
  1571. 'locals': {
  1572. 'doc': 'Frame locals visibility flag.',
  1573. 'default': False,
  1574. 'name': 'show_locals',
  1575. 'type': bool
  1576. },
  1577. 'compact': {
  1578. 'doc': 'Single-line display flag.',
  1579. 'default': False,
  1580. 'type': bool
  1581. },
  1582. 'align': {
  1583. 'doc': 'Align variables in column flag (only if not compact).',
  1584. 'default': False,
  1585. 'type': bool
  1586. },
  1587. 'sort': {
  1588. 'doc': 'Sort variables by name.',
  1589. 'default': False,
  1590. 'type': bool
  1591. }
  1592. }
  1593. def get_frame_lines(self, number, frame, selected=False):
  1594. # fetch frame info
  1595. style = R.style_selected_1 if selected else R.style_selected_2
  1596. frame_id = ansi(str(number), style)
  1597. info = Stack.get_pc_line(frame, style)
  1598. frame_lines = []
  1599. frame_lines.append('[{}] {}'.format(frame_id, info))
  1600. # add frame arguments and locals
  1601. variables = Variables.format_frame(
  1602. frame, self.show_arguments, self.show_locals, self.compact, self.align, self.sort)
  1603. frame_lines.extend(variables)
  1604. return frame_lines
  1605. @staticmethod
  1606. def format_line(prefix, line):
  1607. prefix = ansi(prefix, R.style_low)
  1608. return '{} {}'.format(prefix, line)
  1609. @staticmethod
  1610. def get_pc_line(frame, style):
  1611. frame_pc = ansi(format_address(frame.pc()), style)
  1612. info = 'from {}'.format(frame_pc)
  1613. # if a frame function symbol is available then use it to fetch the
  1614. # current function name and address, otherwise fall back relying on the
  1615. # frame name
  1616. if frame.function():
  1617. name = ansi(frame.function(), style)
  1618. func_start = to_unsigned(frame.function().value())
  1619. offset = ansi(str(frame.pc() - func_start), style)
  1620. info += ' in {}+{}'.format(name, offset)
  1621. elif frame.name():
  1622. name = ansi(frame.name(), style)
  1623. info += ' in {}'.format(name)
  1624. sal = frame.find_sal()
  1625. if sal and sal.symtab:
  1626. file_name = ansi(sal.symtab.filename, style)
  1627. file_line = ansi(str(sal.line), style)
  1628. info += ' at {}:{}'.format(file_name, file_line)
  1629. return info
  1630. class History(Dashboard.Module):
  1631. '''List the last entries of the value history.'''
  1632. def label(self):
  1633. return 'History'
  1634. def lines(self, term_width, term_height, style_changed):
  1635. out = []
  1636. # fetch last entries
  1637. for i in range(-self.limit + 1, 1):
  1638. try:
  1639. value = format_value(gdb.history(i))
  1640. value_id = ansi('$${}', R.style_high).format(abs(i))
  1641. equal = ansi('=', R.style_low)
  1642. line = '{} {} {}'.format(value_id, equal, value)
  1643. out.append(line)
  1644. except gdb.error:
  1645. continue
  1646. return out
  1647. def attributes(self):
  1648. return {
  1649. 'limit': {
  1650. 'doc': 'Maximum number of values to show.',
  1651. 'default': 3,
  1652. 'type': int,
  1653. 'check': check_gt_zero
  1654. }
  1655. }
  1656. class Memory(Dashboard.Module):
  1657. '''Allow to inspect memory regions.'''
  1658. DEFAULT_LENGTH = 16
  1659. class Region():
  1660. def __init__(self, expression, length, module):
  1661. self.expression = expression
  1662. self.length = length
  1663. self.module = module
  1664. self.original = None
  1665. self.latest = None
  1666. def reset(self):
  1667. self.original = None
  1668. self.latest = None
  1669. def format(self, per_line):
  1670. # fetch the memory content
  1671. try:
  1672. address = Memory.parse_as_address(self.expression)
  1673. inferior = gdb.selected_inferior()
  1674. memory = inferior.read_memory(address, self.length)
  1675. # set the original memory snapshot if needed
  1676. if not self.original:
  1677. self.original = memory
  1678. except gdb.error as e:
  1679. msg = 'Cannot access {} bytes starting at {}: {}'
  1680. msg = msg.format(self.length, self.expression, e)
  1681. return [ansi(msg, R.style_error)]
  1682. # format the memory content
  1683. out = []
  1684. for i in range(0, len(memory), per_line):
  1685. region = memory[i:i + per_line]
  1686. pad = per_line - len(region)
  1687. address_str = format_address(address + i)
  1688. # compute changes
  1689. hexa = []
  1690. text = []
  1691. for j in range(len(region)):
  1692. rel = i + j
  1693. byte = memory[rel]
  1694. hexa_byte = '{:02x}'.format(ord(byte))
  1695. text_byte = self.module.format_byte(byte)
  1696. # differences against the latest have the highest priority
  1697. if self.latest and memory[rel] != self.latest[rel]:
  1698. hexa_byte = ansi(hexa_byte, R.style_selected_1)
  1699. text_byte = ansi(text_byte, R.style_selected_1)
  1700. # cumulative changes if enabled
  1701. elif self.module.cumulative and memory[rel] != self.original[rel]:
  1702. hexa_byte = ansi(hexa_byte, R.style_selected_2)
  1703. text_byte = ansi(text_byte, R.style_selected_2)
  1704. # format the text differently for clarity
  1705. else:
  1706. text_byte = ansi(text_byte, R.style_high)
  1707. hexa.append(hexa_byte)
  1708. text.append(text_byte)
  1709. # output the formatted line
  1710. hexa_placeholder = ' {}'.format(self.module.placeholder[0] * 2)
  1711. text_placeholder = self.module.placeholder[0]
  1712. out.append('{} {}{} {}{}'.format(
  1713. ansi(address_str, R.style_low),
  1714. ' '.join(hexa), ansi(pad * hexa_placeholder, R.style_low),
  1715. ''.join(text), ansi(pad * text_placeholder, R.style_low)))
  1716. # update the latest memory snapshot
  1717. self.latest = memory
  1718. return out
  1719. def __init__(self):
  1720. self.table = {}
  1721. def label(self):
  1722. return 'Memory'
  1723. def lines(self, term_width, term_height, style_changed):
  1724. out = []
  1725. for expression, region in self.table.items():
  1726. out.append(divider(term_width, expression))
  1727. out.extend(region.format(self.get_per_line(term_width)))
  1728. return out
  1729. def commands(self):
  1730. return {
  1731. 'watch': {
  1732. 'action': self.watch,
  1733. 'doc': '''Watch a memory region by expression and length.
  1734. The length defaults to 16 bytes.''',
  1735. 'complete': gdb.COMPLETE_EXPRESSION
  1736. },
  1737. 'unwatch': {
  1738. 'action': self.unwatch,
  1739. 'doc': 'Stop watching a memory region by expression.',
  1740. 'complete': gdb.COMPLETE_EXPRESSION
  1741. },
  1742. 'clear': {
  1743. 'action': self.clear,
  1744. 'doc': 'Clear all the watched regions.'
  1745. }
  1746. }
  1747. def attributes(self):
  1748. return {
  1749. 'cumulative': {
  1750. 'doc': 'Highlight changes cumulatively, watch again to reset.',
  1751. 'default': False,
  1752. 'type': bool
  1753. },
  1754. 'full': {
  1755. 'doc': 'Take the whole horizontal space.',
  1756. 'default': False,
  1757. 'type': bool
  1758. },
  1759. 'placeholder': {
  1760. 'doc': 'Placeholder used for missing items and unprintable characters.',
  1761. 'default': '·'
  1762. }
  1763. }
  1764. def watch(self, arg):
  1765. if arg:
  1766. expression, _, length_str = arg.partition(' ')
  1767. length = Memory.parse_as_address(length_str) if length_str else Memory.DEFAULT_LENGTH
  1768. # keep the length when the memory is watched to reset the changes
  1769. region = self.table.get(expression)
  1770. if region and not length_str:
  1771. region.reset()
  1772. else:
  1773. self.table[expression] = Memory.Region(expression, length, self)
  1774. else:
  1775. raise Exception('Specify a memory location')
  1776. def unwatch(self, arg):
  1777. if arg:
  1778. try:
  1779. del self.table[arg]
  1780. except KeyError:
  1781. raise Exception('Memory expression not watched')
  1782. else:
  1783. raise Exception('Specify a matched memory expression')
  1784. def clear(self, arg):
  1785. self.table.clear()
  1786. def format_byte(self, byte):
  1787. # `type(byte) is bytes` in Python 3
  1788. if 0x20 < ord(byte) < 0x7f:
  1789. return chr(ord(byte))
  1790. else:
  1791. return self.placeholder[0]
  1792. def get_per_line(self, term_width):
  1793. if self.full:
  1794. padding = 3 # two double spaces separator (one is part of below)
  1795. elem_size = 4 # HH + 1 space + T
  1796. address_length = gdb.parse_and_eval('$pc').type.sizeof * 2 + 2 # 0x
  1797. return max(int((term_width - address_length - padding) / elem_size), 1)
  1798. else:
  1799. return Memory.DEFAULT_LENGTH
  1800. @staticmethod
  1801. def parse_as_address(expression):
  1802. value = gdb.parse_and_eval(expression)
  1803. return to_unsigned(value)
  1804. class Registers(Dashboard.Module):
  1805. '''Show the CPU registers and their values.'''
  1806. def __init__(self):
  1807. self.table = {}
  1808. def label(self):
  1809. return 'Registers'
  1810. def lines(self, term_width, term_height, style_changed):
  1811. # skip if the current thread is not stopped
  1812. if not gdb.selected_thread().is_stopped():
  1813. return []
  1814. # obtain the registers to display
  1815. if style_changed:
  1816. self.table = {}
  1817. if self.register_list:
  1818. register_list = self.register_list.split()
  1819. else:
  1820. register_list = Registers.fetch_register_list()
  1821. # fetch registers status
  1822. registers = []
  1823. for name in register_list:
  1824. # exclude registers with a dot '.' or parse_and_eval() will fail
  1825. if '.' in name:
  1826. continue
  1827. value = gdb.parse_and_eval('${}'.format(name))
  1828. string_value = Registers.format_value(value)
  1829. # exclude unavailable registers (see #255)
  1830. if string_value == '<unavailable>':
  1831. continue
  1832. changed = self.table and (self.table.get(name, '') != string_value)
  1833. self.table[name] = string_value
  1834. registers.append((name, string_value, changed))
  1835. # handle the empty register list
  1836. if not registers:
  1837. msg = 'No registers to show (check the "dashboard registers -style list" attribute)'
  1838. return [ansi(msg, R.style_error)]
  1839. # compute lengths considering an extra space between and around the
  1840. # entries (hence the +2 and term_width - 1)
  1841. max_name = max(len(name) for name, _, _ in registers)
  1842. max_value = max(len(value) for _, value, _ in registers)
  1843. max_width = max_name + max_value + 2
  1844. columns = min(int((term_width - 1) / max_width) or 1, len(registers))
  1845. rows = int(math.ceil(float(len(registers)) / columns))
  1846. # build the registers matrix
  1847. if self.column_major:
  1848. matrix = list(registers[i:i + rows] for i in range(0, len(registers), rows))
  1849. else:
  1850. matrix = list(registers[i::columns] for i in range(columns))
  1851. # compute the lengths column wise
  1852. max_names_column = list(max(len(name) for name, _, _ in column) for column in matrix)
  1853. max_values_column = list(max(len(value) for _, value, _ in column) for column in matrix)
  1854. line_length = sum(max_names_column) + columns + sum(max_values_column)
  1855. extra = term_width - line_length
  1856. # compute padding as if there were one more column
  1857. base_padding = int(extra / (columns + 1))
  1858. padding_column = [base_padding] * columns
  1859. # distribute the remainder among columns giving the precedence to
  1860. # internal padding
  1861. rest = extra % (columns + 1)
  1862. while rest:
  1863. padding_column[rest % columns] += 1
  1864. rest -= 1
  1865. # format the registers
  1866. out = [''] * rows
  1867. for i, column in enumerate(matrix):
  1868. max_name = max_names_column[i]
  1869. max_value = max_values_column[i]
  1870. for j, (name, value, changed) in enumerate(column):
  1871. name = ' ' * (max_name - len(name)) + ansi(name, R.style_low)
  1872. style = R.style_selected_1 if changed else ''
  1873. value = ansi(value, style) + ' ' * (max_value - len(value))
  1874. padding = ' ' * padding_column[i]
  1875. item = '{}{} {}'.format(padding, name, value)
  1876. out[j] += item
  1877. return out
  1878. def attributes(self):
  1879. return {
  1880. 'column-major': {
  1881. 'doc': 'Show registers in columns instead of rows.',
  1882. 'default': False,
  1883. 'name': 'column_major',
  1884. 'type': bool
  1885. },
  1886. 'list': {
  1887. 'doc': '''String of space-separated register names to display.
  1888. The empty list (default) causes to show all the available registers. For
  1889. architectures different from x86 setting this attribute might be mandatory.''',
  1890. 'default': '',
  1891. 'name': 'register_list',
  1892. }
  1893. }
  1894. @staticmethod
  1895. def format_value(value):
  1896. try:
  1897. if value.type.code in [gdb.TYPE_CODE_INT, gdb.TYPE_CODE_PTR]:
  1898. int_value = to_unsigned(value, value.type.sizeof)
  1899. value_format = '0x{{:0{}x}}'.format(2 * value.type.sizeof)
  1900. return value_format.format(int_value)
  1901. except (gdb.error, ValueError):
  1902. # convert to unsigned but preserve code and flags information
  1903. pass
  1904. return str(value)
  1905. @staticmethod
  1906. def fetch_register_list(*match_groups):
  1907. names = []
  1908. for line in run('maintenance print register-groups').split('\n'):
  1909. fields = line.split()
  1910. if len(fields) != 7:
  1911. continue
  1912. name, _, _, _, _, _, groups = fields
  1913. if not re.match('\w', name):
  1914. continue
  1915. for group in groups.split(','):
  1916. if group in (match_groups or ('general',)):
  1917. names.append(name)
  1918. break
  1919. return names
  1920. class Threads(Dashboard.Module):
  1921. '''List the currently available threads.'''
  1922. def label(self):
  1923. return 'Threads'
  1924. def lines(self, term_width, term_height, style_changed):
  1925. out = []
  1926. selected_thread = gdb.selected_thread()
  1927. # do not restore the selected frame if the thread is not stopped
  1928. restore_frame = gdb.selected_thread().is_stopped()
  1929. if restore_frame:
  1930. selected_frame = gdb.selected_frame()
  1931. # fetch the thread list
  1932. threads = []
  1933. for inferior in gdb.inferiors():
  1934. if self.all_inferiors or inferior == gdb.selected_inferior():
  1935. threads += gdb.Inferior.threads(inferior)
  1936. for thread in threads:
  1937. # skip running threads if requested
  1938. if self.skip_running and thread.is_running():
  1939. continue
  1940. is_selected = (thread.ptid == selected_thread.ptid)
  1941. style = R.style_selected_1 if is_selected else R.style_selected_2
  1942. if self.all_inferiors:
  1943. number = '{}.{}'.format(thread.inferior.num, thread.num)
  1944. else:
  1945. number = str(thread.num)
  1946. number = ansi(number, style)
  1947. tid = ansi(str(thread.ptid[1] or thread.ptid[2]), style)
  1948. info = '[{}] id {}'.format(number, tid)
  1949. if thread.name:
  1950. info += ' name {}'.format(ansi(thread.name, style))
  1951. # switch thread to fetch info (unless is running in non-stop mode)
  1952. try:
  1953. thread.switch()
  1954. frame = gdb.newest_frame()
  1955. info += ' ' + Stack.get_pc_line(frame, style)
  1956. except gdb.error:
  1957. info += ' (running)'
  1958. out.append(info)
  1959. # restore thread and frame
  1960. selected_thread.switch()
  1961. if restore_frame:
  1962. selected_frame.select()
  1963. return out
  1964. def attributes(self):
  1965. return {
  1966. 'skip-running': {
  1967. 'doc': 'Skip running threads.',
  1968. 'default': False,
  1969. 'name': 'skip_running',
  1970. 'type': bool
  1971. },
  1972. 'all-inferiors': {
  1973. 'doc': 'Show threads from all inferiors.',
  1974. 'default': False,
  1975. 'name': 'all_inferiors',
  1976. 'type': bool
  1977. },
  1978. }
  1979. class Expressions(Dashboard.Module):
  1980. '''Watch user expressions.'''
  1981. def __init__(self):
  1982. self.table = []
  1983. def label(self):
  1984. return 'Expressions'
  1985. def lines(self, term_width, term_height, style_changed):
  1986. out = []
  1987. label_width = 0
  1988. if self.align:
  1989. label_width = max(len(expression) for expression in self.table) if self.table else 0
  1990. default_radix = Expressions.get_default_radix()
  1991. for number, expression in enumerate(self.table, start=1):
  1992. label = expression
  1993. match = re.match('^/(\d+) +(.+)$', expression)
  1994. try:
  1995. if match:
  1996. radix, expression = match.groups()
  1997. run('set output-radix {}'.format(radix))
  1998. value = format_value(gdb.parse_and_eval(expression))
  1999. except gdb.error as e:
  2000. value = ansi(e, R.style_error)
  2001. finally:
  2002. if match:
  2003. run('set output-radix {}'.format(default_radix))
  2004. number = ansi(str(number), R.style_selected_2)
  2005. label = ansi(expression, R.style_high) + ' ' * (label_width - len(expression))
  2006. equal = ansi('=', R.style_low)
  2007. out.append('[{}] {} {} {}'.format(number, label, equal, value))
  2008. return out
  2009. def commands(self):
  2010. return {
  2011. 'watch': {
  2012. 'action': self.watch,
  2013. 'doc': 'Watch an expression using the format `[/<radix>] <expression>`.',
  2014. 'complete': gdb.COMPLETE_EXPRESSION
  2015. },
  2016. 'unwatch': {
  2017. 'action': self.unwatch,
  2018. 'doc': 'Stop watching an expression by index.'
  2019. },
  2020. 'clear': {
  2021. 'action': self.clear,
  2022. 'doc': 'Clear all the watched expressions.'
  2023. }
  2024. }
  2025. def attributes(self):
  2026. return {
  2027. 'align': {
  2028. 'doc': 'Align variables in column flag.',
  2029. 'default': False,
  2030. 'type': bool
  2031. }
  2032. }
  2033. def watch(self, arg):
  2034. if arg:
  2035. if arg not in self.table:
  2036. self.table.append(arg)
  2037. else:
  2038. raise Exception('Expression already watched')
  2039. else:
  2040. raise Exception('Specify an expression')
  2041. def unwatch(self, arg):
  2042. if arg:
  2043. try:
  2044. number = int(arg) - 1
  2045. except:
  2046. number = -1
  2047. if 0 <= number < len(self.table):
  2048. self.table.pop(number)
  2049. else:
  2050. raise Exception('Expression not watched')
  2051. else:
  2052. raise Exception('Specify an expression')
  2053. def clear(self, arg):
  2054. self.table.clear()
  2055. @staticmethod
  2056. def get_default_radix():
  2057. try:
  2058. return gdb.parameter('output-radix')
  2059. except RuntimeError:
  2060. # XXX this is a fix for GDB <8.1.x see #161
  2061. message = run('show output-radix')
  2062. match = re.match('^Default output radix for printing of values is (\d+)\.$', message)
  2063. return match.groups()[0] if match else 10 # fallback
  2064. # XXX workaround to support BP_BREAKPOINT in older GDB versions
  2065. setattr(gdb, 'BP_CATCHPOINT', getattr(gdb, 'BP_CATCHPOINT', 26))
  2066. class Breakpoints(Dashboard.Module):
  2067. '''Display the breakpoints list.'''
  2068. NAMES = {
  2069. gdb.BP_BREAKPOINT: 'break',
  2070. gdb.BP_WATCHPOINT: 'watch',
  2071. gdb.BP_HARDWARE_WATCHPOINT: 'write watch',
  2072. gdb.BP_READ_WATCHPOINT: 'read watch',
  2073. gdb.BP_ACCESS_WATCHPOINT: 'access watch',
  2074. gdb.BP_CATCHPOINT: 'catch'
  2075. }
  2076. def label(self):
  2077. return 'Breakpoints'
  2078. def lines(self, term_width, term_height, style_changed):
  2079. out = []
  2080. breakpoints = fetch_breakpoints(watchpoints=True, pending=self.show_pending)
  2081. for breakpoint in breakpoints:
  2082. sub_lines = []
  2083. # format common information
  2084. style = R.style_selected_1 if breakpoint['enabled'] else R.style_selected_2
  2085. number = ansi(breakpoint['number'], style)
  2086. bp_type = ansi(Breakpoints.NAMES[breakpoint['type']], style)
  2087. if breakpoint['temporary']:
  2088. bp_type = bp_type + ' {}'.format(ansi('once', style))
  2089. if not R.ansi and breakpoint['enabled']:
  2090. bp_type = 'disabled ' + bp_type
  2091. line = '[{}] {}'.format(number, bp_type)
  2092. if breakpoint['type'] == gdb.BP_BREAKPOINT:
  2093. for i, address in enumerate(breakpoint['addresses']):
  2094. addr = address['address']
  2095. if i == 0 and addr:
  2096. # this is a regular breakpoint
  2097. line += ' at {}'.format(ansi(format_address(addr), style))
  2098. # format source information
  2099. file_name = address.get('file_name')
  2100. file_line = address.get('file_line')
  2101. if file_name and file_line:
  2102. file_name = ansi(file_name, style)
  2103. file_line = ansi(file_line, style)
  2104. line += ' in {}:{}'.format(file_name, file_line)
  2105. elif i > 0:
  2106. # this is a sub breakpoint
  2107. sub_style = R.style_selected_1 if address['enabled'] else R.style_selected_2
  2108. sub_number = ansi('{}.{}'.format(breakpoint['number'], i), sub_style)
  2109. sub_line = '[{}]'.format(sub_number)
  2110. sub_line += ' at {}'.format(ansi(format_address(addr), sub_style))
  2111. # format source information
  2112. file_name = address.get('file_name')
  2113. file_line = address.get('file_line')
  2114. if file_name and file_line:
  2115. file_name = ansi(file_name, sub_style)
  2116. file_line = ansi(file_line, sub_style)
  2117. sub_line += ' in {}:{}'.format(file_name, file_line)
  2118. sub_lines += [sub_line]
  2119. # format user location
  2120. location = breakpoint['location']
  2121. line += ' for {}'.format(ansi(location, style))
  2122. elif breakpoint['type'] == gdb.BP_CATCHPOINT:
  2123. what = breakpoint['what']
  2124. line += ' {}'.format(ansi(what, style))
  2125. else:
  2126. # format user expression
  2127. expression = breakpoint['expression']
  2128. line += ' for {}'.format(ansi(expression, style))
  2129. # format condition
  2130. condition = breakpoint['condition']
  2131. if condition:
  2132. line += ' if {}'.format(ansi(condition, style))
  2133. # format hit count
  2134. hit_count = breakpoint['hit_count']
  2135. if hit_count:
  2136. word = 'time{}'.format('s' if hit_count > 1 else '')
  2137. line += ' hit {} {}'.format(ansi(breakpoint['hit_count'], style), word)
  2138. # append the main line and possibly sub breakpoints
  2139. out.append(line)
  2140. out.extend(sub_lines)
  2141. return out
  2142. def attributes(self):
  2143. return {
  2144. 'pending': {
  2145. 'doc': 'Also show pending breakpoints.',
  2146. 'default': True,
  2147. 'name': 'show_pending',
  2148. 'type': bool
  2149. }
  2150. }
  2151. # XXX traceback line numbers in this Python block must be increased by 1
  2152. end
  2153. # Better GDB defaults ----------------------------------------------------------
  2154. set history save
  2155. set verbose off
  2156. set print pretty on
  2157. set print array off
  2158. set print array-indexes on
  2159. set python print-stack full
  2160. # Start ------------------------------------------------------------------------
  2161. python Dashboard.start()
  2162. # File variables ---------------------------------------------------------------
  2163. # vim: filetype=python
  2164. # Local Variables:
  2165. # mode: python
  2166. # End: