#! /bin/sh
# SPDX-License-Identifier: Apache-2.0
#
# (c) 2019, Google
progname="${0##*/}"

# Add instrumentation to module_init and probe functions.

USAGE="USAGE: ${progname} [dir|file]

Add debug instrumentation to module_init and probe functions."

if [ X"--help" = X"${1}" -o X"{-h}" = X"${1}" ]; then
  echo "${USAGE}" >&2
  exit
fi
DIR=.
if [ 1 = ${#} ]; then
  DIR=${1}
  shift
fi
if [ 0 != ${#} ]; then
  echo "Unexpected Argument: ${*}" >&2
  echo >&2
  echo "${USAGE}" >&2
  exit 1
fi

# A _real_ embedded tab character
TAB="`echo | tr '\n' '\t'`"
# A _real_ embedded escape character
ESCAPE="`echo | tr '\n' '\033'`"
# Colours
RED="${ESCAPE}[38;5;196m"
ORANGE="${ESCAPE}[38;5;255:165:0m"
BLUE="${ESCAPE}[35m"
NORMAL="${ESCAPE}[0m"

TMP=`mktemp -d`

# ${file} ${call} and ${symbol} input
SYMBOLS=" "
add_debug_printk()
{
  message="printk(KERN_WARNING \"${file##*/}:"
  if [ -z "${call#select}" ]; then
    # We need uniqueness for discovery, so instead of truth of __func__ to
    # cover any of our mistakes(!), we select the expectation.
    message+="${symbol}: ENTER\\\\n\");"
  else
    message+="%s:${call}: ENTER\\\\n\", __func__);"
  fi
  if grep "${TAB}${message%%\\*}" ${file} >/dev/null; then
    if [ "${SYMBOLS}" == "${SYMBOLS#* ${symbol}:${call#select}}" ]; then
      echo INFO: already ${file} instrumented ${call#select} ${symbol} >&2
      SYMBOLS="${SYMBOLS}${symbol}:${call} "
    fi
    return
  fi
  if [ "device_initcall" = "${call}" -o "module_init" = "${call}" -o "module_exit" = "${call}" ] &&
     grep "${TAB}printk(KERN_WARNING \"${file##*/}:%s:\(builtin\|module\)_[a-z]*_driver: ENTER" ${file} >/dev/null; then
    if [ "${SYMBOLS}" == "${SYMBOLS#* ${symbol}:${call#select}}" ]; then
      echo INFO: maybe already ${file} instrumented ${call#select} ${symbol} >&2
    fi
    return
  fi
  sed "/^\(static \)\{0,1\}.* ${symbol}(/ {
         N
         s/^\(static \)\{0,1\}\(.* ${symbol}(\)\(void\)\{0,1\}) {[ ${TAB}]*}\(\n\)/\1\2\3)\4{\4${TAB}${message}\4}\4/
         t exit
         N
         /\/[*][^*]*\$/ {
           : comment_loop
           N
           /\/[*].*[*]\// b comment_done
           b comment_loop
           : comment_done
           /\/[*].*[*]\/\$/ N
         }
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(\n${TAB}\)\(kfree(\|DBG(\|return[ ${TAB}]\|if (\|platform_driver_\|class_destroy(\|pr_info(\|[a-z_0-9]*_register\(_drivers\|_simple\)\{0,1\}(\|[a-z_0-9]*_\(un\|de\)register\(_drivers\|_simple\)\{0,1\}(\|[a-z+_0-9]*_driver(\)/\1${message}&/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(\n${TAB}\)\(DBG(\|return[ ${TAB}]\|if (\|platform_driver_\|pr_info(\|[a-z_0-9]*_register\(_drivers\|_simple\)\{0,1\}(\|[a-z_0-9]*_\(un\|de\)register\(_drivers\|_simple\)\{0,1\}(\)/\1${message}&/
         t exit
         s/\(static void .*\)\(\n${TAB}\)\([a-z].*\)\2}\$/\1\2${message}\2\3\2}/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         t exit
         N
         /printk(KERN_WARNING \"${file##*/}:/ b exit
         s/\(.*\)\(\n${TAB}\)\(.*\n\)\2/&${message}\2/
         : exit
       }
       s/^builtin_\([a-z]*\)_driver(\(${symbol}\));\$/static int __init \2_init(void)\n{\n${TAB}${message}\n${TAB}return \1_driver_register(\&\2);\n}\ndevice_initcall(\2_init);/
       s/^module_\([a-z]*\)_driver(\(${symbol}\));\$/static int __init \2_init(void)\n{\n${TAB}${message}\n${TAB}return \1_driver_register(\&\2);\n}\nmodule_init(\2_init);\n\nstatic void __exit \2_exit(void)\n{\n${TAB}${message}\n${TAB}\1_driver_unregister(\&\2);\n}\nmodule_exit(\2_exit);/
       " ${file} > ${TMP}/${file##*/} &&
    ! cmp -s ${file} ${TMP}/${file##*/} &&
    cp ${TMP}/${file##*/} ${file} &&
    (
      echo INFO: modified ${file} to instrument ${call#select} ${symbol} >&2
      echo ${symbol}
    ) ||
    (
      files=`grep -lr "^\(static \)\{0,1\}[^ ${TAB}][ %{TAB}_a-zA-Z0-9]* ${symbol}(" ${DIR} |
               sed -e '/[.]h$/d' -e 's@^[.]/@@'`
      if [ X"${file}" != X"${files}" ]; then
        echo "${ORANGE}WARNING${NORMAL}: ${symbol} is in ${files}" >&2
        if [ 1 -eq `echo ${files} | wc -w` ]; then
          file=${files}
          add_debug_printk
          exit
        fi
      fi
      if [ "${SYMBOLS}" == "${SYMBOLS#* ${symbol}:${call#select}}" ]; then
        echo "${RED}ERROR${NORMAL}: failed to modify ${file} to instrument" ${call#select} ${symbol} >&2
      fi
      false
    )
    ret=${?}
    rm -f ${TMP}/${file##*/}
    if [ 0 -eq ${ret} ]; then
      SYMBOLS="${SYMBOLS}${symbol}:${call} "
    fi
    return ${ret}
}

# module_init, module_exit, late_initcall ...
(
  grep -Hr '^\(builtin_[a-z]*_driver\|module_[a-z]*_driver\|module_\(init\|exit\)\|[a-z]*_initcall\)(' ${DIR} |
    sed -n 's@^[.]/@@
            s/^\([^:]*\):\(builtin_[a-z]*_driver\|module_[a-z]*_driver\|module_init\|module_exit\|[a-z]*_initcall\)(\([^)]*\));$/\1 \2 \3/p' |
    sort -u
  find ${DIR} -name '*.c' |
    while read file; do
      sed -n "/.* [a-z_0-9]*(void)\$/ {
                : loop
                N
                /^[^\n]* [a-z_0-9]*(void)\n.*\n}\$/ {
                  /.*platform_\(driver\|device\)_\(un\)\{0,1\}register([&][^)]*).*/ {
                    s@^[^\n]* \([a-z_0-9]*\)(void)\n.*@${file} select \1@p
                    b exit
                  }
                  /.*[ ${TAB}]_[a-z]_\(un\)\{0,1\}register_driver([&][^, ${TAB})]*).*/ {
                    s@^[^\n]* \([a-z_0-9]*\)(void)\n.*@${file} select \1@p
                    b exit
                  }
                  /.*i2c_\(add\|del\)_driver([&][^)]*).*/ {
                    s@^[^\n]* \([a-z_0-9]*\)(void)\n.*@${file} select \1@p
                  }
                  b exit
                }
                b loop
              }
              : exit" ${file}
    done |
      sort -u
) |
  while read file call symbol; do
    if ! add_debug_printk && [ "${SYMBOLS}" == "${SYMBOLS#* ${symbol}:${call#select}}" ]; then
      echo FAILED
    fi

    if [ "${call}" != "${call#builtin_*_driver}" -o "${call}" != "${call#module_*_driver}" ]; then
      symbol=${symbol}_init
    fi
    call=probe
    sed -n "/\(static \)\{0,1\}.* ${symbol}(/,/^}/ {
              s/.*platform_\(driver\|device\)_register([&]\([^)]*\)).*/struct platform_\1 \2/p
              s/.*[ ${TAB}]\(_[a-z]\)_register_driver([&]\([^, ${TAB})]*\)).*/struct \1_driver \2/p
              s/.*i2c_add_driver([&]\([^)]*\)).*/struct i2c_driver \1/p
            }
            s/\(builtin\|module\)_\([a-z]*_driver\)(\([^)]*\));/struct \2 \3/p" ${file} |
      sort -u |
      while read struct; do
        sed -n "/\(static \)\{0,1\}${struct} = {/,/^};/ {
                  s/^[ ${TAB}]*[.]probe[ ${TAB}]*=[ ${TAB}]*\([^ ${TAB}]*\),\$/\1/p
                }" ${file} |
          sort -u |
          while read symbol; do
            if ! add_debug_printk && [ "${SYMBOLS}" == "${SYMBOLS#* ${symbol}:${call} }" ]; then
              echo FAILED
            fi
          done
      done
  done |
    sort -u |
    tee ${TMP}/list |
    grep '^FAILED' >/dev/null
ret=${?}
SYMBOLS=`grep -v '^FAILED$' ${TMP}/list`

rm -rf ${TMP}

if [ 0 -ne ${ret} ]; then
  echo "DONE" >&2

  AUTHOR_NAME="`git config user.name`"
  AUTHOR_EMAIL="`git config user.email`"
  if [ -z "${AUTHOR_EMAIL}" ]; then
    AUTHOR_EMAIL="`${USER}@google.com`"
  fi
  if [ -z "${AUTHOR_NAME}" ]; then
    convert_ldap_to_name() {
      echo "${1##*/}" |
        sed "s@[-_]@ @g
             s@   *@ @g" |
        sed 's@\(^\| \)a@\1A@g
             s@\(^\| \)b@\1B@g
             s@\(^\| \)c@\1C@g
             s@\(^\| \)d@\1D@g
             s@\(^\| \)e@\1E@g
             s@\(^\| \)f@\1F@g
             s@\(^\| \)g@\1G@g
             s@\(^\| \)h@\1H@g
             s@\(^\| \)i@\1I@g
             s@\(^\| \)j@\1J@g
             s@\(^\| \)k@\1K@g
             s@\(^\| \)l@\1L@g
             s@\(^\| \)m@\1M@g
             s@\(^\| \)n@\1N@g
             s@\(^\| \)o@\1O@g
             s@\(^\| \)p@\1P@g
             s@\(^\| \)q@\1Q@g
             s@\(^\| \)r@\1R@g
             s@\(^\| \)s@\1S@g
             s@\(^\| \)t@\1T@g
             s@\(^\| \)u@\1U@g
             s@\(^\| \|Mc\)v@\1V@g
             s@\(^\| \)w@\1W@g
             s@\(^\| \)x@\1X@g
             s@\(^\| \)y@\1Y@g
             s@\(^\| \)z@\1Z@g'
    }
    AUTHOR_NAME="`convert_ldap_to_name ${USER}`"
  fi

  git commit -a -m "GKI: DEBUG: add instrumentation to init and probe functions

Automatically generated by ${0}

The following symbols are instrumented:
`echo \"${SYMBOLS}\" |
   sed 's/^/ - /'`

Signed-off-by: ${AUTHOR_NAME} <${AUTHOR_EMAIL}>
Test: compile" ||
    echo "${RED}ERROR${NORMAL}: failed to commit changes to git" >&2
  exit
fi
echo "${RED}FAILED${NORMAL}: could not complete task" >&2
exit 1
