|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * genelf_debug.c | 
|  | * Copyright (C) 2015, Google, Inc | 
|  | * | 
|  | * Contributed by: | 
|  | * 	Stephane Eranian <eranian@google.com> | 
|  | * | 
|  | * based on GPLv2 source code from Oprofile | 
|  | * @remark Copyright 2007 OProfile authors | 
|  | * @author Philippe Elie | 
|  | */ | 
|  | #include <linux/compiler.h> | 
|  | #include <sys/types.h> | 
|  | #include <stdio.h> | 
|  | #include <getopt.h> | 
|  | #include <stddef.h> | 
|  | #include <libelf.h> | 
|  | #include <string.h> | 
|  | #include <stdlib.h> | 
|  | #include <inttypes.h> | 
|  | #include <limits.h> | 
|  | #include <fcntl.h> | 
|  | #include <err.h> | 
|  | #include <dwarf.h> | 
|  |  | 
|  | #include "genelf.h" | 
|  | #include "../util/jitdump.h" | 
|  |  | 
|  | #define BUFFER_EXT_DFL_SIZE	(4 * 1024) | 
|  |  | 
|  | typedef uint32_t uword; | 
|  | typedef uint16_t uhalf; | 
|  | typedef int32_t  sword; | 
|  | typedef int16_t  shalf; | 
|  | typedef uint8_t  ubyte; | 
|  | typedef int8_t   sbyte; | 
|  |  | 
|  | struct buffer_ext { | 
|  | size_t cur_pos; | 
|  | size_t max_sz; | 
|  | void *data; | 
|  | }; | 
|  |  | 
|  | static void | 
|  | buffer_ext_dump(struct buffer_ext *be, const char *msg) | 
|  | { | 
|  | size_t i; | 
|  | warnx("DUMP for %s", msg); | 
|  | for (i = 0 ; i < be->cur_pos; i++) | 
|  | warnx("%4zu 0x%02x", i, (((char *)be->data)[i]) & 0xff); | 
|  | } | 
|  |  | 
|  | static inline int | 
|  | buffer_ext_add(struct buffer_ext *be, void *addr, size_t sz) | 
|  | { | 
|  | void *tmp; | 
|  | size_t be_sz = be->max_sz; | 
|  |  | 
|  | retry: | 
|  | if ((be->cur_pos + sz) < be_sz) { | 
|  | memcpy(be->data + be->cur_pos, addr, sz); | 
|  | be->cur_pos += sz; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!be_sz) | 
|  | be_sz = BUFFER_EXT_DFL_SIZE; | 
|  | else | 
|  | be_sz <<= 1; | 
|  |  | 
|  | tmp = realloc(be->data, be_sz); | 
|  | if (!tmp) | 
|  | return -1; | 
|  |  | 
|  | be->data   = tmp; | 
|  | be->max_sz = be_sz; | 
|  |  | 
|  | goto retry; | 
|  | } | 
|  |  | 
|  | static void | 
|  | buffer_ext_init(struct buffer_ext *be) | 
|  | { | 
|  | be->data = NULL; | 
|  | be->cur_pos = 0; | 
|  | be->max_sz = 0; | 
|  | } | 
|  |  | 
|  | static inline size_t | 
|  | buffer_ext_size(struct buffer_ext *be) | 
|  | { | 
|  | return be->cur_pos; | 
|  | } | 
|  |  | 
|  | static inline void * | 
|  | buffer_ext_addr(struct buffer_ext *be) | 
|  | { | 
|  | return be->data; | 
|  | } | 
|  |  | 
|  | struct debug_line_header { | 
|  | // Not counting this field | 
|  | uword total_length; | 
|  | // version number (2 currently) | 
|  | uhalf version; | 
|  | // relative offset from next field to | 
|  | // program statement | 
|  | uword prolog_length; | 
|  | ubyte minimum_instruction_length; | 
|  | ubyte default_is_stmt; | 
|  | // line_base - see DWARF 2 specs | 
|  | sbyte line_base; | 
|  | // line_range - see DWARF 2 specs | 
|  | ubyte line_range; | 
|  | // number of opcode + 1 | 
|  | ubyte opcode_base; | 
|  | /* follow the array of opcode args nr: ubytes [nr_opcode_base] */ | 
|  | /* follow the search directories index, zero terminated string | 
|  | * terminated by an empty string. | 
|  | */ | 
|  | /* follow an array of { filename, LEB128, LEB128, LEB128 }, first is | 
|  | * the directory index entry, 0 means current directory, then mtime | 
|  | * and filesize, last entry is followed by en empty string. | 
|  | */ | 
|  | /* follow the first program statement */ | 
|  | } __packed; | 
|  |  | 
|  | /* DWARF 2 spec talk only about one possible compilation unit header while | 
|  | * binutils can handle two flavours of dwarf 2, 32 and 64 bits, this is not | 
|  | * related to the used arch, an ELF 32 can hold more than 4 Go of debug | 
|  | * information. For now we handle only DWARF 2 32 bits comp unit. It'll only | 
|  | * become a problem if we generate more than 4GB of debug information. | 
|  | */ | 
|  | struct compilation_unit_header { | 
|  | uword total_length; | 
|  | uhalf version; | 
|  | uword debug_abbrev_offset; | 
|  | ubyte pointer_size; | 
|  | } __packed; | 
|  |  | 
|  | #define DW_LNS_num_opcode (DW_LNS_set_isa + 1) | 
|  |  | 
|  | /* field filled at run time are marked with -1 */ | 
|  | static struct debug_line_header const default_debug_line_header = { | 
|  | .total_length = -1, | 
|  | .version = 2, | 
|  | .prolog_length = -1, | 
|  | .minimum_instruction_length = 1,	/* could be better when min instruction size != 1 */ | 
|  | .default_is_stmt = 1,	/* we don't take care about basic block */ | 
|  | .line_base = -5,	/* sensible value for line base ... */ | 
|  | .line_range = -14,     /* ... and line range are guessed statically */ | 
|  | .opcode_base = DW_LNS_num_opcode | 
|  | }; | 
|  |  | 
|  | static ubyte standard_opcode_length[] = | 
|  | { | 
|  | 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 | 
|  | }; | 
|  | #if 0 | 
|  | { | 
|  | [DW_LNS_advance_pc]   = 1, | 
|  | [DW_LNS_advance_line] = 1, | 
|  | [DW_LNS_set_file] =  1, | 
|  | [DW_LNS_set_column] = 1, | 
|  | [DW_LNS_fixed_advance_pc] = 1, | 
|  | [DW_LNS_set_isa] = 1, | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | /* field filled at run time are marked with -1 */ | 
|  | static struct compilation_unit_header default_comp_unit_header = { | 
|  | .total_length = -1, | 
|  | .version = 2, | 
|  | .debug_abbrev_offset = 0,     /* we reuse the same abbrev entries for all comp unit */ | 
|  | .pointer_size = sizeof(void *) | 
|  | }; | 
|  |  | 
|  | static void emit_uword(struct buffer_ext *be, uword data) | 
|  | { | 
|  | buffer_ext_add(be, &data, sizeof(uword)); | 
|  | } | 
|  |  | 
|  | static void emit_string(struct buffer_ext *be, const char *s) | 
|  | { | 
|  | buffer_ext_add(be, (void *)s, strlen(s) + 1); | 
|  | } | 
|  |  | 
|  | static void emit_unsigned_LEB128(struct buffer_ext *be, | 
|  | unsigned long data) | 
|  | { | 
|  | do { | 
|  | ubyte cur = data & 0x7F; | 
|  | data >>= 7; | 
|  | if (data) | 
|  | cur |= 0x80; | 
|  | buffer_ext_add(be, &cur, 1); | 
|  | } while (data); | 
|  | } | 
|  |  | 
|  | static void emit_signed_LEB128(struct buffer_ext *be, long data) | 
|  | { | 
|  | int more = 1; | 
|  | int negative = data < 0; | 
|  | int size = sizeof(long) * CHAR_BIT; | 
|  | while (more) { | 
|  | ubyte cur = data & 0x7F; | 
|  | data >>= 7; | 
|  | if (negative) | 
|  | data |= - (1 << (size - 7)); | 
|  | if ((data == 0 && !(cur & 0x40)) || | 
|  | (data == -1l && (cur & 0x40))) | 
|  | more = 0; | 
|  | else | 
|  | cur |= 0x80; | 
|  | buffer_ext_add(be, &cur, 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void emit_extended_opcode(struct buffer_ext *be, ubyte opcode, | 
|  | void *data, size_t data_len) | 
|  | { | 
|  | buffer_ext_add(be, (char *)"", 1); | 
|  |  | 
|  | emit_unsigned_LEB128(be, data_len + 1); | 
|  |  | 
|  | buffer_ext_add(be, &opcode, 1); | 
|  | buffer_ext_add(be, data, data_len); | 
|  | } | 
|  |  | 
|  | static void emit_opcode(struct buffer_ext *be, ubyte opcode) | 
|  | { | 
|  | buffer_ext_add(be, &opcode, 1); | 
|  | } | 
|  |  | 
|  | static void emit_opcode_signed(struct buffer_ext  *be, | 
|  | ubyte opcode, long data) | 
|  | { | 
|  | buffer_ext_add(be, &opcode, 1); | 
|  | emit_signed_LEB128(be, data); | 
|  | } | 
|  |  | 
|  | static void emit_opcode_unsigned(struct buffer_ext *be, ubyte opcode, | 
|  | unsigned long data) | 
|  | { | 
|  | buffer_ext_add(be, &opcode, 1); | 
|  | emit_unsigned_LEB128(be, data); | 
|  | } | 
|  |  | 
|  | static void emit_advance_pc(struct buffer_ext *be, unsigned long delta_pc) | 
|  | { | 
|  | emit_opcode_unsigned(be, DW_LNS_advance_pc, delta_pc); | 
|  | } | 
|  |  | 
|  | static void emit_advance_lineno(struct buffer_ext  *be, long delta_lineno) | 
|  | { | 
|  | emit_opcode_signed(be, DW_LNS_advance_line, delta_lineno); | 
|  | } | 
|  |  | 
|  | static void emit_lne_end_of_sequence(struct buffer_ext *be) | 
|  | { | 
|  | emit_extended_opcode(be, DW_LNE_end_sequence, NULL, 0); | 
|  | } | 
|  |  | 
|  | static void emit_set_file(struct buffer_ext *be, unsigned long idx) | 
|  | { | 
|  | emit_opcode_unsigned(be, DW_LNS_set_file, idx); | 
|  | } | 
|  |  | 
|  | static void emit_lne_define_filename(struct buffer_ext *be, | 
|  | const char *filename) | 
|  | { | 
|  | buffer_ext_add(be, (void *)"", 1); | 
|  |  | 
|  | /* LNE field, strlen(filename) + zero termination, 3 bytes for: the dir entry, timestamp, filesize */ | 
|  | emit_unsigned_LEB128(be, strlen(filename) + 5); | 
|  | emit_opcode(be, DW_LNE_define_file); | 
|  | emit_string(be, filename); | 
|  | /* directory index 0=do not know */ | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | /* last modification date on file 0=do not know */ | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | /* filesize 0=do not know */ | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | } | 
|  |  | 
|  | static void emit_lne_set_address(struct buffer_ext *be, | 
|  | void *address) | 
|  | { | 
|  | emit_extended_opcode(be, DW_LNE_set_address, &address, sizeof(unsigned long)); | 
|  | } | 
|  |  | 
|  | static ubyte get_special_opcode(struct debug_entry *ent, | 
|  | unsigned int last_line, | 
|  | unsigned long last_vma) | 
|  | { | 
|  | unsigned int temp; | 
|  | unsigned long delta_addr; | 
|  |  | 
|  | /* | 
|  | * delta from line_base | 
|  | */ | 
|  | temp = (ent->lineno - last_line) - default_debug_line_header.line_base; | 
|  |  | 
|  | if (temp >= default_debug_line_header.line_range) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * delta of addresses | 
|  | */ | 
|  | delta_addr = (ent->addr - last_vma) / default_debug_line_header.minimum_instruction_length; | 
|  |  | 
|  | /* This is not sufficient to ensure opcode will be in [0-256] but | 
|  | * sufficient to ensure when summing with the delta lineno we will | 
|  | * not overflow the unsigned long opcode */ | 
|  |  | 
|  | if (delta_addr <= 256 / default_debug_line_header.line_range) { | 
|  | unsigned long opcode = temp + | 
|  | (delta_addr * default_debug_line_header.line_range) + | 
|  | default_debug_line_header.opcode_base; | 
|  |  | 
|  | return opcode <= 255 ? opcode : 0; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void emit_lineno_info(struct buffer_ext *be, | 
|  | struct debug_entry *ent, size_t nr_entry, | 
|  | unsigned long code_addr) | 
|  | { | 
|  | size_t i; | 
|  |  | 
|  | /* | 
|  | * Machine state at start of a statement program | 
|  | * address = 0 | 
|  | * file    = 1 | 
|  | * line    = 1 | 
|  | * column  = 0 | 
|  | * is_stmt = default_is_stmt as given in the debug_line_header | 
|  | * basic block = 0 | 
|  | * end sequence = 0 | 
|  | */ | 
|  |  | 
|  | /* start state of the state machine we take care of */ | 
|  | unsigned long last_vma = 0; | 
|  | char const  *cur_filename = NULL; | 
|  | unsigned long cur_file_idx = 0; | 
|  | int last_line = 1; | 
|  |  | 
|  | emit_lne_set_address(be, (void *)code_addr); | 
|  |  | 
|  | for (i = 0; i < nr_entry; i++, ent = debug_entry_next(ent)) { | 
|  | int need_copy = 0; | 
|  | ubyte special_opcode; | 
|  |  | 
|  | /* | 
|  | * check if filename changed, if so add it | 
|  | */ | 
|  | if (!cur_filename || strcmp(cur_filename, ent->name)) { | 
|  | emit_lne_define_filename(be, ent->name); | 
|  | cur_filename = ent->name; | 
|  | emit_set_file(be, ++cur_file_idx); | 
|  | need_copy = 1; | 
|  | } | 
|  |  | 
|  | special_opcode = get_special_opcode(ent, last_line, last_vma); | 
|  | if (special_opcode != 0) { | 
|  | last_line = ent->lineno; | 
|  | last_vma  = ent->addr; | 
|  | emit_opcode(be, special_opcode); | 
|  | } else { | 
|  | /* | 
|  | * lines differ, emit line delta | 
|  | */ | 
|  | if (last_line != ent->lineno) { | 
|  | emit_advance_lineno(be, ent->lineno - last_line); | 
|  | last_line = ent->lineno; | 
|  | need_copy = 1; | 
|  | } | 
|  | /* | 
|  | * addresses differ, emit address delta | 
|  | */ | 
|  | if (last_vma != ent->addr) { | 
|  | emit_advance_pc(be, ent->addr - last_vma); | 
|  | last_vma = ent->addr; | 
|  | need_copy = 1; | 
|  | } | 
|  | /* | 
|  | * add new row to matrix | 
|  | */ | 
|  | if (need_copy) | 
|  | emit_opcode(be, DW_LNS_copy); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void add_debug_line(struct buffer_ext *be, | 
|  | struct debug_entry *ent, size_t nr_entry, | 
|  | unsigned long code_addr) | 
|  | { | 
|  | struct debug_line_header * dbg_header; | 
|  | size_t old_size; | 
|  |  | 
|  | old_size = buffer_ext_size(be); | 
|  |  | 
|  | buffer_ext_add(be, (void *)&default_debug_line_header, | 
|  | sizeof(default_debug_line_header)); | 
|  |  | 
|  | buffer_ext_add(be, &standard_opcode_length,  sizeof(standard_opcode_length)); | 
|  |  | 
|  | // empty directory entry | 
|  | buffer_ext_add(be, (void *)"", 1); | 
|  |  | 
|  | // empty filename directory | 
|  | buffer_ext_add(be, (void *)"", 1); | 
|  |  | 
|  | dbg_header = buffer_ext_addr(be) + old_size; | 
|  | dbg_header->prolog_length = (buffer_ext_size(be) - old_size) - | 
|  | offsetof(struct debug_line_header, minimum_instruction_length); | 
|  |  | 
|  | emit_lineno_info(be, ent, nr_entry, code_addr); | 
|  |  | 
|  | emit_lne_end_of_sequence(be); | 
|  |  | 
|  | dbg_header = buffer_ext_addr(be) + old_size; | 
|  | dbg_header->total_length = (buffer_ext_size(be) - old_size) - | 
|  | offsetof(struct debug_line_header, version); | 
|  | } | 
|  |  | 
|  | static void | 
|  | add_debug_abbrev(struct buffer_ext *be) | 
|  | { | 
|  | emit_unsigned_LEB128(be, 1); | 
|  | emit_unsigned_LEB128(be, DW_TAG_compile_unit); | 
|  | emit_unsigned_LEB128(be, DW_CHILDREN_yes); | 
|  | emit_unsigned_LEB128(be, DW_AT_stmt_list); | 
|  | emit_unsigned_LEB128(be, DW_FORM_data4); | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | emit_unsigned_LEB128(be, 0); | 
|  | } | 
|  |  | 
|  | static void | 
|  | add_compilation_unit(struct buffer_ext *be, | 
|  | size_t offset_debug_line) | 
|  | { | 
|  | struct compilation_unit_header *comp_unit_header; | 
|  | size_t old_size = buffer_ext_size(be); | 
|  |  | 
|  | buffer_ext_add(be, &default_comp_unit_header, | 
|  | sizeof(default_comp_unit_header)); | 
|  |  | 
|  | emit_unsigned_LEB128(be, 1); | 
|  | emit_uword(be, offset_debug_line); | 
|  |  | 
|  | comp_unit_header = buffer_ext_addr(be) + old_size; | 
|  | comp_unit_header->total_length = (buffer_ext_size(be) - old_size) - | 
|  | offsetof(struct compilation_unit_header, version); | 
|  | } | 
|  |  | 
|  | static int | 
|  | jit_process_debug_info(uint64_t code_addr, | 
|  | void *debug, int nr_debug_entries, | 
|  | struct buffer_ext *dl, | 
|  | struct buffer_ext *da, | 
|  | struct buffer_ext *di) | 
|  | { | 
|  | struct debug_entry *ent = debug; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < nr_debug_entries; i++) { | 
|  | ent->addr = ent->addr - code_addr; | 
|  | ent = debug_entry_next(ent); | 
|  | } | 
|  | add_compilation_unit(di, buffer_ext_size(dl)); | 
|  | add_debug_line(dl, debug, nr_debug_entries, GEN_ELF_TEXT_OFFSET); | 
|  | add_debug_abbrev(da); | 
|  | if (0) buffer_ext_dump(da, "abbrev"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | jit_add_debug_info(Elf *e, uint64_t code_addr, void *debug, int nr_debug_entries) | 
|  | { | 
|  | Elf_Data *d; | 
|  | Elf_Scn *scn; | 
|  | Elf_Shdr *shdr; | 
|  | struct buffer_ext dl, di, da; | 
|  | int ret; | 
|  |  | 
|  | buffer_ext_init(&dl); | 
|  | buffer_ext_init(&di); | 
|  | buffer_ext_init(&da); | 
|  |  | 
|  | ret = jit_process_debug_info(code_addr, debug, nr_debug_entries, &dl, &da, &di); | 
|  | if (ret) | 
|  | return -1; | 
|  | /* | 
|  | * setup .debug_line section | 
|  | */ | 
|  | scn = elf_newscn(e); | 
|  | if (!scn) { | 
|  | warnx("cannot create section"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d = elf_newdata(scn); | 
|  | if (!d) { | 
|  | warnx("cannot get new data"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d->d_align = 1; | 
|  | d->d_off = 0LL; | 
|  | d->d_buf = buffer_ext_addr(&dl); | 
|  | d->d_type = ELF_T_BYTE; | 
|  | d->d_size = buffer_ext_size(&dl); | 
|  | d->d_version = EV_CURRENT; | 
|  |  | 
|  | shdr = elf_getshdr(scn); | 
|  | if (!shdr) { | 
|  | warnx("cannot get section header"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | shdr->sh_name = 52; /* .debug_line */ | 
|  | shdr->sh_type = SHT_PROGBITS; | 
|  | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | 
|  | shdr->sh_flags = 0; | 
|  | shdr->sh_entsize = 0; | 
|  |  | 
|  | /* | 
|  | * setup .debug_info section | 
|  | */ | 
|  | scn = elf_newscn(e); | 
|  | if (!scn) { | 
|  | warnx("cannot create section"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d = elf_newdata(scn); | 
|  | if (!d) { | 
|  | warnx("cannot get new data"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d->d_align = 1; | 
|  | d->d_off = 0LL; | 
|  | d->d_buf = buffer_ext_addr(&di); | 
|  | d->d_type = ELF_T_BYTE; | 
|  | d->d_size = buffer_ext_size(&di); | 
|  | d->d_version = EV_CURRENT; | 
|  |  | 
|  | shdr = elf_getshdr(scn); | 
|  | if (!shdr) { | 
|  | warnx("cannot get section header"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | shdr->sh_name = 64; /* .debug_info */ | 
|  | shdr->sh_type = SHT_PROGBITS; | 
|  | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | 
|  | shdr->sh_flags = 0; | 
|  | shdr->sh_entsize = 0; | 
|  |  | 
|  | /* | 
|  | * setup .debug_abbrev section | 
|  | */ | 
|  | scn = elf_newscn(e); | 
|  | if (!scn) { | 
|  | warnx("cannot create section"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d = elf_newdata(scn); | 
|  | if (!d) { | 
|  | warnx("cannot get new data"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | d->d_align = 1; | 
|  | d->d_off = 0LL; | 
|  | d->d_buf = buffer_ext_addr(&da); | 
|  | d->d_type = ELF_T_BYTE; | 
|  | d->d_size = buffer_ext_size(&da); | 
|  | d->d_version = EV_CURRENT; | 
|  |  | 
|  | shdr = elf_getshdr(scn); | 
|  | if (!shdr) { | 
|  | warnx("cannot get section header"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | shdr->sh_name = 76; /* .debug_info */ | 
|  | shdr->sh_type = SHT_PROGBITS; | 
|  | shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */ | 
|  | shdr->sh_flags = 0; | 
|  | shdr->sh_entsize = 0; | 
|  |  | 
|  | /* | 
|  | * now we update the ELF image with all the sections | 
|  | */ | 
|  | if (elf_update(e, ELF_C_WRITE) < 0) { | 
|  | warnx("elf_update debug failed"); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } |