source: tools/gdb/utils-gdb.py@ ce7d197

ADT ast-experimental
Last change on this file since ce7d197 was 2e94d94f, checked in by Thierry Delisle <tdelisle@…>, 3 years ago

Fixed a few bugs in the processor listing.

  • Property mode set to 100644
File size: 18.4 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
91class ClusterIter:
92 def __init__(self, root):
93 self.curr = None
94 self.root = root
95
96 def __iter__(self):
97 return self
98
99 def __next__(self):
100 # Clusters form a cycle
101 # If we haven't seen the root yet, then the root is the first
102 if not self.curr:
103 self.curr = self.root
104 return self.curr
105
106 # if we already saw the root, then go forward
107 self.curr = self.curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
108
109 # if we reached the root again, then we are done
110 if self.curr == self.root:
111 raise StopIteration
112
113 # otherwise return the next
114 return self.curr
115
116def all_clusters():
117 """
118 Return: a list of all the clusters as an iterator.
119 obtained from gdb.Value of globalClusters.root (is an address)
120 """
121 if not is_cforall():
122 return []
123
124 cluster_root = gdb.parse_and_eval('_X11mainClusterPS7cluster_1')
125 if cluster_root.address == 0x0:
126 print('No clusters, program terminated')
127 return []
128
129 return ClusterIter(cluster_root)
130
131class ProcIter:
132 def __init__(self, root):
133 self.curr = None
134 self.root = root
135
136 def __iter__(self):
137 return self
138
139 def check(self):
140 # check if this is the last value
141 addr = int(self.curr)
142 mask = 1 << ((8 * int(gdb.parse_and_eval('sizeof(void*)'))) - 1)
143 if 0 != (mask & addr):
144 raise StopIteration
145
146 def __next__(self):
147 cfa_t = get_cfa_types()
148
149 # Processors form a cycle
150 # If we haven't seen the root yet, then the root is the first
151 if not self.curr:
152 my_next = self.root
153 self.curr = my_next.cast(cfa_t.processor_ptr)
154
155 #check if this is an empty list
156 self.check()
157
158 return self.curr
159
160 # if we already saw the root, then go forward
161 my_next = self.curr['_X4linkS5dlink_S9processor__1']['_X4nextPY13__tE_generic__1']
162 self.curr = my_next.cast(cfa_t.processor_ptr)
163
164 #check if we reached the end
165 self.check()
166
167 # otherwise return the next
168 return self.curr
169
170def start_from_dlist(dlist):
171 fs = dlist.type.fields()
172 if len(fs) != 1:
173 print("Error, can't understand dlist type for", dlist, dlist.name, dlist.type)
174 return None
175
176 return dlist[fs[0]]
177
178def proc_list(cluster):
179 """
180 Return: for a given processor, return the active and idle processors, as 2 iterators
181 """
182 cfa_t = get_cfa_types()
183 proclist = cluster['_X5procsS19__cluster_proc_list_1']
184
185 idle = start_from_dlist(proclist['_X5idlesS5dlist_S9processorS5dlink_S9processor___1'])
186 active = start_from_dlist(proclist['_X7activesS5dlist_S9processorS5dlink_S9processor___1'])
187 return ProcIter(active.cast(cfa_t.processor_ptr)), ProcIter(idle.cast(cfa_t.processor_ptr))
188
189def all_processors():
190 procs = []
191 for c in all_clusters():
192 active, idle = proc_list(c)
193 for p in active:
194 procs.append(p)
195
196 for p in idle:
197 procs.append(p)
198
199 print(procs)
200 return procs
201
202def tls_for_pthread(pthrd):
203 prev = gdb.selected_thread()
204 inf = gdb.selected_inferior()
205
206 thrd = inf.thread_from_thread_handle( pthrd )
207 thrd.switch()
208 tls = gdb.parse_and_eval('&_X9kernelTLSS16KernelThreadData_1')
209
210 prev.switch()
211 return tls
212
213def tls_for_proc(proc):
214 return proc['_X10local_dataPS16KernelThreadData_1']
215
216def thread_for_pthread(pthrd):
217 return tls_for_pthread(pthrd)['_X11this_threadVPS7thread$_1']
218
219def thread_for_proc(proc):
220 return tls_for_proc(proc)['_X11this_threadVPS7thread$_1']
221
222
223
224def find_curr_thread():
225 # btstr = gdb.execute('bt', to_string = True).splitlines()
226 # if len(btstr) == 0:
227 # print('error')
228 # return None
229 # return btstr[0].split('this=',1)[1].split(',')[0].split(')')[0]
230 return None
231
232def lookup_cluster(name = None):
233 """
234 Look up one or more cluster given a name
235 @name: str
236 Return: gdb.Value
237 """
238 if not is_cforall():
239 return None
240
241 clusters = all_clusters()
242 if not clusters:
243 return None
244
245 if not name:
246 return clusters.root
247
248 # lookup for the task associated with the id
249 found = [c for c in clusters if c['_X4namePKc_1'].string() == name]
250
251 if not found:
252 print("Cannot find a cluster with the name: {}.".format(name))
253 return None
254
255 return found
256
257
258def lookup_threads_by_cluster(cluster):
259 # Iterate through a circular linked list of threads and accumulate them in an array
260 threads = []
261
262 cfa_t = get_cfa_types()
263 root = cluster['_X7threadsS8__dllist_S7thread$__1']['_X4headPY15__TYPE_generic__1'].cast(cfa_t.thread_ptr)
264
265 if root == 0x0 or root.address == 0x0:
266 print('There are no tasks for cluster: {}'.format(cluster))
267 return threads
268
269 curr = root
270 tid = 0
271 sid = -1
272
273 while True:
274 t = ThreadInfo(cluster, curr)
275 if t.is_system():
276 t.tid = sid
277 sid -= 1
278 else:
279 t.tid = tid
280 tid += 1
281
282 threads.append(t)
283
284 curr = curr['node']['next']
285 if curr == root or curr == 0x0:
286 break
287
288 return threads
289
290def system_thread(thread):
291 return False
292
293def adjust_stack(pc, fp, sp):
294 # pop sp, fp, pc from global stack
295 gdb.execute('set $pc = {}'.format(pc))
296 gdb.execute('set $rbp = {}'.format(fp))
297 gdb.execute('set $sp = {}'.format(sp))
298
299############################ COMMAND IMPLEMENTATION #########################
300
301class Clusters(gdb.Command):
302 """Cforall: Display currently known clusters
303Usage:
304 info clusters : print out all the clusters
305"""
306
307 def __init__(self):
308 super(Clusters, self).__init__('info clusters', gdb.COMMAND_USER)
309
310 def print_cluster(self, cluster_name, cluster_address):
311 print('{:>20} {:>20}'.format(cluster_name, cluster_address))
312
313 #entry point from gdb
314 def invoke(self, arg, from_tty):
315 if not is_cforall():
316 return
317
318 if arg:
319 print("info clusters does not take arguments")
320 print_usage(self)
321 return
322
323 self.print_cluster('Name', 'Address')
324
325 for c in all_clusters():
326 self.print_cluster(c['_X4namePKc_1'].string(), str(c))
327
328 print("")
329
330############
331class Processors(gdb.Command):
332 """Cforall: Display currently known processors
333Usage:
334 info processors : print out all the processors
335 info processors <cluster_name> : print out all processors in a given cluster
336"""
337
338 def __init__(self):
339 super(Processors, self).__init__('info processors', gdb.COMMAND_USER)
340
341 def print_processor(self, processor, in_stats):
342 should_stop = processor['_X12do_terminateVb_1']
343 if not should_stop:
344 status = in_stats
345 else:
346 stop_count = processor['_X10terminatedS9semaphore_1']['_X5counti_1']
347 status_str = 'Last Thread' if stop_count >= 0 else 'Terminating'
348 status = '{}({},{})'.format(status_str, should_stop, stop_count)
349
350 print('{:>20} {:>11} {:<7} {:<}'.format(
351 processor['_X4namePKc_1'].string(),
352 status,
353 str(processor['_X18pending_preemptionb_1']),
354 str(processor)
355 ))
356 tls = tls_for_proc( processor )
357 thrd = thread_for_proc( processor )
358 if thrd != 0x0:
359 tname = '{} {}'.format(thrd['self_cor']['name'].string(), str(thrd))
360 else:
361 tname = None
362
363 print('{:>20} {}'.format('Thread', tname))
364 print('{:>20} {}'.format('TLS', tls))
365
366 #entry point from gdb
367 def invoke(self, arg, from_tty):
368 if not is_cforall():
369 return
370
371 if not arg:
372 clusters = all_clusters()
373 else:
374 clusters = [lookup_cluster(arg)]
375
376 if not clusters:
377 print("No Cluster matching arguments found")
378 return
379
380 print('{:>20} {:>11} {:<7} {}'.format('Processor', '', 'Pending', 'Object'))
381 print('{:>20} {:>11} {:<7} {}'.format('Name', 'Status', 'Yield', 'Address'))
382 for c in clusters:
383 print('Cluster {}'.format(c['_X4namePKc_1'].string()))
384
385 active, idle = proc_list(c)
386 # print the processor information
387 for p in active:
388 self.print_processor(p, 'Active')
389
390 for p in idle:
391 self.print_processor(p, 'Idle')
392
393 print()
394
395 print()
396
397############
398class Threads(gdb.Command):
399 """Cforall: Display currently known threads
400Usage:
401 cfathreads : print Main Cluster threads, application threads only
402 cfathreads all : print all clusters, all threads
403 cfathreads <clusterName> : print cluster threads, application threads only
404 """
405 def __init__(self):
406 # The first parameter of the line below is the name of the command. You
407 # can call it 'uc++ task'
408 super(Threads, self).__init__('info cfathreads', gdb.COMMAND_USER)
409
410 def print_formatted(self, marked, tid, name, state, address):
411 print('{:>1} {:>4} {:>20} {:>10} {:>20}'.format('*' if marked else ' ', tid, name, state, address))
412
413 def print_thread(self, thread, tid, marked):
414 cfa_t = get_cfa_types()
415 ys = str(thread['preempted'].cast(cfa_t.yield_state))
416 if ys == '_X15__NO_PREEMPTIONKM19__Preemption_Reason_1':
417 state = str(thread['state'].cast(cfa_t.thread_state))
418 elif ys == '_X18__ALARM_PREEMPTIONKM19__Preemption_Reason_1':
419 state = 'preempted'
420 elif ys == '_X19__MANUAL_PREEMPTIONKM19__Preemption_Reason_1':
421 state = 'yield'
422 elif ys == '_X17__POLL_PREEMPTIONKM19__Preemption_Reason_1':
423 state = 'poll'
424 else:
425 print("error: thread {} in undefined preemption state {}".format(thread, ys))
426 state = 'error'
427 self.print_formatted(marked, tid, thread['self_cor']['name'].string(), state, str(thread))
428
429 def print_threads_by_cluster(self, cluster, print_system = False):
430 # Iterate through a circular linked list of tasks and print out its
431 # name along with address associated to each cluster
432 threads = lookup_threads_by_cluster(cluster)
433 if not threads:
434 return
435
436 running_thread = find_curr_thread()
437 if running_thread is None:
438 print('Could not identify current thread')
439
440 self.print_formatted(False, '', 'Name', 'State', 'Address')
441
442 for t in threads:
443 if not t.is_system() or print_system:
444 self.print_thread(t.value, t.tid, t.value == running_thread if running_thread else False)
445
446 print()
447
448 def print_all_threads(self):
449 for c in all_clusters():
450 self.print_threads_by_cluster(c, False)
451
452 def invoke(self, arg, from_tty):
453 """
454 @arg: str
455 @from_tty: bool
456 """
457 if not is_cforall():
458 return
459
460 if not arg:
461 cluster = lookup_cluster()
462 if not cluster:
463 print("Could not find Main Cluster")
464 return
465
466 # only tasks and main
467 self.print_threads_by_cluster(cluster, False)
468
469 elif arg == 'all':
470 # all threads, all clusters
471 self.print_all_threads()
472
473 else:
474 cluster = lookup_cluster(arg)
475 if not cluster:
476 print("No matching cluster")
477 return
478
479 # all tasks, specified cluster
480 self.print_threads_by_cluster(cluster, True)
481
482
483############
484class Thread(gdb.Command):
485 """Cforall: Switch to specified user threads
486Usage:
487 cfathread <id> : switch stack to thread id on main cluster
488 cfathread 0x<address> : switch stack to thread on any cluster
489 cfathread <id> <clusterName> : switch stack to thread on specified cluster
490 """
491 def __init__(self):
492 # The first parameter of the line below is the name of the command. You
493 # can call it 'uc++ task'
494 super(Thread, self).__init__('cfathread', gdb.COMMAND_USER)
495
496 ############################ AUXILIARY FUNCTIONS #########################
497
498 def switchto(self, thread):
499 """Change to a new task by switching to a different stack and manually
500 adjusting sp, fp and pc
501 @task_address: str
502 2 supported format:
503 in hex format
504 <hex_address>: literal hexadecimal address
505 Ex: 0xffffff
506 in name of the pointer to the task
507 "task_name": pointer of the variable name of the cluster
508 Ex: T* s -> task_name = s
509 Return: gdb.value of the cluster's address
510 """
511 try:
512 if not gdb.lookup_symbol('__cfactx_switch'):
513 print('__cfactx_switch symbol is unavailable')
514 return
515 except:
516 print('here 3')
517
518 cfa_t = get_cfa_types()
519
520 state = thread['state'].cast(cfa_t.thread_state)
521 try:
522 if state == gdb.parse_and_eval('Halted'):
523 print('Cannot switch to a terminated thread')
524 return
525
526 if state == gdb.parse_and_eval('Start'):
527 print('Cannjot switch to a thread not yet run')
528 return
529 except:
530 print("here 2")
531 return
532
533
534 context = thread['context']
535
536
537
538 # must be at frame 0 to set pc register
539 gdb.execute('select-frame 0')
540 if gdb.selected_frame().architecture().name() != 'i386:x86-64':
541 print('gdb debugging only supported for i386:x86-64 for now')
542 return
543
544 # gdb seems to handle things much better if we pretend we just entered the context switch
545 # pretend the pc is __cfactx_switch and adjust the sp, base pointer doesn't need to change
546 # lookup for sp,fp and uSwitch
547 xsp = context['SP'] + 40 # 40 = 5 64bit registers : %r15, %r14, %r13, %r12, %rbx WARNING: x64 specific
548 xfp = context['FP']
549
550 # convert string so we can strip out the address
551 try:
552 xpc = get_addr(gdb.parse_and_eval('__cfactx_switch').address)
553 except:
554 print("here")
555 return
556
557 # push sp, fp, pc into a global stack
558 global STACK
559 sp = gdb.parse_and_eval('$sp')
560 fp = gdb.parse_and_eval('$fp')
561 pc = gdb.parse_and_eval('$pc')
562 stack_info = StackInfo(sp = sp, fp = fp, pc = pc)
563 STACK.append(stack_info)
564
565 # update registers for new task
566 # print('switching to {} ({}) : [{}, {}, {}]'.format(thread['self_cor']['name'].string(), str(thread), str(xsp), str(xfp), str(xpc)))
567 print('switching to thread {} ({})'.format(str(thread), thread['self_cor']['name'].string()))
568 gdb.execute('set $rsp={}'.format(xsp))
569 gdb.execute('set $rbp={}'.format(xfp))
570 gdb.execute('set $pc={}'.format(xpc))
571
572 def find_matching_gdb_thread_id():
573 """
574 Parse the str from info thread to get the number
575 """
576 info_thread_str = gdb.execute('info thread', to_string=True).splitlines()
577 for thread_str in info_thread_str:
578 if thread_str.find('this={}'.format(task)) != -1:
579 thread_id_pattern = r'^\*?\s+(\d+)\s+Thread'
580 # retrive gdb thread id
581 return re.match(thread_id_pattern, thread_str).group(1)
582
583 # check if the task is running or not
584 if task_state == gdb.parse_and_eval('uBaseTask::Running'):
585 # find the equivalent thread from info thread
586 gdb_thread_id = find_matching_gdb_thread_id()
587 if gdb_thread_id is None:
588 print('cannot find the thread id to switch to')
589 return
590 # switch to that thread based using thread command
591 gdb.execute('thread {}'.format(gdb_thread_id))
592
593 def switchto_id(self, tid, cluster):
594 """
595 @cluster: cluster object
596 @tid: int
597 """
598 threads = lookup_threads_by_cluster( cluster )
599
600 for t in threads:
601 if t.tid == tid:
602 self.switchto(t.value)
603 return
604
605 print("Cound not find thread by id '{}'".format(tid))
606
607 def invoke(self, arg, from_tty):
608 """
609 @arg: str
610 @from_tty: bool
611 """
612 if not is_cforall():
613 return
614
615 argv = parse(arg)
616 if argv[0].isdigit():
617 cname = " ".join(argv[1:]) if len(argv) > 1 else None
618 cluster = lookup_cluster(cname)
619 if not cluster:
620 print("Could not find cluster '{}'".format(cname if cname else "Main Cluster"))
621 return
622
623 try:
624 tid = int(argv[0])
625 except:
626 print("'{}' not a valid thread id".format(argv[0]))
627 print_usage(self)
628 return
629
630 # by id, userCluster
631 self.switchto_id(tid, cluster)
632
633 elif argv[0].startswith('0x') or argv[0].startswith('0X'):
634 self.switchto(argv[0]) # by address, any cluster
635
636############
637class PrevThread(gdb.Command):
638 """Switch back to previous task on the stack"""
639 usage_msg = 'prevtask'
640
641 def __init__(self):
642 super(PrevThread, self).__init__('prevtask', gdb.COMMAND_USER)
643
644 def invoke(self, arg, from_tty):
645 """
646 @arg: str
647 @from_tty: bool
648 """
649 global STACK
650 if len(STACK) != 0:
651 # must be at frame 0 to set pc register
652 gdb.execute('select-frame 0')
653
654 # pop stack
655 stack_info = STACK.pop()
656 pc = get_addr(stack_info.pc)
657 sp = stack_info.sp
658 fp = stack_info.fp
659
660 # pop sp, fp, pc from global stack
661 adjust_stack(pc, fp, sp)
662
663 # must be at C++ frame to access C++ vars
664 gdb.execute('frame 1')
665 else:
666 print('empty stack')
667
668class ResetOriginFrame(gdb.Command):
669 """Reset to the origin frame prior to continue execution again"""
670 usage_msg = 'resetOriginFrame'
671 def __init__(self):
672 super(ResetOriginFrame, self).__init__('reset', gdb.COMMAND_USER)
673
674 def invoke(self, arg, from_tty):
675 """
676 @arg: str
677 @from_tty: bool
678 """
679 global STACK
680 if len(STACK) != 0:
681 stack_info = STACK.pop(0)
682 STACK.clear()
683 pc = get_addr(stack_info.pc)
684 sp = stack_info.sp
685 fp = stack_info.fp
686
687 # pop sp, fp, pc from global stack
688 adjust_stack(pc, fp, sp)
689
690 # must be at C++ frame to access C++ vars
691 gdb.execute('frame 1')
692 #else:
693 #print('reset: empty stack') #probably does not have to print msg
694
695Clusters()
696Processors()
697ResetOriginFrame()
698PrevThread()
699Threads()
700Thread()
701
702# Local Variables: #
703# mode: Python #
704# End: #
Note: See TracBrowser for help on using the repository browser.