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 const size_t count = valid_bp_ids.GetSize(); 248 for (size_t i = 0; i < count; ++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 (interpreter, 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 (interpreter, 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 CommandInterpreter &interpreter, 301 BreakpointOptions *bp_options, 302 CommandReturnObject &result 303 ) 304 { 305 InputReaderSP reader_sp (new InputReader(interpreter.GetDebugger())); 306 std::auto_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); 307 if (reader_sp && data_ap.get()) 308 { 309 BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); 310 bp_options->SetCallback (CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction, baton_sp); 311 312 Error err (reader_sp->Initialize (CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback, 313 bp_options, // baton 314 eInputReaderGranularityLine, // token size, to pass to callback function 315 "DONE", // end token 316 "> ", // prompt 317 true)); // echo input 318 if (err.Success()) 319 { 320 interpreter.GetDebugger().PushInputReader (reader_sp); 321 result.SetStatus (eReturnStatusSuccessFinishNoResult); 322 } 323 else 324 { 325 result.AppendError (err.AsCString()); 326 result.SetStatus (eReturnStatusFailed); 327 } 328 } 329 else 330 { 331 result.AppendError("out of memory"); 332 result.SetStatus (eReturnStatusFailed); 333 } 334 335 } 336 337 size_t 338 CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback 339 ( 340 void *baton, 341 InputReader &reader, 342 lldb::InputReaderAction notification, 343 const char *bytes, 344 size_t bytes_len 345 ) 346 { 347 FILE *out_fh = reader.GetDebugger().GetOutputFileHandle(); 348 349 switch (notification) 350 { 351 case eInputReaderActivate: 352 if (out_fh) 353 { 354 ::fprintf (out_fh, "%s\n", g_reader_instructions); 355 if (reader.GetPrompt()) 356 ::fprintf (out_fh, "%s", reader.GetPrompt()); 357 } 358 break; 359 360 case eInputReaderDeactivate: 361 break; 362 363 case eInputReaderReactivate: 364 if (out_fh && reader.GetPrompt()) 365 ::fprintf (out_fh, "%s", reader.GetPrompt()); 366 break; 367 368 case eInputReaderGotToken: 369 if (bytes && bytes_len && baton) 370 { 371 BreakpointOptions *bp_options = (BreakpointOptions *) baton; 372 if (bp_options) 373 { 374 Baton *bp_options_baton = bp_options->GetBaton(); 375 if (bp_options_baton) 376 ((BreakpointOptions::CommandData *)bp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len); 377 } 378 } 379 if (out_fh && !reader.IsDone() && reader.GetPrompt()) 380 ::fprintf (out_fh, "%s", reader.GetPrompt()); 381 break; 382 383 case eInputReaderDone: 384 break; 385 } 386 387 return bytes_len; 388 } 389 390 391 //------------------------------------------------------------------------- 392 // CommandObjectBreakpointCommandRemove 393 //------------------------------------------------------------------------- 394 395 CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () : 396 CommandObject ("remove", 397 "Remove the set of commands from a breakpoint.", 398 "breakpoint command remove <breakpoint-id>") 399 { 400 } 401 402 CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove () 403 { 404 } 405 406 bool 407 CommandObjectBreakpointCommandRemove::Execute 408 ( 409 CommandInterpreter &interpreter, 410 Args& command, 411 CommandReturnObject &result 412 ) 413 { 414 Target *target = interpreter.GetDebugger().GetCurrentTarget().get(); 415 416 if (target == NULL) 417 { 418 result.AppendError ("There is not a current executable; there are no breakpoints from which to remove commands"); 419 result.SetStatus (eReturnStatusFailed); 420 return false; 421 } 422 423 const BreakpointList &breakpoints = target->GetBreakpointList(); 424 size_t num_breakpoints = breakpoints.GetSize(); 425 426 if (num_breakpoints == 0) 427 { 428 result.AppendError ("No breakpoints exist to have commands removed"); 429 result.SetStatus (eReturnStatusFailed); 430 return false; 431 } 432 433 if (command.GetArgumentCount() == 0) 434 { 435 result.AppendError ("No breakpoint specified from which to remove the commands"); 436 result.SetStatus (eReturnStatusFailed); 437 return false; 438 } 439 440 BreakpointIDList valid_bp_ids; 441 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 442 443 if (result.Succeeded()) 444 { 445 const size_t count = valid_bp_ids.GetSize(); 446 for (size_t i = 0; i < count; ++i) 447 { 448 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 449 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 450 { 451 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 452 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 453 { 454 BreakpointLocationSP bp_loc_sp (bp->FindLocationByID (cur_bp_id.GetLocationID())); 455 if (bp_loc_sp) 456 bp_loc_sp->ClearCallback(); 457 else 458 { 459 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 460 cur_bp_id.GetBreakpointID(), 461 cur_bp_id.GetLocationID()); 462 result.SetStatus (eReturnStatusFailed); 463 return false; 464 } 465 } 466 else 467 { 468 bp->ClearCallback(); 469 } 470 } 471 } 472 } 473 return result.Succeeded(); 474 } 475 476 477 //------------------------------------------------------------------------- 478 // CommandObjectBreakpointCommandList 479 //------------------------------------------------------------------------- 480 481 CommandObjectBreakpointCommandList::CommandObjectBreakpointCommandList () : 482 CommandObject ("List", 483 "List the script or set of commands to be executed when the breakpoint is hit.", 484 "breakpoint command list <breakpoint-id>") 485 { 486 } 487 488 CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList () 489 { 490 } 491 492 bool 493 CommandObjectBreakpointCommandList::Execute 494 ( 495 CommandInterpreter &interpreter, 496 Args& command, 497 CommandReturnObject &result 498 ) 499 { 500 Target *target = interpreter.GetDebugger().GetCurrentTarget().get(); 501 502 if (target == NULL) 503 { 504 result.AppendError ("There is not a current executable; there are no breakpoints for which to list commands"); 505 result.SetStatus (eReturnStatusFailed); 506 return false; 507 } 508 509 const BreakpointList &breakpoints = target->GetBreakpointList(); 510 size_t num_breakpoints = breakpoints.GetSize(); 511 512 if (num_breakpoints == 0) 513 { 514 result.AppendError ("No breakpoints exist for which to list commands"); 515 result.SetStatus (eReturnStatusFailed); 516 return false; 517 } 518 519 if (command.GetArgumentCount() == 0) 520 { 521 result.AppendError ("No breakpoint specified for which to list the commands"); 522 result.SetStatus (eReturnStatusFailed); 523 return false; 524 } 525 526 BreakpointIDList valid_bp_ids; 527 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 528 529 if (result.Succeeded()) 530 { 531 const size_t count = valid_bp_ids.GetSize(); 532 for (size_t i = 0; i < count; ++i) 533 { 534 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 535 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 536 { 537 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 538 539 if (bp) 540 { 541 const BreakpointOptions *bp_options = NULL; 542 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 543 { 544 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 545 if (bp_loc_sp) 546 bp_options = bp_loc_sp->GetOptionsNoCreate(); 547 else 548 { 549 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 550 cur_bp_id.GetBreakpointID(), 551 cur_bp_id.GetLocationID()); 552 result.SetStatus (eReturnStatusFailed); 553 return false; 554 } 555 } 556 else 557 { 558 bp_options = bp->GetOptions(); 559 } 560 561 if (bp_options) 562 { 563 StreamString id_str; 564 BreakpointID::GetCanonicalReference (&id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID()); 565 const Baton *baton = bp_options->GetBaton(); 566 if (baton) 567 { 568 result.GetOutputStream().Printf ("Breakpoint %s:\n", id_str.GetData()); 569 result.GetOutputStream().IndentMore (); 570 baton->GetDescription(&result.GetOutputStream(), eDescriptionLevelFull); 571 result.GetOutputStream().IndentLess (); 572 } 573 else 574 { 575 result.AppendMessageWithFormat ("Breakpoint %s does not have an associated command.\n", id_str.GetData()); 576 } 577 } 578 result.SetStatus (eReturnStatusSuccessFinishResult); 579 } 580 else 581 { 582 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", cur_bp_id.GetBreakpointID()); 583 result.SetStatus (eReturnStatusFailed); 584 } 585 586 } 587 } 588 } 589 590 return result.Succeeded(); 591 } 592 593 //------------------------------------------------------------------------- 594 // CommandObjectBreakpointCommand 595 //------------------------------------------------------------------------- 596 597 CommandObjectBreakpointCommand::CommandObjectBreakpointCommand (CommandInterpreter &interpreter) : 598 CommandObjectMultiword ("command", 599 "A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commmands').", 600 "command <sub-command> [<sub-command-options>] <breakpoint-id>") 601 { 602 bool status; 603 CommandObjectSP add_command_object (new CommandObjectBreakpointCommandAdd ()); 604 CommandObjectSP remove_command_object (new CommandObjectBreakpointCommandRemove ()); 605 CommandObjectSP list_command_object (new CommandObjectBreakpointCommandList ()); 606 607 add_command_object->SetCommandName ("breakpoint command add"); 608 remove_command_object->SetCommandName ("breakpoint command remove"); 609 list_command_object->SetCommandName ("breakpoint command list"); 610 611 status = LoadSubCommand (interpreter, "add", add_command_object); 612 status = LoadSubCommand (interpreter, "remove", remove_command_object); 613 status = LoadSubCommand (interpreter, "list", list_command_object); 614 } 615 616 617 CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand () 618 { 619 } 620 621 bool 622 CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction 623 ( 624 void *baton, 625 StoppointCallbackContext *context, 626 lldb::user_id_t break_id, 627 lldb::user_id_t break_loc_id 628 ) 629 { 630 bool ret_value = true; 631 if (baton == NULL) 632 return true; 633 634 635 BreakpointOptions::CommandData *data = (BreakpointOptions::CommandData *) baton; 636 StringList &commands = data->user_source; 637 638 if (commands.GetSize() > 0) 639 { 640 uint32_t num_commands = commands.GetSize(); 641 CommandReturnObject result; 642 if (context->exe_ctx.target) 643 { 644 645 Debugger &debugger = context->exe_ctx.target->GetDebugger(); 646 CommandInterpreter &interpreter = debugger.GetCommandInterpreter(); 647 648 FILE *out_fh = debugger.GetOutputFileHandle(); 649 FILE *err_fh = debugger.GetErrorFileHandle(); 650 651 uint32_t i; 652 for (i = 0; i < num_commands; ++i) 653 { 654 655 // First time through we use the context from the stoppoint, after that we use whatever 656 // has been set by the previous command. 657 658 if (!interpreter.HandleCommand (commands.GetStringAtIndex(i), false, result, &context->exe_ctx)) 659 break; 660 661 // FIXME: This isn't really the right way to do this. We should be able to peek at the public 662 // to see if there is any new events, but that is racey, since the internal process thread has to run and 663 // deliver the event to the public queue before a run will show up. So for now we check 664 // the internal thread state. 665 666 lldb::StateType internal_state = context->exe_ctx.process->GetPrivateState(); 667 if (internal_state != eStateStopped) 668 { 669 if (i < num_commands - 1) 670 { 671 if (out_fh) 672 ::fprintf (out_fh, "Short-circuiting command execution because target state changed to %s." 673 " last command: \"%s\"\n", StateAsCString(internal_state), 674 commands.GetStringAtIndex(i)); 675 } 676 break; 677 } 678 679 if (out_fh) 680 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 681 if (err_fh) 682 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 683 result.Clear(); 684 result.SetStatus (eReturnStatusSuccessFinishNoResult); 685 } 686 687 if (err_fh && !result.Succeeded() && i < num_commands) 688 ::fprintf (err_fh, "Attempt to execute '%s' failed.\n", commands.GetStringAtIndex(i)); 689 690 if (out_fh) 691 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 692 693 if (err_fh) 694 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 695 } 696 } 697 return ret_value; 698 } 699 700