#!/usr/bin/perl

# **********************************************************
# Copyright (c) 2007 VMware, Inc.  All rights reserved.
# **********************************************************

# 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 VMware, Inc. 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 VMWARE, INC. 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.

## pcache-ws.pl
## by Derek Bruening
## February 2007
##
## Lists persistent cache files and modules in a process's working set,
## using vadump.exe.

$usage = "Usage: $0 <pid>";
$tools = $ENV{'DYNAMORIO_TOOLS'};
if ($tools eq "") {
    # use same dir this script sits in
    $tools = $0;
    if ($tools =~ /[\\\/]/) {
        $tools =~ s/^(.+)[\\\/][^\\\/]+$/\1/;
        $tools =~ s/\\/\\\\/g;
    } else {
        $tools = "./";
    }
    # address_query needs DYNAMORIO_TOOLS set to find DRload.exe
    $ENV{'DYNAMORIO_TOOLS'} = $tools;
}
$vadump = `cygpath -u $tools/external/vadump`;
chomp $vadump;

die $usage if ($#ARGV < 0);
$pid = $ARGV[0];

$cmd = "$vadump -o -p $pid";
print "Running $cmd\n" if ($verbose);

# FIXME: win32 perl (non-cygwin) doesn't need the pipe-hang workaround;
# we try to distinguish via the interpreter path: no guarantees though!
if ($^X =~ /\/usr/) {
    # FIXME: a direct pipe hangs, but passing through something else works
    open(VADUMP, "$cmd 2>&1 | cat |") || die "Error running $cmd\n";
} else {
    # FIXME: can't get stderr redirection working for win32 perl
    open(VADUMP, "$cmd |") || die "Error running $cmd\n";
}
while (<VADUMP>) {
    chomp;
    s/\r//; # in case DOS files get in here!
    if (/^OpenProcess/) {
        die "No such process with pid $pid\n";
    }
    next unless (/^0x[0-9A-F]{8} \((\d+)\) (.*)$/);
    $count = $1;
    $descr = $2;
    print "Got $count $descr\n" if ($verbose);
    next if ($descr =~ /^PRIVATE/i || $descr =~ /^UNKNOWN/ ||
             $descr =~ /^Process/ || $descr =~ /^TEB/ ||
             $descr =~ /^Stack/ || $descr =~ /^DATAFILE.*\.nls$/);
    $descr =~ s/DATAFILE_MAPPED Base 0x[0-9A-F]{8} //;
    $descr = lc($descr);
    $raw_descr = $descr;
    if ($raw_descr ne $last_raw_descr) {
        $finished{$last_descr} = 1;
        # handle duplicate names
        if ($finished{$descr}) {
            $dupnum = 2;
            do {
                $dupdescr = "$descr ($dupnum)";
                $dupnum++;
            } while ($finished{$dupdescr});
            $descr = $dupdescr;
        }
    } else {
        $descr = $last_descr;
    }
    $private{$descr}++ if ($count == 0);
    $shareable{$descr}++ if ($count == 1);
    $shared{$descr}++ if ($count > 1);
    $mem{$descr} = $descr;
    $last_descr = $descr;
    $last_raw_descr = $raw_descr;
}
close(VADUMP);

die "Error running $cmd\n" if (scalar(keys %mem) == 0);
printf "%6s %6s %6s %6s %s \n", "Priv", "S-able", "Shared", "Total";
foreach $i (sort (keys %mem)) {
    printf "%6d %6d %6d %6d  %s \n", $private{$i}, $shareable{$i}, $shared{$i},
    $private{$i} + $shareable{$i} + $shared{$i}, $i;
}