source: tools/gdb/utils-gdb.py @ 20be782

ADTast-experimentalpthread-emulation
Last change on this file since 20be782 was 9dad5b3, checked in by Thierry Delisle <tdelisle@…>, 2 years ago

Fixed gdb printing of processors which was broken for a while now.

  • Property mode set to 100644
File size: 18.3 KB
Line 
1#
2# Copyright (C) Lynn Tran, Jiachen Zhang 2018
3#
4# utils-gdb.py --
5#
6# Author           : Lynn Tran
7# Created On       : Mon Oct 1 22:06:09 2018
8# Last Modified By : Peter A. Buhr
9# Last Modified On : Sat Jan 19 14:16:10 2019
10# Update Count     : 11
11#
12
13"""
14To run this extension, the python name has to be as same as one of the loaded library
15Additionally, the file must exist in a folder which is in gdb's safe path
16"""
17import collections
18import gdb
19import re
20
21# set these signal handlers with some settings (nostop, noprint, pass)
22gdb.execute('handle SIGALRM nostop noprint pass')
23gdb.execute('handle SIGUSR1 nostop noprint pass')
24
25CfaTypes = collections.namedtuple('CfaTypes', 'cluster_ptr processor_ptr thread_ptr int_ptr thread_state yield_state')
26
27class ThreadInfo:
28        tid = 0
29        cluster = None
30        value = None
31
32        def __init__(self, cluster, value):
33                self.cluster = cluster
34                self.value = value
35
36        def is_system(self):
37                return False
38
39# A named tuple representing information about a stack
40StackInfo = collections.namedtuple('StackInfo', 'sp fp pc')
41
42# A global variable to keep track of stack information as one switches from one
43# task to another task
44STACK = []
45
46not_supported_error_msg = "Not a supported command for this language"
47
48def is_cforall():
49        return True
50
51def get_cfa_types():
52        # GDB types for various structures/types in CFA
53        return CfaTypes(cluster_ptr = gdb.lookup_type('struct cluster').pointer(),
54                processor_ptr = gdb.lookup_type('struct processor').pointer(),
55                thread_ptr = gdb.lookup_type('struct thread$').pointer(),
56                int_ptr = gdb.lookup_type('int').pointer(),
57                thread_state = gdb.lookup_type('enum __Coroutine_State'),
58                yield_state = gdb.lookup_type('enum __Preemption_Reason'))
59
60def get_addr(addr):
61        """
62        NOTE: sketchy solution to retrieve address. There is a better solution...
63        @addr: str of an address that can be in a format 0xfffff <type of the object
64        at this address>
65        Return: str of just the address
66        """
67        str_addr = str(addr)
68        ending_addr_index = str_addr.find('<')
69        if ending_addr_index == -1:
70                return str(addr)
71        return str_addr[:ending_addr_index].strip()
72
73def print_usage(obj):
74        print(obj.__doc__)
75
76def parse(args):
77        """
78        Split the argument list in string format, where each argument is separated
79        by whitespace delimiter, to a list of arguments like argv
80        @args: str of arguments
81        Return:
82                [] if args is an empty string
83                list if args is not empty
84        """
85        # parse the string format of arguments and return a list of arguments
86        argv = args.split(' ')
87        if len(argv) == 1 and argv[0] == '':
88                return []
89        return argv
90
91class ClusterIter:
92        def __init__(self, root):
93                self.curr = None
94                self.root = root
95
96        def __iter__(self):
97                return self
98
99        def __next__(self):
100                # Clusters form a cycle
101                # If we haven't seen the root yet, then the root is the first
102                if not self.curr:
103                        self.curr = self.root
104                        return self.curr
105
106                # if we already saw the root, then go forward
107                self.curr = self.curr['_X4nodeS26__cluster____dbg_node_cltr_1']['_X4nextPS7cluster_1']
108
109                # if we reached the root again, then we are done
110                if self.curr == self.root:
111                        raise StopIteration
112
113                # otherwise return the next
114                return self.curr
115
116def all_clusters():
117        """
118        Return: a list of all the clusters as an iterator.
119        obtained from gdb.Value of globalClusters.root (is an address)
120        """
121        if not is_cforall():
122                return []
123
124        cluster_root = gdb.parse_and_eval('_X11mainClusterPS7cluster_1')
125        if cluster_root.address == 0x0:
126                print('No clusters, program terminated')
127                return []
128
129        return ClusterIter(cluster_root)
130
131class ProcIter:
132        def __init__(self, root):
133                self.curr = None
134                self.root = root
135
136        def __iter__(self):
137                return self
138
139        def check(self):
140                # check if this is the last value
141                addr = int(self.curr)
142                mask = 1 << ((8 * int(gdb.parse_and_eval('sizeof(void*)'))) - 1)
143                if 0 != (mask & addr):
144                        raise StopIteration
145
146        def __next__(self):
147                cfa_t = get_cfa_types()
148
149                # Processors form a cycle
150                # If we haven't seen the root yet, then the root is the first
151                if not self.curr:
152                        my_next = self.root
153                        self.curr = my_next.cast(cfa_t.processor_ptr)
154
155                        #check if this is an empty list
156                        self.check()
157
158                        return self.curr
159
160                # if we already saw the root, then go forward
161                my_next = self.curr['__anonymous_object2225']['_X4nextPY13__tE_generic__1']
162                self.curr = my_next.cast(cfa_t.processor_ptr)
163
164                #check if we reached the end
165                self.check()
166
167                # otherwise return the next
168                return self.curr
169
170def proc_list(cluster):
171        """
172        Return: for a given processor, return the active and idle processors, as 2 iterators
173        """
174        cfa_t = get_cfa_types()
175        proclist = cluster['_X5procsS19__cluster_proc_list_1']
176        idle = proclist['_X5idlesS5dlist_S9processorS5dlink_S9processor___1']['__anonymous_object2167']['_X4nextPY13__tE_generic__1']
177        active = proclist['_X7activesS5dlist_S9processorS5dlink_S9processor___1']['__anonymous_object2167']['_X4nextPY13__tE_generic__1']
178        return ProcIter(active.cast(cfa_t.processor_ptr)), ProcIter(idle.cast(cfa_t.processor_ptr))
179
180def all_processors():
181        procs = []
182        for c in all_clusters():
183                active, idle = proc_list(c)
184                for p in active:
185                        procs.append(p)
186
187                for p in idle:
188                        procs.append(p)
189
190        print(procs)
191        return procs
192
193def tls_for_pthread(pthrd):
194        prev = gdb.selected_thread()
195        inf = gdb.selected_inferior()
196
197        thrd = inf.thread_from_thread_handle( pthrd )
198        thrd.switch()
199        tls = gdb.parse_and_eval('&_X9kernelTLSS16KernelThreadData_1')
200
201        prev.switch()
202        return tls
203
204def tls_for_proc(proc):
205        return proc['_X10local_dataPS16KernelThreadData_1']
206
207def thread_for_pthread(pthrd):
208        return tls_for_pthread(pthrd)['_X11this_threadVPS7thread$_1']
209
210def thread_for_proc(proc):
211        return tls_for_proc(proc)['_X11this_threadVPS7thread$_1']
212
213
214
215def find_curr_thread():
216        # btstr = gdb.execute('bt', to_string = True).splitlines()
217        # if len(btstr) == 0:
218        #     print('error')
219        #     return None
220        # return btstr[0].split('this=',1)[1].split(',')[0].split(')')[0]
221        return None
222
223def lookup_cluster(name = None):
224        """
225        Look up one or more cluster given a name
226        @name: str
227        Return: gdb.Value
228        """
229        if not is_cforall():
230                return None
231
232        clusters = all_clusters()
233        if not clusters:
234                return None
235
236        if not name:
237                return clusters.root
238
239        # lookup for the task associated with the id
240        found = [c for c in clusters if c['_X4namePKc_1'].string() == name]
241
242        if not found:
243                print("Cannot find a cluster with the name: {}.".format(name))
244                return None
245
246        return found
247
248
249def lookup_threads_by_cluster(cluster):
250                # Iterate through a circular linked list of threads and accumulate them in an array
251                threads = []
252
253                cfa_t = get_cfa_types()
254                root = cluster['_X7threadsS8__dllist_S7thread$__1']['_X4headPY15__TYPE_generic__1'].cast(cfa_t.thread_ptr)
255
256                if root == 0x0 or root.address == 0x0:
257                        print('There are no tasks for cluster: {}'.format(cluster))
258                        return threads
259
260                curr = root
261                tid = 0
262                sid = -1
263
264                while True:
265                        t = ThreadInfo(cluster, curr)
266                        if t.is_system():
267                                t.tid = sid
268                                sid -= 1
269                        else:
270                                t.tid = tid
271                                tid += 1
272
273                        threads.append(t)
274
275                        curr = curr['node']['next']
276                        if curr == root or curr == 0x0:
277                                break
278
279                return threads
280
281def system_thread(thread):
282        return False
283
284def adjust_stack(pc, fp, sp):
285        # pop sp, fp, pc from global stack
286        gdb.execute('set $pc = {}'.format(pc))
287        gdb.execute('set $rbp = {}'.format(fp))
288        gdb.execute('set $sp = {}'.format(sp))
289
290############################ COMMAND IMPLEMENTATION #########################
291
292class Clusters(gdb.Command):
293        """Cforall: Display currently known clusters
294Usage:
295        info clusters                 : print out all the clusters
296"""
297
298        def __init__(self):
299                super(Clusters, self).__init__('info clusters', gdb.COMMAND_USER)
300
301        def print_cluster(self, cluster_name, cluster_address):
302                print('{:>20}  {:>20}'.format(cluster_name, cluster_address))
303
304        #entry point from gdb
305        def invoke(self, arg, from_tty):
306                if not is_cforall():
307                        return
308
309                if arg:
310                        print("info clusters does not take arguments")
311                        print_usage(self)
312                        return
313
314                self.print_cluster('Name', 'Address')
315
316                for c in all_clusters():
317                        self.print_cluster(c['_X4namePKc_1'].string(), str(c))
318
319                print("")
320
321############
322class Processors(gdb.Command):
323        """Cforall: Display currently known processors
324Usage:
325        info processors                 : print out all the processors
326        info processors <cluster_name>  : print out all processors in a given cluster
327"""
328
329        def __init__(self):
330                super(Processors, self).__init__('info processors', gdb.COMMAND_USER)
331
332        def print_processor(self, processor, in_stats):
333                should_stop = processor['_X12do_terminateVb_1']
334                if not should_stop:
335                        status = in_stats
336                else:
337                        stop_count  = processor['_X10terminatedS9semaphore_1']['_X5counti_1']
338                        status_str  = 'Last Thread' if stop_count >= 0 else 'Terminating'
339                        status      = '{}({},{})'.format(status_str, should_stop, stop_count)
340
341                print('{:>20}  {:>11}  {:<7}  {:<}'.format(
342                        processor['_X4namePKc_1'].string(),
343                        status,
344                        str(processor['_X18pending_preemptionb_1']),
345                        str(processor)
346                ))
347                tls = tls_for_proc( processor )
348                thrd = tls['_X11this_threadVPS7thread$_1']
349                if thrd != 0x0:
350                        tname = '{} {}'.format(thrd['self_cor']['name'].string(), str(thrd))
351                else:
352                        tname = None
353
354                print('{:>20}  {}'.format('Thread', tname))
355                print('{:>20}  {}'.format('TLS', tls))
356
357        #entry point from gdb
358        def invoke(self, arg, from_tty):
359                if not is_cforall():
360                        return
361
362                if not arg:
363                        clusters = all_clusters()
364                else:
365                        clusters = [lookup_cluster(arg)]
366
367                if not clusters:
368                        print("No Cluster matching arguments found")
369                        return
370
371                print('{:>20}  {:>11}  {:<7}  {}'.format('Processor', '', 'Pending', 'Object'))
372                print('{:>20}  {:>11}  {:<7}  {}'.format('Name', 'Status', 'Yield', 'Address'))
373                for c in clusters:
374                        print('Cluster {}'.format(c['_X4namePKc_1'].string()))
375
376                        active, idle = proc_list(c)
377                        # print the processor information
378                        for p in active:
379                                self.print_processor(p, 'Active')
380
381                        for p in idle:
382                                self.print_processor(p, 'Idle')
383
384                        print()
385
386                print()
387
388############
389class Threads(gdb.Command):
390        """Cforall: Display currently known threads
391Usage:
392        cfathreads                           : print Main Cluster threads, application threads only
393        cfathreads all                       : print all clusters, all threads
394        cfathreads <clusterName>             : print cluster threads, application threads only
395        """
396        def __init__(self):
397                # The first parameter of the line below is the name of the command. You
398                # can call it 'uc++ task'
399                super(Threads, self).__init__('info cfathreads', gdb.COMMAND_USER)
400
401        def print_formatted(self, marked, tid, name, state, address):
402                print('{:>1}  {:>4}  {:>20}  {:>10}  {:>20}'.format('*' if marked else ' ', tid, name, state, address))
403
404        def print_thread(self, thread, tid, marked):
405                cfa_t = get_cfa_types()
406                ys = str(thread['preempted'].cast(cfa_t.yield_state))
407                if ys == '_X15__NO_PREEMPTIONKM19__Preemption_Reason_1':
408                        state = str(thread['state'].cast(cfa_t.thread_state))
409                elif ys == '_X18__ALARM_PREEMPTIONKM19__Preemption_Reason_1':
410                        state = 'preempted'
411                elif ys == '_X19__MANUAL_PREEMPTIONKM19__Preemption_Reason_1':
412                        state = 'yield'
413                elif ys == '_X17__POLL_PREEMPTIONKM19__Preemption_Reason_1':
414                        state = 'poll'
415                else:
416                        print("error: thread {} in undefined preemption state {}".format(thread, ys))
417                        state = 'error'
418                self.print_formatted(marked, tid, thread['self_cor']['name'].string(), state, str(thread))
419
420        def print_threads_by_cluster(self, cluster, print_system = False):
421                # Iterate through a circular linked list of tasks and print out its
422                # name along with address associated to each cluster
423                threads = lookup_threads_by_cluster(cluster)
424                if not threads:
425                        return
426
427                running_thread = find_curr_thread()
428                if running_thread is None:
429                        print('Could not identify current thread')
430
431                self.print_formatted(False, '', 'Name', 'State', 'Address')
432
433                for t in threads:
434                        if not t.is_system() or print_system:
435                                self.print_thread(t.value, t.tid, t.value == running_thread if running_thread else False)
436
437                print()
438
439        def print_all_threads(self):
440                for c in all_clusters():
441                        self.print_threads_by_cluster(c, False)
442
443        def invoke(self, arg, from_tty):
444                """
445                @arg: str
446                @from_tty: bool
447                """
448                if not is_cforall():
449                        return
450
451                if not arg:
452                        cluster = lookup_cluster()
453                        if not cluster:
454                                print("Could not find Main Cluster")
455                                return
456
457                        # only tasks and main
458                        self.print_threads_by_cluster(cluster, False)
459
460                elif arg == 'all':
461                        # all threads, all clusters
462                        self.print_all_threads()
463
464                else:
465                        cluster = lookup_cluster(arg)
466                        if not cluster:
467                                print("No matching cluster")
468                                return
469
470                        # all tasks, specified cluster
471                        self.print_threads_by_cluster(cluster, True)
472
473
474############
475class Thread(gdb.Command):
476        """Cforall: Switch to specified user threads
477Usage:
478        cfathread <id>                       : switch stack to thread id on main cluster
479        cfathread 0x<address>                : switch stack to thread on any cluster
480        cfathread <id> <clusterName>         : switch stack to thread on specified cluster
481        """
482        def __init__(self):
483                # The first parameter of the line below is the name of the command. You
484                # can call it 'uc++ task'
485                super(Thread, self).__init__('cfathread', gdb.COMMAND_USER)
486
487        ############################ AUXILIARY FUNCTIONS #########################
488
489        def switchto(self, thread):
490                """Change to a new task by switching to a different stack and manually
491                adjusting sp, fp and pc
492                @task_address: str
493                        2 supported format:
494                                in hex format
495                                        <hex_address>: literal hexadecimal address
496                                        Ex: 0xffffff
497                                in name of the pointer to the task
498                                        "task_name": pointer of the variable name of the cluster
499                                                Ex: T* s -> task_name = s
500                        Return: gdb.value of the cluster's address
501                """
502                try:
503                        if not gdb.lookup_symbol('__cfactx_switch'):
504                                print('__cfactx_switch symbol is unavailable')
505                                return
506                except:
507                        print('here 3')
508
509                cfa_t = get_cfa_types()
510
511                state = thread['state'].cast(cfa_t.thread_state)
512                try:
513                        if state == gdb.parse_and_eval('Halted'):
514                                print('Cannot switch to a terminated thread')
515                                return
516
517                        if state == gdb.parse_and_eval('Start'):
518                                print('Cannjot switch to a thread not yet run')
519                                return
520                except:
521                        print("here 2")
522                        return
523
524
525                context = thread['context']
526
527
528
529                # must be at frame 0 to set pc register
530                gdb.execute('select-frame 0')
531                if gdb.selected_frame().architecture().name() != 'i386:x86-64':
532                        print('gdb debugging only supported for i386:x86-64 for now')
533                        return
534
535                # gdb seems to handle things much better if we pretend we just entered the context switch
536                # pretend the pc is __cfactx_switch and adjust the sp, base pointer doesn't need to change
537                # lookup for sp,fp and uSwitch
538                xsp = context['SP'] + 40 # 40 = 5 64bit registers : %r15, %r14, %r13, %r12, %rbx WARNING: x64 specific
539                xfp = context['FP']
540
541                # convert string so we can strip out the address
542                try:
543                        xpc = get_addr(gdb.parse_and_eval('__cfactx_switch').address)
544                except:
545                        print("here")
546                        return
547
548                # push sp, fp, pc into a global stack
549                global STACK
550                sp = gdb.parse_and_eval('$sp')
551                fp = gdb.parse_and_eval('$fp')
552                pc = gdb.parse_and_eval('$pc')
553                stack_info = StackInfo(sp = sp, fp = fp, pc = pc)
554                STACK.append(stack_info)
555
556                # update registers for new task
557                # print('switching to {} ({}) : [{}, {}, {}]'.format(thread['self_cor']['name'].string(), str(thread), str(xsp), str(xfp), str(xpc)))
558                print('switching to thread {} ({})'.format(str(thread), thread['self_cor']['name'].string()))
559                gdb.execute('set $rsp={}'.format(xsp))
560                gdb.execute('set $rbp={}'.format(xfp))
561                gdb.execute('set $pc={}'.format(xpc))
562
563        def find_matching_gdb_thread_id():
564                """
565                Parse the str from info thread to get the number
566                """
567                info_thread_str = gdb.execute('info thread', to_string=True).splitlines()
568                for thread_str in info_thread_str:
569                        if thread_str.find('this={}'.format(task)) != -1:
570                                thread_id_pattern = r'^\*?\s+(\d+)\s+Thread'
571                                # retrive gdb thread id
572                                return re.match(thread_id_pattern, thread_str).group(1)
573
574                        # check if the task is running or not
575                        if task_state == gdb.parse_and_eval('uBaseTask::Running'):
576                                # find the equivalent thread from info thread
577                                gdb_thread_id = find_matching_gdb_thread_id()
578                                if gdb_thread_id is None:
579                                        print('cannot find the thread id to switch to')
580                                        return
581                                # switch to that thread based using thread command
582                                gdb.execute('thread {}'.format(gdb_thread_id))
583
584        def switchto_id(self, tid, cluster):
585                """
586                @cluster: cluster object
587                @tid: int
588                """
589                threads = lookup_threads_by_cluster( cluster )
590
591                for t in threads:
592                        if t.tid == tid:
593                                self.switchto(t.value)
594                                return
595
596                print("Cound not find thread by id '{}'".format(tid))
597
598        def invoke(self, arg, from_tty):
599                """
600                @arg: str
601                @from_tty: bool
602                """
603                if not is_cforall():
604                        return
605
606                argv = parse(arg)
607                if argv[0].isdigit():
608                        cname = " ".join(argv[1:]) if len(argv) > 1 else None
609                        cluster = lookup_cluster(cname)
610                        if not cluster:
611                                print("Could not find cluster '{}'".format(cname if cname else "Main Cluster"))
612                                return
613
614                        try:
615                                tid = int(argv[0])
616                        except:
617                                print("'{}' not a valid thread id".format(argv[0]))
618                                print_usage(self)
619                                return
620
621                                # by id, userCluster
622                        self.switchto_id(tid, cluster)
623
624                elif argv[0].startswith('0x') or argv[0].startswith('0X'):
625                        self.switchto(argv[0]) # by address, any cluster
626
627############
628class PrevThread(gdb.Command):
629        """Switch back to previous task on the stack"""
630        usage_msg = 'prevtask'
631
632        def __init__(self):
633                super(PrevThread, self).__init__('prevtask', gdb.COMMAND_USER)
634
635        def invoke(self, arg, from_tty):
636                """
637                @arg: str
638                @from_tty: bool
639                """
640                global STACK
641                if len(STACK) != 0:
642                        # must be at frame 0 to set pc register
643                        gdb.execute('select-frame 0')
644
645                        # pop stack
646                        stack_info = STACK.pop()
647                        pc = get_addr(stack_info.pc)
648                        sp = stack_info.sp
649                        fp = stack_info.fp
650
651                        # pop sp, fp, pc from global stack
652                        adjust_stack(pc, fp, sp)
653
654                        # must be at C++ frame to access C++ vars
655                        gdb.execute('frame 1')
656                else:
657                        print('empty stack')
658
659class ResetOriginFrame(gdb.Command):
660        """Reset to the origin frame prior to continue execution again"""
661        usage_msg = 'resetOriginFrame'
662        def __init__(self):
663                super(ResetOriginFrame, self).__init__('reset', gdb.COMMAND_USER)
664
665        def invoke(self, arg, from_tty):
666                """
667                @arg: str
668                @from_tty: bool
669                """
670                global STACK
671                if len(STACK) != 0:
672                        stack_info = STACK.pop(0)
673                        STACK.clear()
674                        pc = get_addr(stack_info.pc)
675                        sp = stack_info.sp
676                        fp = stack_info.fp
677
678                        # pop sp, fp, pc from global stack
679                        adjust_stack(pc, fp, sp)
680
681                        # must be at C++ frame to access C++ vars
682                        gdb.execute('frame 1')
683                #else:
684                        #print('reset: empty stack') #probably does not have to print msg
685
686Clusters()
687Processors()
688ResetOriginFrame()
689PrevThread()
690Threads()
691Thread()
692
693# Local Variables: #
694# mode: Python #
695# End: #
Note: See TracBrowser for help on using the repository browser.