1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include "BreakpointManager.h" 7 8 #include <algorithm> 9 10 #include <AutoDeleter.h> 11 12 #include <kernel.h> 13 #include <util/AutoLock.h> 14 #include <vm/vm.h> 15 16 17 //#define TRACE_BREAKPOINT_MANAGER 18 #ifdef TRACE_BREAKPOINT_MANAGER 19 # define TRACE(x...) dprintf(x) 20 #else 21 # define TRACE(x...) do {} while (false) 22 #endif 23 24 25 // soft limit for the number of breakpoints 26 const int32 kMaxBreakpointCount = 10240; 27 28 29 BreakpointManager::InstalledBreakpoint::InstalledBreakpoint(addr_t address) 30 : 31 breakpoint(NULL), 32 address(address) 33 { 34 } 35 36 37 // #pragma mark - 38 39 40 BreakpointManager::BreakpointManager() 41 : 42 fBreakpointCount(0), 43 fWatchpointCount(0) 44 { 45 rw_lock_init(&fLock, "breakpoint manager"); 46 } 47 48 49 BreakpointManager::~BreakpointManager() 50 { 51 WriteLocker locker(fLock); 52 53 // delete the installed breakpoint objects 54 BreakpointTree::Iterator it = fBreakpoints.GetIterator(); 55 while (InstalledBreakpoint* installedBreakpoint = it.Next()) { 56 it.Remove(); 57 58 // delete underlying software breakpoint 59 if (installedBreakpoint->breakpoint->software) 60 delete installedBreakpoint->breakpoint; 61 62 delete installedBreakpoint; 63 } 64 65 // delete the watchpoints 66 while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead()) 67 delete watchpoint; 68 69 // delete the hardware breakpoint objects 70 while (Breakpoint* breakpoint = fHardwareBreakpoints.RemoveHead()) 71 delete breakpoint; 72 73 rw_lock_destroy(&fLock); 74 } 75 76 77 status_t 78 BreakpointManager::Init() 79 { 80 // create objects for the hardware breakpoints 81 for (int32 i = 0; i < DEBUG_MAX_BREAKPOINTS; i++) { 82 Breakpoint* breakpoint = new(std::nothrow) Breakpoint; 83 if (breakpoint == NULL) 84 return B_NO_MEMORY; 85 86 breakpoint->address = 0; 87 breakpoint->installedBreakpoint = NULL; 88 breakpoint->used = false; 89 breakpoint->software = false; 90 91 fHardwareBreakpoints.Add(breakpoint); 92 } 93 94 return B_OK; 95 } 96 97 98 status_t 99 BreakpointManager::InstallBreakpoint(void* _address) 100 { 101 const addr_t address = (addr_t)_address; 102 103 WriteLocker locker(fLock); 104 105 if (fBreakpointCount >= kMaxBreakpointCount) 106 return B_BUSY; 107 108 // check whether there's already a breakpoint at the address 109 InstalledBreakpoint* installed = fBreakpoints.Lookup(address); 110 if (installed != NULL) 111 return B_BAD_VALUE; 112 113 // create the breakpoint object 114 installed = new(std::nothrow) InstalledBreakpoint(address); 115 if (installed == NULL) 116 return B_NO_MEMORY; 117 ObjectDeleter<InstalledBreakpoint> installedDeleter(installed); 118 119 // If we still have enough hardware breakpoints left, install a hardware 120 // breakpoint. 121 Breakpoint* breakpoint = _GetUnusedHardwareBreakpoint(false); 122 if (breakpoint != NULL) { 123 status_t error = _InstallHardwareBreakpoint(breakpoint, address); 124 if (error != B_OK) 125 return error; 126 127 breakpoint->installedBreakpoint = installed; 128 installed->breakpoint = breakpoint; 129 } else { 130 // install a software breakpoint 131 status_t error = _InstallSoftwareBreakpoint(installed, address); 132 if (error != B_OK) 133 return error; 134 } 135 136 fBreakpoints.Insert(installed); 137 installedDeleter.Detach(); 138 fBreakpointCount++; 139 140 return B_OK; 141 } 142 143 144 status_t 145 BreakpointManager::UninstallBreakpoint(void* _address) 146 { 147 const addr_t address = (addr_t)_address; 148 149 WriteLocker locker(fLock); 150 151 InstalledBreakpoint* installed = fBreakpoints.Lookup(address); 152 if (installed == NULL) 153 return B_BAD_VALUE; 154 155 if (installed->breakpoint->software) 156 _UninstallSoftwareBreakpoint(installed->breakpoint); 157 else 158 _UninstallHardwareBreakpoint(installed->breakpoint); 159 160 fBreakpoints.Remove(installed); 161 delete installed; 162 fBreakpointCount--; 163 164 return B_OK; 165 } 166 167 168 status_t 169 BreakpointManager::InstallWatchpoint(void* _address, uint32 type, int32 length) 170 { 171 const addr_t address = (addr_t)_address; 172 173 WriteLocker locker(fLock); 174 175 InstalledWatchpoint* watchpoint = _FindWatchpoint(address); 176 if (watchpoint != NULL) 177 return B_BAD_VALUE; 178 179 #if DEBUG_SHARED_BREAK_AND_WATCHPOINTS 180 // We need at least one hardware breakpoint for our breakpoint management. 181 if (fWatchpointCount + 1 >= DEBUG_MAX_WATCHPOINTS) 182 return B_BUSY; 183 #else 184 if (fWatchpointCount >= DEBUG_MAX_WATCHPOINTS) 185 return B_BUSY; 186 #endif 187 188 watchpoint = new(std::nothrow) InstalledWatchpoint; 189 if (watchpoint == NULL) 190 return B_NO_MEMORY; 191 ObjectDeleter<InstalledWatchpoint> watchpointDeleter(watchpoint); 192 193 status_t error = _InstallWatchpoint(watchpoint, address, type, length); 194 if (error != B_OK) 195 return error; 196 197 fWatchpoints.Add(watchpointDeleter.Detach()); 198 fWatchpointCount++; 199 return B_OK; 200 } 201 202 203 status_t 204 BreakpointManager::UninstallWatchpoint(void* address) 205 { 206 WriteLocker locker(fLock); 207 208 InstalledWatchpoint* watchpoint = _FindWatchpoint((addr_t)address); 209 if (watchpoint == NULL) 210 return B_BAD_VALUE; 211 212 ObjectDeleter<InstalledWatchpoint> deleter(watchpoint); 213 fWatchpoints.Remove(watchpoint); 214 fWatchpointCount--; 215 216 return _UninstallWatchpoint(watchpoint); 217 } 218 219 220 void 221 BreakpointManager::RemoveAllBreakpoints() 222 { 223 WriteLocker locker(fLock); 224 225 // remove the breakpoints 226 BreakpointTree::Iterator it = fBreakpoints.GetIterator(); 227 while (InstalledBreakpoint* installedBreakpoint = it.Next()) { 228 it.Remove(); 229 230 // uninstall underlying hard/software breakpoint 231 if (installedBreakpoint->breakpoint->software) 232 _UninstallSoftwareBreakpoint(installedBreakpoint->breakpoint); 233 else 234 _UninstallHardwareBreakpoint(installedBreakpoint->breakpoint); 235 236 delete installedBreakpoint; 237 } 238 239 // remove the watchpoints 240 while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead()) { 241 _UninstallWatchpoint(watchpoint); 242 delete watchpoint; 243 } 244 } 245 246 247 /*! \brief Returns whether the given address can be accessed in principle. 248 No check whether there's an actually accessible area is performed, though. 249 */ 250 /*static*/ bool 251 BreakpointManager::CanAccessAddress(const void* _address, bool write) 252 { 253 const addr_t address = (addr_t)_address; 254 255 // user addresses are always fine 256 if (IS_USER_ADDRESS(address)) 257 return true; 258 259 return false; 260 } 261 262 263 /*! \brief Reads data from user memory. 264 265 Tries to read \a size bytes of data from user memory address \a address 266 into the supplied buffer \a buffer. If only a part could be read the 267 function won't fail. The number of bytes actually read is return through 268 \a bytesRead. 269 270 \param address The user memory address from which to read. 271 \param buffer The buffer into which to write. 272 \param size The number of bytes to read. 273 \param bytesRead Will be set to the number of bytes actually read. 274 \return \c B_OK, if reading went fine. Then \a bytesRead will be set to 275 the amount of data actually read. An error indicates that nothing 276 has been read. 277 */ 278 status_t 279 BreakpointManager::ReadMemory(const void* _address, void* buffer, size_t size, 280 size_t& bytesRead) 281 { 282 const addr_t address = (addr_t)_address; 283 284 ReadLocker locker(fLock); 285 286 status_t error = _ReadMemory(address, buffer, size, bytesRead); 287 if (error != B_OK) 288 return error; 289 290 // If we have software breakpoints installed, fix the buffer not to contain 291 // any of them. 292 293 // address of the first possibly intersecting software breakpoint 294 const addr_t startAddress 295 = std::max(address, (addr_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1) 296 - (DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1); 297 298 BreakpointTree::Iterator it = fBreakpoints.GetIterator(startAddress, true, 299 true); 300 while (InstalledBreakpoint* installed = it.Next()) { 301 Breakpoint* breakpoint = installed->breakpoint; 302 if (breakpoint->address >= address + size) 303 break; 304 305 if (breakpoint->software) { 306 // Software breakpoint intersects -- replace the read data with 307 // the data saved in the breakpoint object. 308 addr_t minAddress = std::max(breakpoint->address, address); 309 size_t toCopy = std::min(address + size, 310 breakpoint->address + DEBUG_SOFTWARE_BREAKPOINT_SIZE) 311 - minAddress; 312 memcpy((uint8*)buffer + (minAddress - address), 313 breakpoint->softwareData + (minAddress - breakpoint->address), 314 toCopy); 315 } 316 } 317 318 return B_OK; 319 } 320 321 322 status_t 323 BreakpointManager::WriteMemory(void* _address, const void* _buffer, size_t size, 324 size_t& bytesWritten) 325 { 326 bytesWritten = 0; 327 328 if (size == 0) 329 return B_OK; 330 331 addr_t address = (addr_t)_address; 332 const uint8* buffer = (uint8*)_buffer; 333 334 WriteLocker locker(fLock); 335 336 // We don't want to overwrite software breakpoints, so things are a bit more 337 // complicated. We iterate through the intersecting software breakpoints, 338 // writing the memory between them normally, but skipping the breakpoints 339 // itself. We write into their softwareData instead. 340 341 // Get the first breakpoint -- if it starts before the address, we'll 342 // handle it separately to make things in the main loop simpler. 343 const addr_t startAddress 344 = std::max(address, (addr_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1) 345 - (DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1); 346 347 BreakpointTree::Iterator it = fBreakpoints.GetIterator(startAddress, true, 348 true); 349 InstalledBreakpoint* installed = it.Next(); 350 while (installed != NULL) { 351 Breakpoint* breakpoint = installed->breakpoint; 352 if (breakpoint->address >= address) 353 break; 354 355 if (breakpoint->software) { 356 // We've got a breakpoint that is partially intersecting with the 357 // beginning of the address range to write. 358 size_t toCopy = std::min(address + size, 359 breakpoint->address + DEBUG_SOFTWARE_BREAKPOINT_SIZE) 360 - address; 361 memcpy(breakpoint->softwareData + (address - breakpoint->address), 362 buffer, toCopy); 363 364 address += toCopy; 365 size -= toCopy; 366 bytesWritten += toCopy; 367 buffer += toCopy; 368 } 369 370 installed = it.Next(); 371 } 372 373 // loop through the breakpoints intersecting with the range 374 while (installed != NULL) { 375 Breakpoint* breakpoint = installed->breakpoint; 376 if (breakpoint->address >= address + size) 377 break; 378 379 if (breakpoint->software) { 380 // write the data up to the breakpoint (if any) 381 size_t toCopy = breakpoint->address - address; 382 if (toCopy > 0) { 383 size_t chunkWritten; 384 status_t error = _WriteMemory(address, buffer, toCopy, 385 chunkWritten); 386 if (error != B_OK) 387 return bytesWritten > 0 ? B_OK : error; 388 389 address += chunkWritten; 390 size -= chunkWritten; 391 bytesWritten += chunkWritten; 392 buffer += chunkWritten; 393 394 if (chunkWritten < toCopy) 395 return B_OK; 396 } 397 398 // write to the breakpoint data 399 toCopy = std::min(size, (size_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE); 400 memcpy(breakpoint->softwareData, buffer, toCopy); 401 402 address += toCopy; 403 size -= toCopy; 404 bytesWritten += toCopy; 405 buffer += toCopy; 406 } 407 408 installed = it.Next(); 409 } 410 411 // write remaining data 412 if (size > 0) { 413 size_t chunkWritten; 414 status_t error = _WriteMemory(address, buffer, size, chunkWritten); 415 if (error != B_OK) 416 return bytesWritten > 0 ? B_OK : error; 417 418 bytesWritten += chunkWritten; 419 } 420 421 return B_OK; 422 } 423 424 425 void 426 BreakpointManager::PrepareToContinue(void* _address) 427 { 428 const addr_t address = (addr_t)_address; 429 430 WriteLocker locker(fLock); 431 432 // Check whether there's a software breakpoint at the continuation address. 433 InstalledBreakpoint* installed = fBreakpoints.Lookup(address); 434 if (installed == NULL || !installed->breakpoint->software) 435 return; 436 437 // We need to replace the software breakpoint by a hardware one, or 438 // we can't continue the thread. 439 Breakpoint* breakpoint = _GetUnusedHardwareBreakpoint(true); 440 if (breakpoint == NULL) { 441 dprintf("Failed to allocate a hardware breakpoint.\n"); 442 return; 443 } 444 445 status_t error = _InstallHardwareBreakpoint(breakpoint, address); 446 if (error != B_OK) 447 return; 448 449 _UninstallSoftwareBreakpoint(installed->breakpoint); 450 451 breakpoint->installedBreakpoint = installed; 452 installed->breakpoint = breakpoint; 453 } 454 455 456 BreakpointManager::Breakpoint* 457 BreakpointManager::_GetUnusedHardwareBreakpoint(bool force) 458 { 459 // try to find a free one first 460 for (BreakpointList::Iterator it = fHardwareBreakpoints.GetIterator(); 461 Breakpoint* breakpoint = it.Next();) { 462 if (!breakpoint->used) 463 return breakpoint; 464 } 465 466 if (!force) 467 return NULL; 468 469 // replace one by a software breakpoint 470 for (BreakpointList::Iterator it = fHardwareBreakpoints.GetIterator(); 471 Breakpoint* breakpoint = it.Next();) { 472 if (breakpoint->installedBreakpoint == NULL) 473 continue; 474 475 status_t error = _InstallSoftwareBreakpoint( 476 breakpoint->installedBreakpoint, breakpoint->address); 477 if (error != B_OK) 478 continue; 479 480 if (_UninstallHardwareBreakpoint(breakpoint) == B_OK) 481 return breakpoint; 482 } 483 484 return NULL; 485 } 486 487 488 status_t 489 BreakpointManager::_InstallSoftwareBreakpoint(InstalledBreakpoint* installed, 490 addr_t address) 491 { 492 Breakpoint* breakpoint = new(std::nothrow) Breakpoint; 493 if (breakpoint == NULL) 494 return B_NO_MEMORY; 495 ObjectDeleter<Breakpoint> breakpointDeleter(breakpoint); 496 497 breakpoint->address = address; 498 breakpoint->installedBreakpoint = installed; 499 breakpoint->used = true; 500 breakpoint->software = true; 501 502 // save the memory where the software breakpoint shall be installed 503 size_t bytesTransferred; 504 status_t error = _ReadMemory(address, breakpoint->softwareData, 505 DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesTransferred); 506 if (error != B_OK) 507 return error; 508 if (bytesTransferred != DEBUG_SOFTWARE_BREAKPOINT_SIZE) 509 return B_BAD_ADDRESS; 510 511 // write the breakpoint code 512 error = _WriteMemory(address, DEBUG_SOFTWARE_BREAKPOINT, 513 DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesTransferred); 514 if (error != B_OK) 515 return error; 516 517 if (bytesTransferred < DEBUG_SOFTWARE_BREAKPOINT_SIZE) { 518 // breakpoint written partially only -- undo the written part 519 if (bytesTransferred > 0) { 520 size_t dummy; 521 _WriteMemory(address, breakpoint->softwareData, bytesTransferred, 522 dummy); 523 } 524 return B_BAD_ADDRESS; 525 } 526 527 installed->breakpoint = breakpoint; 528 breakpointDeleter.Detach(); 529 530 TRACE("installed software breakpoint at %#lx\n", address); 531 532 return B_OK; 533 } 534 535 536 status_t 537 BreakpointManager::_UninstallSoftwareBreakpoint(Breakpoint* breakpoint) 538 { 539 size_t bytesWritten; 540 _WriteMemory(breakpoint->address, breakpoint->softwareData, 541 DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesWritten); 542 543 TRACE("uninstalled software breakpoint at %#lx\n", breakpoint->address); 544 545 delete breakpoint; 546 return B_OK; 547 } 548 549 550 status_t 551 BreakpointManager::_InstallHardwareBreakpoint(Breakpoint* breakpoint, 552 addr_t address) 553 { 554 status_t error = arch_set_breakpoint((void*)address); 555 if (error != B_OK) 556 return error; 557 558 // move to the tail of the list 559 fHardwareBreakpoints.Remove(breakpoint); 560 fHardwareBreakpoints.Add(breakpoint); 561 562 TRACE("installed hardware breakpoint at %#lx\n", address); 563 564 breakpoint->address = address; 565 breakpoint->used = true; 566 return B_OK; 567 } 568 569 570 status_t 571 BreakpointManager::_UninstallHardwareBreakpoint(Breakpoint* breakpoint) 572 { 573 status_t error = arch_clear_breakpoint((void*)breakpoint->address); 574 if (error != B_OK) 575 return error; 576 577 TRACE("uninstalled hardware breakpoint at %#lx\n", breakpoint->address); 578 579 breakpoint->used = false; 580 breakpoint->installedBreakpoint = NULL; 581 return B_OK; 582 } 583 584 585 BreakpointManager::InstalledWatchpoint* 586 BreakpointManager::_FindWatchpoint(addr_t address) const 587 { 588 for (InstalledWatchpointList::ConstIterator it = fWatchpoints.GetIterator(); 589 InstalledWatchpoint* watchpoint = it.Next();) { 590 if (address == watchpoint->address) 591 return watchpoint; 592 } 593 594 return NULL; 595 } 596 597 598 status_t 599 BreakpointManager::_InstallWatchpoint(InstalledWatchpoint* watchpoint, 600 addr_t address, uint32 type, int32 length) 601 { 602 #if DEBUG_SHARED_BREAK_AND_WATCHPOINTS 603 // We need a hardware breakpoint. 604 watchpoint->breakpoint = _GetUnusedHardwareBreakpoint(true); 605 if (watchpoint->breakpoint == NULL) { 606 dprintf("Failed to allocate a hardware breakpoint for watchpoint.\n"); 607 return B_BUSY; 608 } 609 #endif 610 611 status_t error = arch_set_watchpoint((void*)address, type, length); 612 if (error != B_OK) 613 return error; 614 615 watchpoint->address = address; 616 617 #if DEBUG_SHARED_BREAK_AND_WATCHPOINTS 618 watchpoint->breakpoint->used = true; 619 #endif 620 621 return B_OK; 622 } 623 624 625 status_t 626 BreakpointManager::_UninstallWatchpoint(InstalledWatchpoint* watchpoint) 627 { 628 #if DEBUG_SHARED_BREAK_AND_WATCHPOINTS 629 watchpoint->breakpoint->used = false; 630 #endif 631 632 return arch_clear_watchpoint((void*)watchpoint->address); 633 } 634 635 636 status_t 637 BreakpointManager::_ReadMemory(const addr_t _address, void* _buffer, 638 size_t size, size_t& bytesRead) 639 { 640 const uint8* address = (const uint8*)_address; 641 uint8* buffer = (uint8*)_buffer; 642 643 // check the parameters 644 if (!CanAccessAddress(address, false)) 645 return B_BAD_ADDRESS; 646 if (size <= 0) 647 return B_BAD_VALUE; 648 649 // If the region to be read crosses page boundaries, we split it up into 650 // smaller chunks. 651 status_t error = B_OK; 652 bytesRead = 0; 653 while (size > 0) { 654 // check whether we're still in user address space 655 if (!CanAccessAddress(address, false)) { 656 error = B_BAD_ADDRESS; 657 break; 658 } 659 660 // don't cross page boundaries in a single read 661 int32 toRead = size; 662 int32 maxRead = B_PAGE_SIZE - (addr_t)address % B_PAGE_SIZE; 663 if (toRead > maxRead) 664 toRead = maxRead; 665 666 error = user_memcpy(buffer, address, toRead); 667 if (error != B_OK) 668 break; 669 670 bytesRead += toRead; 671 address += toRead; 672 buffer += toRead; 673 size -= toRead; 674 } 675 676 // If reading fails, we only fail, if we haven't read anything yet. 677 if (error != B_OK) { 678 if (bytesRead > 0) 679 return B_OK; 680 return error; 681 } 682 683 return B_OK; 684 } 685 686 687 status_t 688 BreakpointManager::_WriteMemory(addr_t _address, const void* _buffer, 689 size_t size, size_t& bytesWritten) 690 { 691 uint8* address = (uint8*)_address; 692 const uint8* buffer = (const uint8*)_buffer; 693 694 // check the parameters 695 if (!CanAccessAddress(address, true)) 696 return B_BAD_ADDRESS; 697 if (size <= 0) 698 return B_BAD_VALUE; 699 700 // If the region to be written crosses area boundaries, we split it up into 701 // smaller chunks. 702 status_t error = B_OK; 703 bytesWritten = 0; 704 while (size > 0) { 705 // check whether we're still in user address space 706 if (!CanAccessAddress(address, true)) { 707 error = B_BAD_ADDRESS; 708 break; 709 } 710 711 // get the area for the address (we need to use _user_area_for(), since 712 // we're looking for a user area) 713 area_id area = _user_area_for(address); 714 if (area < 0) { 715 TRACE("BreakpointManager::_WriteMemory(): area not found for " 716 "address: %p: %lx\n", address, area); 717 error = area; 718 break; 719 } 720 721 area_info areaInfo; 722 status_t error = get_area_info(area, &areaInfo); 723 if (error != B_OK) { 724 TRACE("BreakpointManager::_WriteMemory(): failed to get info for " 725 "area %ld: %lx\n", area, error); 726 error = B_BAD_ADDRESS; 727 break; 728 } 729 730 // restrict this round of writing to the found area 731 int32 toWrite = size; 732 int32 maxWrite = (uint8*)areaInfo.address + areaInfo.size - address; 733 if (toWrite > maxWrite) 734 toWrite = maxWrite; 735 736 // if the area is read-only, we temporarily need to make it writable 737 bool protectionChanged = false; 738 if (!(areaInfo.protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA))) { 739 error = set_area_protection(area, 740 areaInfo.protection | B_WRITE_AREA); 741 if (error != B_OK) { 742 TRACE("BreakpointManager::_WriteMemory(): failed to set new " 743 "protection for area %ld: %lx\n", area, error); 744 break; 745 } 746 protectionChanged = true; 747 } 748 749 // copy the memory 750 error = user_memcpy(address, buffer, toWrite); 751 752 // reset the area protection 753 if (protectionChanged) 754 set_area_protection(area, areaInfo.protection); 755 756 if (error != B_OK) { 757 TRACE("BreakpointManager::_WriteMemory(): user_memcpy() failed: " 758 "%lx\n", error); 759 break; 760 } 761 762 bytesWritten += toWrite; 763 address += toWrite; 764 buffer += toWrite; 765 size -= toWrite; 766 } 767 768 // If writing fails, we only fail, if we haven't written anything yet. 769 if (error != B_OK) { 770 if (bytesWritten > 0) 771 return B_OK; 772 return error; 773 } 774 775 return B_OK; 776 } 777