From 3f7ca26b1c66dbb7b19bb34f6ff978a32061e43e Mon Sep 17 00:00:00 2001 From: Michael Meissner Date: Thu, 25 Apr 1996 18:24:05 +0000 Subject: [PATCH] Add support for linking against shared libraries --- bfd/ChangeLog | 12 ++ bfd/elf32-ppc.c | 393 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 338 insertions(+), 67 deletions(-) diff --git a/bfd/ChangeLog b/bfd/ChangeLog index 9a855ab6c2e..32104243371 100644 --- a/bfd/ChangeLog +++ b/bfd/ChangeLog @@ -1,5 +1,17 @@ Thu Apr 25 13:25:12 1996 Michael Meissner + * elf32-ppc.c (ppc_elf_create_linker_section): Add PLT case. + (ppc_elf_additional_program_headers): If .interp section, bump # + of program headers by 1. + (ppc_elf_adjust_dynamic_symbol): Flesh out support. + (ppc_elf_size_dynamic_sections): Add support for .plt section. + (ppc_elf_check_relocs): Print out filename in debug code. Enable + PLT support. + (ppc_elf_finish_dynamic_symbol): Add support for PLT's, beef up + debug output. + (ppc_elf_relocate_section): If the output section isn't defined, + don't abort, just give an error message. + * elflink.c (_bfd_elf_create_dynamic_sections): If the section being created is .sdata or .sdata2, don't make the symbol dynamic. diff --git a/bfd/elf32-ppc.c b/bfd/elf32-ppc.c index 8b68031a23c..895588b2057 100644 --- a/bfd/elf32-ppc.c +++ b/bfd/elf32-ppc.c @@ -108,6 +108,9 @@ static boolean ppc_elf_set_private_flags PARAMS ((bfd *, flagword)); static boolean ppc_elf_copy_private_bfd_data PARAMS ((bfd *, bfd *)); static boolean ppc_elf_merge_private_bfd_data PARAMS ((bfd *, bfd *)); +static int ppc_elf_additional_program_headers PARAMS ((bfd *)); +static boolean ppc_elf_modify_segment_map PARAMS ((bfd *)); + static boolean ppc_elf_section_from_shdr PARAMS ((bfd *, Elf32_Internal_Shdr *, char *)); @@ -162,6 +165,12 @@ static boolean ppc_elf_finish_dynamic_sections PARAMS ((bfd *, struct bfd_link_i #define ELF_DYNAMIC_INTERPRETER "/usr/lib/ld.so.1" +/* The size in bytes of an entry in the procedure linkage table, and of the initial size + of the plt reserved for the dynamic linker. */ + +#define PLT_ENTRY_SIZE 12 +#define PLT_INITIAL_ENTRY_SIZE 72 + static reloc_howto_type *ppc_elf_howto_table[ (int)R_PPC_max ]; @@ -1212,6 +1221,8 @@ ppc_elf_fake_sections (abfd, shdr, asect) if ((asect->flags & SEC_SORT_ENTRIES) != 0) shdr->sh_type = SHT_ORDERED; + + return true; } @@ -1261,6 +1272,16 @@ ppc_elf_create_linker_section (abfd, info, which) defaults.sym_offset = 4; break; + case LINKER_SECTION_PLT: /* .plt section */ + defaults.name = ".plt"; + defaults.rel_name = ".rela.plt"; + defaults.sym_name = (char *)0; + defaults.max_hole_offset = 0; + defaults.hole_size = 0; + defaults.sym_offset = 0; + defaults.flags &= ~SEC_LOAD; + break; + case LINKER_SECTION_SDATA: /* .sdata/.sbss section */ defaults.name = ".sdata"; defaults.rel_name = ".rela.sdata"; @@ -1275,6 +1296,7 @@ ppc_elf_create_linker_section (abfd, info, which) defaults.bss_name = ".sbss2"; defaults.sym_name = "_SDA2_BASE_"; defaults.sym_offset = 32768; + defaults.flags |= SEC_READONLY; break; } @@ -1284,6 +1306,42 @@ ppc_elf_create_linker_section (abfd, info, which) return lsect; } + +/* If we have a non-zero sized .sbss2 or .PPC.EMB.sbss0 sections, we need to bump up + the number of section headers. */ + +static int +ppc_elf_additional_program_headers (abfd) + bfd *abfd; +{ + asection *s; + int ret; + + ret = 0; + + s = bfd_get_section_by_name (abfd, ".interp"); + if (s != NULL) + ++ret; + + s = bfd_get_section_by_name (abfd, ".sbss2"); + if (s != NULL && (s->flags & SEC_LOAD) != 0 && s->_raw_size > 0) + ++ret; + + s = bfd_get_section_by_name (abfd, ".PPC.EMB.sbss0"); + if (s != NULL && (s->flags & SEC_LOAD) != 0 && s->_raw_size > 0) + ++ret; + + return ret; +} + +/* Modify the segment map if needed */ + +static boolean +ppc_elf_modify_segment_map (abfd) + bfd *abfd; +{ + return true; +} /* Adjust a symbol defined by a dynamic object and referenced by a regular object. The current definition is in some section of the @@ -1296,9 +1354,167 @@ ppc_elf_adjust_dynamic_symbol (info, h) struct bfd_link_info *info; struct elf_link_hash_entry *h; { + bfd *dynobj = elf_hash_table (info)->dynobj; + asection *s; + unsigned int power_of_two; + bfd_vma plt_offset; + #ifdef DEBUG - fprintf (stderr, "ppc_elf_adjust_dynamic_symbol called\n"); + fprintf (stderr, "ppc_elf_adjust_dynamic_symbol called for %s\n", h->root.root.string); #endif + + /* Make sure we know what is going on here. */ + BFD_ASSERT (dynobj != NULL + && ((h->elf_link_hash_flags & ELF_LINK_HASH_NEEDS_PLT) + || h->weakdef != NULL + || ((h->elf_link_hash_flags + & ELF_LINK_HASH_DEF_DYNAMIC) != 0 + && (h->elf_link_hash_flags + & ELF_LINK_HASH_REF_REGULAR) != 0 + && (h->elf_link_hash_flags + & ELF_LINK_HASH_DEF_REGULAR) == 0))); + + + /* If this is a function, put it in the procedure linkage table. We + will fill in the contents of the procedure linkage table later, + when we know the address of the .got section. */ + if (h->type == STT_FUNC + || (h->elf_link_hash_flags & ELF_LINK_HASH_NEEDS_PLT) != 0) + { + if (! info->shared + && (h->elf_link_hash_flags & ELF_LINK_HASH_DEF_DYNAMIC) == 0 + && (h->elf_link_hash_flags & ELF_LINK_HASH_REF_DYNAMIC) == 0) + { + /* This case can occur if we saw a PLT32 reloc in an input + file, but the symbol was never referred to by a dynamic + object. In such a case, we don't actually need to build + a procedure linkage table, and we can just do a PC32 + reloc instead. */ + BFD_ASSERT ((h->elf_link_hash_flags & ELF_LINK_HASH_NEEDS_PLT) != 0); + return true; + } + + /* Make sure this symbol is output as a dynamic symbol. */ + if (h->dynindx == -1) + { + if (! bfd_elf32_link_record_dynamic_symbol (info, h)) + return false; + } + + s = bfd_get_section_by_name (dynobj, ".plt"); + BFD_ASSERT (s != NULL); + + /* If this is the first .plt entry, make room for the special + first entry. */ + if (s->_raw_size == 0) + s->_raw_size += PLT_INITIAL_ENTRY_SIZE; + + /* The PowerPC PLT is actually composed of two parts, the first part + is 2 words (for a load and a jump), and then there is a remaining + word available at the end. */ + plt_offset = (PLT_INITIAL_ENTRY_SIZE + + 8 * ((s->_raw_size - PLT_INITIAL_ENTRY_SIZE) / PLT_ENTRY_SIZE)); + + /* If this symbol is not defined in a regular file, and we are + not generating a shared library, then set the symbol to this + location in the .plt. This is required to make function + pointers compare as equal between the normal executable and + the shared library. */ + if (! info->shared + && (h->elf_link_hash_flags & ELF_LINK_HASH_DEF_REGULAR) == 0) + { + h->root.u.def.section = s; + h->root.u.def.value = plt_offset; + } + + h->plt_offset = plt_offset; + + /* Make room for this entry. */ + s->_raw_size += PLT_ENTRY_SIZE; + + /* We also need to make an entry in the .rela.plt section. */ + + s = bfd_get_section_by_name (dynobj, ".rela.plt"); + BFD_ASSERT (s != NULL); + s->_raw_size += sizeof (Elf32_External_Rela); + + return true; + } + + /* If this is a weak symbol, and there is a real definition, the + processor independent code will have arranged for us to see the + real definition first, and we can just use the same value. */ + if (h->weakdef != NULL) + { + BFD_ASSERT (h->weakdef->root.type == bfd_link_hash_defined + || h->weakdef->root.type == bfd_link_hash_defweak); + h->root.u.def.section = h->weakdef->root.u.def.section; + h->root.u.def.value = h->weakdef->root.u.def.value; + return true; + } + + /* This is a reference to a symbol defined by a dynamic object which + is not a function. */ + + /* If we are creating a shared library, we must presume that the + only references to the symbol are via the global offset table. + For such cases we need not do anything here; the relocations will + be handled correctly by relocate_section. */ + if (info->shared) + return true; + + /* We must allocate the symbol in our .dynbss section, which will + become part of the .bss section of the executable. There will be + an entry for this symbol in the .dynsym section. The dynamic + object will contain position independent code, so all references + from the dynamic object to this symbol will go through the global + offset table. The dynamic linker will use the .dynsym entry to + determine the address it must put in the global offset table, so + both the dynamic object and the regular object will refer to the + same memory location for the variable. */ + + s = bfd_get_section_by_name (dynobj, ".dynbss"); + BFD_ASSERT (s != NULL); + + /* If the symbol is currently defined in the .bss section of the + dynamic object, then it is OK to simply initialize it to zero. + If the symbol is in some other section, we must generate a + R_386_COPY reloc to tell the dynamic linker to copy the initial + value out of the dynamic object and into the runtime process + image. We need to remember the offset into the .rela.bss section + we are going to use. */ + if ((h->root.u.def.section->flags & SEC_LOAD) != 0) + { + asection *srel; + + srel = bfd_get_section_by_name (dynobj, ".rela.bss"); + BFD_ASSERT (srel != NULL); + srel->_raw_size += sizeof (Elf32_External_Rel); + h->elf_link_hash_flags |= ELF_LINK_HASH_NEEDS_COPY; + } + + /* We need to figure out the alignment required for this symbol. I + have no idea how ELF linkers handle this. */ + power_of_two = bfd_log2 (h->size); + if (power_of_two > 3) + power_of_two = 3; + + /* Apply the required alignment. */ + s->_raw_size = BFD_ALIGN (s->_raw_size, + (bfd_size_type) (1 << power_of_two)); + if (power_of_two > bfd_get_section_alignment (dynobj, s)) + { + if (! bfd_set_section_alignment (dynobj, s, power_of_two)) + return false; + } + + /* Define the symbol as being at this point in the section. */ + h->root.u.def.section = s; + h->root.u.def.value = s->_raw_size; + + /* Increment the section size to make room for the symbol. */ + s->_raw_size += h->size; + return true; } @@ -1333,8 +1549,9 @@ ppc_elf_size_dynamic_sections (output_bfd, info) { bfd *dynobj; asection *s; + boolean plt; + boolean relocs; boolean reltext; - boolean relplt; #ifdef DEBUG fprintf (stderr, "ppc_elf_size_dynamic_sections called\n"); @@ -1381,8 +1598,9 @@ ppc_elf_size_dynamic_sections (output_bfd, info) /* The check_relocs and adjust_dynamic_symbol entry points have determined the sizes of the various dynamic sections. Allocate memory for them. */ + plt = false; + relocs = false; reltext = false; - relplt = false; for (s = dynobj->sections; s != NULL; s = s->next) { const char *name; @@ -1397,15 +1615,28 @@ ppc_elf_size_dynamic_sections (output_bfd, info) strip = false; -#if 0 - if (strncmp (name, ".rela", 5) == 0) + if (strcmp (name, ".plt") == 0) + { + if (s->_raw_size == 0) + { + /* Strip this section if we don't need it; see the + comment below. */ + strip = true; + } + else + { + /* Remember whether there is a PLT. */ + plt = true; + } + } + else if (strncmp (name, ".rela", 5) == 0) { if (s->_raw_size == 0) { /* If we don't need this section, strip it from the - output file. This is to handle .rela.bss and - .rel.plt. We must create it in - create_dynamic_sections, because it must be created + output file. This is mostly to handle .rela.bss and + .rela.plt. We must create both sections in + create_dynamic_sections, because they must be created before the linker maps input sections to output sections. The linker does that before adjust_dynamic_symbol is called, and it is that @@ -1417,29 +1648,31 @@ ppc_elf_size_dynamic_sections (output_bfd, info) { asection *target; - /* If this relocation section applies to a read only - section, then we probably need a DT_TEXTREL entry. */ - target = bfd_get_section_by_name (output_bfd, name + 5); - if (target != NULL - && (target->flags & SEC_READONLY) != 0) - reltext = true; - - if (strcmp (name, ".rela.plt") == 0) - relplt = true; + /* Remember whether there are any reloc sections other + than .rel.plt. */ + if (strcmp (name, ".rela.plt") != 0) + { + relocs = true; + + /* If this relocation section applies to a read only + section, then we probably need a DT_TEXTREL + entry. The entries in the .rel.plt section + really apply to the .got section, which we + created ourselves and so know is not readonly. */ + target = bfd_get_section_by_name (output_bfd, name + 4); + if (target != NULL + && (target->flags & SEC_READONLY) != 0) + reltext = true; + } /* We use the reloc_count field as a counter if we need to copy relocs into the output file. */ s->reloc_count = 0; } } - else -#endif - if (strcmp (name, ".plt") != 0 - && strcmp (name, ".got") != 0 - && strcmp (name, ".sdata") != 0 - && strcmp (name, ".sdata2") != 0 - && strcmp (name, ".rela.sdata") != 0 - && strcmp (name, ".rela.sdata2") != 0) + else if (strcmp (name, ".got") != 0 + && strcmp (name, ".sdata") != 0 + && strcmp (name, ".sdata2") != 0) { /* It's not one of our sections, so don't allocate space. */ continue; @@ -1478,22 +1711,23 @@ ppc_elf_size_dynamic_sections (output_bfd, info) return false; } - if (! bfd_elf32_add_dynamic_entry (info, DT_PLTGOT, 0)) - return false; - - if (relplt) + if (plt) { - if (! bfd_elf32_add_dynamic_entry (info, DT_PLTRELSZ, 0) - || ! bfd_elf32_add_dynamic_entry (info, DT_PLTREL, DT_RELA) + if (! bfd_elf32_add_dynamic_entry (info, DT_PLTGOT, 0) + || ! bfd_elf32_add_dynamic_entry (info, DT_PLTRELSZ, 0) + || ! bfd_elf32_add_dynamic_entry (info, DT_PLTREL, DT_REL) || ! bfd_elf32_add_dynamic_entry (info, DT_JMPREL, 0)) return false; } - if (! bfd_elf32_add_dynamic_entry (info, DT_RELA, 0) - || ! bfd_elf32_add_dynamic_entry (info, DT_RELASZ, 0) - || ! bfd_elf32_add_dynamic_entry (info, DT_RELAENT, - sizeof (Elf32_External_Rela))) - return false; + if (relocs) + { + if (! bfd_elf32_add_dynamic_entry (info, DT_RELA, 0) + || ! bfd_elf32_add_dynamic_entry (info, DT_RELASZ, DT_RELA) + || ! bfd_elf32_add_dynamic_entry (info, DT_RELENT, + sizeof (Elf32_External_Rela))) + return false; + } if (reltext) { @@ -1546,6 +1780,7 @@ ppc_elf_check_relocs (abfd, info, sec, relocs) const Elf_Internal_Rela *rel; const Elf_Internal_Rela *rel_end; elf_linker_section_t *got; + elf_linker_section_t *plt; elf_linker_section_t *sdata; elf_linker_section_t *sdata2; asection *sreloc; @@ -1554,8 +1789,9 @@ ppc_elf_check_relocs (abfd, info, sec, relocs) return true; #ifdef DEBUG - fprintf (stderr, "ppc_elf_check_relocs called for section %s\n", - bfd_get_section_name (abfd, sec)); + fprintf (stderr, "ppc_elf_check_relocs called for section %s in %s\n", + bfd_get_section_name (abfd, sec), + bfd_get_filename (abfd)); #endif /* Create the linker generated sections all the time so that the special @@ -1567,6 +1803,15 @@ ppc_elf_check_relocs (abfd, info, sec, relocs) return false; } +#if 0 + if ((plt = elf_linker_section (abfd, LINKER_SECTION_PLT)) == NULL) + { + plt = ppc_elf_create_linker_section (abfd, info, LINKER_SECTION_PLT); + if (!plt) + return false; + } +#endif + if ((sdata = elf_linker_section (abfd, LINKER_SECTION_SDATA)) == NULL) { sdata = ppc_elf_create_linker_section (abfd, info, LINKER_SECTION_SDATA); @@ -1644,7 +1889,6 @@ ppc_elf_check_relocs (abfd, info, sec, relocs) break; -#if 0 case R_PPC_PLT32: case R_PPC_PLTREL24: case R_PPC_PLT16_LO: @@ -1677,6 +1921,7 @@ ppc_elf_check_relocs (abfd, info, sec, relocs) h->elf_link_hash_flags |= ELF_LINK_HASH_NEEDS_PLT; break; +#if 0 case R_SPARC_PC10: case R_SPARC_PC22: if (h != NULL @@ -1766,12 +2011,14 @@ ppc_elf_add_symbol_hook (abfd, info, sym, namep, flagsp, secp, valp) { /* Common symbols less than or equal to -G nn bytes are automatically put into .sdata. */ - bfd *dynobj = elf_hash_table (info)->dynobj; - elf_linker_section_t *sdata = elf_linker_section (dynobj, LINKER_SECTION_SDATA); + elf_linker_section_t *sdata = ppc_elf_create_linker_section (abfd, info, LINKER_SECTION_SDATA); if (!sdata->bss_section) - sdata->bss_section = bfd_make_section (dynobj, sdata->bss_name); + { + sdata->bss_section = bfd_make_section (elf_hash_table (info)->dynobj, sdata->bss_name); + sdata->bss_section->flags = (sdata->bss_section->flags & ~SEC_LOAD) | SEC_IS_COMMON; + } + *secp = sdata->bss_section; - (*secp)->flags |= SEC_IS_COMMON; *valp = sym->st_size; } @@ -1792,7 +2039,7 @@ ppc_elf_finish_dynamic_symbol (output_bfd, info, h, sym) bfd *dynobj; #ifdef DEBUG - fprintf (stderr, "ppc_elf_finish_dynamic_symbol called for %s\n", h->root.root.string); + fprintf (stderr, "ppc_elf_finish_dynamic_symbol called for %s", h->root.root.string); #endif dynobj = elf_hash_table (info)->dynobj; @@ -1804,6 +2051,10 @@ ppc_elf_finish_dynamic_symbol (output_bfd, info, h, sym) asection *srela; Elf_Internal_Rela rela; +#ifdef DEBUG + fprintf (stderr, ", plt_offset = %d", h->plt_offset); +#endif + /* This symbol has an entry in the procedure linkage table. Set it up. */ @@ -1813,28 +2064,18 @@ ppc_elf_finish_dynamic_symbol (output_bfd, info, h, sym) srela = bfd_get_section_by_name (dynobj, ".rela.plt"); BFD_ASSERT (splt != NULL && srela != NULL); - /* Fill in the entry in the procedure linkage table. */ -#if 0 - bfd_put_32 (output_bfd, - PLT_ENTRY_WORD0 + h->plt_offset, - splt->contents + h->plt_offset); - bfd_put_32 (output_bfd, - (PLT_ENTRY_WORD1 - + (((- (h->plt_offset + 4)) >> 2) & 0x3fffff)), - splt->contents + h->plt_offset + 4); - bfd_put_32 (output_bfd, PLT_ENTRY_WORD2, - splt->contents + h->plt_offset + 8); + /* We don't need to fill in the .plt. The solaris dynamic linker will + fill it in. */ /* Fill in the entry in the .rela.plt section. */ rela.r_offset = (splt->output_section->vma + splt->output_offset + h->plt_offset); - rela.r_info = ELF32_R_INFO (h->dynindx, R_SPARC_JMP_SLOT); + rela.r_info = ELF32_R_INFO (h->dynindx, R_PPC_JMP_SLOT); rela.r_addend = 0; bfd_elf32_swap_reloca_out (output_bfd, &rela, ((Elf32_External_Rela *) srela->contents - + h->plt_offset / PLT_ENTRY_SIZE - 4)); -#endif + + ((h->plt_offset - PLT_INITIAL_ENTRY_SIZE) / 8))); if ((h->elf_link_hash_flags & ELF_LINK_HASH_DEF_REGULAR) == 0) { @@ -1851,6 +2092,10 @@ ppc_elf_finish_dynamic_symbol (output_bfd, info, h, sym) /* This symbols needs a copy reloc. Set it up. */ +#ifdef DEBUG + fprintf (stderr, ", copy"); +#endif + BFD_ASSERT (h->dynindx != -1); s = bfd_get_section_by_name (h->root.u.def.section->owner, @@ -1868,6 +2113,10 @@ ppc_elf_finish_dynamic_symbol (output_bfd, info, h, sym) ++s->reloc_count; } +#ifdef DEBUG + fprintf (stderr, "\n"); +#endif + /* Mark some specially defined symbols as absolute. */ if (strcmp (h->root.root.string, "_DYNAMIC") == 0 || strcmp (h->root.root.string, "_GLOBAL_OFFSET_TABLE_") == 0 @@ -1915,10 +2164,10 @@ ppc_elf_finish_dynamic_sections (output_bfd, info) switch (dyn.d_tag) { - case DT_PLTGOT: name = ".plt"; size = false; break; - case DT_PLTRELSZ: name = ".rela.plt"; size = true; break; + case DT_PLTGOT: name = ".plt"; size = false; break; + case DT_PLTRELSZ: name = ".rela.plt"; size = true; break; case DT_JMPREL: name = ".rela.plt"; size = false; break; - default: name = NULL; size = false; break; + default: name = NULL; size = false; break; } if (name != NULL) @@ -2048,9 +2297,9 @@ ppc_elf_relocate_section (output_bfd, info, input_bfd, input_section, Elf_Internal_Shdr *symtab_hdr = &elf_tdata (input_bfd)->symtab_hdr; struct elf_link_hash_entry **sym_hashes = elf_sym_hashes (input_bfd); bfd *dynobj = elf_hash_table (info)->dynobj; - elf_linker_section_t *got = elf_linker_section (dynobj, LINKER_SECTION_GOT); - elf_linker_section_t *sdata = elf_linker_section (dynobj, LINKER_SECTION_SDATA); - elf_linker_section_t *sdata2 = elf_linker_section (dynobj, LINKER_SECTION_SDATA2); + elf_linker_section_t *got = (dynobj) ? elf_linker_section (dynobj, LINKER_SECTION_GOT) : NULL; + elf_linker_section_t *sdata = (dynobj) ? elf_linker_section (dynobj, LINKER_SECTION_SDATA) : NULL; + elf_linker_section_t *sdata2 = (dynobj) ? elf_linker_section (dynobj, LINKER_SECTION_SDATA2) : NULL; Elf_Internal_Rela *rel = relocs; Elf_Internal_Rela *relend = relocs + input_section->reloc_count; boolean ret = true; @@ -2145,9 +2394,17 @@ ppc_elf_relocate_section (output_bfd, info, input_bfd, input_section, || h->root.type == bfd_link_hash_defweak) { sec = h->root.u.def.section; - relocation = (h->root.u.def.value - + sec->output_section->vma - + sec->output_offset); + if (!sec || !sec->output_section) + { + (*_bfd_error_handler) ("%s: Section in shared library for symbol %s\n", + bfd_get_filename (input_bfd), + sym_name); + relocation = 0; + } + else + relocation = (h->root.u.def.value + + sec->output_section->vma + + sec->output_offset); } else if (h->root.type == bfd_link_hash_undefweak) relocation = 0; @@ -2510,5 +2767,7 @@ ppc_elf_relocate_section (output_bfd, info, input_bfd, input_section, #define elf_backend_finish_dynamic_symbol ppc_elf_finish_dynamic_symbol #define elf_backend_finish_dynamic_sections ppc_elf_finish_dynamic_sections #define elf_backend_fake_sections ppc_elf_fake_sections +#define elf_backend_additional_program_headers ppc_elf_additional_program_headers +#define elf_backend_modify_segment_map ppc_elf_modify_segment_map #include "elf32-target.h" -- 2.30.2