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

Last change on this file since ba068c0 was 262d4d51, checked in by Thierry Delisle <tdelisle@…>, 2 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.