source: tools/gdb/utils-gdb.py@ 428adbc

ADT ast-experimental pthread-emulation
Last change on this file since 428adbc was 9dad5b3, checked in by Thierry Delisle <tdelisle@…>, 3 years ago

Fixed gdb printing of processors which was broken for a while now.

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