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

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

Attempt to make gdb utils more robust by pretending threads just entered the context switch.

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