source: tools/gdb/utils-gdb.py @ 72bd9cd

ADTast-experimentalenumforall-pointer-decaypthread-emulationqualifiedEnum
Last change on this file since 72bd9cd was b7d94ac5, checked in by Thierry Delisle <tdelisle@…>, 3 years ago

Last step tools and benchmark

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