1 //===-- CommandObjectBreakpointCommand.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 // C Includes 11 // C++ Includes 12 13 14 #include "CommandObjectBreakpointCommand.h" 15 #include "CommandObjectBreakpoint.h" 16 17 #include "lldb/Interpreter/CommandInterpreter.h" 18 #include "lldb/Interpreter/CommandReturnObject.h" 19 #include "lldb/Target/Target.h" 20 #include "lldb/Target/Thread.h" 21 #include "lldb/Breakpoint/BreakpointIDList.h" 22 #include "lldb/Breakpoint/Breakpoint.h" 23 #include "lldb/Breakpoint/BreakpointLocation.h" 24 #include "lldb/Breakpoint/StoppointCallbackContext.h" 25 #include "lldb/Core/State.h" 26 27 using namespace lldb; 28 using namespace lldb_private; 29 30 //------------------------------------------------------------------------- 31 // CommandObjectBreakpointCommandAdd::CommandOptions 32 //------------------------------------------------------------------------- 33 34 CommandObjectBreakpointCommandAdd::CommandOptions::CommandOptions () : 35 Options () 36 { 37 } 38 39 CommandObjectBreakpointCommandAdd::CommandOptions::~CommandOptions () 40 { 41 } 42 43 lldb::OptionDefinition 44 CommandObjectBreakpointCommandAdd::CommandOptions::g_option_table[] = 45 { 46 { LLDB_OPT_SET_1, true, "script", 's', no_argument, NULL, 0, NULL, 47 "Write the breakpoint command script in the default scripting language."}, 48 49 { LLDB_OPT_SET_2, true, "python", 'p', no_argument, NULL, 0, NULL, 50 "Write the breakpoint command script in the Python scripting language."}, 51 52 { LLDB_OPT_SET_3, true, "commands", 'c', no_argument, NULL, 0, NULL, 53 "Write the breakpoint command script using the command line commands."}, 54 55 { 0, false, NULL, 0, 0, NULL, 0, NULL, NULL } 56 }; 57 58 const lldb::OptionDefinition* 59 CommandObjectBreakpointCommandAdd::CommandOptions::GetDefinitions () 60 { 61 return g_option_table; 62 } 63 64 65 Error 66 CommandObjectBreakpointCommandAdd::CommandOptions::SetOptionValue 67 ( 68 int option_idx, 69 const char *option_arg 70 ) 71 { 72 Error error; 73 char short_option = (char) m_getopt_table[option_idx].val; 74 75 switch (short_option) 76 { 77 case 's': 78 m_use_commands = false; 79 m_use_script_language = true; 80 m_script_language = eScriptLanguageDefault; 81 break; 82 case 'p': 83 m_use_commands = false; 84 m_use_script_language = true; 85 m_script_language = eScriptLanguagePython; 86 break; 87 case 'c': 88 m_use_commands = true; 89 m_use_script_language = false; 90 m_script_language = eScriptLanguageNone; 91 break; 92 default: 93 break; 94 } 95 return error; 96 } 97 98 void 99 CommandObjectBreakpointCommandAdd::CommandOptions::ResetOptionValues () 100 { 101 Options::ResetOptionValues(); 102 103 m_use_commands = false; 104 m_use_script_language = false; 105 m_script_language = eScriptLanguageNone; 106 } 107 108 //------------------------------------------------------------------------- 109 // CommandObjectBreakpointCommandAdd 110 //------------------------------------------------------------------------- 111 112 113 CommandObjectBreakpointCommandAdd::CommandObjectBreakpointCommandAdd () : 114 CommandObject ("add", 115 "Adds a set of commands to a breakpoint to be executed whenever a breakpoint is hit.", 116 "breakpoint command add <cmd-options> <breakpoint-id>") 117 { 118 SetHelpLong ( 119 "\nGeneral information about entering breakpoint commands \n\ 120 ------------------------------------------------------ \n\ 121 \n\ 122 This command will cause you to be prompted to enter the command or set \n\ 123 of commands you wish to be executed when the specified breakpoint is \n\ 124 hit. You will be told to enter your command(s), and will see a '> ' \n\ 125 prompt. Because you can enter one or many commands to be executed when \n\ 126 a breakpoint is hit, you will continue to be prompted after each \n\ 127 new-line that you enter, until you enter the word 'DONE', which will \n\ 128 cause the commands you have entered to be stored with the breakpoint \n\ 129 and executed when the breakpoint is hit. \n\ 130 \n\ 131 Syntax checking is not necessarily done when breakpoint commands are \n\ 132 entered. An improperly written breakpoint command will attempt to get \n\ 133 executed when the breakpoint gets hit, and usually silently fail. If \n\ 134 your breakpoint command does not appear to be getting executed, go \n\ 135 back and check your syntax. \n\ 136 \n\ 137 \n\ 138 Special information about PYTHON breakpoint commands \n\ 139 ---------------------------------------------------- \n\ 140 \n\ 141 You may enter either one line of Python or multiple lines of Python \n\ 142 (including defining whole functions, if desired). If you enter a \n\ 143 single line of Python, that will be passed to the Python interpreter \n\ 144 'as is' when the breakpoint gets hit. If you enter function \n\ 145 definitions, they will be passed to the Python interpreter as soon as \n\ 146 you finish entering the breakpoint command, and they can be called \n\ 147 later (don't forget to add calls to them, if you want them called when \n\ 148 the breakpoint is hit). If you enter multiple lines of Python that \n\ 149 are not function definitions, they will be collected into a new, \n\ 150 automatically generated Python function, and a call to the newly \n\ 151 generated function will be attached to the breakpoint. Important \n\ 152 Note: Because loose Python code gets collected into functions, if you \n\ 153 want to access global variables in the 'loose' code, you need to \n\ 154 specify that they are global, using the 'global' keyword. Be sure to \n\ 155 use correct Python syntax, including indentation, when entering Python \n\ 156 breakpoint commands. \n\ 157 \n\ 158 Example Python one-line breakpoint command: \n\ 159 \n\ 160 (lldb) breakpoint command add -p 1 \n\ 161 Enter your Python command(s). Type 'DONE' to end. \n\ 162 > print \"Hit this breakpoint!\" \n\ 163 > DONE \n\ 164 \n\ 165 Example multiple line Python breakpoint command, using function definition: \n\ 166 \n\ 167 (lldb) breakpoint command add -p 1 \n\ 168 Enter your Python command(s). Type 'DONE' to end. \n\ 169 > def breakpoint_output (bp_no): \n\ 170 > out_string = \"Hit breakpoint number \" + repr (bp_no) \n\ 171 > print out_string \n\ 172 > return True \n\ 173 > breakpoint_output (1) \n\ 174 > DONE \n\ 175 \n\ 176 \n\ 177 Example multiple line Python breakpoint command, using 'loose' Python: \n\ 178 \n\ 179 (lldb) breakpoint command add -p 1 \n\ 180 Enter your Python command(s). Type 'DONE' to end. \n\ 181 > global bp_count \n\ 182 > bp_count = bp_count + 1 \n\ 183 > print \"Hit this breakpoint \" + repr(bp_count) + \" times!\" \n\ 184 > DONE \n\ 185 \n\ 186 In this case, since there is a reference to a global variable, \n\ 187 'bp_count', you will also need to make sure 'bp_count' exists and is \n\ 188 initialized: \n\ 189 \n\ 190 (lldb) script \n\ 191 >>> bp_count = 0 \n\ 192 >>> quit() \n\ 193 \n\ 194 (lldb) \n\ 195 \n\ 196 Special information debugger command breakpoint commands \n\ 197 --------------------------------------------------------- \n\ 198 \n\ 199 You may enter any debugger command, exactly as you would at the \n\ 200 debugger prompt. You may enter as many debugger commands as you like, \n\ 201 but do NOT enter more than one command per line. \n" ); 202 } 203 204 CommandObjectBreakpointCommandAdd::~CommandObjectBreakpointCommandAdd () 205 { 206 } 207 208 bool 209 CommandObjectBreakpointCommandAdd::Execute 210 ( 211 CommandInterpreter &interpreter, 212 Args& command, 213 CommandReturnObject &result 214 ) 215 { 216 Target *target = interpreter.GetDebugger().GetCurrentTarget().get(); 217 218 if (target == NULL) 219 { 220 result.AppendError ("There is not a current executable; there are no breakpoints to which to add commands"); 221 result.SetStatus (eReturnStatusFailed); 222 return false; 223 } 224 225 const BreakpointList &breakpoints = target->GetBreakpointList(); 226 size_t num_breakpoints = breakpoints.GetSize(); 227 228 if (num_breakpoints == 0) 229 { 230 result.AppendError ("No breakpoints exist to have commands added"); 231 result.SetStatus (eReturnStatusFailed); 232 return false; 233 } 234 235 if (command.GetArgumentCount() == 0) 236 { 237 result.AppendError ("No breakpoint specified to which to add the commands"); 238 result.SetStatus (eReturnStatusFailed); 239 return false; 240 } 241 242 BreakpointIDList valid_bp_ids; 243 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 244 245 if (result.Succeeded()) 246 { 247 for (int i = 0; i < valid_bp_ids.Size(); ++i) 248 { 249 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 250 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 251 { 252 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 253 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 254 { 255 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 256 if (bp_loc_sp) 257 { 258 if (m_options.m_use_script_language) 259 { 260 interpreter.GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp_loc_sp->GetLocationOptions(), 261 result); 262 } 263 else 264 { 265 CollectDataForBreakpointCommandCallback (interpreter, bp_loc_sp->GetLocationOptions(), result); 266 } 267 } 268 } 269 else 270 { 271 if (m_options.m_use_script_language) 272 { 273 interpreter.GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp->GetOptions(), 274 result); 275 } 276 else 277 { 278 CollectDataForBreakpointCommandCallback (interpreter, bp->GetOptions(), result); 279 } 280 } 281 } 282 } 283 } 284 285 return result.Succeeded(); 286 } 287 288 Options * 289 CommandObjectBreakpointCommandAdd::GetOptions () 290 { 291 return &m_options; 292 } 293 294 const char *g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end."; 295 296 void 297 CommandObjectBreakpointCommandAdd::CollectDataForBreakpointCommandCallback 298 ( 299 CommandInterpreter &interpreter, 300 BreakpointOptions *bp_options, 301 CommandReturnObject &result 302 ) 303 { 304 InputReaderSP reader_sp (new InputReader(interpreter.GetDebugger())); 305 std::auto_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); 306 if (reader_sp && data_ap.get()) 307 { 308 BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); 309 bp_options->SetCallback (CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction, baton_sp); 310 311 Error err (reader_sp->Initialize (CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback, 312 bp_options, // baton 313 eInputReaderGranularityLine, // token size, to pass to callback function 314 "DONE", // end token 315 "> ", // prompt 316 true)); // echo input 317 if (err.Success()) 318 { 319 interpreter.GetDebugger().PushInputReader (reader_sp); 320 result.SetStatus (eReturnStatusSuccessFinishNoResult); 321 } 322 else 323 { 324 result.AppendError (err.AsCString()); 325 result.SetStatus (eReturnStatusFailed); 326 } 327 } 328 else 329 { 330 result.AppendError("out of memory"); 331 result.SetStatus (eReturnStatusFailed); 332 } 333 334 } 335 336 size_t 337 CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback 338 ( 339 void *baton, 340 InputReader &reader, 341 lldb::InputReaderAction notification, 342 const char *bytes, 343 size_t bytes_len 344 ) 345 { 346 FILE *out_fh = reader.GetDebugger().GetOutputFileHandle(); 347 348 switch (notification) 349 { 350 case eInputReaderActivate: 351 if (out_fh) 352 { 353 ::fprintf (out_fh, "%s\n", g_reader_instructions); 354 if (reader.GetPrompt()) 355 ::fprintf (out_fh, "%s", reader.GetPrompt()); 356 } 357 break; 358 359 case eInputReaderDeactivate: 360 break; 361 362 case eInputReaderReactivate: 363 if (out_fh && reader.GetPrompt()) 364 ::fprintf (out_fh, "%s", reader.GetPrompt()); 365 break; 366 367 case eInputReaderGotToken: 368 if (bytes && bytes_len && baton) 369 { 370 BreakpointOptions *bp_options = (BreakpointOptions *) baton; 371 if (bp_options) 372 { 373 Baton *bp_options_baton = bp_options->GetBaton(); 374 if (bp_options_baton) 375 ((BreakpointOptions::CommandData *)bp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len); 376 } 377 } 378 if (out_fh && !reader.IsDone() && reader.GetPrompt()) 379 ::fprintf (out_fh, "%s", reader.GetPrompt()); 380 break; 381 382 case eInputReaderDone: 383 break; 384 } 385 386 return bytes_len; 387 } 388 389 390 //------------------------------------------------------------------------- 391 // CommandObjectBreakpointCommandRemove 392 //------------------------------------------------------------------------- 393 394 CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () : 395 CommandObject ("remove", 396 "Remove the set of commands from a breakpoint.", 397 "breakpoint command remove <breakpoint-id>") 398 { 399 } 400 401 CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove () 402 { 403 } 404 405 bool 406 CommandObjectBreakpointCommandRemove::Execute 407 ( 408 CommandInterpreter &interpreter, 409 Args& command, 410 CommandReturnObject &result 411 ) 412 { 413 Target *target = interpreter.GetDebugger().GetCurrentTarget().get(); 414 415 if (target == NULL) 416 { 417 result.AppendError ("There is not a current executable; there are no breakpoints from which to remove commands"); 418 result.SetStatus (eReturnStatusFailed); 419 return false; 420 } 421 422 const BreakpointList &breakpoints = target->GetBreakpointList(); 423 size_t num_breakpoints = breakpoints.GetSize(); 424 425 if (num_breakpoints == 0) 426 { 427 result.AppendError ("No breakpoints exist to have commands removed"); 428 result.SetStatus (eReturnStatusFailed); 429 return false; 430 } 431 432 if (command.GetArgumentCount() == 0) 433 { 434 result.AppendError ("No breakpoint specified from which to remove the commands"); 435 result.SetStatus (eReturnStatusFailed); 436 return false; 437 } 438 439 BreakpointIDList valid_bp_ids; 440 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 441 442 if (result.Succeeded()) 443 { 444 for (int i = 0; i < valid_bp_ids.Size(); ++i) 445 { 446 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 447 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 448 { 449 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 450 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 451 { 452 BreakpointLocationSP bp_loc_sp (bp->FindLocationByID (cur_bp_id.GetLocationID())); 453 if (bp_loc_sp) 454 bp_loc_sp->ClearCallback(); 455 else 456 { 457 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 458 cur_bp_id.GetBreakpointID(), 459 cur_bp_id.GetLocationID()); 460 result.SetStatus (eReturnStatusFailed); 461 return false; 462 } 463 } 464 else 465 { 466 bp->ClearCallback(); 467 } 468 } 469 } 470 } 471 return result.Succeeded(); 472 } 473 474 475 //------------------------------------------------------------------------- 476 // CommandObjectBreakpointCommandList 477 //------------------------------------------------------------------------- 478 479 CommandObjectBreakpointCommandList::CommandObjectBreakpointCommandList () : 480 CommandObject ("List", 481 "List the script or set of commands to be executed when the breakpoint is hit.", 482 "breakpoint command list <breakpoint-id>") 483 { 484 } 485 486 CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList () 487 { 488 } 489 490 bool 491 CommandObjectBreakpointCommandList::Execute 492 ( 493 CommandInterpreter &interpreter, 494 Args& command, 495 CommandReturnObject &result 496 ) 497 { 498 Target *target = interpreter.GetDebugger().GetCurrentTarget().get(); 499 500 if (target == NULL) 501 { 502 result.AppendError ("There is not a current executable; there are no breakpoints for which to list commands"); 503 result.SetStatus (eReturnStatusFailed); 504 return false; 505 } 506 507 const BreakpointList &breakpoints = target->GetBreakpointList(); 508 size_t num_breakpoints = breakpoints.GetSize(); 509 510 if (num_breakpoints == 0) 511 { 512 result.AppendError ("No breakpoints exist for which to list commands"); 513 result.SetStatus (eReturnStatusFailed); 514 return false; 515 } 516 517 if (command.GetArgumentCount() == 0) 518 { 519 result.AppendError ("No breakpoint specified for which to list the commands"); 520 result.SetStatus (eReturnStatusFailed); 521 return false; 522 } 523 524 BreakpointIDList valid_bp_ids; 525 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 526 527 if (result.Succeeded()) 528 { 529 for (int i = 0; i < valid_bp_ids.Size(); ++i) 530 { 531 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 532 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 533 { 534 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 535 536 if (bp) 537 { 538 const BreakpointOptions *bp_options = NULL; 539 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 540 { 541 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 542 if (bp_loc_sp) 543 bp_options = bp_loc_sp->GetOptionsNoCreate(); 544 else 545 { 546 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 547 cur_bp_id.GetBreakpointID(), 548 cur_bp_id.GetLocationID()); 549 result.SetStatus (eReturnStatusFailed); 550 return false; 551 } 552 } 553 else 554 { 555 bp_options = bp->GetOptions(); 556 } 557 558 if (bp_options) 559 { 560 StreamString id_str; 561 BreakpointID::GetCanonicalReference (&id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID()); 562 const Baton *baton = bp_options->GetBaton(); 563 if (baton) 564 { 565 result.GetOutputStream().Printf ("Breakpoint %s:\n", id_str.GetData()); 566 result.GetOutputStream().IndentMore (); 567 baton->GetDescription(&result.GetOutputStream(), eDescriptionLevelFull); 568 result.GetOutputStream().IndentLess (); 569 } 570 else 571 { 572 result.AppendMessageWithFormat ("Breakpoint %s does not have an associated command.\n", id_str.GetData()); 573 } 574 } 575 result.SetStatus (eReturnStatusSuccessFinishResult); 576 } 577 else 578 { 579 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", cur_bp_id.GetBreakpointID()); 580 result.SetStatus (eReturnStatusFailed); 581 } 582 583 } 584 } 585 } 586 587 return result.Succeeded(); 588 } 589 590 //------------------------------------------------------------------------- 591 // CommandObjectBreakpointCommand 592 //------------------------------------------------------------------------- 593 594 CommandObjectBreakpointCommand::CommandObjectBreakpointCommand (CommandInterpreter &interpreter) : 595 CommandObjectMultiword ("command", 596 "A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commmands').", 597 "command <sub-command> [<sub-command-options>] <breakpoint-id>") 598 { 599 bool status; 600 CommandObjectSP add_command_object (new CommandObjectBreakpointCommandAdd ()); 601 CommandObjectSP remove_command_object (new CommandObjectBreakpointCommandRemove ()); 602 CommandObjectSP list_command_object (new CommandObjectBreakpointCommandList ()); 603 604 add_command_object->SetCommandName ("breakpoint command add"); 605 remove_command_object->SetCommandName ("breakpoint command remove"); 606 list_command_object->SetCommandName ("breakpoint command list"); 607 608 status = LoadSubCommand (interpreter, "add", add_command_object); 609 status = LoadSubCommand (interpreter, "remove", remove_command_object); 610 status = LoadSubCommand (interpreter, "list", list_command_object); 611 } 612 613 614 CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand () 615 { 616 } 617 618 bool 619 CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction 620 ( 621 void *baton, 622 StoppointCallbackContext *context, 623 lldb::user_id_t break_id, 624 lldb::user_id_t break_loc_id 625 ) 626 { 627 bool ret_value = true; 628 if (baton == NULL) 629 return true; 630 631 632 BreakpointOptions::CommandData *data = (BreakpointOptions::CommandData *) baton; 633 StringList &commands = data->user_source; 634 635 if (commands.GetSize() > 0) 636 { 637 uint32_t num_commands = commands.GetSize(); 638 CommandReturnObject result; 639 if (context->exe_ctx.target) 640 { 641 642 Debugger &debugger = context->exe_ctx.target->GetDebugger(); 643 CommandInterpreter &interpreter = debugger.GetCommandInterpreter(); 644 645 FILE *out_fh = debugger.GetOutputFileHandle(); 646 FILE *err_fh = debugger.GetErrorFileHandle(); 647 648 uint32_t i; 649 for (i = 0; i < num_commands; ++i) 650 { 651 652 // First time through we use the context from the stoppoint, after that we use whatever 653 // has been set by the previous command. 654 655 if (!interpreter.HandleCommand (commands.GetStringAtIndex(i), false, result, &context->exe_ctx)) 656 break; 657 658 // FIXME: This isn't really the right way to do this. We should be able to peek at the public 659 // to see if there is any new events, but that is racey, since the internal process thread has to run and 660 // deliver the event to the public queue before a run will show up. So for now we check 661 // the internal thread state. 662 663 lldb::StateType internal_state = context->exe_ctx.process->GetPrivateState(); 664 if (internal_state != eStateStopped) 665 { 666 if (i < num_commands - 1) 667 { 668 if (out_fh) 669 ::fprintf (out_fh, "Short-circuiting command execution because target state changed to %s." 670 " last command: \"%s\"\n", StateAsCString(internal_state), 671 commands.GetStringAtIndex(i)); 672 } 673 break; 674 } 675 676 if (out_fh) 677 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 678 if (err_fh) 679 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 680 result.Clear(); 681 result.SetStatus (eReturnStatusSuccessFinishNoResult); 682 } 683 684 if (err_fh && !result.Succeeded() && i < num_commands) 685 ::fprintf (err_fh, "Attempt to execute '%s' failed.\n", commands.GetStringAtIndex(i)); 686 687 if (out_fh) 688 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 689 690 if (err_fh) 691 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 692 } 693 } 694 return ret_value; 695 } 696 697