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 Args& command, 212 CommandContext *context, 213 CommandInterpreter *interpreter, 214 CommandReturnObject &result 215 ) 216 { 217 Target *target = context->GetTarget(); 218 219 if (target == NULL) 220 { 221 result.AppendError ("There is not a current executable; there are no breakpoints to which to add commands"); 222 result.SetStatus (eReturnStatusFailed); 223 return false; 224 } 225 226 const BreakpointList &breakpoints = target->GetBreakpointList(); 227 size_t num_breakpoints = breakpoints.GetSize(); 228 229 if (num_breakpoints == 0) 230 { 231 result.AppendError ("No breakpoints exist to have commands added"); 232 result.SetStatus (eReturnStatusFailed); 233 return false; 234 } 235 236 if (command.GetArgumentCount() == 0) 237 { 238 result.AppendError ("No breakpoint specified to which to add the commands"); 239 result.SetStatus (eReturnStatusFailed); 240 return false; 241 } 242 243 BreakpointIDList valid_bp_ids; 244 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 245 246 if (result.Succeeded()) 247 { 248 for (int i = 0; i < valid_bp_ids.Size(); ++i) 249 { 250 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 251 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 252 { 253 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 254 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 255 { 256 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 257 if (bp_loc_sp) 258 { 259 if (m_options.m_use_script_language) 260 { 261 interpreter->GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp_loc_sp->GetLocationOptions(), 262 result); 263 } 264 else 265 { 266 CollectDataForBreakpointCommandCallback (bp_loc_sp->GetLocationOptions(), result); 267 } 268 } 269 } 270 else 271 { 272 if (m_options.m_use_script_language) 273 { 274 interpreter->GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp->GetOptions(), 275 result); 276 } 277 else 278 { 279 CollectDataForBreakpointCommandCallback (bp->GetOptions(), result); 280 } 281 } 282 } 283 } 284 } 285 286 return result.Succeeded(); 287 } 288 289 Options * 290 CommandObjectBreakpointCommandAdd::GetOptions () 291 { 292 return &m_options; 293 } 294 295 const char *g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end."; 296 297 void 298 CommandObjectBreakpointCommandAdd::CollectDataForBreakpointCommandCallback 299 ( 300 BreakpointOptions *bp_options, 301 CommandReturnObject &result 302 ) 303 { 304 InputReaderSP reader_sp (new InputReader()); 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 Debugger::GetSharedInstance().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 = Debugger::GetSharedInstance().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 (Args& command, 407 CommandContext *context, 408 CommandInterpreter *interpreter, 409 CommandReturnObject &result) 410 { 411 Target *target = context->GetTarget(); 412 413 if (target == NULL) 414 { 415 result.AppendError ("There is not a current executable; there are no breakpoints from which to remove commands"); 416 result.SetStatus (eReturnStatusFailed); 417 return false; 418 } 419 420 const BreakpointList &breakpoints = target->GetBreakpointList(); 421 size_t num_breakpoints = breakpoints.GetSize(); 422 423 if (num_breakpoints == 0) 424 { 425 result.AppendError ("No breakpoints exist to have commands removed"); 426 result.SetStatus (eReturnStatusFailed); 427 return false; 428 } 429 430 if (command.GetArgumentCount() == 0) 431 { 432 result.AppendError ("No breakpoint specified from which to remove the commands"); 433 result.SetStatus (eReturnStatusFailed); 434 return false; 435 } 436 437 BreakpointIDList valid_bp_ids; 438 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 439 440 if (result.Succeeded()) 441 { 442 for (int i = 0; i < valid_bp_ids.Size(); ++i) 443 { 444 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 445 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 446 { 447 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 448 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 449 { 450 BreakpointLocationSP bp_loc_sp (bp->FindLocationByID (cur_bp_id.GetLocationID())); 451 if (bp_loc_sp) 452 bp_loc_sp->ClearCallback(); 453 else 454 { 455 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 456 cur_bp_id.GetBreakpointID(), 457 cur_bp_id.GetLocationID()); 458 result.SetStatus (eReturnStatusFailed); 459 return false; 460 } 461 } 462 else 463 { 464 bp->ClearCallback(); 465 } 466 } 467 } 468 } 469 return result.Succeeded(); 470 } 471 472 473 //------------------------------------------------------------------------- 474 // CommandObjectBreakpointCommandList 475 //------------------------------------------------------------------------- 476 477 CommandObjectBreakpointCommandList::CommandObjectBreakpointCommandList () : 478 CommandObject ("List", 479 "List the script or set of commands to be executed when the breakpoint is hit.", 480 "breakpoint command list <breakpoint-id>") 481 { 482 } 483 484 CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList () 485 { 486 } 487 488 bool 489 CommandObjectBreakpointCommandList::Execute (Args& command, 490 CommandContext *context, 491 CommandInterpreter *interpreter, 492 CommandReturnObject &result) 493 { 494 Target *target = context->GetTarget(); 495 496 if (target == NULL) 497 { 498 result.AppendError ("There is not a current executable; there are no breakpoints for which to list commands"); 499 result.SetStatus (eReturnStatusFailed); 500 return false; 501 } 502 503 const BreakpointList &breakpoints = target->GetBreakpointList(); 504 size_t num_breakpoints = breakpoints.GetSize(); 505 506 if (num_breakpoints == 0) 507 { 508 result.AppendError ("No breakpoints exist for which to list commands"); 509 result.SetStatus (eReturnStatusFailed); 510 return false; 511 } 512 513 if (command.GetArgumentCount() == 0) 514 { 515 result.AppendError ("No breakpoint specified for which to list the commands"); 516 result.SetStatus (eReturnStatusFailed); 517 return false; 518 } 519 520 BreakpointIDList valid_bp_ids; 521 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 522 523 if (result.Succeeded()) 524 { 525 for (int i = 0; i < valid_bp_ids.Size(); ++i) 526 { 527 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 528 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 529 { 530 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 531 532 if (bp) 533 { 534 const BreakpointOptions *bp_options = NULL; 535 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 536 { 537 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 538 if (bp_loc_sp) 539 bp_options = bp_loc_sp->GetOptionsNoCreate(); 540 else 541 { 542 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 543 cur_bp_id.GetBreakpointID(), 544 cur_bp_id.GetLocationID()); 545 result.SetStatus (eReturnStatusFailed); 546 return false; 547 } 548 } 549 else 550 { 551 bp_options = bp->GetOptions(); 552 } 553 554 if (bp_options) 555 { 556 StreamString id_str; 557 BreakpointID::GetCanonicalReference (&id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID()); 558 const Baton *baton = bp_options->GetBaton(); 559 if (baton) 560 { 561 result.GetOutputStream().Printf ("Breakpoint %s:\n", id_str.GetData()); 562 result.GetOutputStream().IndentMore (); 563 baton->GetDescription(&result.GetOutputStream(), eDescriptionLevelFull); 564 result.GetOutputStream().IndentLess (); 565 } 566 else 567 { 568 result.AppendMessageWithFormat ("Breakpoint %s does not have an associated command.\n", id_str.GetData()); 569 } 570 } 571 result.SetStatus (eReturnStatusSuccessFinishResult); 572 } 573 else 574 { 575 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", cur_bp_id.GetBreakpointID()); 576 result.SetStatus (eReturnStatusFailed); 577 } 578 579 } 580 } 581 } 582 583 return result.Succeeded(); 584 } 585 586 //------------------------------------------------------------------------- 587 // CommandObjectBreakpointCommand 588 //------------------------------------------------------------------------- 589 590 CommandObjectBreakpointCommand::CommandObjectBreakpointCommand (CommandInterpreter *interpreter) : 591 CommandObjectMultiword ("command", 592 "A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commmands').", 593 "command <sub-command> [<sub-command-options>] <breakpoint-id>") 594 { 595 bool status; 596 CommandObjectSP add_command_object (new CommandObjectBreakpointCommandAdd ()); 597 CommandObjectSP remove_command_object (new CommandObjectBreakpointCommandRemove ()); 598 CommandObjectSP list_command_object (new CommandObjectBreakpointCommandList ()); 599 600 add_command_object->SetCommandName ("breakpoint command add"); 601 remove_command_object->SetCommandName ("breakpoint command remove"); 602 list_command_object->SetCommandName ("breakpoint command list"); 603 604 status = LoadSubCommand (add_command_object, "add", interpreter); 605 status = LoadSubCommand (remove_command_object, "remove", interpreter); 606 status = LoadSubCommand (list_command_object, "list", interpreter); 607 } 608 609 610 CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand () 611 { 612 } 613 614 bool 615 CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction 616 ( 617 void *baton, 618 StoppointCallbackContext *context, 619 lldb::user_id_t break_id, 620 lldb::user_id_t break_loc_id 621 ) 622 { 623 bool ret_value = true; 624 if (baton == NULL) 625 return true; 626 627 628 BreakpointOptions::CommandData *data = (BreakpointOptions::CommandData *) baton; 629 StringList &commands = data->user_source; 630 631 if (commands.GetSize() > 0) 632 { 633 uint32_t num_commands = commands.GetSize(); 634 CommandInterpreter &interpreter = Debugger::GetSharedInstance().GetCommandInterpreter(); 635 CommandReturnObject result; 636 ExecutionContext exe_ctx = context->context; 637 638 FILE *out_fh = Debugger::GetSharedInstance().GetOutputFileHandle(); 639 FILE *err_fh = Debugger::GetSharedInstance().GetErrorFileHandle(); 640 641 642 uint32_t i; 643 for (i = 0; i < num_commands; ++i) 644 { 645 646 // First time through we use the context from the stoppoint, after that we use whatever 647 // has been set by the previous command. 648 649 if (!interpreter.HandleCommand (commands.GetStringAtIndex(i), false, result, &exe_ctx)) 650 break; 651 652 // FIXME: This isn't really the right way to do this. We should be able to peek at the public 653 // to see if there is any new events, but that is racey, since the internal process thread has to run and 654 // deliver the event to the public queue before a run will show up. So for now we check 655 // the internal thread state. 656 657 lldb::StateType internal_state = exe_ctx.process->GetPrivateState(); 658 if (internal_state != eStateStopped) 659 { 660 if (i < num_commands - 1) 661 { 662 if (out_fh) 663 ::fprintf (out_fh, "Short-circuiting command execution because target state changed to %s." 664 " last command: \"%s\"\n", StateAsCString(internal_state), 665 commands.GetStringAtIndex(i)); 666 } 667 break; 668 } 669 670 // First time through we use the context from the stoppoint, after that we use whatever 671 // has been set by the previous command. 672 exe_ctx = Debugger::GetSharedInstance().GetCurrentExecutionContext(); 673 674 675 if (out_fh) 676 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 677 if (err_fh) 678 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 679 result.Clear(); 680 result.SetStatus (eReturnStatusSuccessFinishNoResult); 681 } 682 683 if (err_fh && !result.Succeeded() && i < num_commands) 684 ::fprintf (err_fh, "Attempt to execute '%s' failed.\n", commands.GetStringAtIndex(i)); 685 686 if (out_fh) 687 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 688 689 if (err_fh) 690 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 691 } 692 return ret_value; 693 } 694 695