Sindbad~EG File Manager
/* -*- linux-c -*-
* Symbolic Lookup Functions
* Copyright (C) 2005-2019 Red Hat Inc.
* Copyright (C) 2006 Intel Corporation.
*
* This file is part of systemtap, and is free software. You can
* redistribute it and/or modify it under the terms of the GNU General
* Public License (GPL); either version 2, or (at your option) any
* later version.
*/
#ifndef _STP_SYM_C_
#define _STP_SYM_C_
#include "sym.h"
#include "vma.c"
#include "stp_string.c"
#ifdef STP_NEED_LINE_DATA
#include "unwind/unwind.h"
#endif
#include <asm/unaligned.h>
#include <asm/uaccess.h>
#include <linux/list.h>
#include <linux/module.h>
#ifdef STAPCONF_PROBE_KERNEL
#include <linux/uaccess.h>
#endif
/* Returns absolute address of offset into kernel module/section.
Returns zero when module and section couldn't be found
(aren't in memory yet). */
static unsigned long _stp_kmodule_relocate(const char *module,
const char *section,
unsigned long offset)
{
unsigned i, j;
dbug_sym(1, "%s, %s, %lx\n", module, section, offset);
/* absolute, unrelocated address */
if (!module || !strcmp(section, "")
||_stp_num_modules == 0) {
return offset;
}
for (i = 0; i < _stp_num_modules; i++) {
struct _stp_module *m = _stp_modules[i];
if (strcmp(module, m->name)) /* duplication apprx. not possible for kernel */
continue;
for (j = 0; j < m->num_sections; j++) {
struct _stp_section *s = &m->sections[j];
if (!strcmp(section, s->name)) {
/* mod and sec name match. tsk should match dynamic/static. */
if (s->static_addr != 0) {
unsigned long addr = offset + s->static_addr;
dbug_sym(1, "address=%lx\n", addr);
return addr;
} else {
/* static section, not in memory yet? */
dbug_sym(1, "section %s, not in memory yet?", s->name);
return 0;
}
}
}
}
return 0;
}
static unsigned long _stp_umodule_relocate(const char *path,
unsigned long offset,
struct task_struct *tsk)
{
unsigned i;
unsigned long vm_start = 0;
dbug_sym(1, "[%d] %s, %lx\n", tsk->pid, path, offset);
for (i = 0; i < _stp_num_modules; i++) {
struct _stp_module *m = _stp_modules[i];
if (!m->path
|| strcmp(path, m->path)
|| m->num_sections != 1)
continue;
if (!strcmp(m->sections[0].name, ".absolute"))
return offset;
if (strcmp(m->sections[0].name, ".dynamic"))
continue;
if (stap_find_vma_map_info_user(tsk->group_leader, m,
&vm_start, NULL, NULL) == 0) {
offset += vm_start;
dbug_sym(1, "address=%lx\n", offset);
return offset;
}
}
return 0;
}
/* Return (kernel) module owner and, if sec != NULL, fills in closest
section of the address if found, return NULL otherwise. */
static struct _stp_module *_stp_kmod_sec_lookup(unsigned long addr,
struct _stp_section **sec)
{
unsigned midx = 0;
for (midx = 0; midx < _stp_num_modules; midx++)
{
unsigned secidx;
for (secidx = 0; secidx < _stp_modules[midx]->num_sections; secidx++)
{
unsigned long sec_addr;
unsigned long sec_size;
sec_addr = _stp_modules[midx]->sections[secidx].static_addr;
sec_size = _stp_modules[midx]->sections[secidx].size;
if (addr >= sec_addr && addr < sec_addr + sec_size)
{
if (sec)
*sec = & _stp_modules[midx]->sections[secidx];
return _stp_modules[midx];
}
}
}
return NULL;
}
/* Return (user) module in which the the given addr falls. Returns
NULL when no module can be found that contains the addr. Fills in
vm_start (addr where module is mapped in) and (base) name of module
when given. Note that user modules always have exactly one section
(.dynamic or .absolute). */
static struct _stp_module *_stp_umod_lookup(unsigned long addr,
struct task_struct *task,
const char **name,
unsigned long *offset,
unsigned long *vm_start,
unsigned long *vm_end)
{
void *user = NULL;
#ifdef CONFIG_COMPAT
/* Handle 32bit signed values in 64bit longs, chop off top bits. */
if (_stp_is_compat_task2(task))
addr &= ((compat_ulong_t) ~0);
#endif
if (stap_find_vma_map_info(task->group_leader, addr,
vm_start, vm_end, offset, name, &user) == 0)
if (user != NULL)
{
struct _stp_module *m = (struct _stp_module *)user;
dbug_sym(1, "found module %s at 0x%lx\n", m->path,
vm_start ? *vm_start : 0);
return m;
}
return NULL;
}
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 sect_offset = 0;
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 (_stp_is_compat_task2(task))
addr &= ((compat_ulong_t) ~0);
#endif
m = _stp_umod_lookup(addr, task, modname, §_offset, &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 + sect_offset;
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;
}
#ifdef STP_NEED_LINE_DATA
static void _stp_filename_lookup(struct _stp_module *mod, char ** filename,
uint8_t *dirsecp, uint8_t *enddirsecp,
unsigned fileidx, int user, int compat_task)
{
uint8_t *linep = dirsecp;
static char fullpath [MAXSTRINGLEN];
char *dirname_entry = NULL, *filename_entry = NULL;
unsigned diridx = 0, i, j;
// skip past the directory table in the debug_line info
while (*linep != 0 && linep < enddirsecp)
{
char *entry = (char *) linep;
uint8_t *endnamep = (uint8_t *) memchr (entry, '\0', (size_t) (enddirsecp-linep));
if (endnamep == NULL || (endnamep + 1) > enddirsecp)
return;
linep = endnamep + 1;
}
if ((char) linep[0] != '\0')
return;
++linep;
// at the filename table
for (i = 1; *linep != 0 && linep < enddirsecp; ++i)
{
uint8_t *endnamep = NULL;
filename_entry = (char *) linep;
endnamep = (uint8_t *) memchr (filename_entry, '\0', (size_t) (enddirsecp-linep));
if (endnamep == NULL || (endnamep + 1) > enddirsecp)
return;
// move the line pointer past the file name. account for the null byte
linep = endnamep + 1;
// save the directory index
diridx = read_pointer ((const uint8_t **) &linep, enddirsecp, DW_EH_PE_leb128, user, compat_task);
if (linep > enddirsecp)
return;
if (i == fileidx)
break;
filename_entry = NULL;
// modification time
read_pointer ((const uint8_t **) &linep, enddirsecp, DW_EH_PE_leb128, user, compat_task);
// length of a file
read_pointer ((const uint8_t **) &linep, enddirsecp, DW_EH_PE_leb128, user, compat_task);
// check that nothing went wrong with reading the ulebs
if (linep > enddirsecp)
return;
}
if (filename_entry == NULL)
return; // return just the linenumber
// if dirid == 0, it's the compilation directory. otherwise retrieve the
// directory path if the file path was relative
if (diridx != 0 && filename_entry[0] != '/')
{
linep = dirsecp;
for (j = 1; *linep != 0 && linep < enddirsecp; j++)
{
uint8_t *endnamep = NULL;
dirname_entry = (char *) linep;
endnamep = (uint8_t *) memchr (dirname_entry, '\0', (size_t) (enddirsecp-linep));
if (endnamep == NULL || (endnamep + 1) > enddirsecp)
return;
if (j == diridx)
break;
dirname_entry = NULL;
linep = endnamep + 1;
}
if (dirname_entry == NULL)
return;
}
// bring it all together
// the filename was the full path
if (filename_entry[0] == '/')
*filename = filename_entry;
// relative filename, and the dir corresponds to the compilation dir
else if (diridx == 0)
{
char *slash = strrchr (mod->path, '/');
strlcpy(fullpath, mod->path, (size_t) (2 + slash - mod->path));
strlcat(fullpath, filename_entry, MAXSTRINGLEN);
*filename = fullpath;
}
// relative filename and a directory from the table in the debug line data
else
{
strlcpy(fullpath, dirname_entry, MAXSTRINGLEN);
strlcat(fullpath, "/", MAXSTRINGLEN);
strlcat(fullpath, filename_entry, MAXSTRINGLEN);
*filename = fullpath;
}
}
#endif /* STP_NEED_LINE_DATA */
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)
{
#ifdef CONFIG_COMPAT
/* Handle 32bit signed values in 64bit longs, chop off top bits. */
if (_stp_is_compat_task2(task))
addr &= ((compat_ulong_t) ~0);
#endif
m = _stp_umod_lookup(addr, task, &modname, NULL, NULL, NULL);
}
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)
{
// State machine "curr" values are updated directly.
// A "row" is committed line data that we should compare against, only
// updated for DW_LNS_copy, DW_LNE_end_sequence, and special opcodes.
uint64_t curr_addr = 0, row_addr = 0;
unsigned int curr_file_idx = 1, row_file_idx = 1;
unsigned long curr_linenum = 1, row_linenum = 1;
unsigned int row_end_sequence = 1;
unsigned int length = 4;
unsigned int skip_to_seq_end = 0, op_index = 0;
uint64_t unit_length, hdr_length;
uint8_t *endunitp, *endhdrp, *dirsecp, *stdopcode_lens_secp;
uint16_t version;
uint8_t opcode_base, line_range, min_instr_len = 0, max_ops = 1;
int8_t line_base;
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;
unsigned int commit_row = 0;
unsigned int end_sequence = 0;
if (opcode >= opcode_base) // special opcode
{
// line range was checked before this point. this variable is not altered after it is initialized.
curr_linenum += (line_base + ((opcode - opcode_base) % line_range));
addr_adv = ((opcode - opcode_base) / line_range);
commit_row = 1;
}
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:
// NB: we don't clear "curr" values until after the row is compared.
op_index = 0;
skip_to_seq_end = 0;
end_sequence = 1;
commit_row = 1;
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_copy:
commit_row = 1;
break;
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:
curr_linenum += 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 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)
continue;
// calculate actual address advance
if (addr_adv != 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;
}
curr_addr += addr_adv;
if (commit_row) {
// compare the whole range from the prior committed row
// (except an end_sequence can't be the base)
if (row_end_sequence == 0 && row_addr <= addr && addr < curr_addr)
{
if (need_filename)
_stp_filename_lookup(m, filename, dirsecp, endhdrp,
row_file_idx, user, compat_task);
return row_linenum;
}
if (end_sequence) {
curr_addr = 0;
curr_file_idx = 1;
curr_linenum = 1;
}
row_addr = curr_addr;
row_file_idx = curr_file_idx;
row_linenum = curr_linenum;
row_end_sequence = end_sequence;
}
}
}
#endif /* STP_NEED_LINE_DATA */
// no linenumber was found otherwise this function would have returned before this point
return 0;
}
// Compare two build-id hex strings, each of length m->build_id_len bytes.
// Since mismatches can mystify, produce a hex-textual version of both
// expected and actual strings, and compare textually. Failure messages
// are more intelligible this way.
static int _stp_build_id_check (struct _stp_module *m,
unsigned long notes_addr,
struct task_struct *tsk)
{
enum { max_buildid_hexstring = 65 };
static const char hexnibble[16]="0123456789abcdef";
char hexstring_theory[max_buildid_hexstring], hexstring_practice[max_buildid_hexstring];
int buildid_len = min((max_buildid_hexstring-1)/2, m->build_id_len);
int i, j;
memset(hexstring_theory, '\0', max_buildid_hexstring);
for (i=0, j=0; j<buildid_len; j++) {
unsigned char theory = m->build_id_bits[j];
hexstring_theory[i++] = hexnibble[theory >> 4];
hexstring_theory[i++] = hexnibble[theory & 15];
}
memset(hexstring_practice, '\0', max_buildid_hexstring);
for (i=0, j=0; j<buildid_len; j++) {
#ifdef STAPCONF_SET_FS
/* Use set_fs / get_user to access conceivably invalid addresses.
* If loc2c-runtime.h were more easily usable, a deref() loop
* could do it too. */
mm_segment_t oldfs = get_fs();
#endif
int rc;
unsigned char practice = 0;
#ifdef STAPCONF_PROBE_KERNEL
if (!tsk) {
#ifdef STAPCONF_SET_FS
set_fs(KERNEL_DS);
#endif
rc = probe_kernel_read(&practice, (void*)(notes_addr + j), 1);
}
else
#endif
{
#ifdef STAPCONF_SET_FS
set_fs (tsk ? USER_DS : KERNEL_DS);
#endif
/*
* Why check CONFIG_UTRACE here? If we're using real in-kernel
* utrace, we can always just call get_user() (since we're
* either reading kernel memory or tsk == current).
*
* Since we're only reading here, we can call
* __access_process_vm_noflush(), which only calls things that
* are exported.
*
* PR26811: Kernel 5.10+ after set_fs() removal no longer supports
* reading kernel addresses with get_user.
*/
#if defined(CONFIG_UTRACE) && !defined(STAPCONF_SET_FS)
rc = get_user(practice, ((unsigned char*)(void*)(notes_addr + j)));
#elif defined(STAPCONF_SET_FS)
if (!tsk || tsk == current) {
rc = get_user(practice, ((unsigned char*)(void*)(notes_addr + j)));
}
#else
if (!tsk) {
/* XXX Just reading kernel memory should be sufficient
e.g. per Linux kernel commit ee6e00c8. */
practice = *((unsigned char*)(void*)(notes_addr + j));
rc = 0;
} else if (tsk == current) {
rc = get_user(practice, ((unsigned char*)(void*)(notes_addr + j)));
}
#endif
else {
rc = (__access_process_vm_noflush(tsk, (notes_addr + j), &practice,
1, 0) != 1);
}
}
#ifdef STAPCONF_SET_FS
set_fs(oldfs);
#endif
if (rc == 0) { // got actual data byte
hexstring_practice[i++] = hexnibble[practice >> 4];
hexstring_practice[i++] = hexnibble[practice & 15];
}
}
dbug_sym(1, "build-id validation [%s] %s vs. notes@=%lx %s\n", m->name, hexstring_theory,
(unsigned long) notes_addr, hexstring_practice);
// have two strings, will travel
if (strcmp (hexstring_practice, hexstring_theory)) {
// NB: It is normal for different binaries with the same file path
// coexist in the same system via chroot or namespaces, therefore
// we make sure below is really a warning.
_stp_warn ("Build-id mismatch [man warning::buildid]: \"%s\" pid %ld address "
"%#lx, expected %s actual %s\n",
m->path, (long) (tsk ? tsk->tgid : 0),
notes_addr, hexstring_theory, hexstring_practice);
return 1;
}
return 0;
}
/* Validate all-modules + kernel based on build-id (if present).
* The completed case is the following combination:
* Debuginfo Module Kernel
* X X
* has build-id/not unloaded has build-id/not
* loaded && (has build-id/not)
*
* NB: build-id exists only if ld>=2.18 and kernel>= 2.6.23
*/
static int _stp_module_check(void)
{
struct _stp_module *m = NULL;
unsigned long notes_addr, base_addr;
unsigned i,j;
int rc = 0;
#ifdef STP_NO_BUILDID_CHECK
return 0;
#endif
for (i = 0; i < _stp_num_modules; i++)
{
m = _stp_modules[i];
if (m->build_id_len > 0 && m->notes_sect != 0) {
dbug_sym(1, "build-id validation [%s]\n", m->name); /* kernel only */
/* skip userspace program */
if (m->name[0] == '/') continue;
/* notes end address */
if (!strcmp(m->name, "kernel")) {
notes_addr = _stp_kmodule_relocate("kernel",
"_stext", m->build_id_offset);
base_addr = _stp_kmodule_relocate("kernel",
"_stext", 0);
} else {
notes_addr = m->notes_sect + m->build_id_offset;
base_addr = m->notes_sect;
}
if (notes_addr <= base_addr) { /* shouldn't happen */
_stp_warn ("build-id address %lx <= base %lx\n",
notes_addr, base_addr);
continue;
}
rc |= _stp_build_id_check (m, notes_addr, NULL);
} /* end checking */
} /* end loop */
return rc;
}
/* Iterate over _stp_modules, looking for a kernel module of given
name. Run build-id checking for it. Return 0 on ok. */
static int _stp_kmodule_check (const char *name)
{
struct _stp_module *m = NULL;
unsigned long notes_addr, base_addr;
unsigned i,j;
#ifdef STP_NO_BUILDID_CHECK
return 0;
#endif
WARN_ON(!name || name[0]=='/'); // non-userspace only
for (i = 0; i < _stp_num_modules; i++)
{
m = _stp_modules[i];
/* PR16406 must be unique kernel module name (non-/-prefixed path) */
if (strcmp (name, m->name)) continue;
if (m->build_id_len > 0 && m->notes_sect != 0) {
dbug_sym(1, "build-id validation [%s]\n", m->name);
/* notes end address */
notes_addr = m->notes_sect + m->build_id_offset;
base_addr = m->notes_sect;
if (notes_addr <= base_addr) { /* shouldn't happen */
_stp_warn ("build-id address %lx < base %lx\n",
notes_addr, base_addr);
continue;
}
return _stp_build_id_check (m, notes_addr, NULL);
} /* end checking */
} /* end loop */
return 0; /* not found */
}
/* Validate user module based on build-id (if present) */
static int _stp_usermodule_check(struct task_struct *tsk, const char *path_name, unsigned long addr)
{
struct _stp_module *m = NULL;
unsigned long notes_addr;
unsigned i, j;
unsigned char practice_id_bits[MAXSTRINGLEN];
unsigned long vm_end = 0;
#ifdef STP_NO_BUILDID_CHECK
return 0;
#endif
WARN_ON(!path_name || path_name[0]!='/'); // user-space only
for (i = 0; i < _stp_num_modules; i++)
{
m = _stp_modules[i];
/* PR16406 must be unique userspace name (/-prefixed path); it's also in m->name */
if (!m->path || strcmp(path_name, m->path) != 0) continue;
if (m->build_id_len > 0) {
int ret, build_id_len;
notes_addr = addr + m->build_id_offset /* + m->module_base */;
dbug_sym(1, "build-id validation [%d %s] address=%#lx build_id_offset=%#lx\n",
tsk->pid, m->path, addr, m->build_id_offset);
if (notes_addr <= addr) {
_stp_warn ("build-id address %lx < base %lx\n", notes_addr, addr);
continue;
}
return _stp_build_id_check (m, notes_addr, tsk);
}
}
return 0; /* not found */
}
/** Prints an address based on the _STP_SYM flags.
* @param address The address to lookup.
* @param task The address to lookup (if NULL lookup kernel/module address).
* @note Symbolic lookups should not normally be done within
* a probe because it is too time-consuming. Use at module exit time. */
static int _stp_snprint_addr(char *str, size_t len, unsigned long address,
int flags, struct task_struct *task)
{
const char *modname = NULL;
const char *name = NULL;
char *filename = NULL;
unsigned long offset = 0, size = 0, linenumber = 0;
char *exstr, *poststr, *prestr;
prestr = (flags & _STP_SYM_PRE_SPACE) ? " " : "";
exstr = (((flags & _STP_SYM_INEXACT) && (flags & _STP_SYM_SYMBOL))
? " (inexact)" : "");
if (flags & _STP_SYM_POST_SPACE)
poststr = " ";
else if (flags & _STP_SYM_NEWLINE)
poststr = "\n";
else
poststr = "";
if (flags & (_STP_SYM_SYMBOL | _STP_SYM_MODULE)) {
name = _stp_kallsyms_lookup(address, &size, &offset, &modname, task);
if (name && name[0] == '.')
name++;
}
if (modname && (flags & _STP_SYM_MODULE_BASENAME)) {
char *slash = strrchr (modname, '/');
if (slash)
modname = slash+1;
}
if ((flags & _STP_SYM_LINENUMBER) || (flags & _STP_SYM_FILENAME)) {
linenumber = _stp_linenumber_lookup (address, task, &filename,
(int) (flags & _STP_SYM_FILENAME));
}
switch (flags & ~_STP_SYM_INEXACT) {
case _STP_SYM_SYMBOL:
if (name) {
/* symbol name only */
return _stp_snprintf(str, len, "%s%s%s%s", prestr, name, exstr, poststr);
}
break;
case _STP_SYM_FILENAME:
if (filename) {
/* filename */
return _stp_snprintf(str, len, "%s%s%s%s", prestr, filename, exstr, poststr);
}
break;
case _STP_SYM_LINENUMBER:
if (linenumber) {
/* linenumber */
return _stp_snprintf(str, len, "%s%#lu%s%s", prestr, linenumber, exstr, poststr);
}
break;
case _STP_SYM_FILENAME | _STP_SYM_LINENUMBER:
if (filename && linenumber) {
/* filename, linenumber */
return _stp_snprintf(str, len, "%s%s:%#lu%s%s", prestr, filename, linenumber,
exstr, poststr);
} else if (filename) {
/* filename, linenumber=?? */
return _stp_snprintf(str, len, "%s%s%s%s", prestr, filename, exstr, poststr);
} else if (linenumber) {
/* filename=??, linenumber */
return _stp_snprintf(str, len, "%s??:%#lu%s%s", prestr, linenumber, exstr,
poststr);
}
break;
case _STP_SYM_BRIEF:
if (name) {
/* symbol name and offset */
return _stp_snprintf(str, len, "%s%s+%#lx%s%s", prestr, name, offset, exstr,
poststr);
}
break;
case _STP_SYM_SIMPLE:
if (name && modname) {
/* symbol, module, offset. */
return _stp_snprintf(str, len, "%s%s+%#lx [%s]%s%s", prestr, name, offset,
modname, exstr, poststr);
} else if (name) {
/* symbol name, offset, no module name */
return _stp_snprintf(str, len, "%s%s+%#lx%s%s", prestr, name, offset, exstr,
poststr);
} else if (modname) {
/* hex address, module name, offset */
return _stp_snprintf(str, len, "%s%p [%s+%#lx]%s%s", prestr, (int64_t) address,
modname, offset, exstr, poststr);
}
break;
case _STP_SYM_DATA:
if (name && modname) {
/* symbol, module, offset and size. */
return _stp_snprintf(str, len, "%s%s+%#lx/%#lx [%s]%s%s", prestr, name, offset,
size, modname, exstr, poststr);
} else if (name) {
/* symbol name, offset + size, no module name */
return _stp_snprintf(str, len, "%s%s+%#lx/%#lx%s%s", prestr, name, offset, size,
exstr, poststr);
} else if (modname) {
/* hex address, module name, offset + size */
return _stp_snprintf(str, len, "%s%p [%s+%#lx/%#lx]%s%s", prestr,
(int64_t) address, modname, offset, size, exstr, poststr);
}
break;
case _STP_SYM_FULL:
if (name && modname) {
/* symbol, module, offset and size. */
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx [%s]%s%s", prestr,
(int64_t) address, name, offset, size, modname, exstr,
poststr);
} else if (name) {
/* symbol name, offset + size, no module name */
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx%s%s", prestr, (int64_t) address,
name, offset, size, exstr, poststr);
} else if (modname) {
/* hex address, module name, offset + size */
return _stp_snprintf(str, len, "%s%p [%s+%#lx/%#lx]%s%s", prestr, (int64_t) address,
modname, offset, size, exstr, poststr);
}
break;
case _STP_SYM_FULLER:
if (name && modname && filename) {
if (linenumber)
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx at %s:%#lu [%s]%s%s",
prestr, (int64_t) address, name, offset, size, filename,
linenumber, modname, exstr, poststr);
else
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx at %s [%s]%s%s",
prestr, (int64_t) address, name, offset, size, filename,
modname, exstr, poststr);
} else if (name && filename) {
if (linenumber)
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx at %s:%#lu%s%s",
prestr, (int64_t) address, name, offset, size, filename,
linenumber, exstr, poststr);
else
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx at %s%s%s",
prestr, (int64_t) address, name, offset, size, filename,
exstr, poststr);
} else if (modname && filename) {
if (linenumber)
return _stp_snprintf(str, len, "%s%p [%s+%#lx/%#lx] at %s:%#lu%s%s", prestr,
(int64_t) address, modname, offset, size, filename,
linenumber, exstr, poststr);
else
return _stp_snprintf(str, len, "%s%p [%s+%#lx/%#lx] at %s%s%s", prestr,
(int64_t) address, modname, offset, size, filename,
exstr, poststr);
} else if (name && modname) {
/* symbol, module, offset and size. */
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx [%s]%s%s", prestr,
(int64_t) address, name, offset, size, modname,
exstr, poststr);
} else if (name) {
/* symbol name, offset + size, no module name */
return _stp_snprintf(str, len, "%s%p : %s+%#lx/%#lx%s%s", prestr,
(int64_t) address, name, offset, size, exstr, poststr);
} else if (modname) {
/* hex address, module name, offset + size */
return _stp_snprintf(str, len, "%s%p [%s+%#lx/%#lx]%s%s", prestr,
(int64_t) address, modname, offset, size, exstr, poststr);
}
break;
}
/* no names, hex only */
return _stp_snprintf(str, len, "%s%p%s%s", prestr, (int64_t) address, exstr, poststr);
}
static void _stp_print_addr(unsigned long address, int flags,
struct task_struct *task)
{
_stp_snprint_addr(NULL, 0, address, flags, task);
}
/** @} */
/* Update the given module/section's offset value. Assume that there
is no need for locking or for super performance. NB: this is only
for kernel modules, which exist singly at run time. User-space
modules (executables, shared libraries) exist at different
addresses in different processes, so are tracked in the
_stp_tf_vma_map. */
static void _stp_kmodule_update_address(const char* module,
const char* reloc, /* NULL="all" */
unsigned long address)
{
unsigned mi, si;
for (mi=0; mi<_stp_num_modules; mi++)
{
const char *note_sectname = ".note.gnu.build-id";
if (strcmp (_stp_modules[mi]->name, module))
continue;
if (!reloc || !strcmp (note_sectname, reloc)) {
dbug_sym(1, "module %s special section %s address %#lx\n",
_stp_modules[mi]->name,
note_sectname,
address);
_stp_modules[mi]->notes_sect = address; /* cache this particular address */
}
for (si=0; si<_stp_modules[mi]->num_sections; si++)
{
if (reloc && strcmp (_stp_modules[mi]->sections[si].name, reloc))
continue;
else
{
dbug_sym(1, "module %s section %s address %#lx\n",
_stp_modules[mi]->name,
_stp_modules[mi]->sections[si].name,
address);
_stp_modules[mi]->sections[si].static_addr = address;
if (reloc) break;
else continue; /* wildcarded - will have more hits */
}
} /* loop over sections */
} /* loop over modules */
}
#if !defined(STAPCONF_KALLSYMS_LOOKUP_NAME_EXPORTED)
#define KALLSYMS_H_INCLUDED
#include <linux/kallsyms.h>
typedef typeof(&kallsyms_lookup_name) kallsyms_lookup_name_fn;
// XXX Will be linked in place of the kernel's kallsyms_lookup_name:
unsigned long kallsyms_lookup_name (const char *name)
{
/* First, try to use a kallsyms_lookup_name address passed to us
through the relocation mechanism. */
if (_stp_kallsyms_lookup_name != NULL)
return (* (kallsyms_lookup_name_fn)_stp_kallsyms_lookup_name)(name);
/* NB: PR14804: definitely don't use _stp_error here. It's called
too early for the actual message buffer goo to be allocated. */
/* Don't even printk. A user can't do anything about it, and some
callers may want to retry at a later point when the relocation
might be available. */
/* printk (KERN_ERR "kallsyms_lookup_name unavailable for %s\n", name); */
return 0;
}
#endif
#if defined(STAPCONF_KALLSYMS_ON_EACH_SYMBOL) && !defined(STAPCONF_KALLSYMS_ON_EACH_SYMBOL_EXPORTED)
#ifndef KALLSYMS_H_INCLUDED
#include <linux/kallsyms.h>
#endif
typedef typeof(&kallsyms_on_each_symbol) kallsyms_on_each_symbol_fn;
// XXX Will be linked in place of the kernel's kallsyms_on_each_symbol:
int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
unsigned long),
void *data)
{
/* First, try to use a kallsyms_lookup_name address passed to us
through the relocation mechanism. */
if (_stp_kallsyms_on_each_symbol != NULL)
return (* (kallsyms_on_each_symbol_fn)_stp_kallsyms_on_each_symbol)(fn, data);
/* Next, give up and signal a BUG. We should have detected
that this function is not available and used a different
mechanism! */
_stp_error("BUG: attempting to use unavailable kallsyms_on_each_symbol!!\n");
return 0;
}
#endif
#endif /* _STP_SYM_C_ */
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists