| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (c) 2011 The Native Client Authors. All rights reserved. | 2 * Copyright (c) 2011 The Native Client Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 /* | 7 /* |
| 8 * NaCl helper functions to deal with elf images | 8 * NaCl helper functions to deal with elf images |
| 9 */ | 9 */ |
| 10 | 10 |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 148 | 148 |
| 149 | 149 |
| 150 NaClErrorCode NaClElfImageValidateElfHeader(struct NaClElfImage *image) { | 150 NaClErrorCode NaClElfImageValidateElfHeader(struct NaClElfImage *image) { |
| 151 const Elf_Ehdr *hdr = &image->ehdr; | 151 const Elf_Ehdr *hdr = &image->ehdr; |
| 152 | 152 |
| 153 if (memcmp(hdr->e_ident, ELFMAG, SELFMAG)) { | 153 if (memcmp(hdr->e_ident, ELFMAG, SELFMAG)) { |
| 154 NaClLog(LOG_ERROR, "bad elf magic\n"); | 154 NaClLog(LOG_ERROR, "bad elf magic\n"); |
| 155 return LOAD_BAD_ELF_MAGIC; | 155 return LOAD_BAD_ELF_MAGIC; |
| 156 } | 156 } |
| 157 | 157 |
| 158 #if NACL_TARGET_SUBARCH == 64 | |
| 159 if (ELFCLASS64 != hdr->e_ident[EI_CLASS]) { | |
| 160 NaClLog(LOG_ERROR, "bad elf class\n"); | |
| 161 return LOAD_NOT_64_BIT; | |
| 162 } | |
| 163 #else | |
| 164 if (ELFCLASS32 != hdr->e_ident[EI_CLASS]) { | 158 if (ELFCLASS32 != hdr->e_ident[EI_CLASS]) { |
| 165 NaClLog(LOG_ERROR, "bad elf class\n"); | 159 NaClLog(LOG_ERROR, "bad elf class\n"); |
| 166 return LOAD_NOT_32_BIT; | 160 return LOAD_NOT_32_BIT; |
| 167 } | 161 } |
| 168 #endif | |
| 169 | 162 |
| 170 if (ET_EXEC != hdr->e_type) { | 163 if (ET_EXEC != hdr->e_type) { |
| 171 NaClLog(LOG_ERROR, "non executable\n"); | 164 NaClLog(LOG_ERROR, "non executable\n"); |
| 172 return LOAD_NOT_EXEC; | 165 return LOAD_NOT_EXEC; |
| 173 } | 166 } |
| 174 | 167 |
| 175 if (EM_EXPECTED_BY_NACL != hdr->e_machine) { | 168 if (EM_EXPECTED_BY_NACL != hdr->e_machine) { |
| 176 NaClLog(LOG_ERROR, "bad machine: %"NACL_PRIxElf_Half"\n", hdr->e_machine); | 169 NaClLog(LOG_ERROR, "bad machine: %"NACL_PRIxElf_Half"\n", hdr->e_machine); |
| 177 return LOAD_BAD_MACHINE; | 170 return LOAD_BAD_MACHINE; |
| 178 } | 171 } |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 274 return LOAD_SEGMENT_BAD_LOC; | 267 return LOAD_SEGMENT_BAD_LOC; |
| 275 } | 268 } |
| 276 if (php->p_vaddr < NACL_TRAMPOLINE_END) { | 269 if (php->p_vaddr < NACL_TRAMPOLINE_END) { |
| 277 NaClLog(2, | 270 NaClLog(2, |
| 278 ("Segment %d: virtual address (0x%08"NACL_PRIxElf_Addr | 271 ("Segment %d: virtual address (0x%08"NACL_PRIxElf_Addr |
| 279 ") too low\n"), | 272 ") too low\n"), |
| 280 segnum, | 273 segnum, |
| 281 php->p_vaddr); | 274 php->p_vaddr); |
| 282 return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; | 275 return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; |
| 283 } | 276 } |
| 284 /* | 277 if (php->p_vaddr >= ((uint64_t) 1U << addr_bits) || |
| 285 * integer overflow? Elf_Addr and Elf_Word are uint32_t or | 278 ((uint64_t) 1U << addr_bits) - php->p_vaddr < php->p_memsz) { |
| 286 * uint64_t, so the addition/comparison is well defined. | 279 if (php->p_vaddr + php->p_memsz < php->p_vaddr) { |
| 287 */ | 280 NaClLog(2, |
| 288 if (php->p_vaddr + php->p_memsz < php->p_vaddr) { | 281 "Segment %d: p_memsz caused integer overflow\n", |
| 289 NaClLog(2, | 282 segnum); |
| 290 "Segment %d: p_memsz caused integer overflow\n", | 283 } else { |
| 291 segnum); | 284 NaClLog(2, |
| 292 return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; | 285 "Segment %d: too large, ends at 0x%08"NACL_PRIxElf_Addr"\n", |
| 293 } | 286 segnum, |
| 294 if (php->p_vaddr + php->p_memsz >= ((Elf_Addr) 1U << addr_bits)) { | 287 php->p_vaddr + php->p_memsz); |
| 295 NaClLog(2, | 288 } |
| 296 "Segment %d: too large, ends at 0x%08"NACL_PRIxElf_Addr"\n", | |
| 297 segnum, | |
| 298 php->p_vaddr + php->p_memsz); | |
| 299 return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; | 289 return LOAD_SEGMENT_OUTSIDE_ADDRSPACE; |
| 300 } | 290 } |
| 301 if (php->p_filesz > php->p_memsz) { | 291 if (php->p_filesz > php->p_memsz) { |
| 302 NaClLog(2, | 292 NaClLog(2, |
| 303 ("Segment %d: file size 0x%08"NACL_PRIxElf_Xword" larger" | 293 ("Segment %d: file size 0x%08"NACL_PRIxElf_Xword" larger" |
| 304 " than memory size 0x%08"NACL_PRIxElf_Xword"\n"), | 294 " than memory size 0x%08"NACL_PRIxElf_Xword"\n"), |
| 305 segnum, | 295 segnum, |
| 306 php->p_filesz, | 296 php->p_filesz, |
| 307 php->p_memsz); | 297 php->p_memsz); |
| 308 return LOAD_SEGMENT_BAD_PARAM; | 298 return LOAD_SEGMENT_BAD_PARAM; |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 356 */ | 346 */ |
| 357 | 347 |
| 358 return LOAD_OK; | 348 return LOAD_OK; |
| 359 } | 349 } |
| 360 | 350 |
| 361 | 351 |
| 362 struct NaClElfImage *NaClElfImageNew(struct Gio *gp, | 352 struct NaClElfImage *NaClElfImageNew(struct Gio *gp, |
| 363 NaClErrorCode *err_code) { | 353 NaClErrorCode *err_code) { |
| 364 struct NaClElfImage *result; | 354 struct NaClElfImage *result; |
| 365 struct NaClElfImage image; | 355 struct NaClElfImage image; |
| 356 union { |
| 357 Elf32_Ehdr ehdr32; |
| 358 #if NACL_TARGET_SUBARCH == 64 |
| 359 Elf64_Ehdr ehdr64; |
| 360 #endif |
| 361 } ehdr; |
| 366 int cur_ph; | 362 int cur_ph; |
| 367 | 363 |
| 368 memset(image.loadable, 0, sizeof image.loadable); | 364 memset(image.loadable, 0, sizeof image.loadable); |
| 369 if (-1 == (*gp->vtbl->Seek)(gp, 0, 0)) { | 365 if (-1 == (*gp->vtbl->Seek)(gp, 0, 0)) { |
| 370 NaClLog(2, "could not seek to beginning of Gio object containing nexe\n"); | 366 NaClLog(2, "could not seek to beginning of Gio object containing nexe\n"); |
| 371 if (NULL != err_code) { | 367 if (NULL != err_code) { |
| 372 *err_code = LOAD_READ_ERROR; | 368 *err_code = LOAD_READ_ERROR; |
| 373 } | 369 } |
| 374 return 0; | 370 return 0; |
| 375 } | 371 } |
| 372 |
| 373 /* |
| 374 * We read the larger size of an ELFCLASS64 header even if it turns out |
| 375 * we're reading an ELFCLASS32 file. No usable ELFCLASS32 binary could |
| 376 * be so small that it's not larger than Elf64_Ehdr anyway. |
| 377 */ |
| 376 if ((*gp->vtbl->Read)(gp, | 378 if ((*gp->vtbl->Read)(gp, |
| 377 &image.ehdr, | 379 &ehdr, |
| 378 sizeof image.ehdr) | 380 sizeof ehdr) |
| 379 != sizeof image.ehdr) { | 381 != sizeof ehdr) { |
| 380 if (NULL != err_code) { | 382 if (NULL != err_code) { |
| 381 *err_code = LOAD_READ_ERROR; | 383 *err_code = LOAD_READ_ERROR; |
| 382 } | 384 } |
| 383 NaClLog(2, "could not load elf headers\n"); | 385 NaClLog(2, "could not load elf headers\n"); |
| 384 return 0; | 386 return 0; |
| 385 } | 387 } |
| 386 | 388 |
| 389 #if NACL_TARGET_SUBARCH == 64 |
| 390 if (ELFCLASS64 == ehdr.ehdr64.e_ident[EI_CLASS]) { |
| 391 /* |
| 392 * Convert ELFCLASS64 format to ELFCLASS32 format. |
| 393 * The initial four fields are the same in both classes. |
| 394 */ |
| 395 memcpy(image.ehdr.e_ident, ehdr.ehdr64.e_ident, EI_NIDENT); |
| 396 image.ehdr.e_ident[EI_CLASS] = ELFCLASS32; |
| 397 image.ehdr.e_type = ehdr.ehdr64.e_type; |
| 398 image.ehdr.e_machine = ehdr.ehdr64.e_machine; |
| 399 image.ehdr.e_version = ehdr.ehdr64.e_version; |
| 400 if (ehdr.ehdr64.e_entry > 0xffffffffU || |
| 401 ehdr.ehdr64.e_phoff > 0xffffffffU || |
| 402 ehdr.ehdr64.e_shoff > 0xffffffffU) { |
| 403 if (NULL != err_code) { |
| 404 *err_code = LOAD_EHDR_OVERFLOW; |
| 405 } |
| 406 NaClLog(2, "ELFCLASS64 file header fields overflow 32 bits\n"); |
| 407 return 0; |
| 408 } |
| 409 image.ehdr.e_entry = (Elf32_Addr) ehdr.ehdr64.e_entry; |
| 410 image.ehdr.e_phoff = (Elf32_Off) ehdr.ehdr64.e_phoff; |
| 411 image.ehdr.e_shoff = (Elf32_Off) ehdr.ehdr64.e_shoff; |
| 412 image.ehdr.e_flags = ehdr.ehdr64.e_flags; |
| 413 if (ehdr.ehdr64.e_ehsize != sizeof(ehdr.ehdr64)) { |
| 414 if (NULL != err_code) { |
| 415 *err_code = LOAD_BAD_EHSIZE; |
| 416 } |
| 417 NaClLog(2, "ELFCLASS64 file e_ehsize != %d\n", (int) sizeof(ehdr.ehdr64)); |
| 418 return 0; |
| 419 } |
| 420 image.ehdr.e_ehsize = sizeof(image.ehdr); |
| 421 image.ehdr.e_phentsize = sizeof(image.phdrs[0]); |
| 422 image.ehdr.e_phnum = ehdr.ehdr64.e_phnum; |
| 423 image.ehdr.e_shentsize = ehdr.ehdr64.e_shentsize; |
| 424 image.ehdr.e_shnum = ehdr.ehdr64.e_shnum; |
| 425 image.ehdr.e_shstrndx = ehdr.ehdr64.e_shstrndx; |
| 426 } else |
| 427 #endif |
| 428 { |
| 429 image.ehdr = ehdr.ehdr32; |
| 430 } |
| 431 |
| 387 NaClDumpElfHeader(2, &image.ehdr); | 432 NaClDumpElfHeader(2, &image.ehdr); |
| 388 | 433 |
| 389 /* read program headers */ | 434 /* read program headers */ |
| 390 if (image.ehdr.e_phnum > NACL_MAX_PROGRAM_HEADERS) { | 435 if (image.ehdr.e_phnum > NACL_MAX_PROGRAM_HEADERS) { |
| 391 if (NULL != err_code) | 436 if (NULL != err_code) |
| 392 *err_code = LOAD_TOO_MANY_PROG_HDRS; | 437 *err_code = LOAD_TOO_MANY_PROG_HDRS; |
| 393 NaClLog(2, "too many prog headers\n"); | 438 NaClLog(2, "too many prog headers\n"); |
| 394 return 0; | 439 return 0; |
| 395 } | 440 } |
| 396 | 441 |
| 397 if (image.ehdr.e_phentsize < sizeof image.phdrs[0]) { | |
| 398 if (NULL != err_code) { | |
| 399 *err_code = LOAD_PROG_HDR_SIZE_TOO_SMALL; | |
| 400 } | |
| 401 NaClLog(2, "bad prog headers size\n"); | |
| 402 NaClLog(2, " image.ehdr.e_phentsize = 0x%"NACL_PRIxElf_Half"\n", | |
| 403 image.ehdr.e_phentsize); | |
| 404 NaClLog(2, " sizeof image.phdrs[0] = 0x%"NACL_PRIxS"\n", | |
| 405 sizeof image.phdrs[0]); | |
| 406 return 0; | |
| 407 } | |
| 408 | |
| 409 /* | |
| 410 * NB: cast from e_phoff to off_t may not be valid, since off_t can be | |
| 411 * smaller than Elf64_off, but since invalid values will be rejected | |
| 412 * by Seek() the cast is safe (cf bsy) | |
| 413 */ | |
| 414 if ((*gp->vtbl->Seek)(gp, | 442 if ((*gp->vtbl->Seek)(gp, |
| 415 (off_t) image.ehdr.e_phoff, | 443 (off_t) image.ehdr.e_phoff, |
| 416 SEEK_SET) == (off_t) -1) { | 444 SEEK_SET) == (off_t) -1) { |
| 417 if (NULL != err_code) { | 445 if (NULL != err_code) { |
| 418 *err_code = LOAD_READ_ERROR; | 446 *err_code = LOAD_READ_ERROR; |
| 419 } | 447 } |
| 420 NaClLog(2, "cannot seek tp prog headers\n"); | 448 NaClLog(2, "cannot seek tp prog headers\n"); |
| 421 return 0; | 449 return 0; |
| 422 } | 450 } |
| 423 | 451 |
| 424 if ((size_t) (*gp->vtbl->Read)(gp, | 452 #if NACL_TARGET_SUBARCH == 64 |
| 425 &image.phdrs[0], | 453 if (ELFCLASS64 == ehdr.ehdr64.e_ident[EI_CLASS]) { |
| 426 image.ehdr.e_phnum * sizeof image.phdrs[0]) | 454 /* |
| 427 != (image.ehdr.e_phnum * sizeof image.phdrs[0])) { | 455 * We'll load the 64-bit phdrs and convert them to 32-bit format. |
| 428 if (NULL != err_code) { | 456 */ |
| 429 *err_code = LOAD_READ_ERROR; | 457 Elf64_Phdr phdr64[NACL_MAX_PROGRAM_HEADERS]; |
| 458 |
| 459 if (ehdr.ehdr64.e_phentsize != sizeof(Elf64_Phdr)) { |
| 460 if (NULL != err_code) { |
| 461 *err_code = LOAD_BAD_PHENTSIZE; |
| 462 } |
| 463 NaClLog(2, "bad prog headers size\n"); |
| 464 NaClLog(2, " ehdr64.e_phentsize = 0x%"NACL_PRIxElf_Half"\n", |
| 465 ehdr.ehdr64.e_phentsize); |
| 466 NaClLog(2, " sizeof(Elf64_Phdr) = 0x%"NACL_PRIxS"\n", |
| 467 sizeof(Elf64_Phdr)); |
| 468 return 0; |
| 430 } | 469 } |
| 431 NaClLog(2, "cannot load tp prog headers\n"); | 470 |
| 432 return 0; | 471 /* |
| 472 * We know the multiplication won't overflow since we rejected |
| 473 * e_phnum values larger than the small constant NACL_MAX_PROGRAM_HEADERS. |
| 474 */ |
| 475 if ((size_t) (*gp->vtbl->Read)(gp, |
| 476 &phdr64[0], |
| 477 image.ehdr.e_phnum * sizeof phdr64[0]) |
| 478 != (image.ehdr.e_phnum * sizeof phdr64[0])) { |
| 479 if (NULL != err_code) { |
| 480 *err_code = LOAD_READ_ERROR; |
| 481 } |
| 482 NaClLog(2, "cannot load tp prog headers\n"); |
| 483 return 0; |
| 484 } |
| 485 |
| 486 for (cur_ph = 0; cur_ph < image.ehdr.e_phnum; ++cur_ph) { |
| 487 if (phdr64[cur_ph].p_offset > 0xffffffffU || |
| 488 phdr64[cur_ph].p_vaddr > 0xffffffffU || |
| 489 phdr64[cur_ph].p_paddr > 0xffffffffU || |
| 490 phdr64[cur_ph].p_filesz > 0xffffffffU || |
| 491 phdr64[cur_ph].p_memsz > 0xffffffffU || |
| 492 phdr64[cur_ph].p_align > 0xffffffffU) { |
| 493 if (NULL != err_code) { |
| 494 *err_code = LOAD_PHDR_OVERFLOW; |
| 495 } |
| 496 NaClLog(2, "ELFCLASS64 program header fields overflow 32 bits\n"); |
| 497 return 0; |
| 498 } |
| 499 image.phdrs[cur_ph].p_type = phdr64[cur_ph].p_type; |
| 500 image.phdrs[cur_ph].p_offset = (Elf32_Off) phdr64[cur_ph].p_offset; |
| 501 image.phdrs[cur_ph].p_vaddr = (Elf32_Addr) phdr64[cur_ph].p_vaddr; |
| 502 image.phdrs[cur_ph].p_paddr = (Elf32_Addr) phdr64[cur_ph].p_paddr; |
| 503 image.phdrs[cur_ph].p_filesz = (Elf32_Word) phdr64[cur_ph].p_filesz; |
| 504 image.phdrs[cur_ph].p_memsz = (Elf32_Word) phdr64[cur_ph].p_memsz; |
| 505 image.phdrs[cur_ph].p_flags = phdr64[cur_ph].p_flags; |
| 506 image.phdrs[cur_ph].p_align = (Elf32_Word) phdr64[cur_ph].p_align; |
| 507 } |
| 508 } else |
| 509 #endif |
| 510 { |
| 511 if (image.ehdr.e_phentsize != sizeof image.phdrs[0]) { |
| 512 if (NULL != err_code) { |
| 513 *err_code = LOAD_BAD_PHENTSIZE; |
| 514 } |
| 515 NaClLog(2, "bad prog headers size\n"); |
| 516 NaClLog(2, " image.ehdr.e_phentsize = 0x%"NACL_PRIxElf_Half"\n", |
| 517 image.ehdr.e_phentsize); |
| 518 NaClLog(2, " sizeof image.phdrs[0] = 0x%"NACL_PRIxS"\n", |
| 519 sizeof image.phdrs[0]); |
| 520 return 0; |
| 521 } |
| 522 |
| 523 if ((size_t) (*gp->vtbl->Read)(gp, |
| 524 &image.phdrs[0], |
| 525 image.ehdr.e_phnum * sizeof image.phdrs[0]) |
| 526 != (image.ehdr.e_phnum * sizeof image.phdrs[0])) { |
| 527 if (NULL != err_code) { |
| 528 *err_code = LOAD_READ_ERROR; |
| 529 } |
| 530 NaClLog(2, "cannot load tp prog headers\n"); |
| 531 return 0; |
| 532 } |
| 433 } | 533 } |
| 434 | 534 |
| 435 NaClLog(2, "=================================================\n"); | 535 NaClLog(2, "=================================================\n"); |
| 436 NaClLog(2, "Elf Program headers\n"); | 536 NaClLog(2, "Elf Program headers\n"); |
| 437 NaClLog(2, "==================================================\n"); | 537 NaClLog(2, "==================================================\n"); |
| 438 for (cur_ph = 0; cur_ph < image.ehdr.e_phnum; ++cur_ph) { | 538 for (cur_ph = 0; cur_ph < image.ehdr.e_phnum; ++cur_ph) { |
| 439 NaClDumpElfProgramHeader(2, &image.phdrs[cur_ph]); | 539 NaClDumpElfProgramHeader(2, &image.phdrs[cur_ph]); |
| 440 } | 540 } |
| 441 | 541 |
| 442 /* we delay allocating till the end to avoid cleanup code */ | 542 /* we delay allocating till the end to avoid cleanup code */ |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 601 size_t mapping_size = NaClRoundAllocPage(php->p_memsz); | 701 size_t mapping_size = NaClRoundAllocPage(php->p_memsz); |
| 602 /* | 702 /* |
| 603 * Note that we do not used NACL_ABI_MAP_FIXED because we do not | 703 * Note that we do not used NACL_ABI_MAP_FIXED because we do not |
| 604 * want to silently overwrite any existing mappings, such as the | 704 * want to silently overwrite any existing mappings, such as the |
| 605 * user app's data segment or the stack. We detect overmapping | 705 * user app's data segment or the stack. We detect overmapping |
| 606 * when mmap chooses not to use the preferred address we supply. | 706 * when mmap chooses not to use the preferred address we supply. |
| 607 * (Ideally mmap would provide a MAP_EXCL option for this | 707 * (Ideally mmap would provide a MAP_EXCL option for this |
| 608 * instead.) | 708 * instead.) |
| 609 */ | 709 */ |
| 610 result = NaClCommonSysMmapIntern( | 710 result = NaClCommonSysMmapIntern( |
| 611 nap, (void *) php->p_vaddr, mapping_size, | 711 nap, (void *) (uintptr_t) php->p_vaddr, mapping_size, |
| 612 NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, | 712 NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, |
| 613 NACL_ABI_MAP_ANONYMOUS | NACL_ABI_MAP_PRIVATE, | 713 NACL_ABI_MAP_ANONYMOUS | NACL_ABI_MAP_PRIVATE, |
| 614 -1, 0); | 714 -1, 0); |
| 615 if ((int32_t) php->p_vaddr != result) { | 715 if ((int32_t) php->p_vaddr != result) { |
| 616 NaClLog(1, "NaClElfImageLoadDynamically: failed to map data segment\n"); | 716 NaClLog(1, "NaClElfImageLoadDynamically: failed to map data segment\n"); |
| 617 return LOAD_UNLOADABLE; | 717 return LOAD_UNLOADABLE; |
| 618 } | 718 } |
| 619 if ((Elf_Word) (*gfile->vtbl->Read)(gfile, paddr, php->p_filesz) | 719 if ((Elf_Word) (*gfile->vtbl->Read)(gfile, paddr, php->p_filesz) |
| 620 != php->p_filesz) { | 720 != php->p_filesz) { |
| 621 NaClLog(1, "NaClElfImageLoadDynamically: " | 721 NaClLog(1, "NaClElfImageLoadDynamically: " |
| (...skipping 30 matching lines...) Expand all Loading... |
| 652 | 752 |
| 653 | 753 |
| 654 void NaClElfImageDelete(struct NaClElfImage *image) { | 754 void NaClElfImageDelete(struct NaClElfImage *image) { |
| 655 free(image); | 755 free(image); |
| 656 } | 756 } |
| 657 | 757 |
| 658 | 758 |
| 659 uintptr_t NaClElfImageGetEntryPoint(struct NaClElfImage *image) { | 759 uintptr_t NaClElfImageGetEntryPoint(struct NaClElfImage *image) { |
| 660 return image->ehdr.e_entry; | 760 return image->ehdr.e_entry; |
| 661 } | 761 } |
| OLD | NEW |