def __call__(self, debugger, command, exe_ctx, result): # Set the command to synchronous so the step will complete # before we try to run the frame variable. old_async = debugger.GetAsync() debugger.SetAsync(False) if len(command): seed = int(command) print("Using seed: " + str(seed)) fuzz_rand = random.Random(seed) debugger.HandleCommand("b main") debugger.HandleCommand("run") debugger.HandleCommand( "settings set target.process.thread.step-avoid-regexp \"\"") for i in range(0, fuzz_rand.randrange(300) + 5): debugger.HandleCommand("s") if fuzz_rand.randrange(10) <= 1: res = lldb.SBCommandReturnObject() interp.HandleCommand("bt", res) cmd = "expr " for k in range(3, fuzz_rand.randrange(10)): interp = debugger.GetCommandInterpreter() match_strings = lldb.SBStringList() desc_strings = lldb.SBStringList() num_matches = interp.HandleCompletionWithDescriptions( cmd, len(cmd), 0, -1, match_strings, desc_strings) if num_matches == 0: cmd += fuzz_rand.choice(["+", "-", "*", " = ", "/", ";"]) continue selected = 0 while len(desc_strings.GetStringAtIndex(selected)) == 0: selected = fuzz_rand.randrange(num_matches) if fuzz_rand.uniform(0.0, 1.0) < 0.01: break new_end = match_strings.GetStringAtIndex(selected) if len(new_end) != 0: if new_end.endswith("("): new_end += fuzz_rand.choice([").", ")->", " "]) else: new_end += fuzz_rand.choice(["->", ".", " "]) cmd = add_suffix(cmd, new_end) print("Adding: '" + new_end + "'") print("Command: '" + cmd + "'") debugger.SetAsync(old_async)
def shadowed_bkpt_command_test(self): """Test that options set on the breakpoint and location behave correctly.""" # Breakpoint option propagation from bkpt to loc used to be done the first time # a breakpoint location option was specifically set. After that the other options # on that location would stop tracking the breakpoint. That got fixed, and this test # makes sure only the option touched is affected. bkpt = self.set_breakpoint() commands = ["AAAAAA", "BBBBBB", "CCCCCC"] str_list = lldb.SBStringList() str_list.AppendList(commands, len(commands)) bkpt.SetCommandLineCommands(str_list) cmd_list = lldb.SBStringList() bkpt.GetCommandLineCommands(cmd_list) list_size = str_list.GetSize() self.assertEqual(cmd_list.GetSize(), list_size, "Added the right number of commands") for i in range(0, list_size): self.assertEqual(str_list.GetStringAtIndex(i), cmd_list.GetStringAtIndex(i), "Mismatched commands.") commands = ["DDDDDD", "EEEEEE", "FFFFFF", "GGGGGG"] loc_list = lldb.SBStringList() loc_list.AppendList(commands, len(commands)) bkpt.location[1].SetCommandLineCommands(loc_list) loc_cmd_list = lldb.SBStringList() bkpt.location[1].GetCommandLineCommands(loc_cmd_list) loc_list_size = loc_list.GetSize() # Check that the location has the right commands: self.assertEqual(loc_cmd_list.GetSize(), loc_list_size, "Added the right number of commands to location") for i in range(0, loc_list_size): self.assertEqual(loc_list.GetStringAtIndex(i), loc_cmd_list.GetStringAtIndex(i), "Mismatched commands.") # Check that we didn't mess up the breakpoint level commands: self.assertEqual(cmd_list.GetSize(), list_size, "Added the right number of commands") for i in range(0, list_size): self.assertEqual(str_list.GetStringAtIndex(i), cmd_list.GetStringAtIndex(i), "Mismatched commands.") # And check we didn't mess up another location: untouched_loc_cmds = lldb.SBStringList() bkpt.location[0].GetCommandLineCommands(untouched_loc_cmds) self.assertEqual(untouched_loc_cmds.GetSize(), 0, "Changed the wrong location")
def complete_from_to(self, str_input, patterns, turn_off_re_match=False): """Test that the completion mechanism completes str_input to patterns, where patterns could be a pattern-string or a list of pattern-strings""" # Patterns should not be None in order to proceed. self.assertFalse(patterns is None) # And should be either a string or list of strings. Check for list type # below, if not, make a list out of the singleton string. If patterns # is not a string or not a list of strings, there'll be runtime errors # later on. if not isinstance(patterns, list): patterns = [patterns] interp = self.dbg.GetCommandInterpreter() match_strings = lldb.SBStringList() num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) common_match = match_strings.GetStringAtIndex(0) if num_matches == 0: compare_string = str_input else: if common_match != None and len(common_match) > 0: compare_string = str_input + common_match else: compare_string = "" for idx in range(1, num_matches+1): compare_string += match_strings.GetStringAtIndex(idx) + "\n" for p in patterns: if turn_off_re_match: self.expect( compare_string, msg=COMPLETION_MSG( str_input, p), exe=False, substrs=[p]) else: self.expect( compare_string, msg=COMPLETION_MSG( str_input, p), exe=False, patterns=[p])
def test_swift_error_matching_full_pattern(self): """Tests that swift error throws are correctly caught by the Swift Error breakpoint""" self.build() pattern = lldb.SBStringList() pattern.AppendString("exception-typename") pattern.AppendString("a.EnumError") self.do_test(pattern, True)
def do_check_names(self): bkpt = self.orig_target.BreakpointCreateByLocation( lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()) good_bkpt_name = "GoodBreakpoint" write_bps = lldb.SBBreakpointList(self.orig_target) bkpt.AddName(good_bkpt_name) write_bps.Append(bkpt) error = lldb.SBError() error = self.orig_target.BreakpointsWriteToFile( self.bkpts_file_spec, write_bps) self.assertTrue( error.Success(), "Failed writing breakpoints to file: %s." % (error.GetCString())) copy_bps = lldb.SBBreakpointList(self.copy_target) names_list = lldb.SBStringList() names_list.AppendString("NoSuchName") error = self.copy_target.BreakpointsCreateFromFile( self.bkpts_file_spec, names_list, copy_bps) self.assertTrue( error.Success(), "Failed reading breakpoints from file: %s" % (error.GetCString())) self.assertTrue(copy_bps.GetSize() == 0, "Found breakpoints with a nonexistent name.") names_list.AppendString(good_bkpt_name) error = self.copy_target.BreakpointsCreateFromFile( self.bkpts_file_spec, names_list, copy_bps) self.assertTrue( error.Success(), "Failed reading breakpoints from file: %s" % (error.GetCString())) self.assertTrue(copy_bps.GetSize() == 1, "Found the matching breakpoint.")
def fuzz_obj(obj): obj.IsValid() obj.GetName() obj.SetEnabled(True) obj.IsEnabled() obj.SetOneShot(True) obj.IsOneShot() obj.SetIgnoreCount(1) obj.GetIgnoreCount() obj.SetCondition("1 == 2") obj.GetCondition() obj.SetAutoContinue(False) obj.GetAutoContinue() obj.SetThreadID(0x1234) obj.GetThreadID() obj.SetThreadIndex(10) obj.GetThreadIndex() obj.SetThreadName("AThread") obj.GetThreadName() obj.SetQueueName("AQueue") obj.GetQueueName() obj.SetScriptCallbackFunction("AFunction") commands = lldb.SBStringList() obj.SetCommandLineCommands(commands) obj.GetCommandLineCommands(commands) obj.SetScriptCallbackBody("Insert Python Code here") obj.GetAllowList() obj.SetAllowList(False) obj.GetAllowDelete() obj.SetAllowDelete(False) obj.GetAllowDisable() obj.SetAllowDisable(False) stream = lldb.SBStream() obj.GetDescription(stream)
def test_swift_error_bogus_pattern(self): """Tests that swift error throws are correctly caught by the Swift Error breakpoint""" self.build() pattern = lldb.SBStringList() pattern.AppendString("exception-typename") pattern.AppendString("NoSuchErrorHere") self.do_test(pattern, False)
def complete_command(self, arg, line, pos): """ Returns a list of viable completions for line, and cursor at pos. """ pos = int(pos) result = lldb.SBStringList() if arg == line and line != '': # provide all possible completions when completing 't', 'b', 'di' etc. num = self._ipreter.HandleCompletion('', 0, 1, -1, result) cands = [''] for x in result: if x == line: cands.insert(1, x) elif x.startswith(line): cands.append(x) else: num = self._ipreter.HandleCompletion(line, pos, 1, -1, result) cands = [x for x in result] if len(cands) > 1: if cands[0] == '' and arg != '': if not cands[1].startswith(arg) or not cands[-1].startswith(arg): return [] return cands[1:] else: return []
def complete(self, prefix, state): completion.suppress_append = True # lldb appends its own spaces buf = rl.readline.get_line_buffer() if self.lastbuf != buf: # new buffer, redo completion self.res = [] matches = lldb.SBStringList() r = self.ci.HandleCompletion(buf, completion.rl_point, completion.rl_point, -1, matches) log.debug("completion: got matches: " + str([ matches.GetStringAtIndex(i) for i in range(matches.GetSize()) ])) # if there's a single fragment if len(matches.GetStringAtIndex(0).strip()) > 0: # add it match = prefix + matches.GetStringAtIndex(0) log.debug("completion: partial: " + match) self.res.append(match) else: # otherwise, add the other possible matches for i in range(1, matches.GetSize()): match = matches.GetStringAtIndex(i)[len(buf.split()[-1]):] self.res.append(match) # store buffer self.lastbuf = buf log.debug("completion: returning: " + self.res[state]) return self.res[state]
def create_breakpoint_with_api(self, target, typename): types = lldb.SBStringList() if typename: types.AppendString("exception-typename") types.AppendString(typename) return target.BreakpointCreateForException(lldb.eLanguageTypeSwift, False, True, types).GetID()
def completeCommand(self, a, l, p): """ Returns a list of viable completions for command a with length l and cursor at p """ assert l[0] == 'L' # Remove first 'L' character that all commands start with l = l[1:] # Adjust length as string has 1 less character p = int(p) - 1 result = lldb.SBStringList() num = self.commandInterpreter.HandleCompletion(l, p, 1, -1, result) if num == -1: # FIXME: insert completion character... what's a completion character? pass elif num == -2: # FIXME: replace line with result.GetStringAtIndex(0) pass if result.GetSize() > 0: results = filter(None, [result.GetStringAtIndex(x) for x in range(result.GetSize())]) return results else: return []
def source_regex_locations(self): """ Test that restricting source expressions to files & to functions. """ # Create a target by the debugger. exe = self.getBuildArtifact("a.out") target = self.dbg.CreateTarget(exe) self.assertTrue(target, VALID_TARGET) # First look just in main: target_files = lldb.SBFileSpecList() target_files.Append(lldb.SBFileSpec("a.c")) func_names = lldb.SBStringList() func_names.AppendString("a_func") source_regex = "Set . breakpoint here" main_break = target.BreakpointCreateBySourceRegex( source_regex, lldb.SBFileSpecList(), target_files, func_names) num_locations = main_break.GetNumLocations() self.assertTrue( num_locations == 1, "a.c in a_func should give one breakpoint, got %d." % (num_locations)) loc = main_break.GetLocationAtIndex(0) self.assertTrue(loc.IsValid(), "Got a valid location.") address = loc.GetAddress() self.assertTrue(address.IsValid(), "Got a valid address from the location.") a_func_line = line_number("a.c", "Set A breakpoint here") line_entry = address.GetLineEntry() self.assertTrue(line_entry.IsValid(), "Got a valid line entry.") self.assertEquals(line_entry.line, a_func_line, "Our line number matches the one lldbtest found.")
def breakpoint_commands_on_creation(self): """Test that setting breakpoint commands when creating the breakpoint works""" exe = self.getBuildArtifact("a.out") target = self.dbg.CreateTarget(exe) self.assertTrue(target.IsValid(), "Created an invalid target.") # Add a breakpoint. lldbutil.run_break_set_by_file_and_line( self, "main.c", self.line, num_expected_locations=1, loc_exact=True, extra_options='-C bt -C "thread list" -C continue') bkpt = target.FindBreakpointByID(1) self.assertTrue(bkpt.IsValid(), "Couldn't find breakpoint 1") com_list = lldb.SBStringList() bkpt.GetCommandLineCommands(com_list) self.assertEqual(com_list.GetSize(), 3, "Got the wrong number of commands") self.assertEqual(com_list.GetStringAtIndex(0), "bt", "First bt") self.assertEqual(com_list.GetStringAtIndex(1), "thread list", "Next thread list") self.assertEqual(com_list.GetStringAtIndex(2), "continue", "Last continue")
def do_check_options(self): """Use Python APIs to check serialization of breakpoint options.""" empty_module_list = lldb.SBFileSpecList() empty_cu_list = lldb.SBFileSpecList() blubby_file_spec = lldb.SBFileSpec( os.path.join(self.getSourceDir(), "blubby.c")) # It isn't actually important for these purposes that these breakpoint # actually have locations. source_bps = lldb.SBBreakpointList(self.orig_target) bkpt = self.orig_target.BreakpointCreateByLocation( lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()) bkpt.SetEnabled(False) bkpt.SetOneShot(True) bkpt.SetThreadID(10) source_bps.Append(bkpt) # Make sure we get one right: self.check_equivalence(source_bps) source_bps.Clear() bkpt = self.orig_target.BreakpointCreateByName( "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list) bkpt.SetIgnoreCount(10) bkpt.SetThreadName("grubby") source_bps.Append(bkpt) bkpt = self.orig_target.BreakpointCreateByName( "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list) bkpt.SetCondition("gonna remove this") bkpt.SetCondition("") source_bps.Append(bkpt) bkpt = self.orig_target.BreakpointCreateByName( "blubby", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list) bkpt.SetCondition("something != something_else") bkpt.SetQueueName("grubby") bkpt.AddName("FirstName") bkpt.AddName("SecondName") bkpt.SetScriptCallbackBody( '\tprint("I am a function that prints.")\n\tprint("I don\'t do anything else")\n' ) source_bps.Append(bkpt) bkpt = self.orig_target.BreakpointCreateBySourceRegex( "dont really care", blubby_file_spec) cmd_list = lldb.SBStringList() cmd_list.AppendString("frame var") cmd_list.AppendString("thread backtrace") bkpt.SetCommandLineCommands(cmd_list) source_bps.Append(bkpt) self.check_equivalence(source_bps)
def get_completions(self, cmd): retval = [] matches = lldb.SBStringList() self.plugin.interpreter.HandleCompletion(cmd.encode('ascii'), len(cmd), len(cmd), -1, matches) for i in range(0, matches.GetSize()): retval.append(matches.GetStringAtIndex(i)) return retval
def test_SBStringList(self): obj = lldb.SBStringList() if self.TraceOn(): print obj self.assertFalse(obj) # Do fuzz testing on the invalid obj, it should not crash lldb. import sb_stringlist sb_stringlist.fuzz_obj(obj)
def handle_completion(self, current_line, cursor_pos): matches = lldb.SBStringList() interpreter = self.debugger.GetCommandInterpreter() interpreter.HandleCompletion( current_line.encode('utf-8'), cursor_pos, 0, 1000, matches) self.listener.notify_event( 'completion', matches=[m.decode('unicode-escape') for m in matches], )
def check_name_in_target(self, bkpt_name): name_list = lldb.SBStringList() self.target.GetBreakpointNames(name_list) found_it = False for name in name_list: if name == bkpt_name: found_it = True break self.assertTrue(found_it, "Didn't find the name %s in the target's name list:"%(bkpt_name))
def get_cache_line_size(): value_list = lldb.SBStringList() value_list = self.dbg.GetInternalVariableValue(property_name, self.dbg.GetInstanceName()) self.assertEqual(value_list.GetSize(), 1) try: return int(value_list.GetStringAtIndex(0)) except ValueError as error: self.fail("Value is not a number: " + error)
def DEBUG_completions(self, args): interp = self.debugger.GetCommandInterpreter() text = to_lldb_str(args['text']) column = int(args['column']) matches = lldb.SBStringList() result = interp.HandleCompletion(text, column-1, 0, -1, matches) targets = [] for match in matches: targets.append({ 'label': match }) return { 'targets': targets }
def source_regex_restrictions(self): """ Test that restricting source expressions to files & to functions. """ # Create a target by the debugger. exe = self.getBuildArtifact("a.out") target = self.dbg.CreateTarget(exe) self.assertTrue(target, VALID_TARGET) # First look just in main: target_files = lldb.SBFileSpecList() target_files.Append(lldb.SBFileSpec("main.c")) source_regex = "Set . breakpoint here" main_break = target.BreakpointCreateBySourceRegex( source_regex, lldb.SBFileSpecList(), target_files, lldb.SBStringList()) num_locations = main_break.GetNumLocations() self.assertTrue( num_locations == 2, "main.c should have 2 matches, got %d." % (num_locations)) # Now look in both files: target_files.Append(lldb.SBFileSpec("a.c")) main_break = target.BreakpointCreateBySourceRegex( source_regex, lldb.SBFileSpecList(), target_files, lldb.SBStringList()) num_locations = main_break.GetNumLocations() self.assertTrue( num_locations == 4, "main.c and a.c should have 4 matches, got %d." % (num_locations)) # Now restrict it to functions: func_names = lldb.SBStringList() func_names.AppendString("main_func") main_break = target.BreakpointCreateBySourceRegex( source_regex, lldb.SBFileSpecList(), target_files, func_names) num_locations = main_break.GetNumLocations() self.assertTrue( num_locations == 2, "main_func in main.c and a.c should have 2 matches, got %d." % (num_locations))
def fuzz_obj(obj): obj.AppendString("another string") obj.AppendString(None) obj.AppendList(None, 0) obj.AppendList(lldb.SBStringList()) obj.GetSize() obj.GetStringAtIndex(0xffffffff) obj.Clear() for n in obj: s = str(n)
def completions_contain_str(self, input, needle): interp = self.dbg.GetCommandInterpreter() match_strings = lldb.SBStringList() num_matches = interp.HandleCompletion(input, len(input), 0, -1, match_strings) found_needle = False for match in match_strings: if needle in match: found_needle = True break self.assertTrue(found_needle, "Returned completions: " + "\n".join(match_strings))
def assume_no_completions(self, str_input, cursor_pos = None): interp = self.dbg.GetCommandInterpreter() match_strings = lldb.SBStringList() if cursor_pos is None: cursor_pos = len(str_input) num_matches = interp.HandleCompletion(str_input, cursor_pos, 0, -1, match_strings) available_completions = [] for m in match_strings: available_completions.append(m) self.assertEquals(num_matches, 0, "Got matches, but didn't expect any: " + str(available_completions))
def check_option_values(self, bp_object): self.assertEqual(bp_object.IsOneShot(), self.is_one_shot, "IsOneShot") self.assertEqual(bp_object.GetIgnoreCount(), self.ignore_count, "IgnoreCount") self.assertEqual(bp_object.GetCondition(), self.condition, "Condition") self.assertEqual(bp_object.GetAutoContinue(), self.auto_continue, "AutoContinue") self.assertEqual(bp_object.GetThreadID(), self.tid, "Thread ID") self.assertEqual(bp_object.GetThreadIndex(), self.tidx, "Thread Index") self.assertEqual(bp_object.GetThreadName(), self.thread_name, "Thread Name") self.assertEqual(bp_object.GetQueueName(), self.queue_name, "Queue Name") set_cmds = lldb.SBStringList() bp_object.GetCommandLineCommands(set_cmds) self.assertEqual(set_cmds.GetSize(), self.cmd_list.GetSize(), "Size of command line commands") for idx in range(0, set_cmds.GetSize()): self.assertEqual(self.cmd_list.GetStringAtIndex(idx), set_cmds.GetStringAtIndex(idx), "Command %d"%(idx))
def test_creating_and_modifying_environment(self): env = lldb.SBEnvironment() self.assertEqual(env.Get("FOO"), None) self.assertEqual(env.Get("BAR"), None) # We also test empty values self.assertTrue(env.Set("FOO", "", overwrite=False)) env.Set("BAR", "foo", overwrite=False) self.assertEqual(env.Get("FOO"), "") self.assertEqual(env.Get("BAR"), "foo") self.assertEqual(env.GetNumValues(), 2) self.assertEqualEntries(env, ["FOO=", "BAR=foo"]) # Make sure modifications work self.assertFalse(env.Set("FOO", "bar", overwrite=False)) self.assertEqual(env.Get("FOO"), "") env.PutEntry("FOO=bar") self.assertEqual(env.Get("FOO"), "bar") self.assertEqualEntries(env, ["FOO=bar", "BAR=foo"]) # Make sure we can unset self.assertTrue(env.Unset("FOO")) self.assertFalse(env.Unset("FOO")) self.assertEqual(env.Get("FOO"), None) # Test SetEntries entries = lldb.SBStringList() entries.AppendList(["X=x", "Y=y"], 2) env.SetEntries(entries, append=True) self.assertEqualEntries(env, ["BAR=foo", "X=x", "Y=y"]) env.SetEntries(entries, append=False) self.assertEqualEntries(env, ["X=x", "Y=y"]) entries.Clear() entries.AppendList(["X=y", "Y=x"], 2) env.SetEntries(entries, append=True) self.assertEqualEntries(env, ["X=y", "Y=x"]) # Test clear env.Clear() self.assertEqualEntries(env, [])
def do_check_names(self): """Use Python APIs to check that we can set & retrieve breakpoint names""" bkpt = self.target.BreakpointCreateByLocation(self.main_file_spec, 10) bkpt_name = "ABreakpoint" other_bkpt_name = "_AnotherBreakpoint" # Add a name and make sure we match it: success = bkpt.AddName(bkpt_name) self.assertTrue(success, "We couldn't add a legal name to a breakpoint.") matches = bkpt.MatchesName(bkpt_name) self.assertTrue(matches, "We didn't match the name we just set") # Make sure we don't match irrelevant names: matches = bkpt.MatchesName("NotABreakpoint") self.assertTrue(not matches, "We matched a name we didn't set.") # Make sure the name is also in the target: self.check_name_in_target(bkpt_name) # Add another name, make sure that works too: bkpt.AddName(other_bkpt_name) matches = bkpt.MatchesName(bkpt_name) self.assertTrue( matches, "Adding a name means we didn't match the name we just set") self.check_name_in_target(other_bkpt_name) # Remove the name and make sure we no longer match it: bkpt.RemoveName(bkpt_name) matches = bkpt.MatchesName(bkpt_name) self.assertTrue(not matches, "We still match a name after removing it.") # Make sure the name list has the remaining name: name_list = lldb.SBStringList() bkpt.GetNames(name_list) num_names = name_list.GetSize() self.assertTrue(num_names == 1, "Name list has %d items, expected 1." % (num_names)) name = name_list.GetStringAtIndex(0) self.assertTrue( name == other_bkpt_name, "Remaining name was: %s expected %s." % (name, other_bkpt_name))
def completions_contain(self, str_input, items): interp = self.dbg.GetCommandInterpreter() match_strings = lldb.SBStringList() num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) common_match = match_strings.GetStringAtIndex(0) for item in items: found = False for m in match_strings: if m == item: found = True if not found: # Transform match_strings to a python list with strings available_completions = [] for m in match_strings: available_completions.append(m) self.assertTrue(found, "Couldn't find completion " + item + " in completions " + str(available_completions))
def tabCompleteCallback(content): self.data = content matches = lldb.SBStringList() commandinterpreter = self.getCommandInterpreter() commandinterpreter.HandleCompletion(self.data, self.el.index, 0, -1, matches) if matches.GetSize() == 2: self.el.content += matches.GetStringAtIndex(0) self.el.index = len(self.el.content) self.el.draw() else: self.win.move(self.el.starty, self.el.startx) self.win.scroll(1) self.win.addstr("Available Completions:") self.win.scroll(1) for m in islice(matches, 1, None): self.win.addstr(self.win.getyx()[0], 0, m) self.win.scroll(1) self.el.draw()
def setUp(self): # Call super's setUp(). TestBase.setUp(self) # These are the settings we're going to be putting into names & breakpoints: self.bp_name_string = "ABreakpoint" self.is_one_shot = True self.ignore_count = 1000 self.condition = "1 == 2" self.auto_continue = True self.tid = 0xaaaa self.tidx = 10 self.thread_name = "Fooey" self.queue_name = "Blooey" self.cmd_list = lldb.SBStringList() self.cmd_list.AppendString("frame var") self.cmd_list.AppendString("bt") self.help_string = "I do something interesting"