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

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

Updating gdb tools to latest invoke.h changes

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