/* SPDX-License-Identifier: GPL-2.0-only Copyright (C) 2019 Arnaldo Carvalho de Melo */ #include #include #include #include #include #include #include #include #include #include #include "libctf.h" #include "ctf.h" #include "dutil.h" #include "gobuffer.h" bool ctf__ignore_symtab_function(const GElf_Sym *sym, const char *sym_name) { return (!elf_sym__is_local_function(sym) || elf_sym__visibility(sym) != STV_DEFAULT || sym->st_size == 0 || memcmp(sym_name, "__libc_csu_", sizeof("__libc_csu_") - 1) == 0); } bool ctf__ignore_symtab_object(const GElf_Sym *sym, const char *sym_name) { return (!elf_sym__is_local_object(sym) || sym->st_size == 0 || elf_sym__visibility(sym) != STV_DEFAULT || strchr(sym_name, '.') != NULL); } uint16_t ctf__get16(struct ctf *ctf, uint16_t *p) { uint16_t val = *p; if (ctf->swapped) val = ((val >> 8) | (val << 8)); return val; } uint32_t ctf__get32(struct ctf *ctf, uint32_t *p) { uint32_t val = *p; if (ctf->swapped) val = ((val >> 24) | ((val >> 8) & 0x0000ff00) | ((val << 8) & 0x00ff0000) | (val << 24)); return val; } void ctf__put16(struct ctf *ctf, uint16_t *p, uint16_t val) { if (ctf->swapped) val = ((val >> 8) | (val << 8)); *p = val; } void ctf__put32(struct ctf *ctf, uint32_t *p, uint32_t val) { if (ctf->swapped) val = ((val >> 24) | ((val >> 8) & 0x0000ff00) | ((val << 8) & 0x00ff0000) | (val << 24)); *p = val; } static int ctf__decompress(struct ctf *ctf, void *orig_buf, size_t orig_size) { struct ctf_header *hp = orig_buf; const char *err_str; z_stream state; size_t len; void *new; len = (ctf__get32(ctf, &hp->ctf_str_off) + ctf__get32(ctf, &hp->ctf_str_len)); new = malloc(len + sizeof(*hp)); if (!new) { fprintf(stderr, "CTF decompression allocation failure.\n"); return -ENOMEM; } memcpy(new, hp, sizeof(*hp)); memset(&state, 0, sizeof(state)); state.next_in = (Bytef *) (hp + 1); state.avail_in = orig_size - sizeof(*hp); state.next_out = new + sizeof(*hp); state.avail_out = len; if (inflateInit(&state) != Z_OK) { err_str = "struct ctf decompression inflateInit failure."; goto err; } if (inflate(&state, Z_FINISH) != Z_STREAM_END) { err_str = "struct ctf decompression inflate failure."; goto err; } if (inflateEnd(&state) != Z_OK) { err_str = "struct ctf decompression inflateEnd failure."; goto err; } if (state.total_out != len) { err_str = "struct ctf decompression truncation error."; goto err; } ctf->buf = new; ctf->size = len + sizeof(*hp); return 0; err: fputs(err_str, stderr); free(new); return -EINVAL; } int ctf__load(struct ctf *ctf) { int err = -ENOTSUP; GElf_Shdr shdr; Elf_Scn *sec = elf_section_by_name(ctf->elf, &shdr, ".SUNW_ctf", NULL); if (sec == NULL) return -ESRCH; Elf_Data *data = elf_getdata(sec, NULL); if (data == NULL) { fprintf(stderr, "%s: cannot get data of CTF section.\n", __func__); return -1; } struct ctf_header *hp = data->d_buf; size_t orig_size = data->d_size; if (hp->ctf_version != CTF_VERSION) goto out; err = -EINVAL; if (hp->ctf_magic == CTF_MAGIC) ctf->swapped = 0; else if (hp->ctf_magic == CTF_MAGIC_SWAP) ctf->swapped = 1; else goto out; if (!(hp->ctf_flags & CTF_FLAGS_COMPR)) { err = -ENOMEM; ctf->buf = malloc(orig_size); if (ctf->buf != NULL) { memcpy(ctf->buf, hp, orig_size); ctf->size = orig_size; err = 0; } } else err = ctf__decompress(ctf, hp, orig_size); out: return err; } bool ctf__verbose; struct ctf *ctf__new(const char *filename, Elf *elf) { struct ctf *ctf = zalloc(sizeof(*ctf)); if (ctf != NULL) { ctf->filename = strdup(filename); if (ctf->filename == NULL) goto out_delete; if (elf != NULL) { ctf->in_fd = -1; ctf->elf = elf; } else { ctf->in_fd = open(filename, O_RDONLY); if (ctf->in_fd < 0) goto out_delete_filename; if (elf_version(EV_CURRENT) == EV_NONE) { fprintf(stderr, "%s: cannot set libelf version.\n", __func__); goto out_close; } ctf->elf = elf_begin(ctf->in_fd, ELF_C_READ_MMAP, NULL); if (!ctf->elf) { fprintf(stderr, "%s: cannot read %s ELF file.\n", __func__, filename); goto out_close; } } if (gelf_getehdr(ctf->elf, &ctf->ehdr) == NULL) { if (ctf__verbose) fprintf(stderr, "%s: cannot get elf header.\n", __func__); goto out_elf_end; } switch (ctf->ehdr.e_ident[EI_CLASS]) { case ELFCLASS32: ctf->wordsize = 4; break; case ELFCLASS64: ctf->wordsize = 8; break; default: ctf->wordsize = 0; break; } } return ctf; out_elf_end: if (elf == NULL) elf_end(ctf->elf); out_close: if (elf == NULL) close(ctf->in_fd); out_delete_filename: zfree(&ctf->filename); out_delete: free(ctf); return NULL; } void ctf__delete(struct ctf *ctf) { if (ctf != NULL) { if (ctf->in_fd != -1) { elf_end(ctf->elf); close(ctf->in_fd); } __gobuffer__delete(&ctf->objects); __gobuffer__delete(&ctf->types); __gobuffer__delete(&ctf->funcs); elf_symtab__delete(ctf->symtab); zfree(&ctf->filename); zfree(&ctf->buf); free(ctf); } } char *ctf__string(struct ctf *ctf, uint32_t ref) { struct ctf_header *hp = ctf->buf; uint32_t off = CTF_REF_OFFSET(ref); char *name; if (CTF_REF_TBL_ID(ref) != CTF_STR_TBL_ID_0) return "(external ref)"; if (off >= ctf__get32(ctf, &hp->ctf_str_len)) return "(ref out-of-bounds)"; if ((off + ctf__get32(ctf, &hp->ctf_str_off)) >= ctf->size) return "(string table truncated)"; name = ((char *)(hp + 1) + ctf__get32(ctf, &hp->ctf_str_off) + off); return name[0] == '\0' ? NULL : name; } void *ctf__get_buffer(struct ctf *ctf) { return ctf->buf; } size_t ctf__get_size(struct ctf *ctf) { return ctf->size; } int ctf__load_symtab(struct ctf *ctf) { ctf->symtab = elf_symtab__new(".symtab", ctf->elf); return ctf->symtab == NULL ? -1 : 0; } void ctf__set_strings(struct ctf *ctf, struct strings *strings) { ctf->strings = strings; } uint32_t ctf__add_base_type(struct ctf *ctf, uint32_t name, uint16_t size) { struct ctf_full_type t; t.base.ctf_name = name; t.base.ctf_info = CTF_INFO_ENCODE(CTF_TYPE_KIND_INT, 0, 0); t.base.ctf_size = size; t.ctf_size_high = CTF_TYPE_INT_ENCODE(0, 0, size); gobuffer__add(&ctf->types, &t, sizeof(t) - sizeof(uint32_t)); return ++ctf->type_index; } uint32_t ctf__add_short_type(struct ctf *ctf, uint16_t kind, uint16_t type, uint32_t name) { struct ctf_short_type t; t.ctf_name = name; t.ctf_info = CTF_INFO_ENCODE(kind, 0, 0); t.ctf_type = type; gobuffer__add(&ctf->types, &t, sizeof(t)); return ++ctf->type_index; } uint32_t ctf__add_fwd_decl(struct ctf *ctf, uint32_t name) { return ctf__add_short_type(ctf, CTF_TYPE_KIND_FWD, 0, name); } uint32_t ctf__add_array(struct ctf *ctf, uint16_t type, uint16_t index_type, uint32_t nelems) { struct { struct ctf_short_type t; struct ctf_array a; } array; array.t.ctf_name = 0; array.t.ctf_info = CTF_INFO_ENCODE(CTF_TYPE_KIND_ARR, 0, 0); array.t.ctf_size = 0; array.a.ctf_array_type = type; array.a.ctf_array_index_type = index_type; array.a.ctf_array_nelems = nelems; gobuffer__add(&ctf->types, &array, sizeof(array)); return ++ctf->type_index; } void ctf__add_short_member(struct ctf *ctf, uint32_t name, uint16_t type, uint16_t offset, int64_t *position) { struct ctf_short_member m = { .ctf_member_name = name, .ctf_member_type = type, .ctf_member_offset = offset, }; memcpy(gobuffer__ptr(&ctf->types, *position), &m, sizeof(m)); *position += sizeof(m); } void ctf__add_full_member(struct ctf *ctf, uint32_t name, uint16_t type, uint64_t offset, int64_t *position) { struct ctf_full_member m = { .ctf_member_name = name, .ctf_member_type = type, .ctf_member_offset_high = offset >> 32, .ctf_member_offset_low = offset & 0xffffffffl, }; memcpy(gobuffer__ptr(&ctf->types, *position), &m, sizeof(m)); *position += sizeof(m); } uint32_t ctf__add_struct(struct ctf *ctf, uint16_t kind, uint32_t name, uint64_t size, uint16_t nr_members, int64_t *position) { const bool is_short = size < CTF_SHORT_MEMBER_LIMIT; uint32_t members_len = ((is_short ? sizeof(struct ctf_short_member) : sizeof(struct ctf_full_member)) * nr_members); struct ctf_full_type t; int len; t.base.ctf_name = name; t.base.ctf_info = CTF_INFO_ENCODE(kind, nr_members, 0); if (size < 0xffff) { len = sizeof(t.base); t.base.ctf_size = size; } else { len = sizeof(t); t.base.ctf_size = 0xffff; t.ctf_size_high = size >> 32; t.ctf_size_low = size & 0xffffffff; } gobuffer__add(&ctf->types, &t, len); *position = gobuffer__allocate(&ctf->types, members_len); return ++ctf->type_index; } void ctf__add_parameter(struct ctf *ctf, uint16_t type, int64_t *position) { uint16_t *parm = gobuffer__ptr(&ctf->types, *position); *parm = type; *position += sizeof(*parm); } uint32_t ctf__add_function_type(struct ctf *ctf, uint16_t type, uint16_t nr_parms, bool varargs, int64_t *position) { struct ctf_short_type t; int len = sizeof(uint16_t) * (nr_parms + !!varargs); /* * Round up to next multiple of 4 to maintain 32-bit alignment. */ if (len & 0x2) len += 0x2; t.ctf_name = 0; t.ctf_info = CTF_INFO_ENCODE(CTF_TYPE_KIND_FUNC, nr_parms + !!varargs, 0); t.ctf_type = type; gobuffer__add(&ctf->types, &t, sizeof(t)); *position = gobuffer__allocate(&ctf->types, len); if (varargs) { unsigned int pos = *position + (nr_parms * sizeof(uint16_t)); uint16_t *end_of_args = gobuffer__ptr(&ctf->types, pos); *end_of_args = 0; } return ++ctf->type_index; } uint32_t ctf__add_enumeration_type(struct ctf *ctf, uint32_t name, uint16_t size, uint16_t nr_entries, int64_t *position) { struct ctf_short_type e; e.ctf_name = name; e.ctf_info = CTF_INFO_ENCODE(CTF_TYPE_KIND_ENUM, nr_entries, 0); e.ctf_size = size; gobuffer__add(&ctf->types, &e, sizeof(e)); *position = gobuffer__allocate(&ctf->types, nr_entries * sizeof(struct ctf_enum)); return ++ctf->type_index; } void ctf__add_enumerator(struct ctf *ctf, uint32_t name, uint32_t value, int64_t *position) { struct ctf_enum m = { .ctf_enum_name = name, .ctf_enum_val = value, }; memcpy(gobuffer__ptr(&ctf->types, *position), &m, sizeof(m)); *position += sizeof(m); } void ctf__add_function_parameter(struct ctf *ctf, uint16_t type, int64_t *position) { uint16_t *parm = gobuffer__ptr(&ctf->funcs, *position); *parm = type; *position += sizeof(*parm); } int ctf__add_function(struct ctf *ctf, uint16_t type, uint16_t nr_parms, bool varargs, int64_t *position) { struct ctf_short_type func; int len = sizeof(uint16_t) * (nr_parms + !!varargs); /* * Round up to next multiple of 4 to maintain 32-bit alignment. */ if (len & 0x2) len += 0x2; func.ctf_info = CTF_INFO_ENCODE(CTF_TYPE_KIND_FUNC, nr_parms + !!varargs, 0); func.ctf_type = type; /* * We don't store the name for the function, it comes from the * symtab. */ gobuffer__add(&ctf->funcs, &func.ctf_info, sizeof(func) - sizeof(func.ctf_name)); *position = gobuffer__allocate(&ctf->funcs, len); if (varargs) { unsigned int pos = *position + (nr_parms * sizeof(uint16_t)); uint16_t *end_of_args = gobuffer__ptr(&ctf->funcs, pos); *end_of_args = 0; } return 0; } int ctf__add_object(struct ctf *ctf, uint16_t type) { return gobuffer__add(&ctf->objects, &type, sizeof(type)) >= 0 ? 0 : -ENOMEM; } #if 0 static const void *ctf__compress(void *orig_buf, unsigned int *size) { z_stream z = { .zalloc = Z_NULL, .zfree = Z_NULL, .opaque = Z_NULL, .avail_in = *size, .next_in = (Bytef *)orig_buf, }; void *bf = NULL; unsigned int bf_size = 0; if (deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK) goto out; #define _GOBUFFER__ZCHUNK 16384 * 1024 do { const unsigned int new_bf_size = bf_size + _GOBUFFER__ZCHUNK; void *nbf = realloc(bf, new_bf_size); if (nbf == NULL) goto out_close_and_free; bf = nbf; z.avail_out = _GOBUFFER__ZCHUNK; z.next_out = (Bytef *)bf + bf_size; bf_size = new_bf_size; if (deflate(&z, Z_FULL_FLUSH) == Z_STREAM_ERROR) goto out_close_and_free; #if 0 fprintf(stderr, "%s: size=%d, bf_size=%d, total_out=%ld, total_in=%ld\n", __func__, *size, bf_size, z.total_out, z.total_in); #endif } while (z.total_in != *size); if (deflate(&z, Z_FINISH) == Z_STREAM_ERROR) goto out_close_and_free; deflateEnd(&z); *size = z.total_out; out: return bf; out_close_and_free: deflateEnd(&z); free(bf); bf = NULL; goto out; } int ctf__encode(struct ctf *ctf, uint8_t flags) { struct ctf_header *hdr; unsigned int size; void *bf = NULL; int err = -1; /* Empty file, nothing to do, so... done! */ if (gobuffer__size(&ctf->types) == 0) return 0; size = (gobuffer__size(&ctf->types) + gobuffer__size(&ctf->objects) + gobuffer__size(&ctf->funcs) + strings__size(ctf->strings)); ctf->size = sizeof(*hdr) + size; ctf->buf = malloc(ctf->size); if (ctf->buf == NULL) { fprintf(stderr, "%s: malloc failed!\n", __func__); return -ENOMEM; } hdr = ctf->buf; memset(hdr, 0, sizeof(*hdr)); hdr->ctf_magic = CTF_MAGIC; hdr->ctf_version = 2; hdr->ctf_flags = flags; uint32_t offset = 0; hdr->ctf_object_off = offset; offset += gobuffer__size(&ctf->objects); hdr->ctf_func_off = offset; offset += gobuffer__size(&ctf->funcs); hdr->ctf_type_off = offset; offset += gobuffer__size(&ctf->types); hdr->ctf_str_off = offset; hdr->ctf_str_len = strings__size(ctf->strings); void *payload = ctf->buf + sizeof(*hdr); gobuffer__copy(&ctf->objects, payload + hdr->ctf_object_off); gobuffer__copy(&ctf->funcs, payload + hdr->ctf_func_off); gobuffer__copy(&ctf->types, payload + hdr->ctf_type_off); strings__copy(ctf->strings, payload + hdr->ctf_str_off); *(char *)(ctf->buf + sizeof(*hdr) + hdr->ctf_str_off) = '\0'; if (flags & CTF_FLAGS_COMPR) { bf = (void *)ctf__compress(ctf->buf + sizeof(*hdr), &size); if (bf == NULL) { printf("%s: ctf__compress failed!\n", __func__); return -ENOMEM; } void *new_bf = malloc(sizeof(*hdr) + size); if (new_bf == NULL) return -ENOMEM; memcpy(new_bf, hdr, sizeof(*hdr)); memcpy(new_bf + sizeof(*hdr), bf, size); free(bf); bf = new_bf; size += sizeof(*hdr); } else { bf = ctf->buf; size = ctf->size; } #if 0 printf("\n\ntypes:\n entries: %d\n size: %u" "\nstrings:\n size: %u\ncompressed size: %d\n", ctf->type_index, gobuffer__size(&ctf->types), strings__size(ctf->strings), size); #endif int fd = open(ctf->filename, O_RDWR); if (fd < 0) { fprintf(stderr, "Cannot open %s\n", ctf->filename); return -1; } if (elf_version(EV_CURRENT) == EV_NONE) { fprintf(stderr, "Cannot set libelf version.\n"); goto out_close; } Elf *elf = elf_begin(fd, ELF_C_RDWR, NULL); if (elf == NULL) { fprintf(stderr, "Cannot update ELF file.\n"); goto out_close; } elf_flagelf(elf, ELF_C_SET, ELF_F_DIRTY); GElf_Ehdr ehdr_mem; GElf_Ehdr *ehdr = gelf_getehdr(elf, &ehdr_mem); if (ehdr == NULL) { fprintf(stderr, "%s: elf_getehdr failed.\n", __func__); goto out_close; } /* * First we look if there was already a .SUNW_ctf section to overwrite. */ Elf_Data *data = NULL; size_t strndx; GElf_Shdr shdr_mem; GElf_Shdr *shdr; Elf_Scn *scn = NULL; elf_getshdrstrndx(elf, &strndx); while ((scn = elf_nextscn(elf, scn)) != NULL) { shdr = gelf_getshdr(scn, &shdr_mem); if (shdr == NULL) continue; char *secname = elf_strptr(elf, strndx, shdr->sh_name); if (strcmp(secname, ".SUNW_ctf") == 0) { data = elf_getdata(scn, data); goto out_update; } } /* FIXME * OK, if we have the section, that is ok, we can just replace the * data, if not, I made a mistake on the small amount of boilerplate * below, probably .relA.ted to relocations... */ #if 0 /* Now we look if the ".SUNW_ctf" string is in the strings table */ scn = elf_getscn(elf, strndx); shdr = gelf_getshdr(scn, &shdr_mem); data = elf_getdata(scn, data); fprintf(stderr, "Looking for the string\n"); size_t ctf_name_offset = 1; /* First byte is '\0' */ while (ctf_name_offset < data->d_size) { const char *cur_str = data->d_buf + ctf_name_offset; fprintf(stderr, "*-> %s\n", cur_str); if (strcmp(cur_str, ".SUNW_ctf") == 0) goto found_SUNW_ctf_str; ctf_name_offset += strlen(cur_str) + 1; } /* Add the section name */ const size_t ctf_name_len = strlen(".SUNW_ctf") + 1; char *new_strings_table = malloc(data->d_size + ctf_name_len); if (new_strings_table == NULL) goto out_close; memcpy(new_strings_table, data->d_buf, data->d_size); strcpy(new_strings_table + data->d_size, ".SUNW_ctf"); ctf_name_offset = data->d_size; data->d_size += ctf_name_len; data->d_buf = new_strings_table; elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY); elf_flagshdr(scn, ELF_C_SET, ELF_F_DIRTY); Elf_Scn *newscn; found_SUNW_ctf_str: newscn = elf_newscn(elf); if (newscn == NULL) goto out_close; data = elf_newdata(newscn); if (data == NULL) goto out_close; shdr = gelf_getshdr(newscn, &shdr_mem); shdr->sh_name = ctf_name_offset; shdr->sh_type = SHT_PROGBITS; gelf_update_shdr(newscn, &shdr_mem); elf_flagshdr(newscn, ELF_C_SET, ELF_F_DIRTY); #else char pathname[PATH_MAX]; snprintf(pathname, sizeof(pathname), "%s.SUNW_ctf", ctf->filename); fd = creat(pathname, S_IRUSR | S_IWUSR); if (fd == -1) { fprintf(stderr, "%s: open(%s) failed!\n", __func__, pathname); goto out_close; } if (write(fd, bf, size) != size) goto out_close; if (close(fd) < 0) goto out_unlink; char cmd[PATH_MAX * 2]; snprintf(cmd, sizeof(cmd), "objcopy --add-section .SUNW_ctf=%s %s", pathname, ctf->filename); if (system(cmd) == 0) err = 0; out_unlink: unlink(pathname); return err; #endif out_update: data->d_buf = bf; data->d_size = size; elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY); if (elf_update(elf, ELF_C_NULL) < 0) goto out_close; if (elf_update(elf, ELF_C_WRITE) < 0) goto out_close; elf_end(elf); err = 0; out_close: if (bf != ctf->buf) free(bf); close(fd); return err; } #endif