X-Git-Url: http://git.lttng.org/?a=blobdiff_plain;f=src%2Fcommon%2Flttng-elf.c;fp=src%2Fcommon%2Flttng-elf.c;h=779beee012929c5fa3a927f78a5a15cb0b527b3e;hb=d0927b411543c173c4fb83abf785fd71a9d29ed1;hp=0000000000000000000000000000000000000000;hpb=4b223a6755662f272f7db155ee380528728e5dd1;p=lttng-tools.git diff --git a/src/common/lttng-elf.c b/src/common/lttng-elf.c new file mode 100644 index 000000000..779beee01 --- /dev/null +++ b/src/common/lttng-elf.c @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2015 Antoine Busque + * Copyright (C) 2017 Francis Deslauriers + * Copyright (C) 2017 Erica Bugden + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BUF_LEN 4096 +#define TEXT_SECTION_NAME ".text" +#define SYMBOL_TAB_SECTION_NAME ".symtab" +#define STRING_TAB_SECTION_NAME ".strtab" +#define NOTE_STAPSDT_SECTION_NAME ".note.stapsdt" +#define NOTE_STAPSDT_NAME "stapsdt" +#define NOTE_STAPSDT_TYPE 3 + +#if BYTE_ORDER == LITTLE_ENDIAN +#define NATIVE_ELF_ENDIANNESS ELFDATA2LSB +#else +#define NATIVE_ELF_ENDIANNESS ELFDATA2MSB +#endif + +#define bswap(x) \ + do { \ + switch (sizeof(x)) { \ + case 8: \ + x = be64toh(x); \ + break; \ + case 4: \ + x = be32toh(x); \ + break; \ + case 2: \ + x = be16toh(x); \ + break; \ + case 1: \ + break; \ + default: \ + abort(); \ + } \ + } while (0) + +#define bswap_shdr(shdr) \ + do { \ + bswap((shdr).sh_name); \ + bswap((shdr).sh_type); \ + bswap((shdr).sh_flags); \ + bswap((shdr).sh_addr); \ + bswap((shdr).sh_offset); \ + bswap((shdr).sh_size); \ + bswap((shdr).sh_link); \ + bswap((shdr).sh_info); \ + bswap((shdr).sh_addralign); \ + bswap((shdr).sh_entsize); \ + } while (0) + +#define bswap_ehdr(ehdr) \ + do { \ + bswap((ehdr).e_type); \ + bswap((ehdr).e_machine); \ + bswap((ehdr).e_version); \ + bswap((ehdr).e_entry); \ + bswap((ehdr).e_phoff); \ + bswap((ehdr).e_shoff); \ + bswap((ehdr).e_flags); \ + bswap((ehdr).e_ehsize); \ + bswap((ehdr).e_phentsize); \ + bswap((ehdr).e_phnum); \ + bswap((ehdr).e_shentsize); \ + bswap((ehdr).e_shnum); \ + bswap((ehdr).e_shstrndx); \ + } while (0) + +#define copy_shdr(src_shdr, dst_shdr) \ + do { \ + (dst_shdr).sh_name = (src_shdr).sh_name; \ + (dst_shdr).sh_type = (src_shdr).sh_type; \ + (dst_shdr).sh_flags = (src_shdr).sh_flags; \ + (dst_shdr).sh_addr = (src_shdr).sh_addr; \ + (dst_shdr).sh_offset = (src_shdr).sh_offset; \ + (dst_shdr).sh_size = (src_shdr).sh_size; \ + (dst_shdr).sh_link = (src_shdr).sh_link; \ + (dst_shdr).sh_info = (src_shdr).sh_info; \ + (dst_shdr).sh_addralign = (src_shdr).sh_addralign; \ + (dst_shdr).sh_entsize = (src_shdr).sh_entsize; \ + } while (0) + +#define copy_ehdr(src_ehdr, dst_ehdr) \ + do { \ + (dst_ehdr).e_type = (src_ehdr).e_type; \ + (dst_ehdr).e_machine = (src_ehdr).e_machine; \ + (dst_ehdr).e_version = (src_ehdr).e_version; \ + (dst_ehdr).e_entry = (src_ehdr).e_entry; \ + (dst_ehdr).e_phoff = (src_ehdr).e_phoff; \ + (dst_ehdr).e_shoff = (src_ehdr).e_shoff; \ + (dst_ehdr).e_flags = (src_ehdr).e_flags; \ + (dst_ehdr).e_ehsize = (src_ehdr).e_ehsize; \ + (dst_ehdr).e_phentsize = (src_ehdr).e_phentsize; \ + (dst_ehdr).e_phnum = (src_ehdr).e_phnum; \ + (dst_ehdr).e_shentsize = (src_ehdr).e_shentsize; \ + (dst_ehdr).e_shnum = (src_ehdr).e_shnum; \ + (dst_ehdr).e_shstrndx = (src_ehdr).e_shstrndx; \ + } while (0) + +#define copy_sym(src_sym, dst_sym) \ + do { \ + dst_sym.st_name = src_sym.st_name; \ + dst_sym.st_info = src_sym.st_info; \ + dst_sym.st_other = src_sym.st_other; \ + dst_sym.st_shndx = src_sym.st_shndx; \ + dst_sym.st_value = src_sym.st_value; \ + dst_sym.st_size = src_sym.st_size; \ + } while (0) + +/* Both 32bit and 64bit use the same 1 byte field for type. (See elf.h) */ +#define ELF_ST_TYPE(val) ELF32_ST_TYPE(val) + +struct lttng_elf_ehdr { + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint64_t e_entry; + uint64_t e_phoff; + uint64_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +}; + +struct lttng_elf_shdr { + uint32_t sh_name; + uint32_t sh_type; + uint64_t sh_flags; + uint64_t sh_addr; + uint64_t sh_offset; + uint64_t sh_size; + uint32_t sh_link; + uint32_t sh_info; + uint64_t sh_addralign; + uint64_t sh_entsize; +}; + +/* + * This struct can hold both 32bit and 64bit symbol description. It's used with + * the copy_sym() macro. Using this abstraction, we can use the same code for + * both bitness. + */ +struct lttng_elf_sym { + uint32_t st_name; + uint8_t st_info; + uint8_t st_other; + uint16_t st_shndx; + uint64_t st_value; + uint64_t st_size; +}; + +struct lttng_elf { + int fd; + uint8_t bitness; + uint8_t endianness; + /* Offset in bytes to start of section names string table. */ + off_t section_names_offset; + /* Size in bytes of section names string table. */ + size_t section_names_size; + struct lttng_elf_ehdr *ehdr; +}; + +static inline +int is_elf_32_bit(struct lttng_elf *elf) +{ + return elf->bitness == ELFCLASS32; +} + +static inline +int is_elf_native_endian(struct lttng_elf *elf) +{ + return elf->endianness == NATIVE_ELF_ENDIANNESS; +} + +static +int populate_section_header(struct lttng_elf * elf, struct lttng_elf_shdr *shdr, + uint32_t index) +{ + int ret = 0; + off_t offset; + + /* Compute the offset of the section in the file */ + offset = (off_t) elf->ehdr->e_shoff + + (off_t) index * elf->ehdr->e_shentsize; + + if (lseek(elf->fd, offset, SEEK_SET) < 0) { + PERROR("Error seeking to the beginning of ELF section header"); + ret = -1; + goto error; + } + + if (is_elf_32_bit(elf)) { + Elf32_Shdr elf_shdr; + + if (lttng_read(elf->fd, &elf_shdr, sizeof(elf_shdr)) < sizeof(elf_shdr)) { + PERROR("Error reading ELF section header"); + ret = -1; + goto error; + } + if (!is_elf_native_endian(elf)) { + bswap_shdr(elf_shdr); + } + copy_shdr(elf_shdr, *shdr); + } else { + Elf64_Shdr elf_shdr; + + if (lttng_read(elf->fd, &elf_shdr, sizeof(elf_shdr)) < sizeof(elf_shdr)) { + PERROR("Error reading ELF section header"); + ret = -1; + goto error; + } + if (!is_elf_native_endian(elf)) { + bswap_shdr(elf_shdr); + } + copy_shdr(elf_shdr, *shdr); + } + +error: + return ret; +} + +static +int populate_elf_header(struct lttng_elf *elf) +{ + int ret = 0; + + /* + * Move the read pointer back to the beginning to read the full header + * and copy it in our structure. + */ + if (lseek(elf->fd, 0, SEEK_SET) < 0) { + PERROR("Error seeking to the beginning of the file"); + ret = -1; + goto error; + } + + /* + * Use macros to set fields in the ELF header struct for both 32bit and + * 64bit. + */ + if (is_elf_32_bit(elf)) { + Elf32_Ehdr elf_ehdr; + + if (lttng_read(elf->fd, &elf_ehdr, sizeof(elf_ehdr)) < sizeof(elf_ehdr)) { + ret = -1; + goto error; + } + if (!is_elf_native_endian(elf)) { + bswap_ehdr(elf_ehdr); + } + copy_ehdr(elf_ehdr, *(elf->ehdr)); + } else { + Elf64_Ehdr elf_ehdr; + + if (lttng_read(elf->fd, &elf_ehdr, sizeof(elf_ehdr)) < sizeof(elf_ehdr)) { + ret = -1; + goto error; + } + if (!is_elf_native_endian(elf)) { + bswap_ehdr(elf_ehdr); + } + copy_ehdr(elf_ehdr, *(elf->ehdr)); + } +error: + return ret; +} + +/* + * Retrieve the nth (where n is the `index` argument) shdr (section + * header) from the given elf instance. + * + * A pointer to the shdr is returned on success, NULL on failure. + */ +static +struct lttng_elf_shdr *lttng_elf_get_section_hdr(struct lttng_elf *elf, + uint16_t index) +{ + struct lttng_elf_shdr *section_header = NULL; + int ret = 0; + + if (!elf) { + goto error; + } + + if (index >= elf->ehdr->e_shnum) { + goto error; + } + + section_header = zmalloc(sizeof(struct lttng_elf_shdr)); + if (!section_header) { + goto error; + } + + ret = populate_section_header(elf, section_header, index); + if (ret) { + ret = LTTNG_ERR_ELF_PARSING; + DBG("Error populating section header."); + goto error; + } + return section_header; + +error: + free(section_header); + return NULL; +} + +/* + * Lookup a section's name from a given offset (usually from an shdr's + * sh_name value) in bytes relative to the beginning of the section + * names string table. + * + * If no name is found, NULL is returned. + */ +static +char *lttng_elf_get_section_name(struct lttng_elf *elf, off_t offset) +{ + char *name = NULL; + size_t name_length = 0, to_read; /* name_length does not include \0 */ + + if (!elf) { + goto error; + } + + if (offset >= elf->section_names_size) { + goto error; + } + + if (lseek(elf->fd, elf->section_names_offset + offset, SEEK_SET) < 0) { + PERROR("Error seeking to the beginning of ELF string table section"); + goto error; + } + + to_read = elf->section_names_size - offset; + + /* Find first \0 after or at current location, remember name_length. */ + for (;;) { + char buf[BUF_LEN]; + ssize_t read_len; + size_t i; + + if (!to_read) { + goto error; + } + read_len = lttng_read(elf->fd, buf, min_t(size_t, BUF_LEN, to_read)); + if (read_len <= 0) { + PERROR("Error reading ELF string table section"); + goto error; + } + for (i = 0; i < read_len; i++) { + if (buf[i] == '\0') { + name_length += i; + goto end; + } + } + name_length += read_len; + to_read -= read_len; + } +end: + /* + * We found the length of the section name, now seek back to the + * beginning of the name and copy it in the newly allocated buffer. + */ + name = zmalloc(sizeof(char) * (name_length + 1)); /* + 1 for \0 */ + if (!name) { + PERROR("Error allocating ELF section name buffer"); + goto error; + } + if (lseek(elf->fd, elf->section_names_offset + offset, SEEK_SET) < 0) { + PERROR("Error seeking to the offset of the ELF section name"); + goto error; + } + if (lttng_read(elf->fd, name, name_length + 1) < name_length + 1) { + PERROR("Error reading the ELF section name"); + goto error; + } + + return name; + +error: + free(name); + return NULL; +} + +static +int lttng_elf_validate_and_populate(struct lttng_elf *elf) +{ + uint8_t version; + uint8_t e_ident[EI_NIDENT]; + uint8_t *magic_number = NULL; + int ret = 0; + + if (elf->fd == -1) { + DBG("fd error"); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* + * First read the magic number, endianness and version to later populate + * the ELF header with the correct endianness and bitness. + * (see elf.h) + */ + + if (lseek(elf->fd, 0, SEEK_SET) < 0) { + PERROR("Error seeking the beginning of ELF file"); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + ret = lttng_read(elf->fd, e_ident, EI_NIDENT); + if (ret < EI_NIDENT) { + DBG("Error reading the ELF identification fields"); + if (ret == -1) { + PERROR("Error reading the ELF identification fields"); + } + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* + * Copy fields used to check that the target file is in fact a valid ELF + * file. + */ + elf->bitness = e_ident[EI_CLASS]; + elf->endianness = e_ident[EI_DATA]; + version = e_ident[EI_VERSION]; + magic_number = &e_ident[EI_MAG0]; + + /* + * Check the magic number. + */ + if (memcmp(magic_number, ELFMAG, SELFMAG) != 0) { + DBG("Error check ELF magic number."); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* + * Check the bitness is either ELFCLASS32 or ELFCLASS64. + */ + if (elf->bitness <= ELFCLASSNONE || elf->bitness >= ELFCLASSNUM) { + DBG("ELF class error."); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* + * Check the endianness is either ELFDATA2LSB or ELFDATA2MSB. + */ + if (elf->endianness <= ELFDATANONE || elf->endianness >= ELFDATANUM) { + DBG("ELF endianness error."); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* + * Check the version is ELF_CURRENT. + */ + if (version <= EV_NONE || version >= EV_NUM) { + DBG("Wrong ELF version."); + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + elf->ehdr = zmalloc(sizeof(struct lttng_elf_ehdr)); + if (!elf->ehdr) { + PERROR("Error allocation buffer for ELF header"); + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* + * Copy the content of the elf header. + */ + ret = populate_elf_header(elf); + if (ret) { + DBG("Error reading ELF header,"); + goto free_elf_error; + } + + goto end; + +free_elf_error: + free(elf->ehdr); + elf->ehdr = NULL; +end: + return ret; +} + +/* + * Create an instance of lttng_elf for the ELF file located at + * `path`. + * + * Return a pointer to the instance on success, NULL on failure. + */ +static +struct lttng_elf *lttng_elf_create(int fd) +{ + struct lttng_elf_shdr *section_names_shdr; + struct lttng_elf *elf = NULL; + int ret; + + if (fd < 0) { + goto error; + } + + elf = zmalloc(sizeof(struct lttng_elf)); + if (!elf) { + PERROR("Error allocating struct lttng_elf"); + goto error; + } + + elf->fd = dup(fd); + if (elf->fd < 0) { + PERROR("Error duplicating file descriptor to binary"); + goto error; + } + + ret = lttng_elf_validate_and_populate(elf); + if (ret) { + goto error; + } + + section_names_shdr = lttng_elf_get_section_hdr(elf, elf->ehdr->e_shstrndx); + if (!section_names_shdr) { + goto error; + } + + elf->section_names_offset = section_names_shdr->sh_offset; + elf->section_names_size = section_names_shdr->sh_size; + + free(section_names_shdr); + return elf; + +error: + if (elf) { + if (elf->ehdr) { + free(elf->ehdr); + } + if (elf->fd >= 0) { + if (close(elf->fd)) { + PERROR("Error closing file descriptor in error path"); + abort(); + } + } + free(elf); + } + return NULL; +} + +/* + * Destroy the given lttng_elf instance. + */ +static +void lttng_elf_destroy(struct lttng_elf *elf) +{ + if (!elf) { + return; + } + + free(elf->ehdr); + if (close(elf->fd)) { + PERROR("Error closing file description in error path"); + abort(); + } + free(elf); +} + +static +int lttng_elf_get_section_hdr_by_name(struct lttng_elf *elf, + const char *section_name, struct lttng_elf_shdr **section_hdr) +{ + int i; + char *curr_section_name; + for (i = 0; i < elf->ehdr->e_shnum; ++i) { + *section_hdr = lttng_elf_get_section_hdr(elf, i); + curr_section_name = lttng_elf_get_section_name(elf, + (*section_hdr)->sh_name); + + if (!curr_section_name) { + continue; + } + if (strcmp(curr_section_name, section_name) == 0) { + return 0; + } + } + return LTTNG_ERR_ELF_PARSING; +} + +static +char *lttng_elf_get_section_data(struct lttng_elf *elf, + struct lttng_elf_shdr *shdr) +{ + int ret; + off_t section_offset; + char *data; + + if (!elf || !shdr) { + goto error; + } + + section_offset = shdr->sh_offset; + if (lseek(elf->fd, section_offset, SEEK_SET) < 0) { + PERROR("Error seeking to section offset"); + goto error; + } + + data = zmalloc(shdr->sh_size); + if (!data) { + PERROR("Error allocating buffer for ELF section data"); + goto error; + } + ret = lttng_read(elf->fd, data, shdr->sh_size); + if (ret == -1) { + PERROR("Error reading ELF section data"); + goto free_error; + } + + return data; + +free_error: + free(data); +error: + return NULL; +} + +/* + * Convert the virtual address in a binary's mapping to the offset of + * the corresponding instruction in the binary file. + * This function assumes the address is in the text section. + * + * Returns the offset on success or non-zero in case of failure. + */ +static +int lttng_elf_convert_addr_in_text_to_offset(struct lttng_elf *elf_handle, + size_t addr, uint64_t *offset) +{ + int ret = 0; + off_t text_section_offset; + off_t text_section_addr_beg; + off_t text_section_addr_end; + off_t offset_in_section; + struct lttng_elf_shdr *text_section_hdr = NULL; + + if (!elf_handle) { + DBG("Invalid ELF handle."); + ret = LTTNG_ERR_ELF_PARSING; + goto error; + } + + /* Get a pointer to the .text section header. */ + ret = lttng_elf_get_section_hdr_by_name(elf_handle, + TEXT_SECTION_NAME, &text_section_hdr); + if (ret) { + DBG("Text section not found in binary."); + ret = LTTNG_ERR_ELF_PARSING; + goto error; + } + + text_section_offset = text_section_hdr->sh_offset; + text_section_addr_beg = text_section_hdr->sh_addr; + text_section_addr_end = text_section_addr_beg + text_section_hdr->sh_size; + + /* + * Verify that the address is within the .text section boundaries. + */ + if (addr < text_section_addr_beg || addr > text_section_addr_end) { + DBG("Address found is outside of the .text section addr=0x%zx, " + ".text section=[0x%jd - 0x%jd].", addr, (intmax_t)text_section_addr_beg, + (intmax_t)text_section_addr_end); + ret = LTTNG_ERR_ELF_PARSING; + goto error; + } + + offset_in_section = addr - text_section_addr_beg; + + /* + * Add the target offset in the text section to the offset of this text + * section from the beginning of the binary file. + */ + *offset = text_section_offset + offset_in_section; + +error: + return ret; +} + +/* + * Compute the offset of a symbol from the begining of the ELF binary. + * + * On success, returns 0 offset parameter is set to the computed value + * On failure, returns -1. + */ +int lttng_elf_get_symbol_offset(int fd, char *symbol, uint64_t *offset) +{ + int ret = 0; + int sym_found = 0; + int sym_count = 0; + int sym_idx = 0; + uint64_t addr = 0; + char *curr_sym_str = NULL; + char *symbol_table_data = NULL; + char *string_table_data = NULL; + struct lttng_elf_shdr *symtab_hdr = NULL; + struct lttng_elf_shdr *strtab_hdr = NULL; + struct lttng_elf *elf = NULL; + + if (!symbol || !offset ) { + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + elf = lttng_elf_create(fd); + if (!elf) { + ret = LTTNG_ERR_ELF_PARSING; + goto end; + } + + /* Get the symbol table section header. */ + ret = lttng_elf_get_section_hdr_by_name(elf, SYMBOL_TAB_SECTION_NAME, + &symtab_hdr); + if (ret) { + DBG("Cannot get ELF Symbol Table section."); + ret = LTTNG_ERR_ELF_PARSING; + goto destroy_elf; + } + /* Get the data associated with the symbol table section. */ + symbol_table_data = lttng_elf_get_section_data(elf, symtab_hdr); + if (symbol_table_data == NULL) { + DBG("Cannot get ELF Symbol Table data."); + ret = LTTNG_ERR_ELF_PARSING; + goto destroy_elf; + } + + /* Get the string table section header. */ + ret = lttng_elf_get_section_hdr_by_name(elf, STRING_TAB_SECTION_NAME, + &strtab_hdr); + if (ret) { + DBG("Cannot get ELF string table section."); + goto free_symbol_table_data; + } + + /* Get the data associated with the string table section. */ + string_table_data = lttng_elf_get_section_data(elf, strtab_hdr); + if (string_table_data == NULL) { + DBG("Cannot get ELF string table section data."); + ret = LTTNG_ERR_ELF_PARSING; + goto free_symbol_table_data; + } + + /* Get the number of symbol in the table for the iteration. */ + sym_count = symtab_hdr->sh_size / symtab_hdr->sh_entsize; + + /* Loop over all symbol. */ + for (sym_idx = 0; sym_idx < sym_count; sym_idx++) { + struct lttng_elf_sym curr_sym; + + /* Get the symbol at the current index. */ + if (is_elf_32_bit(elf)) { + Elf32_Sym tmp = ((Elf32_Sym *) symbol_table_data)[sym_idx]; + copy_sym(tmp, curr_sym); + } else { + Elf64_Sym tmp = ((Elf64_Sym *) symbol_table_data)[sym_idx]; + copy_sym(tmp, curr_sym); + } + + /* + * If the st_name field is zero, there is no string name for + * this symbol; skip to the next symbol. + */ + if (curr_sym.st_name == 0) { + continue; + } + + /* + * Use the st_name field in the lttng_elf_sym struct to get offset of + * the symbol's name from the beginning of the string table. + */ + curr_sym_str = string_table_data + curr_sym.st_name; + + /* + * If the current symbol is not a function; skip to the next symbol. + */ + if (ELF_ST_TYPE(curr_sym.st_info) != STT_FUNC) { + continue; + } + + /* + * Compare with the search symbol. If there is a match set the address + * output parameter and return success. + */ + if (strcmp(symbol, curr_sym_str) == 0 ) { + sym_found = 1; + addr = curr_sym.st_value; + break; + } + } + + if (!sym_found) { + DBG("Symbol not found."); + ret = LTTNG_ERR_ELF_PARSING; + goto free_string_table_data; + } + + /* + * Use the virtual address of the symbol to compute the offset of this + * symbol from the beginning of the executable file. + */ + ret = lttng_elf_convert_addr_in_text_to_offset(elf, addr, offset); + if (ret) { + DBG("Cannot convet addr to offset."); + goto free_string_table_data; + } + + +free_string_table_data: + free(string_table_data); +free_symbol_table_data: + free(symbol_table_data); +destroy_elf: + lttng_elf_destroy(elf); +end: + return ret; +}