1 //===-- CommandObjectFrame.cpp ----------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "CommandObjectFrame.h" 11 12 // C Includes 13 // C++ Includes 14 #include <string> 15 // Other libraries and framework includes 16 // Project includes 17 #include "lldb/Breakpoint/Watchpoint.h" 18 #include "lldb/Core/DataVisualization.h" 19 #include "lldb/Core/Debugger.h" 20 #include "lldb/Core/Module.h" 21 #include "lldb/Core/StreamFile.h" 22 #include "lldb/Core/StreamString.h" 23 #include "lldb/Core/Timer.h" 24 #include "lldb/Core/Value.h" 25 #include "lldb/Core/ValueObject.h" 26 #include "lldb/Core/ValueObjectVariable.h" 27 #include "lldb/Host/Host.h" 28 #include "lldb/Interpreter/Args.h" 29 #include "lldb/Interpreter/CommandInterpreter.h" 30 #include "lldb/Interpreter/CommandReturnObject.h" 31 #include "lldb/Interpreter/Options.h" 32 #include "lldb/Interpreter/OptionGroupFormat.h" 33 #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" 34 #include "lldb/Interpreter/OptionGroupVariable.h" 35 #include "lldb/Interpreter/OptionGroupWatchpoint.h" 36 #include "lldb/Symbol/ClangASTType.h" 37 #include "lldb/Symbol/ClangASTContext.h" 38 #include "lldb/Symbol/ObjectFile.h" 39 #include "lldb/Symbol/SymbolContext.h" 40 #include "lldb/Symbol/Type.h" 41 #include "lldb/Symbol/Variable.h" 42 #include "lldb/Symbol/VariableList.h" 43 #include "lldb/Target/Process.h" 44 #include "lldb/Target/StackFrame.h" 45 #include "lldb/Target/Thread.h" 46 #include "lldb/Target/Target.h" 47 48 using namespace lldb; 49 using namespace lldb_private; 50 51 #pragma mark CommandObjectFrameInfo 52 53 //------------------------------------------------------------------------- 54 // CommandObjectFrameInfo 55 //------------------------------------------------------------------------- 56 57 class CommandObjectFrameInfo : public CommandObject 58 { 59 public: 60 61 CommandObjectFrameInfo (CommandInterpreter &interpreter) : 62 CommandObject (interpreter, 63 "frame info", 64 "List information about the currently selected frame in the current thread.", 65 "frame info", 66 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused) 67 { 68 } 69 70 ~CommandObjectFrameInfo () 71 { 72 } 73 74 bool 75 Execute (Args& command, 76 CommandReturnObject &result) 77 { 78 ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); 79 StackFrame *frame = exe_ctx.GetFramePtr(); 80 if (frame) 81 { 82 frame->DumpUsingSettingsFormat (&result.GetOutputStream()); 83 result.SetStatus (eReturnStatusSuccessFinishResult); 84 } 85 else 86 { 87 result.AppendError ("no current frame"); 88 result.SetStatus (eReturnStatusFailed); 89 } 90 return result.Succeeded(); 91 } 92 }; 93 94 #pragma mark CommandObjectFrameSelect 95 96 //------------------------------------------------------------------------- 97 // CommandObjectFrameSelect 98 //------------------------------------------------------------------------- 99 100 class CommandObjectFrameSelect : public CommandObject 101 { 102 public: 103 104 class CommandOptions : public Options 105 { 106 public: 107 108 CommandOptions (CommandInterpreter &interpreter) : 109 Options(interpreter) 110 { 111 OptionParsingStarting (); 112 } 113 114 virtual 115 ~CommandOptions () 116 { 117 } 118 119 virtual Error 120 SetOptionValue (uint32_t option_idx, const char *option_arg) 121 { 122 Error error; 123 bool success = false; 124 char short_option = (char) m_getopt_table[option_idx].val; 125 switch (short_option) 126 { 127 case 'r': 128 relative_frame_offset = Args::StringToSInt32 (option_arg, INT32_MIN, 0, &success); 129 if (!success) 130 error.SetErrorStringWithFormat ("invalid frame offset argument '%s'", option_arg); 131 break; 132 133 default: 134 error.SetErrorStringWithFormat ("invalid short option character '%c'", short_option); 135 break; 136 } 137 138 return error; 139 } 140 141 void 142 OptionParsingStarting () 143 { 144 relative_frame_offset = INT32_MIN; 145 } 146 147 const OptionDefinition* 148 GetDefinitions () 149 { 150 return g_option_table; 151 } 152 153 // Options table: Required for subclasses of Options. 154 155 static OptionDefinition g_option_table[]; 156 int32_t relative_frame_offset; 157 }; 158 159 CommandObjectFrameSelect (CommandInterpreter &interpreter) : 160 CommandObject (interpreter, 161 "frame select", 162 "Select a frame by index from within the current thread and make it the current frame.", 163 NULL, 164 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused), 165 m_options (interpreter) 166 { 167 CommandArgumentEntry arg; 168 CommandArgumentData index_arg; 169 170 // Define the first (and only) variant of this arg. 171 index_arg.arg_type = eArgTypeFrameIndex; 172 index_arg.arg_repetition = eArgRepeatOptional; 173 174 // There is only one variant this argument could be; put it into the argument entry. 175 arg.push_back (index_arg); 176 177 // Push the data for the first argument into the m_arguments vector. 178 m_arguments.push_back (arg); 179 } 180 181 ~CommandObjectFrameSelect () 182 { 183 } 184 185 virtual 186 Options * 187 GetOptions () 188 { 189 return &m_options; 190 } 191 192 193 bool 194 Execute (Args& command, 195 CommandReturnObject &result) 196 { 197 ExecutionContext exe_ctx (m_interpreter.GetExecutionContext()); 198 Thread *thread = exe_ctx.GetThreadPtr(); 199 if (thread) 200 { 201 const uint32_t num_frames = thread->GetStackFrameCount(); 202 uint32_t frame_idx = UINT32_MAX; 203 if (m_options.relative_frame_offset != INT32_MIN) 204 { 205 // The one and only argument is a signed relative frame index 206 frame_idx = thread->GetSelectedFrameIndex (); 207 if (frame_idx == UINT32_MAX) 208 frame_idx = 0; 209 210 if (m_options.relative_frame_offset < 0) 211 { 212 if (frame_idx >= -m_options.relative_frame_offset) 213 frame_idx += m_options.relative_frame_offset; 214 else 215 { 216 if (frame_idx == 0) 217 { 218 //If you are already at the bottom of the stack, then just warn and don't reset the frame. 219 result.AppendError("Already at the bottom of the stack"); 220 result.SetStatus(eReturnStatusFailed); 221 return false; 222 } 223 else 224 frame_idx = 0; 225 } 226 } 227 else if (m_options.relative_frame_offset > 0) 228 { 229 if (num_frames - frame_idx > m_options.relative_frame_offset) 230 frame_idx += m_options.relative_frame_offset; 231 else 232 { 233 if (frame_idx == num_frames - 1) 234 { 235 //If we are already at the top of the stack, just warn and don't reset the frame. 236 result.AppendError("Already at the top of the stack"); 237 result.SetStatus(eReturnStatusFailed); 238 return false; 239 } 240 else 241 frame_idx = num_frames - 1; 242 } 243 } 244 } 245 else 246 { 247 if (command.GetArgumentCount() == 1) 248 { 249 const char *frame_idx_cstr = command.GetArgumentAtIndex(0); 250 frame_idx = Args::StringToUInt32 (frame_idx_cstr, UINT32_MAX, 0); 251 } 252 else if (command.GetArgumentCount() == 0) 253 { 254 frame_idx = thread->GetSelectedFrameIndex (); 255 if (frame_idx == UINT32_MAX) 256 { 257 frame_idx = 0; 258 } 259 } 260 else 261 { 262 result.AppendError ("invalid arguments.\n"); 263 m_options.GenerateOptionUsage (result.GetErrorStream(), this); 264 } 265 } 266 267 if (frame_idx < num_frames) 268 { 269 thread->SetSelectedFrameByIndex (frame_idx); 270 exe_ctx.SetFrameSP(thread->GetSelectedFrame ()); 271 StackFrame *frame = exe_ctx.GetFramePtr(); 272 if (frame) 273 { 274 bool already_shown = false; 275 SymbolContext frame_sc(frame->GetSymbolContext(eSymbolContextLineEntry)); 276 if (m_interpreter.GetDebugger().GetUseExternalEditor() && frame_sc.line_entry.file && frame_sc.line_entry.line != 0) 277 { 278 already_shown = Host::OpenFileInExternalEditor (frame_sc.line_entry.file, frame_sc.line_entry.line); 279 } 280 281 bool show_frame_info = true; 282 bool show_source = !already_shown; 283 uint32_t source_lines_before = 3; 284 uint32_t source_lines_after = 3; 285 if (frame->GetStatus (result.GetOutputStream(), 286 show_frame_info, 287 show_source, 288 source_lines_before, 289 source_lines_after)) 290 { 291 result.SetStatus (eReturnStatusSuccessFinishResult); 292 return result.Succeeded(); 293 } 294 } 295 } 296 result.AppendErrorWithFormat ("Frame index (%u) out of range.\n", frame_idx); 297 } 298 else 299 { 300 result.AppendError ("no current thread"); 301 } 302 result.SetStatus (eReturnStatusFailed); 303 return false; 304 } 305 protected: 306 307 CommandOptions m_options; 308 }; 309 310 OptionDefinition 311 CommandObjectFrameSelect::CommandOptions::g_option_table[] = 312 { 313 { LLDB_OPT_SET_1, false, "relative", 'r', required_argument, NULL, 0, eArgTypeOffset, "A relative frame index offset from the current frame index."}, 314 { 0, false, NULL, 0, 0, NULL, NULL, eArgTypeNone, NULL } 315 }; 316 317 #pragma mark CommandObjectFrameVariable 318 //---------------------------------------------------------------------- 319 // List images with associated information 320 //---------------------------------------------------------------------- 321 class CommandObjectFrameVariable : public CommandObject 322 { 323 public: 324 325 CommandObjectFrameVariable (CommandInterpreter &interpreter) : 326 CommandObject (interpreter, 327 "frame variable", 328 "Show frame variables. All argument and local variables " 329 "that are in scope will be shown when no arguments are given. " 330 "If any arguments are specified, they can be names of " 331 "argument, local, file static and file global variables. " 332 "Children of aggregate variables can be specified such as " 333 "'var->child.x'. " 334 "You can choose to watch a variable with the '-w' option. " 335 "Note that hardware resources for watching are often limited. " 336 "See alo 'watchpoint set' where you can use an expression to " 337 "specify the address to watch for.", 338 NULL, 339 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused), 340 m_option_group (interpreter), 341 m_option_variable(true), // Include the frame specific options by passing "true" 342 m_option_format (eFormatDefault), 343 m_option_watchpoint(), 344 m_varobj_options() 345 { 346 CommandArgumentEntry arg; 347 CommandArgumentData var_name_arg; 348 349 // Define the first (and only) variant of this arg. 350 var_name_arg.arg_type = eArgTypeVarName; 351 var_name_arg.arg_repetition = eArgRepeatStar; 352 353 // There is only one variant this argument could be; put it into the argument entry. 354 arg.push_back (var_name_arg); 355 356 // Push the data for the first argument into the m_arguments vector. 357 m_arguments.push_back (arg); 358 359 m_option_group.Append (&m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 360 m_option_group.Append (&m_option_format, OptionGroupFormat::OPTION_GROUP_FORMAT | OptionGroupFormat::OPTION_GROUP_GDB_FMT, LLDB_OPT_SET_1); 361 m_option_group.Append (&m_option_watchpoint, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 362 m_option_group.Append (&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 363 m_option_group.Finalize(); 364 } 365 366 virtual 367 ~CommandObjectFrameVariable () 368 { 369 } 370 371 virtual 372 Options * 373 GetOptions () 374 { 375 return &m_option_group; 376 } 377 378 379 virtual bool 380 Execute 381 ( 382 Args& command, 383 CommandReturnObject &result 384 ) 385 { 386 ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); 387 StackFrame *frame = exe_ctx.GetFramePtr(); 388 if (frame == NULL) 389 { 390 result.AppendError ("you must be stopped in a valid stack frame to view frame variables."); 391 result.SetStatus (eReturnStatusFailed); 392 return false; 393 } 394 395 Stream &s = result.GetOutputStream(); 396 397 bool get_file_globals = true; 398 399 // Be careful about the stack frame, if any summary formatter runs code, it might clear the StackFrameList 400 // for the thread. So hold onto a shared pointer to the frame so it stays alive. 401 402 VariableList *variable_list = frame->GetVariableList (get_file_globals); 403 404 VariableSP var_sp; 405 ValueObjectSP valobj_sp; 406 407 const char *name_cstr = NULL; 408 size_t idx; 409 410 SummaryFormatSP summary_format_sp; 411 if (!m_option_variable.summary.empty()) 412 DataVisualization::NamedSummaryFormats::GetSummaryFormat(ConstString(m_option_variable.summary.c_str()), summary_format_sp); 413 414 ValueObject::DumpValueObjectOptions options; 415 416 options.SetPointerDepth(m_varobj_options.ptr_depth) 417 .SetMaximumDepth(m_varobj_options.max_depth) 418 .SetShowTypes(m_varobj_options.show_types) 419 .SetShowLocation(m_varobj_options.show_location) 420 .SetUseObjectiveC(m_varobj_options.use_objc) 421 .SetUseDynamicType(m_varobj_options.use_dynamic) 422 .SetUseSyntheticValue((lldb::SyntheticValueType)m_varobj_options.use_synth) 423 .SetFlatOutput(m_varobj_options.flat_output) 424 .SetOmitSummaryDepth(m_varobj_options.no_summary_depth) 425 .SetIgnoreCap(m_varobj_options.ignore_cap); 426 427 if (m_varobj_options.be_raw) 428 options.SetRawDisplay(true); 429 430 if (variable_list) 431 { 432 // If watching a variable, there are certain restrictions to be followed. 433 if (m_option_watchpoint.watch_variable) 434 { 435 if (command.GetArgumentCount() != 1) { 436 result.GetErrorStream().Printf("error: specify exactly one variable when using the '-w' option\n"); 437 result.SetStatus(eReturnStatusFailed); 438 return false; 439 } else if (m_option_variable.use_regex) { 440 result.GetErrorStream().Printf("error: specify your variable name exactly (no regex) when using the '-w' option\n"); 441 result.SetStatus(eReturnStatusFailed); 442 return false; 443 } 444 445 // Things have checked out ok... 446 // m_option_watchpoint.watch_type specifies the type of watching. 447 } 448 449 const Format format = m_option_format.GetFormat(); 450 451 if (command.GetArgumentCount() > 0) 452 { 453 VariableList regex_var_list; 454 455 // If we have any args to the variable command, we will make 456 // variable objects from them... 457 for (idx = 0; (name_cstr = command.GetArgumentAtIndex(idx)) != NULL; ++idx) 458 { 459 if (m_option_variable.use_regex) 460 { 461 const uint32_t regex_start_index = regex_var_list.GetSize(); 462 RegularExpression regex (name_cstr); 463 if (regex.Compile(name_cstr)) 464 { 465 size_t num_matches = 0; 466 const size_t num_new_regex_vars = variable_list->AppendVariablesIfUnique(regex, 467 regex_var_list, 468 num_matches); 469 if (num_new_regex_vars > 0) 470 { 471 for (uint32_t regex_idx = regex_start_index, end_index = regex_var_list.GetSize(); 472 regex_idx < end_index; 473 ++regex_idx) 474 { 475 var_sp = regex_var_list.GetVariableAtIndex (regex_idx); 476 if (var_sp) 477 { 478 valobj_sp = frame->GetValueObjectForFrameVariable (var_sp, m_varobj_options.use_dynamic); 479 if (valobj_sp) 480 { 481 // if (format != eFormatDefault) 482 // valobj_sp->SetFormat (format); 483 484 if (m_option_variable.show_decl && var_sp->GetDeclaration ().GetFile()) 485 { 486 bool show_fullpaths = false; 487 bool show_module = true; 488 if (var_sp->DumpDeclaration(&s, show_fullpaths, show_module)) 489 s.PutCString (": "); 490 } 491 if (summary_format_sp) 492 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 493 ValueObject::DumpValueObject (result.GetOutputStream(), 494 valobj_sp.get(), 495 options, 496 format); 497 } 498 } 499 } 500 } 501 else if (num_matches == 0) 502 { 503 result.GetErrorStream().Printf ("error: no variables matched the regular expression '%s'.\n", name_cstr); 504 } 505 } 506 else 507 { 508 char regex_error[1024]; 509 if (regex.GetErrorAsCString(regex_error, sizeof(regex_error))) 510 result.GetErrorStream().Printf ("error: %s\n", regex_error); 511 else 512 result.GetErrorStream().Printf ("error: unkown regex error when compiling '%s'\n", name_cstr); 513 } 514 } 515 else // No regex, either exact variable names or variable expressions. 516 { 517 Error error; 518 uint32_t expr_path_options = StackFrame::eExpressionPathOptionCheckPtrVsMember; 519 lldb::VariableSP var_sp; 520 valobj_sp = frame->GetValueForVariableExpressionPath (name_cstr, 521 m_varobj_options.use_dynamic, 522 expr_path_options, 523 var_sp, 524 error); 525 if (valobj_sp) 526 { 527 // if (format != eFormatDefault) 528 // valobj_sp->SetFormat (format); 529 if (m_option_variable.show_decl && var_sp && var_sp->GetDeclaration ().GetFile()) 530 { 531 var_sp->GetDeclaration ().DumpStopContext (&s, false); 532 s.PutCString (": "); 533 } 534 if (summary_format_sp) 535 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 536 537 Stream &output_stream = result.GetOutputStream(); 538 ValueObject::DumpValueObject (output_stream, 539 valobj_sp.get(), 540 valobj_sp->GetParent() ? name_cstr : NULL, 541 options, 542 format); 543 // Process watchpoint if necessary. 544 if (m_option_watchpoint.watch_variable) 545 { 546 AddressType addr_type; 547 lldb::addr_t addr = 0; 548 size_t size = 0; 549 if (m_option_watchpoint.watch_size == 0) { 550 addr = valobj_sp->GetAddressOf(false, &addr_type); 551 if (addr_type == eAddressTypeLoad) { 552 // We're in business. 553 // Find out the size of this variable. 554 size = valobj_sp->GetByteSize(); 555 } 556 } else { 557 // The '-xsize'/'-x' option means to treat the value object as 558 // a pointer and to watch the pointee with the specified size. 559 addr = valobj_sp->GetValueAsUnsigned(0); 560 size = m_option_watchpoint.watch_size; 561 } 562 uint32_t watch_type = m_option_watchpoint.watch_type; 563 Watchpoint *wp = exe_ctx.GetTargetRef().CreateWatchpoint(addr, size, watch_type).get(); 564 if (wp) 565 { 566 if (var_sp && var_sp->GetDeclaration().GetFile()) 567 { 568 StreamString ss; 569 // True to show fullpath for declaration file. 570 var_sp->GetDeclaration().DumpStopContext(&ss, true); 571 wp->SetDeclInfo(ss.GetString()); 572 } 573 StreamString ss; 574 output_stream.Printf("Watchpoint created: "); 575 wp->GetDescription(&output_stream, lldb::eDescriptionLevelFull); 576 output_stream.EOL(); 577 result.SetStatus(eReturnStatusSuccessFinishResult); 578 } 579 else 580 { 581 result.AppendErrorWithFormat("Watchpoint creation failed.\n"); 582 result.SetStatus(eReturnStatusFailed); 583 } 584 return (wp != NULL); 585 } 586 } 587 else 588 { 589 const char *error_cstr = error.AsCString(NULL); 590 if (error_cstr) 591 result.GetErrorStream().Printf("error: %s\n", error_cstr); 592 else 593 result.GetErrorStream().Printf ("error: unable to find any variable expression path that matches '%s'\n", name_cstr); 594 } 595 } 596 } 597 } 598 else // No command arg specified. Use variable_list, instead. 599 { 600 const uint32_t num_variables = variable_list->GetSize(); 601 if (num_variables > 0) 602 { 603 for (uint32_t i=0; i<num_variables; i++) 604 { 605 var_sp = variable_list->GetVariableAtIndex(i); 606 bool dump_variable = true; 607 switch (var_sp->GetScope()) 608 { 609 case eValueTypeVariableGlobal: 610 dump_variable = m_option_variable.show_globals; 611 if (dump_variable && m_option_variable.show_scope) 612 s.PutCString("GLOBAL: "); 613 break; 614 615 case eValueTypeVariableStatic: 616 dump_variable = m_option_variable.show_globals; 617 if (dump_variable && m_option_variable.show_scope) 618 s.PutCString("STATIC: "); 619 break; 620 621 case eValueTypeVariableArgument: 622 dump_variable = m_option_variable.show_args; 623 if (dump_variable && m_option_variable.show_scope) 624 s.PutCString(" ARG: "); 625 break; 626 627 case eValueTypeVariableLocal: 628 dump_variable = m_option_variable.show_locals; 629 if (dump_variable && m_option_variable.show_scope) 630 s.PutCString(" LOCAL: "); 631 break; 632 633 default: 634 break; 635 } 636 637 if (dump_variable) 638 { 639 // Use the variable object code to make sure we are 640 // using the same APIs as the the public API will be 641 // using... 642 valobj_sp = frame->GetValueObjectForFrameVariable (var_sp, 643 m_varobj_options.use_dynamic); 644 if (valobj_sp) 645 { 646 // if (format != eFormatDefault) 647 // valobj_sp->SetFormat (format); 648 649 // When dumping all variables, don't print any variables 650 // that are not in scope to avoid extra unneeded output 651 if (valobj_sp->IsInScope ()) 652 { 653 if (m_option_variable.show_decl && var_sp->GetDeclaration ().GetFile()) 654 { 655 var_sp->GetDeclaration ().DumpStopContext (&s, false); 656 s.PutCString (": "); 657 } 658 if (summary_format_sp) 659 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 660 ValueObject::DumpValueObject (result.GetOutputStream(), 661 valobj_sp.get(), 662 name_cstr, 663 options, 664 format); 665 } 666 } 667 } 668 } 669 } 670 } 671 result.SetStatus (eReturnStatusSuccessFinishResult); 672 } 673 674 if (m_interpreter.TruncationWarningNecessary()) 675 { 676 result.GetOutputStream().Printf(m_interpreter.TruncationWarningText(), 677 m_cmd_name.c_str()); 678 m_interpreter.TruncationWarningGiven(); 679 } 680 681 return result.Succeeded(); 682 } 683 protected: 684 685 OptionGroupOptions m_option_group; 686 OptionGroupVariable m_option_variable; 687 OptionGroupFormat m_option_format; 688 OptionGroupWatchpoint m_option_watchpoint; 689 OptionGroupValueObjectDisplay m_varobj_options; 690 }; 691 692 693 #pragma mark CommandObjectMultiwordFrame 694 695 //------------------------------------------------------------------------- 696 // CommandObjectMultiwordFrame 697 //------------------------------------------------------------------------- 698 699 CommandObjectMultiwordFrame::CommandObjectMultiwordFrame (CommandInterpreter &interpreter) : 700 CommandObjectMultiword (interpreter, 701 "frame", 702 "A set of commands for operating on the current thread's frames.", 703 "frame <subcommand> [<subcommand-options>]") 704 { 705 LoadSubCommand ("info", CommandObjectSP (new CommandObjectFrameInfo (interpreter))); 706 LoadSubCommand ("select", CommandObjectSP (new CommandObjectFrameSelect (interpreter))); 707 LoadSubCommand ("variable", CommandObjectSP (new CommandObjectFrameVariable (interpreter))); 708 } 709 710 CommandObjectMultiwordFrame::~CommandObjectMultiwordFrame () 711 { 712 } 713 714