#!/usr/bin/env sh
#
# ----------------------------------------------------------------------------
#
# Copyright 2019 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ----------------------------------------------------------------------------
#

set -eu

die() { echo "$*" >&2; exit 1; }

if [ $# -lt 2 ]; then
    die "Usage: $0 <object> <symbol> [-cache]"
fi

cache=0
if [ $# -eq 3 ]; then
    if [ "$3" != "-cache" ]; then
        die "Usage: $0 <object> <symbol> [-cache]"
    fi
    cache=1
fi

obj="$1"
sym="$2"

displ=0
bdispl=0

if [ "$cache" -eq 1 ]; then
    tmp="$obj.S"
    tmp2="$obj.S.$sym"

    if [ ! -f "$tmp" ]; then
        objdump -d "$obj" > "$tmp"
    fi
else
    tmp=$(mktemp)
    tmp2=$(mktemp)
    objdump -d "$obj" > "$tmp"

    cleanup() {
        rm -f "$tmp" "$tmp2"
    }
    trap cleanup EXIT
fi

set +e
chop-marks-dyn-addr "$obj" "$sym" > /dev/null 2> /dev/null
error=$?
set -e
if [ "$error" -eq 0 ] && [ "$(grep -c "<$sym>:" < "$tmp")" -ne 0 ]; then
    displ=$(chop-marks-dyn-addr "$obj" "$sym" 2> /dev/null)
elif [ "$sym" != "_start" ]; then
    static_start_sym=$(readelf -s "$obj" | grep -v UND | grep FUNC | grep GLOBAL | grep " _start$" | tr -s " " | cut -d ' ' -f 3 | sort | uniq)
    dyn_start_sym=$(chop-marks-x86_64 "$obj" "_start" 2> /dev/null | cut -d ' ' -f 2)
    if [ "$dyn_start_sym" = "" ]; then
        bdispl=0x$(chop-marks-dyn-addr "$obj" "_start" 2>&1 | grep "@@" | grep PT_LOAD | sed "s/(nil)/0x0/" | cut -d "x" -f 3 | cut -d ")" -f 1 | sort | uniq)
        if [ "$bdispl" != 0x ]; then
            # shellcheck disable=SC2004
            bdispl=$(($bdispl))
        else
            bdispl=0
        fi
    else
        bdispl=$((0x$dyn_start_sym - 0x$static_start_sym))
    fi
else
    die "$(basename "$0"): Unable to find address for $sym in $obj"
fi

found=1
if [ "$(grep -c "<$sym>:" < "$tmp")" -eq 0 ]; then
    found=0
    # Symbol not found in the objdump, is in the libraries?
    for lib in $(setarch "$(uname -m)" -R ldd "$obj" | grep "=" | cut -d ">" -f 2 | tr "(" ":" | tr -d " " | tr -d ")"); do
        addr="$(echo "$lib" | cut -d ":" -f 2)"
        lib="$(echo "$lib" | cut -d ":" -f 1)"
        objdump -d "$lib" > "$tmp"
        if [ "$(grep -c "<$sym>:" < "$tmp")" -eq 0 ]; then
            continue
        fi
        found=1
        # shellcheck disable=SC2004
        bdispl=$(($addr))
        bdispl=0
        obj=$lib
        # shellcheck disable=SC2039
        echo "-module $(basename "$(readlink -m "$lib")")"
        break
    done;
fi

if [ "$found" -eq 0 ]; then
    exit 1
fi

set +e
readelf -Ws "$obj" | grep "$sym" | grep -q localentry
localentry=$?
set -e

if [ "$sym" = "_start" ]; then
    localentry=1
fi

awk -v sym="<$sym>:" -v le="$localentry" -v d="$displ" -v b="$bdispl" '
$2 ~ sym {
    flag=1;
    dnum = strtonum(d);
    base = strtonum(b);
    if (dnum != 0) {
        displ = dnum - strtonum("0x"$1);
    } else {
        displ = 0;
    }
    if (!le) {
        printf("-begin %x\n", strtonum("0x"$1) + 8 + displ + base);
    } else {
        printf("-begin %x\n", strtonum("0x"$1) + displ + base);
    }
}
flag && $3 ~ "retq" {
    gsub(":", "", $1);
    printf("-end %x\n",strtonum("0x"$1) + displ + base);
}
/^$/ { flag=0; }
' "$tmp" > "$tmp2"

# shellcheck disable=SC2013
for elem in $(grep ^subcall: < "$tmp2" | grep -v "subcall: $sym" | cut -d ' ' -f 2); do
    "$0" "$1" "$elem" | grep -v "^-begin" | sort -n | uniq >> "$tmp2"
done;


if [ "$(grep -v ^subcall:  < "$tmp2" | sort -n | uniq | grep -c end)" -eq 0 ]; then
    die "No -end points found"
fi

if [ "$(grep -v ^subcall:  < "$tmp2" | sort -n | uniq | grep -c begin)" -eq 0 ]; then
    die "No -begin points found"
fi

grep -v ^subcall: < "$tmp2" | sort -n | uniq

if [ $cache -eq 0 ]; then
    rm -f "$tmp" "$tmp2"
fi
