1 /*        $NetBSD: headers.c,v 1.75 2025/05/02 23:03:16 riastradh Exp $          */
2 
3 /*
4  * Copyright 1996 John D. Polstra.
5  * Copyright 1996 Matt Thomas <matt@3am-software.com>
6  * Copyright 2002 Charles M. Hannum <root@ihack.net>
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *      This product includes software developed by John Polstra.
20  * 4. The name of the author may not be used to endorse or promote products
21  *    derived from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 /*
36  * Dynamic linker for ELF.
37  *
38  * John Polstra <jdp@polstra.com>.
39  */
40 
41 #include <sys/cdefs.h>
42 #ifndef lint
43 __RCSID("$NetBSD: headers.c,v 1.75 2025/05/02 23:03:16 riastradh Exp $");
44 #endif /* not lint */
45 
46 #include <err.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <sys/types.h>
55 #include <sys/mman.h>
56 #include <sys/bitops.h>
57 #include <dirent.h>
58 
59 #include "debug.h"
60 #include "rtld.h"
61 
62 /*
63  * Process a shared object's DYNAMIC section, and save the important
64  * information in its Obj_Entry structure.
65  */
66 void
_rtld_digest_dynamic(const char * execname,Obj_Entry * obj)67 _rtld_digest_dynamic(const char *execname, Obj_Entry *obj)
68 {
69           Elf_Dyn        *dynp;
70           Needed_Entry  **needed_tail = &obj->needed;
71           const Elf_Dyn  *dyn_soname = NULL;
72           const Elf_Dyn  *dyn_rpath = NULL;
73           bool                use_pltrel = false;
74           bool                use_pltrela = false;
75           Elf_Addr        relsz = 0, relasz = 0, relrsz = 0;
76           Elf_Addr  pltrel = 0, pltrelsz = 0;
77 #ifdef RTLD_LOADER
78           Elf_Addr  init = 0, fini = 0;
79 #endif
80 
81           dbg(("headers: digesting PT_DYNAMIC at %p", obj->dynamic));
82           for (dynp = obj->dynamic; dynp->d_tag != DT_NULL; ++dynp) {
83                     dbg(("  d_tag %ld at %p", (long)dynp->d_tag, dynp));
84                     switch (dynp->d_tag) {
85 
86                     case DT_REL:
87                               obj->rel = (const Elf_Rel *)
88                                   (obj->relocbase + dynp->d_un.d_ptr);
89                               break;
90 
91                     case DT_RELSZ:
92                               relsz = dynp->d_un.d_val;
93                               break;
94 
95                     case DT_RELENT:
96                               assert(dynp->d_un.d_val == sizeof(Elf_Rel));
97                               break;
98 
99                     case DT_JMPREL:
100                               pltrel = dynp->d_un.d_ptr;
101                               break;
102 
103                     case DT_PLTRELSZ:
104                               pltrelsz = dynp->d_un.d_val;
105                               break;
106 
107                     case DT_RELA:
108                               obj->rela = (const Elf_Rela *)
109                                   (obj->relocbase + dynp->d_un.d_ptr);
110                               break;
111 
112                     case DT_RELASZ:
113                               relasz = dynp->d_un.d_val;
114                               break;
115 
116                     case DT_RELAENT:
117                               assert(dynp->d_un.d_val == sizeof(Elf_Rela));
118                               break;
119 
120                     case DT_RELR:
121                               obj->relr = (const Elf_Relr *)(obj->relocbase +
122                                   dynp->d_un.d_ptr);
123                               break;
124 
125                     case DT_RELRSZ:
126                               relrsz = dynp->d_un.d_val;
127                               break;
128 
129                     case DT_RELRENT:
130                               assert(dynp->d_un.d_val == sizeof(Elf_Relr));
131                               break;
132 
133                     case DT_PLTREL:
134                               use_pltrel = dynp->d_un.d_val == DT_REL;
135                               use_pltrela = dynp->d_un.d_val == DT_RELA;
136                               assert(use_pltrel || use_pltrela);
137                               break;
138 
139                     case DT_SYMTAB:
140                               obj->symtab = (const Elf_Sym *)
141                                         (obj->relocbase + dynp->d_un.d_ptr);
142                               break;
143 
144                     case DT_SYMENT:
145                               assert(dynp->d_un.d_val == sizeof(Elf_Sym));
146                               break;
147 
148                     case DT_STRTAB:
149                               obj->strtab = (const char *)
150                                   (obj->relocbase + dynp->d_un.d_ptr);
151                               break;
152 
153                     case DT_STRSZ:
154                               obj->strsize = dynp->d_un.d_val;
155                               break;
156 
157                     case DT_VERNEED:
158                               obj->verneed = (const Elf_Verneed *)
159                                   (obj->relocbase + dynp->d_un.d_ptr);
160                               break;
161 
162                     case DT_VERNEEDNUM:
163                               obj->verneednum = dynp->d_un.d_val;
164                               break;
165 
166                     case DT_VERDEF:
167                               obj->verdef = (const Elf_Verdef *)
168                                   (obj->relocbase + dynp->d_un.d_ptr);
169                               break;
170 
171                     case DT_VERDEFNUM:
172                               obj->verdefnum = dynp->d_un.d_val;
173                               break;
174 
175                     case DT_VERSYM:
176                               obj->versyms = (const Elf_Versym *)
177                                   (obj->relocbase + dynp->d_un.d_ptr);
178                               break;
179 
180                     case DT_HASH:
181                               {
182                                         uint32_t nbuckets, nchains;
183                                         const Elf_Symindx *hashtab = (const Elf_Symindx *)
184                                             (obj->relocbase + dynp->d_un.d_ptr);
185 
186                                         if (hashtab[0] > UINT32_MAX)
187                                                   nbuckets = UINT32_MAX;
188                                         else
189                                                   nbuckets = hashtab[0];
190                                         obj->nbuckets = nbuckets;
191                                         obj->nchains = (nchains = hashtab[1]);
192                                         obj->buckets = hashtab + 2;
193                                         obj->chains = obj->buckets + obj->nbuckets;
194 
195                                         /* Validity check */
196                                         if (!obj->buckets || !nbuckets || !nchains)
197                                                   continue;
198 
199                                         obj->sysv_hash = true;
200 
201                                         /*
202                                          * Should really be in _rtld_relocate_objects,
203                                          * but _rtld_symlook_obj might be used before.
204                                          */
205                                         fast_divide32_prepare(obj->nbuckets,
206                                             &obj->nbuckets_m,
207                                             &obj->nbuckets_s1,
208                                             &obj->nbuckets_s2);
209                               }
210                               break;
211 
212                     case DT_GNU_HASH:
213                               {
214                                         uint32_t nmaskwords;
215                                         uint32_t nbuckets, symndx;
216                                         int bloom_size32;
217                                         bool nmw_power2;
218                                         const Elf_Symindx *hashtab = (const Elf_Symindx *)
219                                             (obj->relocbase + dynp->d_un.d_ptr);
220 
221                                         if (hashtab[0] > UINT32_MAX)
222                                                   nbuckets = UINT32_MAX;
223                                         else
224                                                   nbuckets = hashtab[0];
225                                         obj->nbuckets_gnu = nbuckets;
226 
227                                         nmaskwords = hashtab[2];
228                                         bloom_size32 = nmaskwords * (ELFSIZE / 32);
229 
230                                         obj->buckets_gnu = (const uint32_t *)(hashtab + 4 + bloom_size32);
231 
232                                         nmw_power2 = powerof2(nmaskwords);
233 
234                                         /* Validity check */
235                                         if (!nmw_power2 || !nbuckets || !obj->buckets_gnu)
236                                                   continue;
237 
238                                         obj->gnu_hash = true;
239 
240                                         obj->mask_bm_gnu = nmaskwords - 1;
241                                         obj->symndx_gnu = (symndx = hashtab[1]);
242                                         obj->shift2_gnu = hashtab[3];
243                                         obj->bloom_gnu = (const Elf_Addr *)(hashtab + 4);
244                                         obj->chains_gnu = obj->buckets_gnu + nbuckets - symndx;
245 
246                                         /*
247                                          * Should really be in _rtld_relocate_objects,
248                                          * but _rtld_symlook_obj might be used before.
249                                          */
250                                         fast_divide32_prepare(nbuckets,
251                                             &obj->nbuckets_m_gnu,
252                                             &obj->nbuckets_s1_gnu,
253                                             &obj->nbuckets_s2_gnu);
254 
255                                         dbg(("found GNU Hash: buckets=%p "
256                                              "nbuckets=%u chains=%p nchains=%lu "
257                                              "bloom=%p mask_bm=%u shift2=%u "
258                                              "symndx=%u",
259                                             obj->buckets_gnu, obj->nbuckets_gnu,
260                                             obj->chains_gnu, obj->nchains_gnu,
261                                             obj->bloom_gnu, obj->mask_bm_gnu,
262                                             obj->shift2_gnu, obj->symndx_gnu));
263                               }
264                               break;
265 
266                     case DT_NEEDED:
267                               {
268                                         Needed_Entry *nep = NEW(Needed_Entry);
269 
270                                         nep->name = dynp->d_un.d_val;
271                                         nep->obj = NULL;
272                                         nep->next = NULL;
273 
274                                         *needed_tail = nep;
275                                         needed_tail = &nep->next;
276                               }
277                               break;
278 
279                     case DT_PLTGOT:
280                               obj->pltgot = (Elf_Addr *)
281                                   (obj->relocbase + dynp->d_un.d_ptr);
282                               break;
283 
284                     case DT_TEXTREL:
285                               obj->textrel = true;
286                               break;
287 
288                     case DT_SYMBOLIC:
289                               obj->symbolic = true;
290                               break;
291 
292                     case DT_RPATH:
293                     case DT_RUNPATH:
294                               /*
295                              * We have to wait until later to process this, because
296                                * we might not have gotten the address of the string
297                                * table yet.
298                              */
299                               dyn_rpath = dynp;
300                               break;
301 
302                     case DT_SONAME:
303                               dyn_soname = dynp;
304                               break;
305 
306                     case DT_INIT:
307 #ifdef RTLD_LOADER
308                               init = dynp->d_un.d_ptr;
309 #endif
310                               break;
311 
312 #ifdef HAVE_INITFINI_ARRAY
313                     case DT_INIT_ARRAY:
314                               obj->init_array =
315                                   (fptr_t *)(obj->relocbase + dynp->d_un.d_ptr);
316                               dbg(("headers: DT_INIT_ARRAY at %p",
317                                   obj->init_array));
318                               break;
319 
320                     case DT_INIT_ARRAYSZ:
321                               obj->init_arraysz = dynp->d_un.d_val / sizeof(fptr_t);
322                               dbg(("headers: DT_INIT_ARRAYZ %zu",
323                                   obj->init_arraysz));
324                               break;
325 #endif
326 
327                     case DT_FINI:
328 #ifdef RTLD_LOADER
329                               fini = dynp->d_un.d_ptr;
330 #endif
331                               break;
332 
333 #ifdef HAVE_INITFINI_ARRAY
334                     case DT_FINI_ARRAY:
335                               obj->fini_array =
336                                   (fptr_t *)(obj->relocbase + dynp->d_un.d_ptr);
337                               dbg(("headers: DT_FINI_ARRAY at %p",
338                                   obj->fini_array));
339                               break;
340 
341                     case DT_FINI_ARRAYSZ:
342                               obj->fini_arraysz = dynp->d_un.d_val / sizeof(fptr_t);
343                               dbg(("headers: DT_FINI_ARRAYZ %zu",
344                                   obj->fini_arraysz));
345                               break;
346 #endif
347 
348                     /*
349                      * Don't process DT_DEBUG on MIPS as the dynamic
350                      * section is mapped read-only.  DT_MIPS_RLD_MAP or
351                      * DT_MIPS_RLD_MAP_REL is used instead.
352                      *
353                      * XXX: n32/n64 may use DT_DEBUG, not sure yet.
354                      */
355 #ifndef __mips__
356                     case DT_DEBUG:
357 #ifdef RTLD_LOADER
358                               dynp->d_un.d_ptr = (Elf_Addr)&_rtld_debug;
359 #endif
360                               break;
361 #endif
362 
363 #ifdef __alpha__
364                     case DT_ALPHA_PLTRO:
365                               obj->secureplt = (dynp->d_un.d_val != 0);
366                               break;
367 #endif
368 #ifdef __mips__
369                     case DT_MIPS_LOCAL_GOTNO:
370                               obj->local_gotno = dynp->d_un.d_val;
371                               break;
372 
373                     case DT_MIPS_SYMTABNO:
374                               obj->symtabno = dynp->d_un.d_val;
375                               break;
376 
377                     case DT_MIPS_GOTSYM:
378                               obj->gotsym = dynp->d_un.d_val;
379                               break;
380 
381                     /*
382                      * The .dynamic section is read-only, so the loader
383                      * can't write to it; instead, the linker reserves
384                      * space in a read/write .rld_map section for the
385                      * loader write to, and leaves a pointer to that space
386                      * in a DT_MIPS_RLD_MAP entry.
387                      *
388                      * Except pointers like that don't work for
389                      * position-independent executables, which use
390                      * DT_MIPS_RLD_MAP_REL instead.
391                      */
392                     case DT_MIPS_RLD_MAP:
393 #ifdef RTLD_LOADER
394                               *((Elf_Addr *)dynp->d_un.d_ptr) =
395                                   (Elf_Addr)&_rtld_debug;
396 #endif
397                               break;
398 
399                     /*
400                      * The .dynamic section is read-only, so the loader
401                      * can't write to it; instead, the linker reserves
402                      * space in a read/write .rld_map section for the
403                      * loader write to, which might be mapped anywhere in
404                      * virtual address space for position-independent
405                      * executables, so the linker leaves its offset
406                      * relative to the .dynamic entry itself in the dynamic
407                      * entry.
408                      */
409                     case DT_MIPS_RLD_MAP_REL:
410 #ifdef RTLD_LOADER
411                               *(Elf_Addr *)((Elf_Addr)dynp + dynp->d_un.d_val) =
412                                   (Elf_Addr)&_rtld_debug;
413 #endif
414                               break;
415 #endif
416 #ifdef __powerpc__
417 #ifdef _LP64
418                     case DT_PPC64_GLINK:
419                               obj->glink = (Elf_Addr)(uintptr_t)obj->relocbase + dynp->d_un.d_ptr;
420                               break;
421 #else
422                     case DT_PPC_GOT:
423                               obj->gotptr = (Elf_Addr *)(obj->relocbase + dynp->d_un.d_ptr);
424                               break;
425 #endif
426 #endif
427                     case DT_FLAGS_1:
428                               obj->z_now =
429                                   ((dynp->d_un.d_val & DF_1_NOW) != 0);
430                               obj->z_nodelete =
431                                   ((dynp->d_un.d_val & DF_1_NODELETE) != 0);
432                               obj->z_initfirst =
433                                   ((dynp->d_un.d_val & DF_1_INITFIRST) != 0);
434                               obj->z_noopen =
435                                   ((dynp->d_un.d_val & DF_1_NOOPEN) != 0);
436                               break;
437                     }
438           }
439 
440           obj->rellim = (const Elf_Rel *)((const uint8_t *)obj->rel + relsz);
441           obj->relalim = (const Elf_Rela *)((const uint8_t *)obj->rela + relasz);
442           obj->relrlim = (const Elf_Relr *)((const uint8_t *)obj->relr + relrsz);
443           if (use_pltrel) {
444                     obj->pltrel = (const Elf_Rel *)(obj->relocbase + pltrel);
445                     obj->pltrellim = (const Elf_Rel *)(obj->relocbase + pltrel + pltrelsz);
446                     obj->pltrelalim = 0;
447                     /* On PPC and SPARC, at least, REL(A)SZ may include JMPREL.
448                        Trim rel(a)lim to save time later. */
449                     if (obj->rellim && obj->pltrel &&
450                         obj->rellim > obj->pltrel &&
451                         obj->rellim <= obj->pltrellim)
452                               obj->rellim = obj->pltrel;
453           } else if (use_pltrela) {
454                     obj->pltrela = (const Elf_Rela *)(obj->relocbase + pltrel);
455                     obj->pltrellim = 0;
456                     obj->pltrelalim = (const Elf_Rela *)(obj->relocbase + pltrel + pltrelsz);
457                     /* On PPC and SPARC, at least, REL(A)SZ may include JMPREL.
458                        Trim rel(a)lim to save time later. */
459                     if (obj->relalim && obj->pltrela &&
460                         obj->relalim > obj->pltrela &&
461                         obj->relalim <= obj->pltrelalim)
462                               obj->relalim = obj->pltrela;
463           }
464 
465           /* If the ELF Hash is present, "nchains" is the same in both hashes. */
466           if (!obj->sysv_hash && obj->gnu_hash) {
467                     uint_fast32_t i, nbucket, symndx;
468 
469                     /* Otherwise, count the entries from the GNU Hash chain. */
470                     nbucket = obj->nbuckets_gnu;
471                     symndx = obj->symndx_gnu;
472 
473                     for (i = 0; i < nbucket; i++) {
474                               Elf_Word bkt = obj->buckets_gnu[i];
475                               if (bkt == 0)
476                                         continue;
477                               const uint32_t *hashval = &obj->chains_gnu[bkt];
478                               do {
479                                         symndx++;
480                               } while ((*hashval++ & 1U) == 0);
481                     }
482                     obj->nchains_gnu = (uint32_t)symndx;
483           }
484 
485 #ifdef RTLD_LOADER
486 #if defined(__HAVE_FUNCTION_DESCRIPTORS)
487           if (init != 0)
488                     obj->init = (void (*)(void))
489                         _rtld_function_descriptor_alloc(obj, NULL, init);
490           if (fini != 0)
491                     obj->fini = (void (*)(void))
492                         _rtld_function_descriptor_alloc(obj, NULL, fini);
493 #else
494           if (init != 0)
495                     obj->init = (void (*)(void)) (obj->relocbase + init);
496           if (fini != 0)
497                     obj->fini = (void (*)(void)) (obj->relocbase + fini);
498 #endif
499 #endif
500 
501           if (dyn_rpath != NULL) {
502                     _rtld_add_paths(execname, &obj->rpaths, obj->strtab +
503                         dyn_rpath->d_un.d_val);
504           }
505           if (dyn_soname != NULL) {
506                     _rtld_object_add_name(obj, obj->strtab +
507                         dyn_soname->d_un.d_val);
508           }
509 }
510 
511 #ifdef RTLD_LOADER
512 /*
513  * Process a shared object's program header.  This is used only for the
514  * main program, when the kernel has already loaded the main program
515  * into memory before calling the dynamic linker.  It creates and
516  * returns an Obj_Entry structure.
517  */
518 Obj_Entry *
_rtld_digest_phdr(const Elf_Phdr * phdr,int phnum,caddr_t entry)519 _rtld_digest_phdr(const Elf_Phdr *phdr, int phnum, caddr_t entry)
520 {
521           Obj_Entry      *obj;
522           const Elf_Phdr *phlimit = phdr + phnum;
523           const Elf_Phdr *ph;
524           bool            first_seg = true;
525           Elf_Addr        vaddr;
526           size_t          size;
527 
528           obj = _rtld_obj_new();
529 
530           for (ph = phdr; ph < phlimit; ++ph) {
531                     if (ph->p_type != PT_PHDR)
532                               continue;
533 
534                     obj->relocbase = (caddr_t)((uintptr_t)phdr - (uintptr_t)ph->p_vaddr);
535                     obj->phdr = phdr; /* Equivalent to relocbase + p_vaddr. */
536                     obj->phsize = ph->p_memsz;
537                     dbg(("headers: phdr %p (%p) phsize %zu relocbase %p",
538                         obj->phdr, phdr, obj->phsize, obj->relocbase));
539                     break;
540           }
541 
542           for (ph = phdr; ph < phlimit; ++ph) {
543                     vaddr = (Elf_Addr)(uintptr_t)(obj->relocbase + ph->p_vaddr);
544                     switch (ph->p_type) {
545 
546                     case PT_INTERP:
547                               obj->interp = (const char *)(uintptr_t)vaddr;
548                               dbg(("headers: %s %p phsize %" PRImemsz,
549                                   "PT_INTERP", (void *)(uintptr_t)vaddr,
550                                    ph->p_memsz));
551                               break;
552 
553                     case PT_LOAD:
554                               size = round_up(vaddr + ph->p_memsz) - obj->vaddrbase;
555                               if (first_seg) {    /* First load segment */
556                                         obj->vaddrbase = round_down(vaddr);
557                                         obj->mapbase = (caddr_t)(uintptr_t)obj->vaddrbase;
558                                         obj->textsize = size;
559                                         obj->mapsize = size;
560                                         first_seg = false;
561                               } else {            /* Last load segment */
562                                         obj->mapsize = MAX(obj->mapsize, size);
563                               }
564                               dbg(("headers: %s %p phsize %" PRImemsz,
565                                   "PT_LOAD", (void *)(uintptr_t)vaddr,
566                                    ph->p_memsz));
567                               break;
568 
569                     case PT_DYNAMIC:
570                               obj->dynamic = (Elf_Dyn *)(uintptr_t)vaddr;
571                               dbg(("headers: %s %p phsize %" PRImemsz,
572                                   "PT_DYNAMIC", (void *)(uintptr_t)vaddr,
573                                    ph->p_memsz));
574                               break;
575 
576 #ifdef GNU_RELRO
577                     case PT_GNU_RELRO:
578                               /* rounding happens later. */
579                               obj->relro_page = obj->relocbase + ph->p_vaddr;
580                               obj->relro_size = ph->p_memsz;
581                               dbg(("headers: %s %p phsize %" PRImemsz,
582                                   "PT_GNU_RELRO", (void *)(uintptr_t)vaddr,
583                                    ph->p_memsz));
584                               break;
585 #endif
586 
587 #if defined(__HAVE_TLS_VARIANT_I) || defined(__HAVE_TLS_VARIANT_II)
588                     case PT_TLS:
589                               obj->tlsindex = 1;
590                               obj->tlssize = ph->p_memsz;
591                               obj->tlsalign = ph->p_align;
592                               obj->tlsinitsize = ph->p_filesz;
593                               obj->tlsinit = (void *)(obj->relocbase +
594                                   (uintptr_t)ph->p_vaddr);
595                               dbg(("headers: %s %p phsize %" PRImemsz,
596                                   "PT_TLS", (void *)(uintptr_t)vaddr,
597                                    ph->p_memsz));
598                               break;
599 #endif
600 #ifdef __ARM_EABI__
601                     case PT_ARM_EXIDX:
602                               obj->exidx_start = (void *)(uintptr_t)vaddr;
603                               obj->exidx_sz = ph->p_memsz;
604                               dbg(("headers: %s %p phsize %" PRImemsz,
605                                   "PT_ARM_EXIDX", (void *)(uintptr_t)vaddr,
606                                    ph->p_memsz));
607                               break;
608 #endif
609                     }
610           }
611 
612           obj->entry = entry;
613           return obj;
614 }
615 #endif
616