Beispiel #1
0
    def _call_one(self, obj: drgn.Object) -> Iterable[drgn.Object]:
        nelems = 0

        obj_type = sdb.type_canonicalize(obj.type_)
        if obj_type.kind == drgn.TypeKind.ARRAY:
            array_elems = len(obj)
            if self.args.nelems is not None:
                nelems = self.args.nelems
                if nelems > array_elems:
                    print(
                        f"warning: requested size {nelems} exceeds type size {array_elems}"
                    )
            else:
                nelems = array_elems

        elif obj_type.kind == drgn.TypeKind.POINTER:
            if self.args.nelems is None:
                err_msg = (f"'{obj.type_.type_name()}' is a pointer - "
                           "please specify the number of elements")
                raise sdb.CommandError(self.name, err_msg)
            nelems = self.args.nelems

            if not obj_type.type.is_complete():
                err_msg = ("can't walk pointer array of incomplete type "
                           f"'{obj_type.type.type_name()}'")
                raise sdb.CommandError(self.name, err_msg)
        else:
            raise sdb.CommandError(
                self.name,
                f"'{obj.type_.type_name()}' is not an array nor a pointer type")

        for i in range(nelems):
            yield obj[i]
Beispiel #2
0
    def validate_args(self, prog: drgn.Program,
                      args: argparse.Namespace) -> None:
        if args.function:
            try:
                func = prog[args.function]
            except KeyError:
                raise sdb.CommandError(
                    self.name,
                    "symbol '{:s}' does not exist".format(args.function))
            if func.type_.kind != drgn.TypeKind.FUNCTION:
                raise sdb.CommandError(
                    self.name, "'{:s}' is not a function".format(args.function))

        task_states = Stacks.TASK_STATES.values()
        task_states_lowercase = list(map(lambda x: x.lower(), task_states))
        state_shortcuts = Stacks.TASK_STATE_SHORTCUTS
        if args.tstate and not args.tstate.lower(
        ) in task_states_lowercase and not args.tstate in state_shortcuts:
            raise sdb.CommandError(
                self.name,
                "'{:s}' is not a valid task state (acceptable states: {:s})".
                format(args.tstate, ", ".join(task_states)))

        if args.module and Stacks.find_module_memory_segment(
                self.prog, args.module)[0] == -1:
            raise sdb.CommandError(
                self.name,
                "module '{:s}' doesn't exist or isn't currently loaded".format(
                    args.module))
Beispiel #3
0
    def _validate_type_dereference(self, obj: drgn.Object,
                                   sep: MemberExprSep) -> None:
        """
        The conventions when dereferencing structure members and array
        elements come straight from the standard C notation:
        [1] The dot notation/operator `.` can be used to reference
            members in an embedded struct.
        [2] `->` can be used to walk/dereference struct members that
            are pointers.
        [3] Array notation can be used to look into a specific element
            of an array using the index notation. This include both
            formally specified arrays (like char[100]) and pointers
            that are used as arrays allocated at runtime.

        Something worth noting from a usability perspective for struct
        members that are pointers (see [2] above) is that SDB relaxes
        the notation and allows dereference with the `.` operator too.
        This means that something like this:
        `... | member ptr0->ptr1->ptr2`
        could be also written like this:
        `... | member ptr0.ptr1.ptr2`
        Besides saving one keystroke, this improves usability as users
        don't have to care about the exact type of the member being a
        struct or a pointer to a struct. This should also help users
        that are switching between DRGN and SDB during development and
        people coming from Acid.
        """
        kind = sdb.type_canonicalize(obj.type_).kind

        # This is the first term, no need to do validation.
        if sep == MemberExprSep.START:
            return

        if kind == drgn.TypeKind.STRUCT and sep != MemberExprSep.DOT:
            raise sdb.CommandError(
                self.name,
                "'{}' is a struct - use the dot(.) notation for member access".
                format(obj.type_))

        if kind == drgn.TypeKind.POINTER:
            assert sep in [
                MemberExprSep.PTR, MemberExprSep.DOT, MemberExprSep.ARRAY
            ]
            return

        if kind == drgn.TypeKind.ARRAY and sep != MemberExprSep.ARRAY:
            raise sdb.CommandError(
                self.name,
                "'{}' is an array - cannot use '{}' notation".format(
                    obj.type_, sep.value))
Beispiel #4
0
 def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     sname = get_valid_struct_name(self, self.args.struct_name)
     for obj in objs:
         try:
             yield from list_for_each_entry(sname, obj, self.args.member)
         except LookupError as err:
             raise sdb.CommandError(self.name, str(err))
Beispiel #5
0
    def call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
        try:
            for obj in objs:
                lhs = eval(self.lhs_code, {'__builtins__': None}, {'obj': obj})
                rhs = eval(self.rhs_code, {'__builtins__': None}, {'obj': obj})

                if not isinstance(lhs, drgn.Object):
                    raise sdb.CommandInvalidInputError(
                        self.name,
                        "left hand side has unsupported type ({})".format(
                            type(lhs).__name__))

                if isinstance(rhs, str):
                    lhs = lhs.string_().decode("utf-8")
                elif isinstance(rhs, int):
                    rhs = drgn.Object(self.prog, type=lhs.type_, value=rhs)
                elif isinstance(rhs, bool):
                    pass
                elif isinstance(rhs, drgn.Object):
                    pass
                else:
                    raise sdb.CommandInvalidInputError(
                        self.name,
                        "right hand side has unsupported type ({})".format(
                            type(rhs).__name__))

                if eval("lhs {} rhs".format(self.compare),
                        {'__builtins__': None}, {
                            'lhs': lhs,
                            'rhs': rhs
                        }):
                    yield obj
        except (AttributeError, TypeError, ValueError) as err:
            raise sdb.CommandError(self.name, str(err))
Beispiel #6
0
 def _call_one(self, obj: drgn.Object) -> Iterable[drgn.Object]:
     try:
         sum_ = drgn_percpu.percpu_counter_sum(obj)
     except AttributeError as err:
         raise sdb.CommandError(self.name,
                                "input is not a percpu_counter") from err
     yield drgn.Object(sdb.get_prog(), type="s64", value=sum_)
Beispiel #7
0
 def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     # pylint: disable=eval-used
     func = lambda obj: eval(self.code, {'__builtins__': None}, {'obj': obj})
     try:
         yield from filter(func, objs)
     except (TypeError, AttributeError) as err:
         raise sdb.CommandError(self.name, str(err))
Beispiel #8
0
    def _eval_member_terms(
            self, initial_obj: drgn.Object,
            terms: List[Tuple[MemberExprSep, str]]) -> drgn.Object:
        """
        Evaluates member terms passed to us by _parse_member_tokens()
        """
        obj = initial_obj
        for term in terms:
            sep, token = term
            self._validate_type_dereference(obj, sep)

            if sep == MemberExprSep.ARRAY:
                idx = int(token)
                self._validate_array_index(obj.type_, idx)
                obj = obj[idx]
            else:
                try:
                    obj = obj.member_(token)
                except (LookupError, TypeError) as err:
                    #
                    # The expected error messages that we get from
                    # member_() are good enough to be propagated
                    # as-is.
                    #
                    raise sdb.CommandError(self.name, str(err))
        return obj
Beispiel #9
0
 def call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     for obj in objs:
         for idx in range(self.args.count):
             try:
                 yield obj[idx]
             except TypeError as err:
                 raise sdb.CommandError(self.name, str(err))
Beispiel #10
0
    def call(self, objs: Iterable[drgn.Object]) -> None:  # type: ignore
        baked = [(sdb.prog.type(type_), class_)
                 for type_, class_ in sdb.PrettyPrinter.all_printers.items()]
        handlingClass = None
        first_obj_type, objs = sdb.get_first_type(objs)
        if first_obj_type is not None:
            for type_, class_ in baked:
                if type_ == first_obj_type and hasattr(class_, "pretty_print"):
                    handlingClass = class_
                    break

        if not handlingClass:
            print("The following types have pretty-printers:")
            print("\t%-20s %-20s" % ("PRINTER", "TYPE"))
            for type_, class_ in baked:
                if hasattr(class_, "pretty_print"):
                    print("\t%-20s %-20s" % (class_.names[0], type_))
            if first_obj_type:
                msg = 'could not find pretty-printer for type {}'.format(
                    first_obj_type)
            else:
                msg = 'could not find appropriate pretty-printer'
            raise sdb.CommandError(self.name, msg)

        handlingClass().pretty_print(objs)
Beispiel #11
0
    def _call(self, objs: Iterable[drgn.Object]) -> None:
        baked = {
            sdb.type_canonicalize_name(type_): class_
            for type_, class_ in sdb.PrettyPrinter.all_printers.items()
        }

        handling_class = None
        first_obj_type, objs = sdb.get_first_type(objs)
        if first_obj_type is not None:
            first_obj_type_name = sdb.type_canonical_name(first_obj_type)
            if first_obj_type_name in baked:
                handling_class = baked[first_obj_type_name]

        if handling_class is None:
            if first_obj_type is not None:
                msg = 'could not find pretty-printer for type {}\n'.format(
                    first_obj_type)
            else:
                msg = 'could not find pretty-printer\n'
            msg += "The following types have pretty-printers:\n"
            msg += f"\t{'PRINTER':<20s} {'TYPE':<20s}\n"
            for type_name, class_ in sdb.PrettyPrinter.all_printers.items():
                msg += f"\t{class_.names[0]:<20s} {type_name:<20s}\n"
            raise sdb.CommandError(self.name, msg)

        handling_class().pretty_print(objs)
Beispiel #12
0
 def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     sname = get_valid_struct_name(self, self.args.struct_name)
     for obj in objs:
         try:
             container_obj = drgn.container_of(obj, sname, self.args.member)
         except (TypeError, LookupError) as err:
             raise sdb.CommandError(self.name, str(err))
         yield container_obj
Beispiel #13
0
    def caller(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
        """
        This method will dispatch to the appropriate instance function
        based on the type of the input we receive.
        """

        out_type = sdb.prog.type(self.output_type)
        has_input = False
        for i in objs:
            has_input = True

            # try subclass-specified input types first, so that they can
            # override any other behavior
            try:
                for (_, method) in inspect.getmembers(self, inspect.ismethod):
                    if not hasattr(method, "input_typename_handled"):
                        continue

                    # Cache parsed type by setting an attribute on the
                    # function that this method is bound to (same place
                    # the input_typename_handled attribute is set).
                    if not hasattr(method, "input_type_handled"):
                        method.__func__.input_type_handled = sdb.prog.type(
                            method.input_typename_handled)

                    if i.type_ == method.input_type_handled:
                        yield from method(i)
                        raise StopIteration
            except StopIteration:
                continue

            # try passthrough of output type
            # note, this may also be handled by subclass-specified input types
            if i.type_ == out_type:
                yield i
                continue

            # try walkers
            try:
                # pylint: disable=import-outside-toplevel
                #
                # The reason we do the above is that putting
                # the import at the top-level hits a cyclic
                # import error which pretty-much breaks
                # everything. We should reconsider how we
                # handle all our imports.
                from sdb.commands.walk import Walk
                for obj in Walk().call([i]):
                    yield drgn.cast(out_type, obj)
                continue
            except sdb.CommandError:
                pass

            # error
            raise sdb.CommandError(
                self.name, 'no handler for input of type {}'.format(i.type_))
        if not has_input:
            yield from self.no_input()
Beispiel #14
0
 def from_vdev(self, vdev: drgn.Object) -> Iterable[drgn.Object]:
     if self.args.vdev_ids:
         raise sdb.CommandError(
             self.name, "when providing a vdev, "
             "specific child vdevs can not be requested")
     yield vdev
     for cid in range(0, int(vdev.vdev_children)):
         cvd = vdev.vdev_child[cid]
         yield from self.from_vdev(cvd)
Beispiel #15
0
 def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     result = 0
     for obj in objs:
         type_ = sdb.type_canonicalize(obj.type_)
         if type_.kind != drgn.TypeKind.INT and type_.kind != drgn.TypeKind.POINTER:
             raise sdb.CommandError(
                 self.name, f"'{type_.type_name()}' is not an integer type")
         result += int(obj.value_())
     yield sdb.create_object('uint64_t', result)
Beispiel #16
0
def get_valid_struct_name(cmd: sdb.Command, tname: str) -> str:
    """
    If tname is a name of a type that's a typedef to a
    struct, this function return will it as is. If this
    is a name of a struct, then it returns the canonical
    name (e.g. adds "struct" prefix). Otherwise, raises
    an error.

    Used for shorthands in providing names of structure
    types to be consumed by drgn interfaces in string
    form (linux_list, container_of, etc..).
    """
    if tname in ['struct', 'union', 'class']:
        #
        # Note: We have to do this because currently in drgn
        # prog.type('struct') returns a different error than
        # prog.type('bogus'). The former returns a SyntaxError
        # "null identifier" while the latter returns LookupError
        # "could not find typedef bogus". The former is not
        # user-friendly and thus we just avoid that situation
        # by instructing the user to skip such keywords.
        #
        raise sdb.CommandError(cmd.name,
                               f"skip keyword '{tname}' and try again")

    try:
        type_ = sdb.get_prog().type(tname)
    except LookupError:
        # Check for struct
        struct_name = f"struct {tname}"
        try:
            type_ = sdb.get_prog().type(struct_name)
        except LookupError as err:
            raise sdb.CommandError(cmd.name, str(err))
        return struct_name

    # Check for typedef to struct
    if type_.kind != drgn.TypeKind.TYPEDEF:
        raise sdb.CommandError(
            cmd.name, f"{tname} is not a struct nor a typedef to a struct")
    if sdb.type_canonicalize(type_).kind != drgn.TypeKind.STRUCT:
        raise sdb.CommandError(cmd.name,
                               f"{tname} is not a typedef to a struct")
    return tname
Beispiel #17
0
    def __init__(self, args: str = "", name: str = "_") -> None:
        super().__init__(args, name)
        if not self.args.type:
            self.parser.error("the following arguments are required: <type>")

        tname = " ".join(self.args.type)
        try:
            self.type = sdb.prog.type(tname)
        except LookupError:
            raise sdb.CommandError(self.name, f"could not find type '{tname}'")
Beispiel #18
0
 def validate_context(self) -> None:
     #
     # This implementation only works for linux kernel targets
     # (crash dumps or live systems). When support for userland is added we can
     # refactor the kernel code into its own function and switch to the correct
     # codepath depending on the target.
     #
     if not sdb.get_target_flags() & drgn.ProgramFlags.IS_LINUX_KERNEL:
         raise sdb.CommandError(self.name,
                                "userland targets are not supported yet")
     self.validate_args()
Beispiel #19
0
 def _call_one(self, obj: drgn.Object) -> Iterable[drgn.Object]:
     cpus = self.args.cpus
     if not cpus:
         cpus = range(self.ncpus)
     for cpu in cpus:
         if cpu >= self.ncpus:
             raise sdb.CommandError(
                 self.name,
                 f"available CPUs [0-{self.ncpus -1}] - requested CPU {cpu}"
             )
         yield drgn_percpu.per_cpu_ptr(obj, cpu)
Beispiel #20
0
 def from_spa(self, spa: drgn.Object) -> Iterable[drgn.Object]:
     if self.args.vdev_ids:
         # yield the requested top-level vdevs
         for i in self.args.vdev_ids:
             if i >= spa.spa_root_vdev.vdev_children:
                 raise sdb.CommandError(
                     self.name,
                     "vdev id {} not valid; there are only {} vdevs in {}".
                     format(i, int(spa.spa_root_vdev.vdev_children),
                            spa.spa_name.string_().decode("utf-8")))
             yield spa.spa_root_vdev.vdev_child[i]
     else:
         yield from self.from_vdev(spa.spa_root_vdev)
Beispiel #21
0
    def validate_args(self) -> None:
        if self.args.function:
            try:
                #
                # It would be simpler to resolve the symbol from the function
                # name directly but we use the address due to osandov/drgn#47.
                #
                func = sdb.get_object(self.args.function)
                sym = sdb.get_symbol(func.address_of_())
            except KeyError as err:
                raise sdb.CommandError(
                    self.name,
                    f"symbol '{self.args.function}' does not exist") from err
            if func.type_.kind != drgn.TypeKind.FUNCTION:
                raise sdb.CommandError(
                    self.name, f"'{self.args.function}' is not a function")
            self.func_start = sym.address
            self.func_end = self.func_start + sym.size

        if self.args.tstate:
            self.match_state = Stacks.resolve_state(self.args.tstate)
            task_states = Stacks.TASK_STATES.values()
            if self.match_state not in task_states:
                valid_states = ", ".join(task_states)
                raise sdb.CommandError(
                    self.name,
                    f"'{self.args.tstate}' is not a valid task state"
                    f" (acceptable states: {valid_states})")

        if self.args.module:
            if Stacks.find_module_memory_segment(self.args.module)[0] == -1:
                raise sdb.CommandError(
                    self.name,
                    f"module '{self.args.module}' doesn't exist or isn't currently loaded"
                )
            self.mod_start, mod_size = Stacks.find_module_memory_segment(
                self.args.module)
            assert self.mod_start != -1
            self.mod_end = self.mod_start + mod_size
Beispiel #22
0
 def call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     for obj in objs:
         for member in self.args.members:
             try:
                 obj = obj.member_(member)
             except (LookupError, TypeError) as err:
                 #
                 # The expected error messages that we get from
                 # member_() are good enough to be propagated
                 # as-is.
                 #
                 raise sdb.CommandError(self.name, str(err))
         yield obj
Beispiel #23
0
 def from_vdev(self, vdev: drgn.Object) -> Iterable[drgn.Object]:
     if self.args.metaslab_ids:
         # yield the requested metaslabs
         for i in self.args.metaslab_ids:
             if i >= vdev.vdev_ms_count:
                 raise sdb.CommandError(
                     self.name, "metaslab id {} not valid; "
                     "there are only {} metaslabs in vdev id {}".format(
                         i, int(vdev.vdev_ms_count), int(vdev.vdev_id)))
             yield vdev.vdev_ms[i]
     else:
         for i in range(0, int(vdev.vdev_ms_count)):
             msp = vdev.vdev_ms[i]
             yield msp
Beispiel #24
0
    def __pp_parse_args(self
                       ) -> (str, List[str], Dict[str, Callable[[Any], str]]):
        fields = SplKmemCaches.DEFAULT_FIELDS
        if self.args.o:
            #
            # HACK: Until we have a proper lexer for SDB we can
            #       only pass the comma-separated list as a
            #       string (e.g. quoted). Until this is fixed
            #       we make sure to unquote such strings.
            #
            if self.args.o[0] == '"' and self.args.o[-1] == '"':
                self.args.o = self.args.o[1:-1]
            fields = self.args.o.split(",")
        elif self.args.v:
            fields = list(SplKmemCaches.FIELDS.keys())

        for field in fields:
            if field not in SplKmemCaches.FIELDS:
                raise sdb.CommandError(
                    self.name, "'{:s}' is not a valid field".format(field))

        sort_field = ""
        if self.args.s:
            if self.args.s not in fields:
                msg = f"'{self.args.s}' is not in field set ({', '.join(fields)})"
                raise sdb.CommandInvalidInputError(self.name,
                                                   textwrap.fill(msg, width=80))
            sort_field = self.args.s
        else:
            #
            # If a sort_field hasn't been specified try the following
            # defaults. If these are not part of the field-set then
            # sort by the first field in the set.
            #
            for field in self.DEFAULT_SORT_FIELDS:
                if field in fields:
                    sort_field = field
                    break
            if not sort_field:
                sort_field = fields[0]

        formatters = {
            "active_memory": size_nicenum,
            "total_memory": size_nicenum
        }
        if self.args.p:
            formatters = {}

        return sort_field, fields, formatters
Beispiel #25
0
    def call(self, objs: Iterable[drgn.Object]) -> None:
        """
        This function will call pretty_print() on each input object,
        verifying the types as we go.
        """

        assert self.input_type is not None
        type_ = sdb.prog.type(self.input_type)
        for obj in objs:
            if obj.type_ != type_:
                raise sdb.CommandError(
                    self.name,
                    'no handler for input of type {}'.format(obj.type_))

            self.pretty_print([obj])
Beispiel #26
0
    def call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
        """
        This function will call walk() on each input object, verifying
        the types as we go.
        """
        assert self.input_type is not None
        type_ = sdb.prog.type(self.input_type)
        for obj in objs:
            if obj.type_ != type_:
                raise sdb.CommandError(
                    self.name,
                    'expected input of type {}, but received {}'.format(
                        type_, obj.type_))

            yield from self.walk(obj)
Beispiel #27
0
def for_each_object_in_spl_cache(cache: drgn.Object) -> Iterable[drgn.Object]:
    assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
    #
    # ZFSonLinux initially implemented OFFSLAB caches for certain cases
    # that never showed up and thus have never been used in practice.
    # Ensure here that we are not looking at such a cache.
    #
    if 'KMC_OFFSLAB' in list(for_each_slab_flag_in_cache(cache)):
        raise sdb.CommandError("spl_caches",
                               "KMC_OFFSLAB caches are not supported")

    for slab_list in [cache.skc_complete_list, cache.skc_partial_list]:
        for slab in drgn_list.list_for_each_entry("spl_kmem_slab_t",
                                                  slab_list.address_of_(),
                                                  "sks_list"):
            yield from for_each_onslab_object_in_slab(slab)
Beispiel #28
0
    def call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
        baked = [(sdb.prog.type(type_), class_)
                 for type_, class_ in sdb.Walker.allWalkers.items()]
        has_input = False
        for i in objs:
            has_input = True

            try:
                for type_, class_ in baked:
                    if i.type_ == type_:
                        yield from class_().walk(i)
                        raise StopIteration
            except StopIteration:
                continue

            raise sdb.CommandError(self.name, Walk._help_message(i.type_))
        # If we got no input and we're the last thing in the pipeline, we're
        # probably the first thing in the pipeline. Print out the available
        # walkers.
        if not has_input and self.islast:
            print(Walk._help_message())
Beispiel #29
0
    def __pp_parse_args(self) -> Tuple[str, List[str], Dict[str, Any]]:
        fields = self.DEFAULT_FIELDS
        if self.args.o:
            fields = self.args.o.split(",")
        elif self.args.v:
            fields = list(Slabs.FIELDS.keys())

        for field in fields:
            if field not in self.FIELDS:
                raise sdb.CommandError(
                    self.name, "'{:s}' is not a valid field".format(field))

        sort_field = ""
        if self.args.s:
            if self.args.s not in fields:
                msg = f"'{self.args.s}' is not in field set ({', '.join(fields)})"
                raise sdb.CommandInvalidInputError(
                    self.name, textwrap.fill(msg, width=80))
            sort_field = self.args.s
        else:
            #
            # If a sort_field hasn't been specified try the following
            # defaults. If these are not part of the field-set then
            # sort by the first field in the set.
            #
            for field in self.DEFAULT_SORT_FIELDS:
                if field in fields:
                    sort_field = field
                    break
            if not sort_field:
                sort_field = fields[0]

        formatters = {
            "total_memory": size_nicenum,
            "active_memory": size_nicenum
        }
        if self.args.p:
            formatters = {}

        return sort_field, fields, formatters
Beispiel #30
0
    def _eval_member_terms(
            self, initial_obj: drgn.Object,
            terms: List[Tuple[str, str]]) -> Tuple[drgn.Object, str]:
        """
        Evaluates member terms passed to us by _parse_member_tokens()
        """
        obj = initial_obj
        for term in terms:
            sep, token = term
            self._validate_type_dereference(obj, sep)

            #
            # Ensure we are not trying to dereference NULL.
            #
            if obj.value_() == 0x0:
                warning = f"cannot dereference NULL member of type '{obj.type_}'"
                if not obj.address_ is None:
                    #
                    # This is possible when the object was piped to
                    # us through `echo`.
                    #
                    warning += f" at address {hex(obj.address_of_().value_())}"
                return initial_obj, warning

            if sep == MemberExprSep.ARRAY:
                idx = int(token)
                self._validate_array_index(obj.type_, idx)
                obj = obj[idx]
            else:
                try:
                    obj = obj.member_(token)
                except (LookupError, TypeError) as err:
                    #
                    # The expected error messages that we get from
                    # member_() are good enough to be propagated
                    # as-is.
                    #
                    raise sdb.CommandError(self.name, str(err))
        return obj, ""