static const char *_stp_kallsyms_lookup(unsigned long addr, unsigned long *symbolsize, unsigned long *offset, const char **modname, /* char ** secname? */ struct task_struct *task) { struct _stp_module *m = NULL; struct _stp_section *sec = NULL; struct _stp_symbol *s = NULL; unsigned end, begin = 0; unsigned long rel_addr = 0; if (addr == 0) return NULL; if (task) { unsigned long vm_start = 0; unsigned long vm_end = 0; #ifdef CONFIG_COMPAT /* Handle 32bit signed values in 64bit longs, chop off top bits. _stp_umod_lookup does the same, but we need it here for the binary search on addr below. */ if (test_tsk_thread_flag(task, TIF_32BIT)) addr &= ((compat_ulong_t) ~0); #endif m = _stp_umod_lookup(addr, task, modname, &vm_start, &vm_end); if (m) { sec = &m->sections[0]; /* XXX .absolute sections really shouldn't be here... */ if (strcmp(".dynamic", m->sections[0].name) == 0) rel_addr = addr - vm_start; else rel_addr = addr; } if (modname && *modname) { /* In case no symbol is found, fill in based on module. */ if (offset) *offset = addr - vm_start; if (symbolsize) *symbolsize = vm_end - vm_start; } } else { m = _stp_kmod_sec_lookup(addr, &sec); if (m) { rel_addr = addr - sec->static_addr; if (modname) *modname = m->name; } } if (unlikely (m == NULL || sec == NULL)) return NULL; /* NB: relativize the address to the section. */ addr = rel_addr; end = sec->num_symbols; /* binary search for symbols within the module */ do { unsigned mid = (begin + end) / 2; if (addr < sec->symbols[mid].addr) end = mid; else begin = mid; } while (begin + 1 < end); /* result index in $begin */ s = & sec->symbols[begin]; if (likely(addr >= s->addr)) { if (offset) *offset = addr - s->addr; /* We could also pass sec->name here. */ if (symbolsize) { if ((begin + 1) < sec->num_symbols) *symbolsize = sec->symbols[begin + 1].addr - s->addr; else *symbolsize = 0; // NB: This is only a heuristic. Sometimes there are large // gaps between text areas of modules. } return s->symbol; } return NULL; }
unsigned long _stp_linenumber_lookup(unsigned long addr, struct task_struct *task, char ** filename, int need_filename) { struct _stp_module *m; struct _stp_section *sec; const char *modname = NULL; uint8_t *linep, *enddatap; int compat_task = _stp_is_compat_task(); int user = (task ? 1 : 0); // the portion below is encased in this conditional because some of the functions // and constants needed are encased in a similar condition #ifdef STP_NEED_LINE_DATA if (addr == 0) return 0; if (task) { unsigned long vm_start = 0; unsigned long vm_end = 0; #ifdef CONFIG_COMPAT /* Handle 32bit signed values in 64bit longs, chop off top bits. */ if (test_tsk_thread_flag(task, TIF_32BIT)) addr &= ((compat_ulong_t) ~0); #endif m = _stp_umod_lookup(addr, task, &modname, &vm_start, &vm_end); } else m = _stp_kmod_sec_lookup(addr, &sec); if (m == NULL || m->debug_line == NULL) return 0; // if addr is a kernel address, it will need to be adjusted if (!task) { int i; unsigned long offset = 0; // have to factor in the load_offset of (specifically) the .text section for (i=0; i<m->num_sections; i++) if (!strcmp(m->sections[i].name, ".text")) { offset = (m->sections[i].static_addr - m->sections[i].sec_load_offset); break; } if (addr < offset) return 0; addr = addr - offset; } linep = m->debug_line; enddatap = m->debug_line + m->debug_line_len; while (linep < enddatap) { // similar to print_debug_line_section() in elfutils unsigned int length = 4, curr_file_idx = 1, prev_file_idx = 1; unsigned int skip_to_seq_end = 0, op_index = 0; uint64_t unit_length, hdr_length, curr_addr = 0; uint8_t *endunitp, *endhdrp, *dirsecp, *stdopcode_lens_secp; uint16_t version; uint8_t opcode_base, line_range, min_instr_len = 0, max_ops = 1; unsigned long curr_linenum = 1; int8_t line_base; long cumm_line_adv = 0; unit_length = (uint64_t) read_pointer ((const uint8_t **) &linep, enddatap, DW_EH_PE_data4, user, compat_task); if (unit_length == 0xffffffff) { if (unlikely (linep + 8 > enddatap)) return 0; unit_length = (uint64_t) read_pointer ((const uint8_t **) &linep, enddatap, DW_EH_PE_data8, user, compat_task); length = 8; } if (unit_length < (length + 2) || (linep + unit_length) > enddatap) return 0; endunitp = linep + unit_length; version = read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data2, user, compat_task); if (length == 4) hdr_length = (uint64_t) read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data4, user, compat_task); else hdr_length = (uint64_t) read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data8, user, compat_task); if ((linep + hdr_length) > endunitp || hdr_length < (version >= 4 ? 6 : 5)) return 0; endhdrp = linep + hdr_length; // minimum instruction length min_instr_len = *linep++; // max operations per instruction if (version >= 4) { max_ops = *linep++; if (max_ops == 0) return 0; // max operations per instruction is supposed to > 0; } // default value of the is_stmt register ++linep; // line base. this is a signed value. line_base = *linep++; // line range line_range = *linep++; if (line_range == 0) return 0; // opcode base opcode_base = *linep++; // opcodes stdopcode_lens_secp = linep - 1; // need this check if the header length check covers this region? if ((linep + opcode_base - 1) >= endhdrp) return 0; linep += opcode_base - 1; // at the directory table. don't need an other information from the header // in order to find the desired line number, so we will save a pointer to // this point and skip ahead to the end of the header. this portion of the // header will be visited again after a line number has been found if a // filename is needed. dirsecp = linep; linep = endhdrp; // iterating through the opcodes. will deal with three defined types of // opcode: special, extended and standard. there is also a portion at // the end of this loop that will deal with unknown (standard) opcodes. while (linep < endunitp) { uint8_t opcode = *linep++; long addr_adv = 0; if (opcode >= opcode_base) // special opcode { // line range was checked before this point. this variable is not altered after it is initialized. cumm_line_adv += (line_base + ((opcode - opcode_base) % line_range)); addr_adv = ((opcode - opcode_base) / line_range); } else if (opcode == 0) // extended opcode { int len; uint8_t subopcode; if (linep + 1 > endunitp) return 0; len = *linep++; if (linep + len > endunitp || len < 1) return 0; subopcode = *linep++; // the sub opcode switch (subopcode) { case DW_LNE_end_sequence: // reset the line and address. cumm_line_adv = 1 - curr_linenum; addr_adv = 0 - curr_addr; skip_to_seq_end = 0; op_index = 0; break; case DW_LNE_set_address: if ((len - 1) == 4) // account for the opcode (the -1) curr_addr = (uint64_t) read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data4, user, compat_task); else if ((len - 1) == 8) curr_addr = (uint64_t) read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data8, user, compat_task); else return 0; // if the set address is past the address we want, iterate // to the end of the sequence without doing more address // and linenumber calcs than necessary if (curr_addr > addr) skip_to_seq_end = 1; op_index = 0; break; default: // advance the ptr by the specified amount linep += len-1; break; } } else if (opcode <= DW_LNS_set_isa) // known standard opcode { uint8_t *linep_before = linep; switch (opcode) { case DW_LNS_advance_pc: addr_adv = read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128, user, compat_task); break; case DW_LNS_fixed_advance_pc: addr_adv = read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_data2, user, compat_task); if (linep_before == linep) // the read failed return 0; op_index = 0; break; case DW_LNS_advance_line: cumm_line_adv += read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128+DW_EH_PE_signed, user, compat_task); break; case DW_LNS_set_file: curr_file_idx = read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128, user, compat_task); break; case DW_LNS_set_column: read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128, user, compat_task); break; case DW_LNS_const_add_pc: addr_adv = ((255 - opcode_base) / line_range); break; case DW_LNS_set_isa: read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128, user, compat_task); break; } if (linep > endunitp) // reading in the leb128 failed return 0; } else { int i; for (i=stdopcode_lens_secp[opcode]; i>0; --i) { read_pointer ((const uint8_t **) &linep, endunitp, DW_EH_PE_leb128, user, compat_task); if (linep > endunitp) return 0; } } // don't worry about doing line/address advances since we are waiting // till we hit the end of the sequence or the end of the unit at which // point the address and linenumber will be reset if (skip_to_seq_end == 1) continue; // calculate actual address advance if (opcode != 0 && opcode != DW_LNS_fixed_advance_pc) { addr_adv = min_instr_len * (op_index + addr_adv) / max_ops; op_index = (op_index + addr_adv) % max_ops; } // found an address that at least sort of matches the address we // were looking for if ((curr_addr <= addr && addr < (curr_addr + addr_adv)) || (curr_addr == addr && addr_adv != 0)) { if (need_filename) _stp_filename_lookup(m, filename, dirsecp, endhdrp, prev_file_idx, user, compat_task); return curr_linenum; } // update the linenumber and file index if the curr_addr is to be updated if (addr_adv != 0) { prev_file_idx = curr_file_idx; } if (prev_file_idx == curr_file_idx) { curr_linenum += cumm_line_adv; cumm_line_adv = 0; } curr_addr += addr_adv; } } #endif /* STP_NEED_LINE_DATA */ // no linenumber was found otherwise this function would have returned before this point return 0; }