3232except ImportError :
3333 has_ipython = False
3434
35+ MAX_DEREFERENCE_COUNT = 4 # the max number of times display_type should follow pointers
36+
3537
3638class Volshell (interfaces .plugins .PluginInterface ):
3739 """Shell environment to directly interact with a memory image."""
@@ -394,6 +396,28 @@ def disassemble(
394396 for i in disasm_types [architecture ].disasm (remaining_data , offset ):
395397 print (f"0x{ i .address :x} :\t { i .mnemonic } \t { i .op_str } " )
396398
399+ def _get_type_name_with_pointer (
400+ self ,
401+ member_type : Union [
402+ str , interfaces .objects .ObjectInterface , interfaces .objects .Template
403+ ],
404+ depth : int = 0 ,
405+ ) -> str :
406+ """Takes a member_type from and returns the subtype name with a * if the member_type is
407+ a pointer otherwise it returns just the normal type name."""
408+ pointer_marker = "*" * depth
409+ try :
410+ if member_type .vol .object_class == objects .Pointer :
411+ sub_member_type = member_type .vol .subtype
412+ # follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops
413+ if depth < MAX_DEREFERENCE_COUNT :
414+ return self ._get_type_name_with_pointer (sub_member_type , depth + 1 )
415+ except AttributeError :
416+ pass # not all objects get a `object_class`, and those that don't are not pointers.
417+ finally :
418+ member_type_name = pointer_marker + member_type .vol .type_name
419+ return member_type_name
420+
397421 def display_type (
398422 self ,
399423 object : Union [
@@ -402,6 +426,9 @@ def display_type(
402426 offset : Optional [int ] = None ,
403427 ):
404428 """Display Type describes the members of a particular object in alphabetical order"""
429+
430+ MAX_TYPENAME_DISPLAY_LENGTH = 256
431+
405432 if not isinstance (
406433 object ,
407434 (str , interfaces .objects .ObjectInterface , interfaces .objects .Template ),
@@ -426,75 +453,202 @@ def display_type(
426453 volobject .vol .type_name , layer_name = self .current_layer , offset = offset
427454 )
428455
429- if hasattr (volobject .vol , "size" ):
430- print (f"{ volobject .vol .type_name } ({ volobject .vol .size } bytes)" )
431- elif hasattr (volobject .vol , "data_format" ):
432- data_format = volobject .vol .data_format
433- print (
434- "{} ({} bytes, {} endian, {})" .format (
435- volobject .vol .type_name ,
436- data_format .length ,
437- data_format .byteorder ,
438- "signed" if data_format .signed else "unsigned" ,
439- )
440- )
456+ # add special case for pointer so that information about the struct the
457+ # pointer is pointing to is shown rather than simply the fact this is a
458+ # pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to
459+ # guard against loops
460+ dereference_count = 0
461+ while (
462+ isinstance (volobject , objects .Pointer )
463+ and dereference_count < MAX_DEREFERENCE_COUNT
464+ ):
465+ # before defreerencing the pointer, show it's information
466+ print (f'{ " " * dereference_count } { self ._display_simple_type (volobject )} ' )
467+
468+ # check that we can follow the pointer before dereferencing and do not
469+ # attempt to follow null pointers.
470+ if volobject .is_readable () and volobject != 0 :
471+ # now deference the pointer and store this as the new volobject
472+ volobject = volobject .dereference ()
473+ dereference_count = dereference_count + 1
474+ else :
475+ # if we aren't able to follow the pointers anymore then there will
476+ # be no more information to display as we've already printed the
477+ # details of this pointer including the fact that we're not able to
478+ # follow it anywhere
479+ return
441480
442481 if hasattr (volobject .vol , "members" ):
482+ # display the header for this object, if the orginal object was just a type string, display the type information
483+ struct_header = f'{ " " * dereference_count } { volobject .vol .type_name } ({ volobject .vol .size } bytes)'
484+ if isinstance (object , str ) and offset is None :
485+ suffix = ":"
486+ else :
487+ # this is an actual object or an offset was given so the offset should be displayed
488+ suffix = f" @ { hex (volobject .vol .offset )} :"
489+ print (struct_header + suffix )
490+
491+ # it is a more complex type, so all members also need information displayed
443492 longest_member = longest_offset = longest_typename = 0
444493 for member in volobject .vol .members :
445494 relative_offset , member_type = volobject .vol .members [member ]
446495 longest_member = max (len (member ), longest_member )
447496 longest_offset = max (len (hex (relative_offset )), longest_offset )
448- longest_typename = max (len (member_type .vol .type_name ), longest_typename )
497+ member_type_name = self ._get_type_name_with_pointer (
498+ member_type
499+ ) # special case for pointers to show what they point to
500+
501+ # find the longest typename
502+ longest_typename = max (len (member_type_name ), longest_typename )
503+
504+ # if the typename is very long then limit it to MAX_TYPENAME_DISPLAY_LENGTH
505+ longest_typename = min (longest_typename , MAX_TYPENAME_DISPLAY_LENGTH )
449506
450507 for member in sorted (
451508 volobject .vol .members , key = lambda x : (volobject .vol .members [x ][0 ], x )
452509 ):
453510 relative_offset , member_type = volobject .vol .members [member ]
454511 len_offset = len (hex (relative_offset ))
455512 len_member = len (member )
456- len_typename = len (member_type .vol .type_name )
513+
514+ member_type_name = self ._get_type_name_with_pointer (
515+ member_type
516+ ) # special case for pointers to show what they point to
517+ len_typename = len (member_type_name )
518+ if len (member_type_name ) > MAX_TYPENAME_DISPLAY_LENGTH :
519+ len_typename = MAX_TYPENAME_DISPLAY_LENGTH
520+ member_type_name = f"{ member_type_name [:len_typename - 3 ]} ..."
457521
458522 if isinstance (volobject , interfaces .objects .ObjectInterface ):
459523 # We're an instance, so also display the data
460524 try :
461525 value = self ._display_value (volobject .member (member ))
462526 except exceptions .InvalidAddressException :
463527 value = self ._display_value (renderers .NotAvailableValue ())
464-
465528 print (
529+ " " * dereference_count ,
466530 " " * (longest_offset - len_offset ),
467531 hex (relative_offset ),
468532 ": " ,
469533 member ,
470534 " " * (longest_member - len_member ),
471535 " " ,
472- member_type . vol . type_name ,
536+ member_type_name ,
473537 " " * (longest_typename - len_typename ),
474538 " " ,
475539 value ,
476540 )
477541 else :
542+ # not provided with an actual object, nor an offset so just display the types
478543 print (
544+ " " * dereference_count ,
479545 " " * (longest_offset - len_offset ),
480546 hex (relative_offset ),
481547 ": " ,
482548 member ,
483549 " " * (longest_member - len_member ),
484550 " " ,
485- member_type . vol . type_name ,
551+ member_type_name ,
486552 )
487553
488- @classmethod
489- def _display_value (cls , value : Any ) -> str :
490- if isinstance (value , interfaces .renderers .BaseAbsentValue ):
491- return "N/A"
492- elif isinstance (value , objects .PrimitiveObject ):
493- return repr (value )
494- elif isinstance (value , objects .Array ):
495- return repr ([cls ._display_value (val ) for val in value ])
554+ else : # simple type with no members, only one line to print
555+ # if the orginal object was just a type string, display the type information
556+ if isinstance (object , str ) and offset is None :
557+ print (self ._display_simple_type (volobject , include_value = False ))
558+
559+ # if the original object was an actual volobject or was a type string
560+ # with an offset. Then append the actual data to the display.
561+ else :
562+ print (" " * dereference_count , self ._display_simple_type (volobject ))
563+
564+ def _display_simple_type (
565+ self ,
566+ volobject : Union [
567+ interfaces .objects .ObjectInterface , interfaces .objects .Template
568+ ],
569+ include_value : bool = True ,
570+ ) -> str :
571+ # build the display_type_string based on the available information
572+
573+ if hasattr (volobject .vol , "size" ):
574+ # the most common type to display, this shows their full size, e.g.:
575+ # (layer_name) >>> dt('task_struct')
576+ # symbol_table_name1!task_struct (1784 bytes)
577+ display_type_string = (
578+ f"{ volobject .vol .type_name } ({ volobject .vol .size } bytes)"
579+ )
580+ elif hasattr (volobject .vol , "data_format" ):
581+ # this is useful for very simple types like ints, e.g.:
582+ # (layer_name) >>> dt('int')
583+ # symbol_table_name1!int (4 bytes, little endian, signed)
584+ data_format = volobject .vol .data_format
585+ display_type_string = "{} ({} bytes, {} endian, {})" .format (
586+ volobject .vol .type_name ,
587+ data_format .length ,
588+ data_format .byteorder ,
589+ "signed" if data_format .signed else "unsigned" ,
590+ )
591+ elif hasattr (volobject .vol , "type_name" ):
592+ # types like void have almost no values to display other than their name, e.g.:
593+ # (layer_name) >>> dt('void')
594+ # symbol_table_name1!void
595+ display_type_string = volobject .vol .type_name
596+ else :
597+ # it should not be possible to have a volobject without at least a type_name
598+ raise AttributeError ("Unable to find any details for object" )
599+
600+ if include_value : # if include_value is true also add the value to the display
601+ if isinstance (volobject , objects .Pointer ):
602+ # for pointers include the location of the pointer and where it points to
603+ return f"{ display_type_string } @ { hex (volobject .vol .offset )} -> { self ._display_value (volobject )} "
604+ else :
605+ return f"{ display_type_string } : { self ._display_value (volobject )} "
606+
496607 else :
497- return hex (value .vol .offset )
608+ return display_type_string
609+
610+ def _display_value (self , value : Any ) -> str :
611+ try :
612+ # if value is a BaseAbsentValue they display N/A
613+ if isinstance (value , interfaces .renderers .BaseAbsentValue ):
614+ return "N/A"
615+ else :
616+ # volobject branch
617+ if isinstance (
618+ value ,
619+ (interfaces .objects .ObjectInterface , interfaces .objects .Template ),
620+ ):
621+ if isinstance (value , objects .Pointer ):
622+ # show pointers in hex to match output for struct addrs
623+ # highlight null or unreadable pointers
624+ if value == 0 :
625+ suffix = " (null pointer)"
626+ elif not value .is_readable ():
627+ suffix = " (unreadable pointer)"
628+ else :
629+ suffix = ""
630+ return f"{ hex (value )} { suffix } "
631+ elif isinstance (value , objects .PrimitiveObject ):
632+ return repr (value )
633+ elif isinstance (value , objects .Array ):
634+ return repr ([self ._display_value (val ) for val in value ])
635+ else :
636+ if self .context .layers [self .current_layer ].is_valid (
637+ value .vol .offset
638+ ):
639+ return f"offset: 0x{ value .vol .offset :x} "
640+ else :
641+ return f"offset: 0x{ value .vol .offset :x} (unreadable)"
642+ else :
643+ # non volobject
644+ if value is None :
645+ return "N/A"
646+ else :
647+ return repr (value )
648+
649+ except exceptions .InvalidAddressException :
650+ # if value causes an InvalidAddressException like BaseAbsentValue then display N/A
651+ return "N/A"
498652
499653 def generate_treegrid (
500654 self , plugin : Type [interfaces .plugins .PluginInterface ], ** kwargs
0 commit comments