source: tools/gdb/utils-gdb.py@ 968f280

ADT ast-experimental enum forall-pointer-decay pthread-emulation qualifiedEnum
Last change on this file since 968f280 was b7d94ac5, checked in by Thierry Delisle <tdelisle@…>, 4 years ago

Last step tools and benchmark

  • Property mode set to 100644
File size: 17.6 KB
Line 
1#
2# Copyright (C) Lynn Tran, Jiachen Zhang 2018
3#
4# utils-gdb.py --
5#
6# Author : Lynn Tran
7# Created On : Mon Oct 1 22:06:09 2018
8# Last Modified By : Peter A. Buhr
9# Last Modified On : Sat Jan 19 14:16:10 2019
10# Update Count : 11
11#
12
13"""
14To run this extension, the python name has to be as same as one of the loaded library
15Additionally, the file must exist in a folder which is in gdb's safe path
16"""
17import collections
18import gdb
19import re
20
21# set these signal handlers with some settings (nostop, noprint, pass)
22gdb.execute('handle SIGALRM nostop noprint pass')
23gdb.execute('handle SIGUSR1 nostop noprint pass')
24
25CfaTypes = collections.namedtuple('CfaTypes', 'cluster_ptr processor_ptr thread_ptr int_ptr thread_state yield_state')
26
27class ThreadInfo:
28 tid = 0
29 cluster = None
30 value = None
31
32 def __init__(self, cluster, value):
33 self.cluster = cluster
34 self.value = value
35
36 def is_system(self):
37 return False
38
39# A named tuple representing information about a stack
40StackInfo = collections.namedtuple('StackInfo', 'sp fp pc')
41
42# A global variable to keep track of stack information as one switches from one
43# task to another task
44STACK = []
45
46not_supported_error_msg = "Not a supported command for this language"
47
48def is_cforall():
49 return True
50
51def get_cfa_types():
52 # GDB types for various structures/types in CFA
53 return CfaTypes(cluster_ptr = gdb.lookup_type('struct cluster').pointer(),
54 processor_ptr = gdb.lookup_type('struct processor').pointer(),
55 thread_ptr = gdb.lookup_type('struct thread$').pointer(),
56 int_ptr = gdb.lookup_type('int').pointer(),
57 thread_state = gdb.lookup_type('enum __Coroutine_State'),
58 yield_state = gdb.lookup_type('enum __Preemption_Reason'))
59
60def get_addr(addr):
61 """
62 NOTE: sketchy solution to retrieve address. There is a better solution...
63 @addr: str of an address that can be in a format 0xfffff <type of the object
64 at this address>
65 Return: str of just the address
66 """
67 str_addr = str(addr)
68 ending_addr_index = str_addr.find('<')
69 if ending_addr_index == -1:
70 return str(addr)
71 return str_addr[:ending_addr_index].strip()
72
73def print_usage(obj):
74 print(obj.__doc__)
75
76def parse(args):
77 """
78 Split the argument list in string format, where each argument is separated
79 by whitespace delimiter, to a list of arguments like argv
80 @args: str of arguments
81 Return:
82 [] if args is an empty string
83 list if args is not empty
84 """
85 # parse the string format of arguments and return a list of arguments
86 argv = args.split(' ')
87 if len(argv) == 1 and argv[0] == '':
88 return []
89 return argv
90
91def get_cluster_root():
92 """
93 Return: gdb.Value of globalClusters.root (is an address)
94 """
95 cluster_root = gdb.parse_and_eval('_X11mainClusterPS7cluster_1')
96 if cluster_root.address == 0x0:
97 print('No clusters, program terminated')
98 return cluster_root
99
100def get_sched_lock():
101 """
102 Return: gdb.Value of __scheduler_lock
103 """
104 lock = gdb.parse_and_eval('_X16__scheduler_lockPS20__scheduler_RWLock_t_1')
105 if lock.address == 0x0:
106 print('No scheduler lock, program terminated')
107 return lock
108
109def all_clusters():
110 if not is_cforall():
111 return None
112
113 cluster_root = get_cluster_root()
114 if cluster_root.address == 0x0:
115 return
116
117 curr = cluster_root
118 ret = [curr]
119
120 while True:
121 curr = curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
122 if curr == cluster_root:
123 break
124
125 ret.append(curr)
126
127 return ret
128
129def all_processors():
130 if not is_cforall():
131 return None
132
133 cfa_t = get_cfa_types()
134
135 # get processors from registration to the RWlock
136 lock = get_sched_lock()
137
138 #get number of elements
139 count = lock['_X5readyVj_1']
140
141 #find all the procs
142 raw_procs = [lock['_X4dataPS21__scheduler_lock_id_t_1'][i]['_X6handleVPS16__processor_id_t_1'] for i in range(count)]
143
144 # pre cast full procs
145 procs = [p.cast(cfa_t.processor_ptr) for p in raw_procs if p['_X9full_procb_1']]
146
147 # sort procs by clusters
148 return sorted(procs, key=lambda p: p['_X4cltrPS7cluster_1'])
149
150def tls_for_pthread(pthrd):
151 prev = gdb.selected_thread()
152 inf = gdb.selected_inferior()
153
154 thrd = inf.thread_from_thread_handle( pthrd )
155 thrd.switch()
156 tls = gdb.parse_and_eval('&_X9kernelTLSS16KernelThreadData_1')
157
158 prev.switch()
159 return tls
160
161def tls_for_proc(proc):
162 return tls_for_pthread(proc['_X13kernel_threadm_1'])
163
164def thread_for_pthread(pthrd):
165 return tls_for_pthread(pthrd)['_X11this_threadVPS7thread$_1']
166
167def thread_for_proc(proc):
168 return tls_for_proc(proc)['_X11this_threadVPS7thread$_1']
169
170
171
172def find_curr_thread():
173 # btstr = gdb.execute('bt', to_string = True).splitlines()
174 # if len(btstr) == 0:
175 # print('error')
176 # return None
177 # return btstr[0].split('this=',1)[1].split(',')[0].split(')')[0]
178 return None
179
180def lookup_cluster(name = None):
181 """
182 Look up a cluster given its ID
183 @name: str
184 Return: gdb.Value
185 """
186 if not is_cforall():
187 return None
188
189 root = get_cluster_root()
190 if root.address == 0x0:
191 return None
192
193 if not name:
194 return root
195
196 # lookup for the task associated with the id
197 cluster = None
198 curr = root
199 while True:
200 if curr['_X4namePKc_1'].string() == name:
201 cluster = curr.address
202 break
203 curr = curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
204 if curr == root or curr == 0x0:
205 break
206
207 if not cluster:
208 print("Cannot find a cluster with the name: {}.".format(name))
209 return None
210
211 return cluster
212
213def lookup_threads_by_cluster(cluster):
214 # Iterate through a circular linked list of threads and accumulate them in an array
215 threads = []
216
217 cfa_t = get_cfa_types()
218 root = cluster['_X7threadsS8__dllist_S7thread$__1']['_X4headPY15__TYPE_generic__1'].cast(cfa_t.thread_ptr)
219
220 if root == 0x0 or root.address == 0x0:
221 print('There are no tasks for cluster: {}'.format(cluster))
222 return threads
223
224 curr = root
225 tid = 0
226 sid = -1
227
228 while True:
229 t = ThreadInfo(cluster, curr)
230 if t.is_system():
231 t.tid = sid
232 sid -= 1
233 else:
234 t.tid = tid
235 tid += 1
236
237 threads.append(t)
238
239 curr = curr['node']['next']
240 if curr == root or curr == 0x0:
241 break
242
243 return threads
244
245def system_thread(thread):
246 return False
247
248def adjust_stack(pc, fp, sp):
249 # pop sp, fp, pc from global stack
250 gdb.execute('set $pc = {}'.format(pc))
251 gdb.execute('set $rbp = {}'.format(fp))
252 gdb.execute('set $sp = {}'.format(sp))
253
254############################ COMMAND IMPLEMENTATION #########################
255
256class Clusters(gdb.Command):
257 """Cforall: Display currently known clusters
258Usage:
259 info clusters : print out all the clusters
260"""
261
262 def __init__(self):
263 super(Clusters, self).__init__('info clusters', gdb.COMMAND_USER)
264
265 def print_cluster(self, cluster_name, cluster_address):
266 print('{:>20} {:>20}'.format(cluster_name, cluster_address))
267
268 #entry point from gdb
269 def invoke(self, arg, from_tty):
270 if not is_cforall():
271 return
272
273 if arg:
274 print("info clusters does not take arguments")
275 print_usage(self)
276 return
277
278 self.print_cluster('Name', 'Address')
279
280 for c in all_clusters():
281 self.print_cluster(c['_X4namePKc_1'].string(), str(c))
282
283 print("")
284
285############
286class Processors(gdb.Command):
287 """Cforall: Display currently known processors
288Usage:
289 info processors : print out all the processors
290 info processors <cluster_name> : print out all processors in a given cluster
291"""
292
293 def __init__(self):
294 super(Processors, self).__init__('info processors', gdb.COMMAND_USER)
295
296 def print_processor(self, processor):
297 should_stop = processor['_X12do_terminateVb_1']
298 if not should_stop:
299 midle = processor['_X6$linksS7$dlinks_S9processor__1']['_X4nextS9$mgd_link_Y13__tE_generic___1']['_X4elemPY13__tE_generic__1'] != 0x0
300 end = processor['_X6$linksS7$dlinks_S9processor__1']['_X4nextS9$mgd_link_Y13__tE_generic___1']['_X10terminatorPv_1'] != 0x0
301
302 status = 'Idle' if midle or end else 'Active'
303 else:
304 stop_count = processor['_X10terminatedS9semaphore_1']['_X5counti_1']
305 status_str = 'Last Thread' if stop_count >= 0 else 'Terminating'
306 status = '{}({},{})'.format(status_str, should_stop, stop_count)
307
308 print('{:>20} {:>11} {:<7} {:<}'.format(
309 processor['_X4namePKc_1'].string(),
310 status,
311 str(processor['_X18pending_preemptionb_1']),
312 str(processor)
313 ))
314 tls = tls_for_proc( processor )
315 thrd = tls['_X11this_threadVPS7thread$_1']
316 if thrd != 0x0:
317 tname = '{} {}'.format(thrd['self_cor']['name'].string(), str(thrd))
318 else:
319 tname = None
320
321 print('{:>20} {}'.format('Thread', tname))
322 print('{:>20} {}'.format('TLS', tls))
323
324 #entry point from gdb
325 def invoke(self, arg, from_tty):
326 if not is_cforall():
327 return
328
329 if not arg:
330 clusters = all_clusters()
331 else:
332 clusters = [lookup_cluster(arg)]
333
334 if not clusters:
335 print("No Cluster matching arguments found")
336 return
337
338 procs = all_processors()
339
340 print('{:>20} {:>11} {:<7} {}'.format('Processor', '', 'Pending', 'Object'))
341 print('{:>20} {:>11} {:<7} {}'.format('Name', 'Status', 'Yield', 'Address'))
342 cl = None
343 for p in procs:
344 # if this is a different cluster print it
345 if cl != p['_X4cltrPS7cluster_1']:
346 if cl:
347 print()
348 cl = p['_X4cltrPS7cluster_1']
349 print('Cluster {}'.format(cl['_X4namePKc_1'].string()))
350
351 # print the processor information
352 self.print_processor(p)
353
354 print()
355
356############
357class Threads(gdb.Command):
358 """Cforall: Display currently known threads
359Usage:
360 cfathreads : print Main Cluster threads, application threads only
361 cfathreads all : print all clusters, all threads
362 cfathreads <clusterName> : print cluster threads, application threads only
363 """
364 def __init__(self):
365 # The first parameter of the line below is the name of the command. You
366 # can call it 'uc++ task'
367 super(Threads, self).__init__('info cfathreads', gdb.COMMAND_USER)
368
369 def print_formatted(self, marked, tid, name, state, address):
370 print('{:>1} {:>4} {:>20} {:>10} {:>20}'.format('*' if marked else ' ', tid, name, state, address))
371
372 def print_thread(self, thread, tid, marked):
373 cfa_t = get_cfa_types()
374 ys = str(thread['preempted'].cast(cfa_t.yield_state))
375 if ys == '_X15__NO_PREEMPTIONKM19__Preemption_Reason_1':
376 state = str(thread['state'].cast(cfa_t.thread_state))
377 elif ys == '_X18__ALARM_PREEMPTIONKM19__Preemption_Reason_1':
378 state = 'preempted'
379 elif ys == '_X19__MANUAL_PREEMPTIONKM19__Preemption_Reason_1':
380 state = 'yield'
381 elif ys == '_X17__POLL_PREEMPTIONKM19__Preemption_Reason_1':
382 state = 'poll'
383 else:
384 print("error: thread {} in undefined preemption state {}".format(thread, ys))
385 state = 'error'
386 self.print_formatted(marked, tid, thread['self_cor']['name'].string(), state, str(thread))
387
388 def print_threads_by_cluster(self, cluster, print_system = False):
389 # Iterate through a circular linked list of tasks and print out its
390 # name along with address associated to each cluster
391 threads = lookup_threads_by_cluster(cluster)
392 if not threads:
393 return
394
395 running_thread = find_curr_thread()
396 if running_thread is None:
397 print('Could not identify current thread')
398
399 self.print_formatted(False, '', 'Name', 'State', 'Address')
400
401 for t in threads:
402 if not t.is_system() or print_system:
403 self.print_thread(t.value, t.tid, t.value == running_thread if running_thread else False)
404
405 print()
406
407 def print_all_threads(self):
408 for c in all_clusters():
409 self.print_threads_by_cluster(c, False)
410
411 def invoke(self, arg, from_tty):
412 """
413 @arg: str
414 @from_tty: bool
415 """
416 if not is_cforall():
417 return
418
419 if not arg:
420 cluster = lookup_cluster()
421 if not cluster:
422 print("Could not find Main Cluster")
423 return
424
425 # only tasks and main
426 self.print_threads_by_cluster(cluster, False)
427
428 elif arg == 'all':
429 # all threads, all clusters
430 self.print_all_threads()
431
432 else:
433 cluster = lookup_cluster(arg)
434 if not cluster:
435 print("Could not find cluster '{}'".format(arg))
436 return
437
438 # all tasks, specified cluster
439 self.print_threads_by_cluster(cluster, True)
440
441
442############
443class Thread(gdb.Command):
444 """Cforall: Switch to specified user threads
445Usage:
446 cfathread <id> : switch stack to thread id on main cluster
447 cfathread 0x<address> : switch stack to thread on any cluster
448 cfathread <id> <clusterName> : switch stack to thread on specified cluster
449 """
450 def __init__(self):
451 # The first parameter of the line below is the name of the command. You
452 # can call it 'uc++ task'
453 super(Thread, self).__init__('cfathread', gdb.COMMAND_USER)
454
455 ############################ AUXILIARY FUNCTIONS #########################
456
457 def switchto(self, thread):
458 """Change to a new task by switching to a different stack and manually
459 adjusting sp, fp and pc
460 @task_address: str
461 2 supported format:
462 in hex format
463 <hex_address>: literal hexadecimal address
464 Ex: 0xffffff
465 in name of the pointer to the task
466 "task_name": pointer of the variable name of the cluster
467 Ex: T* s -> task_name = s
468 Return: gdb.value of the cluster's address
469 """
470 try:
471 if not gdb.lookup_symbol('__cfactx_switch'):
472 print('__cfactx_switch symbol is unavailable')
473 return
474 except:
475 print('here 3')
476
477 cfa_t = get_cfa_types()
478
479 state = thread['state'].cast(cfa_t.thread_state)
480 try:
481 if state == gdb.parse_and_eval('Halted'):
482 print('Cannot switch to a terminated thread')
483 return
484
485 if state == gdb.parse_and_eval('Start'):
486 print('Cannjot switch to a thread not yet run')
487 return
488 except:
489 print("here 2")
490 return
491
492
493 context = thread['context']
494
495
496
497 # must be at frame 0 to set pc register
498 gdb.execute('select-frame 0')
499 if gdb.selected_frame().architecture().name() != 'i386:x86-64':
500 print('gdb debugging only supported for i386:x86-64 for now')
501 return
502
503 # gdb seems to handle things much better if we pretend we just entered the context switch
504 # pretend the pc is __cfactx_switch and adjust the sp, base pointer doesn't need to change
505 # lookup for sp,fp and uSwitch
506 xsp = context['SP'] + 40 # 40 = 5 64bit registers : %r15, %r14, %r13, %r12, %rbx WARNING: x64 specific
507 xfp = context['FP']
508
509 # convert string so we can strip out the address
510 try:
511 xpc = get_addr(gdb.parse_and_eval('__cfactx_switch').address)
512 except:
513 print("here")
514 return
515
516 # push sp, fp, pc into a global stack
517 global STACK
518 sp = gdb.parse_and_eval('$sp')
519 fp = gdb.parse_and_eval('$fp')
520 pc = gdb.parse_and_eval('$pc')
521 stack_info = StackInfo(sp = sp, fp = fp, pc = pc)
522 STACK.append(stack_info)
523
524 # update registers for new task
525 # print('switching to {} ({}) : [{}, {}, {}]'.format(thread['self_cor']['name'].string(), str(thread), str(xsp), str(xfp), str(xpc)))
526 print('switching to thread {} ({})'.format(str(thread), thread['self_cor']['name'].string()))
527 gdb.execute('set $rsp={}'.format(xsp))
528 gdb.execute('set $rbp={}'.format(xfp))
529 gdb.execute('set $pc={}'.format(xpc))
530
531 def find_matching_gdb_thread_id():
532 """
533 Parse the str from info thread to get the number
534 """
535 info_thread_str = gdb.execute('info thread', to_string=True).splitlines()
536 for thread_str in info_thread_str:
537 if thread_str.find('this={}'.format(task)) != -1:
538 thread_id_pattern = r'^\*?\s+(\d+)\s+Thread'
539 # retrive gdb thread id
540 return re.match(thread_id_pattern, thread_str).group(1)
541
542 # check if the task is running or not
543 if task_state == gdb.parse_and_eval('uBaseTask::Running'):
544 # find the equivalent thread from info thread
545 gdb_thread_id = find_matching_gdb_thread_id()
546 if gdb_thread_id is None:
547 print('cannot find the thread id to switch to')
548 return
549 # switch to that thread based using thread command
550 gdb.execute('thread {}'.format(gdb_thread_id))
551
552 def switchto_id(self, tid, cluster):
553 """
554 @cluster: cluster object
555 @tid: int
556 """
557 threads = lookup_threads_by_cluster( cluster )
558
559 for t in threads:
560 if t.tid == tid:
561 self.switchto(t.value)
562 return
563
564 print("Cound not find thread by id '{}'".format(tid))
565
566 def invoke(self, arg, from_tty):
567 """
568 @arg: str
569 @from_tty: bool
570 """
571 if not is_cforall():
572 return
573
574 argv = parse(arg)
575 if argv[0].isdigit():
576 cname = " ".join(argv[1:]) if len(argv) > 1 else None
577 cluster = lookup_cluster(cname)
578 if not cluster:
579 print("Could not find cluster '{}'".format(cname if cname else "Main Cluster"))
580 return
581
582 try:
583 tid = int(argv[0])
584 except:
585 print("'{}' not a valid thread id".format(argv[0]))
586 print_usage(self)
587 return
588
589 # by id, userCluster
590 self.switchto_id(tid, cluster)
591
592 elif argv[0].startswith('0x') or argv[0].startswith('0X'):
593 self.switchto(argv[0]) # by address, any cluster
594
595############
596class PrevThread(gdb.Command):
597 """Switch back to previous task on the stack"""
598 usage_msg = 'prevtask'
599
600 def __init__(self):
601 super(PrevThread, self).__init__('prevtask', gdb.COMMAND_USER)
602
603 def invoke(self, arg, from_tty):
604 """
605 @arg: str
606 @from_tty: bool
607 """
608 global STACK
609 if len(STACK) != 0:
610 # must be at frame 0 to set pc register
611 gdb.execute('select-frame 0')
612
613 # pop stack
614 stack_info = STACK.pop()
615 pc = get_addr(stack_info.pc)
616 sp = stack_info.sp
617 fp = stack_info.fp
618
619 # pop sp, fp, pc from global stack
620 adjust_stack(pc, fp, sp)
621
622 # must be at C++ frame to access C++ vars
623 gdb.execute('frame 1')
624 else:
625 print('empty stack')
626
627class ResetOriginFrame(gdb.Command):
628 """Reset to the origin frame prior to continue execution again"""
629 usage_msg = 'resetOriginFrame'
630 def __init__(self):
631 super(ResetOriginFrame, self).__init__('reset', gdb.COMMAND_USER)
632
633 def invoke(self, arg, from_tty):
634 """
635 @arg: str
636 @from_tty: bool
637 """
638 global STACK
639 if len(STACK) != 0:
640 stack_info = STACK.pop(0)
641 STACK.clear()
642 pc = get_addr(stack_info.pc)
643 sp = stack_info.sp
644 fp = stack_info.fp
645
646 # pop sp, fp, pc from global stack
647 adjust_stack(pc, fp, sp)
648
649 # must be at C++ frame to access C++ vars
650 gdb.execute('frame 1')
651 #else:
652 #print('reset: empty stack') #probably does not have to print msg
653
654Clusters()
655Processors()
656ResetOriginFrame()
657PrevThread()
658Threads()
659Thread()
660
661# Local Variables: #
662# mode: Python #
663# End: #
Note: See TracBrowser for help on using the repository browser.