/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * dldump(3c) creates a new file image from the specified input file. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include "libld.h" #include "msg.h" #include "_librtld.h" /* * Generic clean up routine */ static void cleanup(Elf *ielf, Elf *oelf, Elf *melf, Cache *icache, Cache *mcache, int fd, const char *opath) { if (icache) { Cache * _icache = icache; for (++_icache; _icache->c_flags != FLG_C_END; _icache++) { if (_icache->c_info) (void) free(_icache->c_info); } (void) free((void *)icache); } if (mcache) (void) free((void *)mcache); if (ielf) (void) elf_end(ielf); if (oelf) (void) elf_end(oelf); if (melf) (void) elf_end(melf); if (fd) (void) close(fd); if (opath) (void) unlink(opath); } /* * The dldump(3x) interface directs control to the runtime linker. The runtime * linker brings in librtld.so.1 to provide the underlying support for this * call (this is because librtld.so.1 requires libelf.so.1, and the whole wad * is rather expensive to drag around with ld.so.1). * * rt_dldump(Rt_map * lmp, const char * opath, int flags, Addr addr) * * lmp provides the link-map of the ipath (the input file). * * opath specifies the output file. * * flags provides a variety of options that control how the new image will be * relocated (if required). * * addr indicates the base address at which the associated input image is mapped * within the process. * * The modes of operation and the various flags provide a number of combinations * of images that can be created, some are useful, some maybe not. The * following provide a couple of basic models for dldump(3x) use: * * new executable - dldump(0, outfile, RTLD_MEMORY) * * A dynamic executable may undergo some initialization * and the results of this saved in a new file for later * execution. The executable will presumable update * parts of its data segment and heap (note that the heap * should be acquired using malloc() so that it follows * the end of the data segment for this technique to be * useful). These updated memory elements are saved to the * new file, including a new .SUNW_heap section if * required. * * For greatest flexibility, no relocated information * should be saved (by default any relocated information is * returned to the value it had in its original file). * This allows the new image to bind to new dynamic objects * when executed on the same or newer upgrades of the OS. * * Fixing relocations by applying RTLD_REL_ALL will bind * the image to the dependencies presently mapped as part * of the process. Thus the new executable will only work * correctly when these same dependencies map to exactly * to the same locations. (note that RTLD_REL_RELATIVE will * have no effect as dynamic executables commonly don't * contain any relative relocations). * * new shared object - dldump(infile, outfile, RTLD_REL_RELATIVE) * * A shared object can be fixed to a known address so as * to reduce its relocation overhead on startup. Because * the new file is fixed to a new base address (which is * the address at which the object was found mapped to the * process) it is now a dynamic executable. * * Data changes that have occurred due to the object * gaining control (at the least this would be .init * processing) will not be carried over to the new image. * * By only performing relative relocations all global * relocations are available for unique binding to each * process - thus interposition etc. is still available. * * Using RTLD_REL_ALL will fix all relocations in the new * file, which will certainly provide for faster startup * of the new image, but at the loss of interposition * flexibility. */ int rt_dldump(Rt_map *lmp, const char *opath, int flags, Addr addr) { Elf * ielf = 0, *oelf = 0, *melf = 0; Ehdr *iehdr, *oehdr, *mehdr; Phdr *iphdr, *ophdr, *data_phdr = 0; Cache *icache = 0, *_icache, *mcache = 0, *_mcache; Cache *data_cache = 0, *dyn_cache = 0; Xword rel_null_no = 0, rel_data_no = 0, rel_func_no = 0; Xword rel_entsize; Rel *rel_base = 0, *rel_null, *rel_data, *rel_func; Elf_Scn *scn; Shdr *shdr; Elf_Data *data; Half endx = 1; int fd = 0, err, num; size_t shstr_size = 1; Addr edata; char *shstr, *_shstr, *ipath = NAME(lmp); prstatus_t *status = 0, _status; if (lmp == lml_main.lm_head) { char proc[16]; int pfd; /* * Get a /proc descriptor. */ (void) snprintf(proc, 16, MSG_ORIG(MSG_FMT_PROC), (int)getpid()); if ((pfd = open(proc, O_RDONLY)) == -1) { err = errno; eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), proc, strerror(err)); return (1); } /* * If we've been asked to process the dynamic executable we * might not know its full path (this is prior to realpath() * processing becoming default), and thus use /proc to obtain a * file descriptor of the input file. */ if ((fd = ioctl(pfd, PIOCOPENM, (void *)0)) == -1) { err = errno; eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_PROC), ipath, strerror(err)); (void) close(pfd); return (1); } /* * Obtain the process's status structure from which we can * determine the size of the process's heap. Note, if the * application is using mapmalloc then the heap size is going * to be zero, and if we're dumping a data section that makes * reference to the malloc'ed area we're not going to get a * useful image. */ if (!(flags & RTLD_NOHEAP)) { if (ioctl(pfd, PIOCSTATUS, (void *)&_status) == -1) { err = errno; eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_PROC), ipath, strerror(err)); (void) close(fd); (void) close(pfd); return (1); } if ((flags & RTLD_MEMORY) && _status.pr_brksize) status = &_status; } (void) close(pfd); } else { /* * Open the specified file. */ if ((fd = open(ipath, O_RDONLY, 0)) == -1) { err = errno; eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), ipath, strerror(err)); return (1); } } /* * Initialize with the ELF library and make sure this is a suitable * ELF file we're dealing with. */ (void) elf_version(EV_CURRENT); if ((ielf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_BEGIN), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, 0); return (1); } (void) close(fd); if ((elf_kind(ielf) != ELF_K_ELF) || ((iehdr = elf_getehdr(ielf)) == NULL) || ((iehdr->e_type != ET_EXEC) && (iehdr->e_type != ET_DYN))) { eprintf(ERR_FATAL, MSG_INTL(MSG_IMG_ELF), ipath); cleanup(ielf, oelf, melf, icache, mcache, 0, 0); return (1); } /* * Make sure we can create the new output file. */ if ((fd = open(opath, (O_RDWR | O_CREAT | O_TRUNC), 0777)) == -1) { err = errno; eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), opath, strerror(err)); cleanup(ielf, oelf, melf, icache, mcache, 0, 0); return (1); } if ((oelf = elf_begin(fd, ELF_C_WRITE, NULL)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_BEGIN), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } /* * Obtain the input program headers. Remember the last data segments * program header entry as this will be updated later to reflect any new * heap section size. */ if ((iphdr = elf_getphdr(ielf)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETPHDR), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } for (num = 0, ophdr = iphdr; num != iehdr->e_phnum; num++, ophdr++) { /* * Save the program header that contains the NOBITS section, or * the last loadable program header if no NOBITS exists. A * NOBITS section translates to a memory size requirement that * is greater than the file data it is mapped from. Note that * we inspect all headers just incase there only exist text * segments. */ if (ophdr->p_type == PT_LOAD) { if (ophdr->p_filesz != ophdr->p_memsz) data_phdr = ophdr; else if (data_phdr) { if (data_phdr->p_vaddr < ophdr->p_vaddr) data_phdr = ophdr; } else data_phdr = ophdr; } } /* * If there is no data segment, and a heap section is required, * warn the user and disable the heap addition (Note that you can't * simply append the heap to the last segment, as it might be a text * segment, and would therefore have the wrong permissions). */ if (status && !data_phdr) { eprintf(ERR_WARNING, MSG_INTL(MSG_IMG_DATASEG), ipath); status = 0; } /* * Obtain the input files section header string table. */ if ((scn = elf_getscn(ielf, iehdr->e_shstrndx)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETSCN), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((data = elf_getdata(scn, NULL)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETDATA), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } shstr = (char *)data->d_buf; /* * Construct a cache to maintain the input files section information. * Obtain an extra cache element if a heap addition is required. Also * add an additional entry (marked FLG_C_END) to make the processing of * this cache easier. */ num = iehdr->e_shnum; if (status) num++; if ((icache = malloc((num + 1) * sizeof (Cache))) == 0) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } icache[num].c_flags = FLG_C_END; _icache = icache; _icache++; /* * Traverse each section from the input file collecting the appropriate * ELF information. Indicate how the section will be processed to * generate the output image. */ for (scn = 0; scn = elf_nextscn(ielf, scn); _icache++) { if ((_icache->c_shdr = shdr = elf_getshdr(scn)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETSHDR), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((_icache->c_data = elf_getdata(scn, NULL)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETDATA), ipath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } _icache->c_name = shstr + (size_t)(shdr->sh_name); _icache->c_scn = scn; _icache->c_flags = 0; _icache->c_info = 0; /* * If the section has no address it is not part of the mapped * image, and is unlikely to require any further processing. * The section header string table will be rewritten (this isn't * always necessary, it's only really required when relocation * sections are renamed or sections are stripped, but we do * things the same way regardless). */ if (shdr->sh_addr == 0) { if ((shdr->sh_type == SHT_STRTAB) && ((strcmp(_icache->c_name, MSG_ORIG(MSG_SCN_SHSTR))) == 0)) _icache->c_flags = FLG_C_SHSTR; else if (flags & RTLD_STRIP) { _icache->c_flags = FLG_C_EXCLUDE; continue; } } /* * Skip relocation sections for the time being, they'll be * analyzed after all sections have been processed. */ if ((shdr->sh_type == M_REL_SHT_TYPE) && shdr->sh_addr) continue; /* * Sections at this point will simply be passed through to the * output file. Keep track of the section header string table * size. */ shstr_size += strlen(_icache->c_name) + 1; /* * If a heap section is to be added to the output image, * indicate that it will be added following the last data * section. */ if (shdr->sh_addr && ((shdr->sh_addr + shdr->sh_size) == (data_phdr->p_vaddr + data_phdr->p_memsz))) { data_cache = _icache; if (status) { _icache++; _icache->c_name = (char *)MSG_ORIG(MSG_SCN_HEAP); _icache->c_flags = FLG_C_HEAP; _icache->c_scn = 0; _icache->c_shdr = 0; _icache->c_data = 0; _icache->c_info = 0; shstr_size += strlen(_icache->c_name) + 1; } } } /* * Now that we've processed all input sections count the relocation * entries (relocation sections need to reference their symbol tables). */ _icache = icache; for (_icache++; _icache->c_flags != FLG_C_END; _icache++) { if ((shdr = _icache->c_shdr) == 0) continue; /* * If any form of relocations are to be applied to the output * image determine what relocation counts exist. These will be * used to reorganize (localize) the relocation records. */ if ((shdr->sh_type == M_REL_SHT_TYPE) && shdr->sh_addr) { rel_entsize = shdr->sh_entsize; if (count_reloc(icache, _icache, lmp, flags, addr, &rel_null_no, &rel_data_no, &rel_func_no)) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } } } /* * If any form of relocations are to be applied to the output image * then we will reorganize (localize) the relocation records. If this * reorganization occurs, the relocation sections will no longer have a * one-to-one relationship with the section they relocate, hence we * rename them to a more generic name. */ _icache = icache; for (_icache++; _icache->c_flags != FLG_C_END; _icache++) { if ((shdr = _icache->c_shdr) == 0) continue; if ((shdr->sh_type == M_REL_SHT_TYPE) && shdr->sh_addr) { if (rel_null_no) { _icache->c_flags = FLG_C_RELOC; _icache->c_name = (char *)MSG_ORIG(MSG_SCN_RELOC); } shstr_size += strlen(_icache->c_name) + 1; } } /* * If there is no data section, and a heap is required, warn the user * and disable the heap addition. */ if (!data_cache) { eprintf(ERR_WARNING, MSG_INTL(MSG_IMG_DATASEC), ipath); status = 0; endx = 0; } /* * Determine the value of _edata (which will also be _end) and its * section index for updating the data segments phdr and symbol table * information later. If a new heap section is being added, update * the values appropriately. */ edata = data_phdr->p_vaddr + data_phdr->p_memsz; if (status) edata += status->pr_brksize; if (endx) { /* LINTED */ endx = (Half)elf_ndxscn(data_cache->c_scn); if (status) endx++; } /* * We're now ready to construct the new elf image. * * Obtain a new elf header and initialize it with any basic information * that isn't calculated as part of elf_update(). */ if ((oehdr = elf_newehdr(oelf)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_NEWEHDR), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } oehdr->e_machine = iehdr->e_machine; oehdr->e_flags = iehdr->e_flags; oehdr->e_type = ET_EXEC; oehdr->e_entry = iehdr->e_entry; if (addr) oehdr->e_entry += addr; /* * Obtain a new set of program headers. Initialize these with the same * information as the input program headers. Update the virtual address * and the data segments size to reflect any new heap section. */ if ((ophdr = elf_newphdr(oelf, iehdr->e_phnum)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_NEWPHDR), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } for (num = 0; num != iehdr->e_phnum; num++, iphdr++, ophdr++) { *ophdr = *iphdr; if ((ophdr->p_type != PT_INTERP) && (ophdr->p_type != PT_NOTE)) ophdr->p_vaddr += addr; if (data_phdr == iphdr) { if (status) ophdr->p_memsz = edata - ophdr->p_vaddr; ophdr->p_filesz = ophdr->p_memsz; } } /* * Establish a buffer for the new section header string table. This * will be filled in as each new section is created. */ if ((shstr = malloc(shstr_size)) == 0) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } _shstr = shstr; *_shstr++ = '\0'; /* * Use the input files cache information to generate new sections. */ _icache = icache; for (_icache++; _icache->c_flags != FLG_C_END; _icache++) { /* * Skip any excluded sections. */ if (_icache->c_flags == FLG_C_EXCLUDE) continue; /* * Create a matching section header in the output file. */ if ((scn = elf_newscn(oelf)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_NEWSCN), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((shdr = elf_getshdr(scn)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_NEWSHDR), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } /* * If this is the heap section initialize the appropriate * entries, otherwise simply use the original section header * information. */ if (_icache->c_flags == FLG_C_HEAP) { shdr->sh_type = SHT_PROGBITS; shdr->sh_flags = SHF_ALLOC | SHF_WRITE; } else *shdr = *_icache->c_shdr; /* * Create a matching data buffer for this section. */ if ((data = elf_newdata(scn)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_NEWDATA), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } /* * Determine what data will be used for this section. */ if (_icache->c_flags == FLG_C_SHSTR) { /* * Reassign the shstrtab to the new data buffer we're * creating. Insure that the new elf header references * this section header table. */ *data = *_icache->c_data; data->d_buf = (void *)shstr; data->d_size = shstr_size; _icache->c_info = shstr; /* LINTED */ oehdr->e_shstrndx = (Half)elf_ndxscn(scn); } else if (_icache->c_flags == FLG_C_HEAP) { /* * Assign the heap to the appropriate memory offset. */ data->d_buf = status->pr_brkbase; data->d_type = ELF_T_BYTE; data->d_size = (size_t)status->pr_brksize; data->d_off = 0; data->d_align = 1; data->d_version = EV_CURRENT; shdr->sh_addr = data_cache->c_shdr->sh_addr + data_cache->c_shdr->sh_size; } else if (_icache->c_flags == FLG_C_RELOC) { /* * If some relocations are to be saved in the new image * then the relocation sections will be reorganized to * localize their contents. These relocation sections * will no longer have a one-to-one relationship with * the section they relocate, hence we rename them and * remove their sh_info info. */ *data = *_icache->c_data; shdr->sh_info = 0; } else { /* * By default simply pass the section through. If * we've been asked to use the memory image of the * input file reestablish the data buffer address. */ *data = *_icache->c_data; if ((shdr->sh_addr) && (flags & RTLD_MEMORY)) data->d_buf = (void *)(shdr->sh_addr + addr); /* * Update any NOBITS section to indicate that it now * contains data. If this image is being created * directly from the input file, zero out the .bss * section (this saves ld.so.1 having to zero out memory * or do any /dev/zero mappings). */ if (shdr->sh_type == SHT_NOBITS) { shdr->sh_type = SHT_PROGBITS; if (!(flags & RTLD_MEMORY)) { if ((data->d_buf = calloc(1, data->d_size)) == 0) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } } } } /* * Update the section header string table. */ /* LINTED */ shdr->sh_name = (Word)(_shstr - shstr); (void) strcpy(_shstr, _icache->c_name); _shstr = _shstr + strlen(_icache->c_name) + 1; /* * For each section that has a virtual address update its * address to the fixed location of the new image. */ if (shdr->sh_addr) shdr->sh_addr += addr; /* * If we've inserted a new section any later sections may need * their sh_link fields updated (.stabs comes to mind). */ if (status && endx && (shdr->sh_link >= endx)) shdr->sh_link++; } /* * Generate the new image, and obtain a new elf descriptor that will * allow us to write and update the new image. */ if (elf_update(oelf, ELF_C_WRIMAGE) == -1) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_UPDATE), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((melf = elf_begin(0, ELF_C_IMAGE, oelf)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_BEGIN), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((mehdr = elf_getehdr(melf)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETEHDR), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } /* * Construct a cache to maintain the memory files section information. */ if ((mcache = malloc(mehdr->e_shnum * sizeof (Cache))) == 0) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } _mcache = mcache; _mcache++; for (scn = 0; scn = elf_nextscn(melf, scn); _mcache++) { if ((_mcache->c_shdr = elf_getshdr(scn)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETSHDR), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } if ((_mcache->c_data = elf_getdata(scn, NULL)) == NULL) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_GETDATA), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } } /* * Now that we have a complete description of the new image update any * sections that are required. * * o reset any symbol table entries. * * o reset any relocation entries. * * o reset dynamic entries. */ _mcache = &mcache[0]; for (_icache = &icache[1]; _icache->c_flags != FLG_C_END; _icache++) { if (_icache->c_flags == FLG_C_EXCLUDE) continue; _mcache++; shdr = _mcache->c_shdr; /* * Update the symbol table entries. _end and _edata will be * changed to reflect any heap addition. All global symbols * will be updated to their new fixed address. */ if ((shdr->sh_type == SHT_SYMTAB) || (shdr->sh_type == SHT_DYNSYM)) { update_sym(mcache, _mcache, edata, endx, addr); continue; } /* * Update any relocations. All relocation requirements will * have been established in count_reloc(). */ if (shdr->sh_type == M_REL_SHT_TYPE) { if (rel_base == (Rel *)0) { rel_base = (Rel *)_mcache->c_data->d_buf; rel_null = rel_base; rel_data = (Rel *)((Xword)rel_null + (rel_null_no * rel_entsize)); rel_func = (Rel *)((Xword)rel_data + (rel_data_no * rel_entsize)); } update_reloc(mcache, icache, _icache, opath, lmp, &rel_null, &rel_data, &rel_func); continue; } /* * Perform any dynamic entry updates after all relocation * processing has been carried out (as its possible the .dynamic * section could occur before the .rel sections, delay this * processing until last). */ if (shdr->sh_type == SHT_DYNAMIC) dyn_cache = _mcache; } if (dyn_cache) { Xword off = (Xword)rel_base - (Xword)mehdr; /* * If we're dumping a fixed object (typically the dynamic * executable) compensate for its real base address. */ if (!addr) off += ADDR(lmp); if (update_dynamic(mcache, dyn_cache, lmp, flags, addr, off, opath, rel_null_no, rel_data_no, rel_func_no, rel_entsize, elf_checksum(melf))) { cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } } /* * Having completed all section updates write the memory file out. */ if (elf_update(oelf, ELF_C_WRITE) == -1) { eprintf(ERR_ELF, MSG_ORIG(MSG_ELF_UPDATE), opath); cleanup(ielf, oelf, melf, icache, mcache, fd, opath); return (1); } cleanup(ielf, oelf, melf, icache, mcache, fd, 0); return (0); }