source: tools/gdb/utils-gdb.py @ 19a8c40

ADTast-experimental
Last change on this file since 19a8c40 was 2e94d94f, checked in by Thierry Delisle <tdelisle@…>, 2 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.