source: tools/gdb/utils-gdb.py @ 1cfdee9

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since 1cfdee9 was 1cfdee9, checked in by Thierry Delisle <tdelisle@…>, 4 years ago

First draft changes to gdb support

  • Property mode set to 100644
File size: 22.5 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')
26
27class Thread:
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
46# A global variable to keep all system task name
47SysTask_Name = ["uLocalDebuggerReader", "uLocalDebugger", "uProcessorTask", "uBootTask", "uSystemTask",
48"uProcessorTask", "uPthread", "uProfiler"]
49
50not_supported_error_msg = "Not a supported command for this language"
51
52def is_cforall():
53    return True
54
55def get_cfa_types():
56    # GDB types for various structures/types in CFA
57    return CfaTypes(cluster_ptr = gdb.lookup_type('struct cluster').pointer(),
58                  processor_ptr = gdb.lookup_type('struct processor').pointer(),
59                     thread_ptr = gdb.lookup_type('struct $thread').pointer(),
60                        int_ptr = gdb.lookup_type('int').pointer(),
61                   thread_state = gdb.lookup_type('enum coroutine_state'))
62
63def get_addr(addr):
64    """
65    NOTE: sketchy solution to retrieve address. There is a better solution...
66    @addr: str of an address that can be in a format 0xfffff <type of the object
67    at this address>
68    Return: str of just the address
69    """
70    str_addr = str(addr)
71    ending_addr_index = str_addr.find('<')
72    if ending_addr_index == -1:
73        return str(addr)
74    return str_addr[:ending_addr_index].strip()
75
76def print_usage(msg):
77    """
78    Print out usage message
79    @msg: str
80    """
81    print('Usage: ' + msg)
82
83def parse(args):
84    """
85    Split the argument list in string format, where each argument is separated
86    by whitespace delimiter, to a list of arguments like argv
87    @args: str of arguments
88    Return:
89        [] if args is an empty string
90        list if args is not empty
91    """
92    # parse the string format of arguments and return a list of arguments
93    argv = args.split(' ')
94    if len(argv) == 1 and argv[0] == '':
95        return []
96    return argv
97
98def get_cluster_root():
99    """
100    Return: gdb.Value of globalClusters.root (is an address)
101    """
102    cluster_root = gdb.parse_and_eval('_X11mainClusterPS7cluster_1')
103    if cluster_root.address == 0x0:
104        print('No clusters, program terminated')
105    return cluster_root
106
107def lookup_cluster(name = None):
108    """
109    Look up a cluster given its ID
110    @name: str
111    Return: gdb.Value
112    """
113    if not is_cforall():
114        return None
115
116    root = get_cluster_root()
117    if root.address == 0x0:
118        return None
119
120    if not name:
121        return root
122
123    # lookup for the task associated with the id
124    cluster = None
125    curr = root
126    while True:
127        if curr['_X4namePKc_1'].string() == name:
128            cluster = curr.address
129            break
130        curr = curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
131        if curr == root or curr == 0x0:
132            break
133
134    if not cluster:
135        print("Cannot find a cluster with the name: {}.".format(name))
136        return None
137
138    return cluster
139
140def system_thread(thread):
141    return False
142
143def adjust_stack(pc, fp, sp):
144    # pop sp, fp, pc from global stack
145    gdb.execute('set $pc = {}'.format(pc))
146    gdb.execute('set $rbp = {}'.format(fp))
147    gdb.execute('set $sp = {}'.format(sp))
148
149############################ COMMAND IMPLEMENTATION #########################
150
151class Clusters(gdb.Command):
152    """Print out the list of available clusters"""
153    usage_msg = 'clusters'
154    str_format = ''
155
156    def __init__(self):
157        super(Clusters, self).__init__('clusters', gdb.COMMAND_USER)
158
159    def print_cluster(self, cluster_name, cluster_address):
160        print('{:>20}  {:>20}'.format(cluster_name, cluster_address))
161
162    def invoke(self, arg, from_tty):
163        """
164        Iterate through a circular linked list of clusters and print out its
165        name along with address associated to each cluster
166        @arg: str
167        @from_tty: bool
168        """
169        if not is_cforall():
170            return
171
172        argv = parse(arg)
173        if len(argv) != 0:
174            print_usage('clusters')
175            return
176
177        cluster_root = get_cluster_root()
178        if cluster_root.address == 0x0:
179            return
180
181        curr = cluster_root
182        self.print_cluster('Name', 'Address')
183
184        while True:
185            self.print_cluster(curr['_X4namePKc_1'].string(), str(curr))
186            curr = curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
187            if curr == cluster_root:
188                break
189
190        print("")
191
192############
193class Processors(gdb.Command):
194    """Display a list of all info about all available processors on a particular cluster"""
195    usage_msg = """
196    processors                 : print out all the processors in the Main Cluster
197    processors <cluster_name>  : print out all processors in a given cluster"""
198
199    def __init__(self):
200        super(Processors, self).__init__('processors', gdb.COMMAND_USER)
201
202    def print_processor(self, name, status, pending, address):
203        print('{:>20}  {:>20}  {:>13}  {:>20}'.format(name, status, pending, address))
204
205    def iterate_procs(self, root):
206        if root == 0x0:
207            print('{:>20}'.format("None"))
208            return
209
210        cfa_t = get_cfa_types()
211
212        self.print_processor('Name', 'Termination Status', 'Yield Pending', 'Address')
213        curr = root
214
215        while True:
216            processor = curr
217            should_stop = processor['_X12do_terminateVb_1']
218            stop_count  = processor['_X10terminatedS9semaphore_1']['_X5counti_1']
219            status_str  = 'Running' if not should_stop else 'Last Thread' if stop_count >= 0 else 'Terminating'
220            status      = '{}({},{})'.format(status_str, should_stop, stop_count)
221
222            self.print_processor(processor['_X4namePKc_1'].string(),
223                    status, str(processor['_X18pending_preemptionb_1']), str(processor)
224                )
225
226            curr = curr['_X4nodeS28__processor____dbg_node_proc_1']['_X4nextPS9processor_1']
227
228            if curr == root or curr == 0x0:
229                break
230
231        print()
232
233    def invoke(self, arg, from_tty):
234        """
235        Iterate through a circular linked list of tasks and print out all
236        info about each processor in that cluster
237        @arg: str
238        @from_tty: bool
239        """
240        if not is_cforall():
241            return
242
243        cfa_t = get_cfa_types()
244
245        argv = parse(arg)
246        if len(argv) > 1:
247            print_usage(self.usage_msg)
248            return
249
250        cluster = lookup_cluster(argv[0] if len(argv) > 0 else None)
251
252        if cluster == 0x0 or cluster == None:
253            print("No Cluster matching arguments found")
254            return
255
256        print('Cluster {}({})'.format(cluster['_X4namePKc_1'].string(), cluster.cast(cfa_t.cluster_ptr)))
257
258        active_root = cluster.cast(cfa_t.cluster_ptr) \
259                ['_X5procsS8__dllist_S9processor__1'] \
260                ['_X4headPY15__TYPE_generic__1'] \
261                .cast(cfa_t.processor_ptr)
262
263        idle_root = cluster.cast(cfa_t.cluster_ptr) \
264                ['_X5idlesS8__dllist_S9processor__1'] \
265                ['_X4headPY15__TYPE_generic__1'] \
266                .cast(cfa_t.processor_ptr)
267
268        print("Active Processors")
269        self.iterate_procs(active_root)
270
271        print("\nIdle Processors")
272        self.iterate_procs(idle_root)
273
274############
275class Threads(gdb.Command):
276    def __init__(self):
277        # The first parameter of the line below is the name of the command. You
278        # can call it 'uc++ task'
279        super(Threads, self).__init__('cfathread', gdb.COMMAND_USER)
280
281    def print_usage(self):
282        print_usage("""
283    cfathread                            : print userCluster tasks, application tasks only
284    cfathread <clusterName>              : print cluster tasks, application tasks only
285    cfathread all                        : print all clusters, all tasks
286    cfathread <id>                       : switch stack to thread id on userCluster
287    cfathread 0x<address>                    : switch stack to thread on any cluster
288    cfathread <id> <clusterName>         : switch stack to thread on specified cluster
289    """)
290
291    ############################ AUXILIARY FUNCTIONS #########################
292
293    def print_formatted(self, marked, tid, name, state, address):
294        print('{:>1}  {:>4}  {:>20}  {:>10}  {:>20}'.format('*' if marked else ' ', tid, name, state, address))
295
296    def print_thread(self, thread, tid, marked):
297        cfa_t = get_cfa_types()
298        self.print_formatted(marked, tid, thread['self_cor']['name'].string(), str(thread['state'].cast(cfa_t.thread_state)), str(thread))
299
300    def print_formatted_cluster(self, str_format, cluster_name, cluster_addr):
301        print(str_format.format(cluster_name, cluster_addr))
302
303    def find_curr_thread(self):
304        # btstr = gdb.execute('bt', to_string = True).splitlines()
305        # if len(btstr) == 0:
306        #     print('error')
307        #     return None
308        # return btstr[0].split('this=',1)[1].split(',')[0].split(')')[0]
309        return None
310
311    def print_tasks_by_cluster_all(self, cluster_address):
312        """
313        Display a list of all info about all available tasks on a particular cluster
314        @cluster_address: gdb.Value
315        """
316        cluster_address = cluster_address.cast(uCPPTypes.ucluster_ptr)
317        task_root = cluster_address['tasksOnCluster']['root']
318
319        if task_root == 0x0 or task_root.address == 0x0:
320            print('There are no tasks for cluster at address: {}'.format(cluster_address))
321            return
322
323        self.print_formatted_task('', 'Task Name', 'Address', 'State')
324        curr = task_root
325        task_id = 0
326        systask_id = -1
327
328        breakpoint_addr = self.find_curr_breakpoint_addr()
329        if breakpoint_addr is None:
330            return
331
332        while True:
333            global SysTask_Name
334            if (curr['task_']['name'].string() in SysTask_Name):
335                self.print_formatted_tasks(systask_id, breakpoint_addr, curr)
336                systask_id -= 1
337            else:
338                self.print_formatted_tasks(task_id, breakpoint_addr, curr)
339                task_id += 1
340
341            curr = curr['next'].cast(uCPPTypes.uBaseTaskDL_ptr_type)
342            if curr == task_root:
343                break
344
345    def print_tasks_by_cluster_address_all(self, cluster_address):
346        """
347        Display a list of all info about all available tasks on a particular cluster
348        @cluster_address: str
349        """
350        # Iterate through a circular linked list of tasks and print out its
351        # name along with address associated to each cluster
352
353        # convert hex string to hex number
354        try:
355            hex_addr = int(cluster_address, 16)
356        except:
357            self.print_usage()
358            return
359
360        cluster_address = gdb.Value(hex_addr)
361        if not self.print_tasks_by_cluster_all(cluster_address):
362            return
363
364    def threads_by_cluster(self, cluster):
365        # Iterate through a circular linked list of threads and accumulate them in an array
366        threads = []
367
368        cfa_t = get_cfa_types()
369        root = cluster['_X7threadsS8__dllist_S7$thread__1']['_X4headPY15__TYPE_generic__1'].cast(cfa_t.thread_ptr)
370
371        if root == 0x0 or root.address == 0x0:
372            print('There are no tasks for cluster: {}'.format(cluster))
373            return threads
374
375        curr = root
376        tid = 0
377        sid = -1
378
379        while True:
380            t = Thread(cluster, curr)
381            if t.is_system():
382                t.tid = sid
383                sid -= 1
384            else:
385                t.tid = tid
386                tid += 1
387
388            threads.append(t)
389
390            curr = curr['node']['next']
391            if curr == root or curr == 0x0:
392                break
393
394        return threads
395
396    def print_threads_by_cluster(self, cluster, print_system = False):
397        """
398        Display a list of limited info about all available threads on a particular cluster
399        @cluster: str
400        @print_system: bool
401        """
402        # Iterate through a circular linked list of tasks and print out its
403        # name along with address associated to each cluster
404
405        threads = self.threads_by_cluster(cluster)
406        if not threads:
407            return
408
409        running_thread = self.find_curr_thread()
410        if running_thread is None:
411            print('Could not identify current thread')
412
413        self.print_formatted(False, '', 'Name', 'State', 'Address')
414
415        for t in threads:
416            if not t.is_system() or print_system:
417                self.print_thread(t.value, t.tid, tivalue == running_thread if running_thread else False)
418
419        print()
420
421    ############################ COMMAND FUNCTIONS #########################
422
423    def print_all_threads(self):
424        """Iterate through each cluster, iterate through all tasks and  print out info about all the tasks
425        in those clusters"""
426        uCPPTypes = None
427        try:
428            uCPPTypes = get_uCPP_types()
429        except gdb.error:
430            print(not_supported_error_msg)
431            print(gdb.error)
432            return
433
434        cluster_root = get_cluster_root()
435        if cluster_root.address == 0x0:
436            return
437
438        curr = cluster_root
439        self.print_formatted_cluster(self.cluster_str_format, 'Cluster Name', 'Address')
440
441        while True:
442            addr = str(curr['cluster_'].reference_value())[1:]
443            self.print_formatted_cluster(self.cluster_str_format, curr['cluster_']['name'].string(), addr)
444
445            self.print_tasks_by_cluster_address_all(addr)
446            curr = curr['next'].cast(uCPPTypes.uClusterDL_ptr_type)
447            if curr == cluster_root:
448                break
449
450    def switchto(self, thread):
451        """Change to a new task by switching to a different stack and manually
452        adjusting sp, fp and pc
453        @task_address: str
454            2 supported format:
455                in hex format
456                    <hex_address>: literal hexadecimal address
457                    Ex: 0xffffff
458                in name of the pointer to the task
459                    "task_name": pointer of the variable name of the cluster
460                        Ex: T* s -> task_name = s
461            Return: gdb.value of the cluster's address
462        """
463        # uCPPTypes = None
464        # try:
465        #     uCPPTypes = get_uCPP_types()
466        # except gdb.error:
467        #     print(not_supported_error_msg)
468        #     print(gdb.error)
469        #     return
470
471        # # Task address has a format "task_address", which implies that it is the
472        # # name of the variable, and it needs to be evaluated
473        # if task_address.startswith('"') and task_address.endswith('"'):
474        #     task = gdb.parse_and_eval(task_address.replace('"', ''))
475        # else:
476        # # Task address format does not include the quotation marks, which implies
477        # # that it is a hex address
478        #     # convert hex string to hex number
479        #     try:
480        #         hex_addr = int(task_address, 16)
481        #     except:
482        #         self.print_usage()
483        #         return
484        #     task_address = gdb.Value(hex_addr)
485        #     task = task_address.cast(uCPPTypes.uBaseTask_ptr_type)
486        try:
487            if not gdb.lookup_symbol('__cfactx_switch'):
488                print('__cfactx_switch symbol is unavailable')
489                return
490        except:
491            print('here 3')
492
493        cfa_t = get_cfa_types()
494
495        state = thread['state'].cast(cfa_t.thread_state)
496        try:
497            if state == gdb.parse_and_eval('Halted'):
498                print('Cannot switch to a terminated thread')
499                return
500
501            if state == gdb.parse_and_eval('Start'):
502                print('Cannjot switch to a thread not yet run')
503                return
504        except:
505            print("here 2")
506            return
507
508
509        context = thread['context']
510
511        # lookup for sp,fp and uSwitch
512        xsp = context['SP'] + 48
513        xfp = context['FP']
514
515        # convert string so we can strip out the address
516        try:
517            xpc = get_addr(gdb.parse_and_eval('__cfactx_switch').address + 28)
518        except:
519            print("here")
520            return
521
522        # must be at frame 0 to set pc register
523        gdb.execute('select-frame 0')
524
525        # push sp, fp, pc into a global stack
526        global STACK
527        sp = gdb.parse_and_eval('$sp')
528        fp = gdb.parse_and_eval('$fp')
529        pc = gdb.parse_and_eval('$pc')
530        stack_info = StackInfo(sp = sp, fp = fp, pc = pc)
531        STACK.append(stack_info)
532
533        # update registers for new task
534        print('switching to ')
535        gdb.execute('set $rsp={}'.format(xsp))
536        gdb.execute('set $rbp={}'.format(xfp))
537        gdb.execute('set $pc={}'.format(xpc))
538
539    def find_matching_gdb_thread_id():
540        """
541        Parse the str from info thread to get the number
542        """
543        info_thread_str = gdb.execute('info thread', to_string=True).splitlines()
544        for thread_str in info_thread_str:
545            if thread_str.find('this={}'.format(task)) != -1:
546                thread_id_pattern = r'^\*?\s+(\d+)\s+Thread'
547                # retrive gdb thread id
548                return re.match(thread_id_pattern, thread_str).group(1)
549
550            # check if the task is running or not
551            if task_state == gdb.parse_and_eval('uBaseTask::Running'):
552                # find the equivalent thread from info thread
553                gdb_thread_id = find_matching_gdb_thread_id()
554                if gdb_thread_id is None:
555                    print('cannot find the thread id to switch to')
556                    return
557                # switch to that thread based using thread command
558                gdb.execute('thread {}'.format(gdb_thread_id))
559
560    def switchto_id(self, tid, cluster):
561        """
562        @cluster: cluster object
563        @tid: int
564        """
565        threads = self.threads_by_cluster( cluster )
566
567        for t in threads:
568            if t.tid == tid:
569                self.switchto(t.value)
570                return
571
572        print("Cound not find thread by id '{}'".format(tid))
573
574    def invoke(self, arg, from_tty):
575        """
576        @arg: str
577        @from_tty: bool
578        """
579        if not is_cforall():
580            return
581
582        argv = parse(arg)
583        print(argv)
584        if len(argv) == 0:
585            """
586            Iterate only Main Thread, print only tasks and main
587            """
588            cluster = lookup_cluster()
589            if not cluster:
590                print("Could not find Main Cluster")
591                return
592
593            # only tasks and main
594            self.print_threads_by_cluster(cluster, False)
595
596        elif len(argv) == 1:
597            if argv[0] == 'help':
598                self.print_usage()
599            # push task
600            elif argv[0].isdigit():
601                cluster = lookup_cluster()
602                if not cluster:
603                    print("Could not find Main Cluster")
604                    return
605
606                try:
607                    tid = int(argv[0])
608                except:
609                    print("'{}' not a valid thread id".format(argv[0]))
610                    self.print_usage()
611                    return
612
613                 # by id, userCluster
614                self.switchto_id(tid, cluster)
615
616            elif argv[0].startswith('0x') or argv[0].startswith('0X'):
617                self.switchto(argv[0]) # by address, any cluster
618            # print tasks
619            elif argv[0] == 'all':
620                self.print_all_threads() # all tasks, all clusters
621            else:
622                """
623                Print out all the tasks available in the specified cluster
624                @cluster_name: str
625                """
626                print("cfathread by name")
627                cluster = lookup_cluster(argv[0])
628                if not cluster:
629                    return
630
631                # all tasks, specified cluster
632                self.print_threads_by_cluster(cluster, True)
633
634        elif len(argv) == 2:
635            # push task
636            self.pushtask_by_id(argv[0], argv[1]) # by id, specified cluster
637        else:
638            print('Invalid arguments')
639            self.print_usage()
640
641############
642class PrevThread(gdb.Command):
643    """Switch back to previous task on the stack"""
644    usage_msg = 'prevtask'
645
646    def __init__(self):
647        super(PrevThread, self).__init__('prevtask', gdb.COMMAND_USER)
648
649    def invoke(self, arg, from_tty):
650        """
651        @arg: str
652        @from_tty: bool
653        """
654        global STACK
655        if len(STACK) != 0:
656            # must be at frame 0 to set pc register
657            gdb.execute('select-frame 0')
658
659            # pop stack
660            stack_info = STACK.pop()
661            pc = get_addr(stack_info.pc)
662            sp = stack_info.sp
663            fp = stack_info.fp
664
665            # pop sp, fp, pc from global stack
666            adjust_stack(pc, fp, sp)
667
668            # must be at C++ frame to access C++ vars
669            gdb.execute('frame 1')
670        else:
671            print('empty stack')
672
673class ResetOriginFrame(gdb.Command):
674    """Reset to the origin frame prior to continue execution again"""
675    usage_msg = 'resetOriginFrame'
676    def __init__(self):
677        super(ResetOriginFrame, self).__init__('reset', gdb.COMMAND_USER)
678
679    def invoke(self, arg, from_tty):
680        """
681        @arg: str
682        @from_tty: bool
683        """
684        global STACK
685        if len(STACK) != 0:
686            stack_info = STACK.pop(0)
687            STACK.clear()
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('reset: empty stack') #probably does not have to print msg
699
700Clusters()
701Processors()
702ResetOriginFrame()
703PrevThread()
704Threads()
705
706# Local Variables: #
707# mode: Python #
708# End: #
Note: See TracBrowser for help on using the repository browser.