/*
 * ===========================================================================
 *
 *	File: sys_call_table.c
 *
 *	Description: test kernel driver that can be loaded via "insmod";
 *	             this driver checks for sys_call_table symbol and
 *	             output is written to the /var/log/messages file;
 *                   this driver is based on openafs sys_call_table patch
 *                   by Chaskiel M Grundman <cg2v@andrew.cmu.edu>
 *
 *	Author(s): Juan Villacis, George Artz, Intel Corp.
 *
 *	System: VTune Linux Sampling Driver
 *
 * ===========================================================================
 */

#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/sched.h>

#ifdef EXPORTED_SYSCALL_HEADER
#include <linux/syscall.h>
#endif

#include <asm/unistd.h>

#if defined(EXPORTED_KALLSYMS_SYMBOL) || defined(EXPORTED_KALLSYMS_ADDRESS)
#include <linux/kallsyms.h>
#endif

#include "vtdef.h"

#ifdef EXPORTED_SYSCALL_HEADER
/*
 * scan_sys_open_sys_exit() : scans for sys_call_table symbol between
 *                            sys_exit and sys_open (requires syscall.h)
 */
void *scan_sys_open_sys_exit(void)
{
  unsigned long *ptr;
  unsigned long offset;
  unsigned long datalen;
  void *sys_call_table = 0;

  ptr = (unsigned long *)&init_mm;
  datalen = 16384;
  for (offset=0; offset <datalen; ptr++,offset++) 
  {
    if (ptr[0] == (unsigned long)&sys_exit &&
	ptr[__NR_open - __NR_exit] == (unsigned long)&sys_open) 
    {
      sys_call_table = ptr - __NR_exit;
      break;
    }
  }

  return (sys_call_table);
}
#endif /* EXPORTED_SYSCALL_HEADER */

#ifdef EXPORTED_KALLSYMS_SYMBOL
/*
 * scan_kallsyms_symbol() : scans for sys_call_table symbol using
 *                          kallsyms_symbol_to_address (requires kallsyms.h)
 */
void *scan_kallsyms_symbol(void)
{
  int ret;
  unsigned long token;
  const char *mod_name;
  unsigned long mod_start;
  unsigned long mod_end;
  const char *sec_name;
  unsigned long sec_start;
  unsigned long sec_end;
  const char *sym_name;
  unsigned long sym_start;
  unsigned long sym_end;
  void *sys_call_table = 0;

  SampPrint(KERN_INFO "VTUNE: using kallsyms_symbol_to_address() to scan for sys_call_table symbol\n");
  ret = 1;
  token = 0;
  while (ret) 
  {
    sym_start = 0;
    ret = kallsyms_symbol_to_address("sys_call_table", &token, &mod_name,
				     &mod_start, &mod_end, &sec_name, &sec_start, &sec_end,
				     &sym_name, &sym_start, &sym_end);
    if (ret && !strcmp(mod_name, "kernel"))
      break;
  }
  if (ret && sym_start) 
  {
    sys_call_table = sym_start;
  }

  return (sys_call_table);
}
#endif /* EXPORTED_KALLSYMS_SYMBOL */

#ifdef EXPORTED_KALLSYMS_ADDRESS
/*
 * scan_kallsyms_address() : scans for sys_call_table symbol using
 *                           kallsyms_address_to_symbol (requires kallsyms.h)
 */
void *scan_kallsyms_address(void)
{
  int ret;
  const char *mod_name;
  unsigned long mod_start;
  unsigned long mod_end;
  const char *sec_name;
  unsigned long sec_start;
  unsigned long sec_end;
  const char *sym_name;
  unsigned long sym_start;
  unsigned long sym_end;
  unsigned long *ptr;
  unsigned long datalen;
  void *sys_call_table = 0;

  SampPrint(KERN_INFO "VTUNE: using kallsyms_address_to_symbol() to scan for sys_call_table symbol\n");
  ret = kallsyms_address_to_symbol((unsigned long)&init_mm, &mod_name,
				   &mod_start, &mod_end, &sec_name, &sec_start, &sec_end,
				   &sym_name, &sym_start, &sym_end);
  ptr = (unsigned long *)sec_start;
  datalen = (sec_end-sec_start)/sizeof(unsigned long);
  ret = kallsyms_address_to_symbol((unsigned long)sys_call_table, &mod_name,
				   &mod_start, &mod_end, &sec_name, &sec_start, &sec_end,
				   &sym_name, &sym_start, &sym_end);
  if (ret && strcmp(sym_name, "sys_call_table"))
    sys_call_table = 0;

  return (sys_call_table);
}
#endif /* EXPORTED_KALLSYMS_ADDRESS */

/*
 * find_sys_call_table_symbol() : scans for sys_call_table symbol and returns
 *                                pointer to it if found (NULL otherwise)
 */
void *find_sys_call_table_symbol(void)
{
  void *sys_call_table = 0;

#ifdef EXPORTED_KALLSYMS_SYMBOL
  if (! sys_call_table)
    sys_call_table = scan_kallsyms_symbol();
#endif /* EXPORTED_KALLSYMS_SYMBOL */

#ifdef EXPORTED_KALLSYMS_ADDRESS
  if (! sys_call_table)
    sys_call_table = scan_kallsyms_address();
#endif /* EXPORTED_KALLSYMS_ADDRESS */

#ifdef EXPORTED_SYSCALL_HEADER
  if (! sys_call_table)
    sys_call_table = scan_sys_open_sys_exit();
#endif /* EXPORTED_SYSCALL_HEADER */

  if (sys_call_table) 
  {
    SampPrint(KERN_INFO "VTUNE: sys_call_table symbol found at address 0x%lx\n",(long)sys_call_table);
  }
  else
  {
    SampPrint(KERN_INFO "VTUNE: failed to find address of sys_call_table!\n");
  }

  return (sys_call_table);
}
