#! /bin/bash
#
# Copyright (c) 2015-2025, Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#  * Neither the name of Intel Corporation nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

set -e

prog=`basename $0`

outdir="."
dryrun=0

buildid_cache=$(perf config buildid.dir || true)
if [[ -z $buildid_cache ]]; then
    buildid_cache="$HOME/.debug"
fi

usage() {
    cat <<EOF
usage: $prog [<options>] <perf.data-file>

Scan the perf data file for MMAP records and copy the referenced files to the
output directory set via the -o option.

options:

  -h        this text
  -o <dir>  set the output directory to <dir> (current: $outdir)
  -b <dir>  set the buildid cache directory to <dir> (current: $buildid_cache)
  -n        print commands instead of executing them

<perf.data-file> defaults to perf.data.
EOF
}

while getopts "ho:b:n" opt; do
    case $opt in
        h)
            usage
            exit 0
            ;;
        o)
            outdir=$OPTARG
            ;;
        b)
            buildid_cache=$OPTARG
            ;;
        n)
            dryrun=1
            ;;
    esac
done

shift $(($OPTIND-1))


if [[ $# == 0 ]]; then
    file="perf.data"
elif [[ $# == 1 ]]; then
    file="$1"
    shift
fi

if [[ $# != 0 ]]; then
    echo "$prog: unknown argument: $1.  use -h for help."
    exit 1
fi


# Read the vdsos first.
#
# This creates the output directory for vdsos.
#
have_vdso32=$(which perf-read-vdso32 2>/dev/null || true)
have_vdsox32=$(which perf-read-vdsox32 2>/dev/null || true)

if [[ $dryrun == 0 ]]; then
    mkdir -p "$outdir/vdso"
else
    echo "mkdir -p $outdir/vdso"
fi

if [[ -n $have_vdso32 && ! -e "$outdir/vdso/vdso-ia32.so" ]]; then
    if [[ $dryrun == 0 ]]; then
        perf-read-vdso32 > "$outdir/vdso/vdso-ia32.so"
    else
        echo "perf-read-vdso32 > $outdir/vdso/vdso-ia32.so"
    fi
fi

if [[ -n $have_vdsox32 && ! -e "$outdir/vdso/vdso-x32.so" ]]; then
    if [[ $dryrun == 0 ]]; then
        perf-read-vdsox32 > "$outdir/vdso/vdso-x32.so"
    else
        echo "perf-read-vdsox32 > $outdir/vdso/vdso-x32.so"
    fi
fi

# If we have a buildid cache, use it.
#
if [[ -d $buildid_cache ]]; then
    perf buildid-list -i "$file" | gawk -F' ' -- '
        function run(cmd) {
            if (dryrun != 0) {
                printf("%s\n", cmd)
            } else {
                system(cmd)
            }
        }

        function dirname(file) {
            items = split(file, parts, "/", seps)

            delete parts[items]

            dname = ""
            for (part in parts) {
                dname = dname seps[part-1] parts[part]
            }

            return dname
        }

        function copy(src, dst) {
            # do not overwrite existing files
            #
            status = system("ls " dst " > /dev/null 2>&1")
            if (status == 0) {
                return
            }

            dir = dirname(dst)

            run("mkdir -p " dir)
            run("cp " src " " dst)
        }

        /^[0-9a-z]+ *\[vdso/ {
            src = cache "/[vdso]/" $1 "/vdso"

            status = system("file " src " | grep -e \"ELF 64-bit.*x86-64\" >/dev/null 2>&1")
            if (status == 0) {
                copy(src, outdir "/vdso/vdso-x64.so")
                next
            }

            status = system("file " src " | grep -e \"ELF 32-bit.*x86-64\" >/dev/null 2>&1")
            if (status == 0) {
                copy(src, outdir "/vdso/vdso-x32.so")
                next
            }

            status = system("file " src " | grep -e \"ELF 32-bit.*Intel 80386\" >/dev/null 2>&1")
            if (status == 0) {
                copy(src, outdir "/vdso/vdso-x32.so")
                next
            }

            printf("%s: failed to determine flavor of %s.\n", prog, src)
            next
        }

        /^[0-9a-z]+ *\[/ {
            next
        }

        /^[0-9a-z]+ *.*.ko$/ {
            next
        }

        /^[0-9a-z]+ *.*.ko.xz$/ {
            next
        }

        /^[0-9a-z]+ *\// {
            copy(cache $2 "/" $1 "/elf", outdir $2)
            next
        }

    ' dryrun="$dryrun" outdir="$outdir" cache="$buildid_cache"
fi

# Copy files that were referenced but not in the buildid cache.
#
# We will skip files we already have so we don't overwrite anything we found in
# the buildid cache.
#
perf script --no-itrace -i "$file" -D | gawk -F' ' -- '
    function run(cmd) {
            if (dryrun != 0) {
                printf("%s\n", cmd)
            } else {
                system(cmd)
            }
    }

    function dirname(file) {
        items = split(file, parts, "/", seps)

        delete parts[items]

        dname = ""
        for (part in parts) {
            dname = dname seps[part-1] parts[part]
        }

        return dname
    }

    function handle_mmap(file) {
        # ignore any non-absolute filename
        #
        # this covers pseudo-files like [kallsyms] or [vdso]
        #
        if (substr(file, 0, 1) != "/") {
            return
        }

        # ignore kernel modules
        #
        # we rely on kcore
        #
        if (match(file, /\.ko$/) != 0) {
            return
        }
        if (match(file, /\.ko.xz$/) != 0) {
            return
        }

        # ignore //anon
        #
        if (file == "//anon") {
            return
        }

        dst = outdir file

        # do not overwrite existing files
        #
        status = system("ls " dst " > /dev/null 2>&1")
        if (status == 0) {
            return
        }

        dir = dirname(dst)

        run("mkdir -p " dir)
        run("cp " file " " dst)
    }

    /PERF_RECORD_MMAP/     { handle_mmap($NF) }
' dryrun="$dryrun" outdir="$outdir"
