Commit 98f28c6b authored by Jason Hiser's avatar Jason Hiser 🚜

Merge branch 'master' of git.zephyr-software.com:opensrc/irdb-cookbook-examples

Conflicts:
	initialize_stack/initialize_stack.cpp
parents 360c7317 fdd908b7
University of Virginia libEHP
University of Virginia IRDB Cookbook
Copyright 2017-2019 University of Virginia
This product includes software developed at
......
# The IRDB Cookbook
This project is the "Cookbook" for building an IRDB transform.
## License
Copyright 2018-2019 Zephyr Software, LLC.
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.
## Description
This project is the "cookbook" for building an IRDB transform -- as in, it is designed to be recipes you can build from.
Included are three sample transforms:
1. initialize\_stack -- intialize a stack frame upon entry to a function.
This transform may be useful to prevent attacks that leverage uninit'd data on the stack.
This transform may be useful to prevent attacks that leverage uninitialized data on the stack.
1. stack\_stamp -- "Stamp" return addresses passed to
functions by xoring the return value with a random value. This transform
may be useful to prevent attacks that overwrite a return address, a classic attack vector.
......@@ -11,21 +34,43 @@ may be useful to prevent attacks that overwrite a return address, a classic atta
in a program do not contain a useful value, and "kill" the register by writing a random value to it.
This transform is only useful for demonstration and testing.
To build this, one must:
1. Download the IRDB's SDK and set the IRDB\_SDK environment variable appropriately. The IRDB SDK can be found [here](https://git.zephyr-software.com/opensrc/irdb-sdk).
1. Download the IRDB libraries and set IRDB\_LIBS environment variable appropriately. Documentation on obtaining the IRDB libraries is not yet available.
## Obtaining and Building the Cookbook
To use these transforms, one should:
### Docker (Recommended)
The easiest way to test these transforms is to use Zephyr's docker registry:
1. set `PSPATH` to include `$COOKBOOK_HOME/plugins_install`, e.g. in `bash`:
```
docker run git.zephyr-software.com:4567/opensrc/irdb-sdk/zipr-dev <args>
```
The recommended value for `<args>` is `help`. Once logged into the docker image, you can `cd irdb-cookbook-examples` and run `scons`.
### Manually (Not recommended)
To build these examples by manually, one must:
1. Download the IRDB SDK and set the IRDB\_SDK environment variable appropriately. The IRDB SDK can be found [here](https://git.zephyr-software.com/opensrc/irdb-sdk).
1. Download the IRDB libraries, set IRDB\_LIBS environment variable appropriately and install `pszr`. Documentation on obtaining the these components not yet
available, however, they are available via this reduced docker image: `git.zephyr-software.com:4567/opensrc/irdb-sdk/zipr-dev`
1. Install g++ and scons.
1. Type `scons` to perform the build.
1. Set the `PSPATH` environment variable to include `$COOKBOOK_HOME/plugins_install`, e.g. in `bash`:
```
export PSPATH=$PSPATH:$COOKBOOK_HOME/plugins_install
```
This can be achieved by `source set_env_vars` in bash, or the equivilent in your shell. Viewing `set_env_vars` is recommended.
1. Run run `ps_zipr.sh` with the proper step enabled, e.g.:
/path/to/ps_zipr.sh --step stack_stamp /bin/ls ./ls.stamped
```
Documentation on obtaining ps_zipr is not yet available.
This step can be achieved by `source set_env_vars` in bash, or the equivilent in your shell. Reviewing `set_env_vars` is recommended for additional education.
## Running the Cookbook Examples
To use these transforms, one should:
1. Install and run `pszr` with the proper step enabled, e.g.:
```
/path/to/pszr --step stack_stamp /bin/ls ./ls.stamped
```
Documentation on obtaining, building and running pszr without docker is not yet available.
......@@ -32,12 +32,12 @@ irdb_env.Append(LIBPATH= Split(" $IRDB_LIBS " )) # this is
irdb_env.Replace(INSTALL_PATH=os.environ['PWD']+"/plugins_install" ) # this is where to place plugins.
#
# export the new environment for children subconstructs
# export the new environment for children sub-conscripts
#
Export('irdb_env')
#
# include the child sconscript files.
# include the children sconscript files.
#
dirs=Split("initialize_stack kill_deads stack_stamp")
libs = list()
......
......@@ -14,6 +14,10 @@
# limitations under the License.
#
#
# Scons is python based. We import OS to get at the environment.
#
import os
......@@ -23,11 +27,11 @@ import os
env=Environment()
#
# Include environment variables. These lines throw semi-readable errors if environment is not defined properly.
# Include environment variables. These lines throw semi-readable errors if the environment is not defined properly.
#
env.Replace(COOKBOOK_HOME=os.environ['COOKBOOK_HOME']) # add cookbook home var to env. for other scons files
env.Replace(IRDB_SDK=os.environ['IRDB_SDK']) # IRDB_SDK and IRDB_LIB by convention to find headers and libraries.
env.Replace(IRDB_LIBS=os.environ['IRDB_LIBS'])
env.Replace(IRDB_SDK= os.environ['IRDB_SDK'] ) # IRDB_SDK and IRDB_LIB by convention to find headers and libraries.
env.Replace(IRDB_LIBS= os.environ['IRDB_LIBS'] )
#
......@@ -44,14 +48,14 @@ env.Append(LINKFLAGS=" -Wl,-unresolved-symbols=ignore-in-shared-libs ") # irdb l
# if we are building in debug mode, use -g, else use -O
if int(env['debug']) == 1:
env.Append(CFLAGS=" -g ")
env.Append(CXXFLAGS=" -g ")
env.Append(LINKFLAGS=" -g ")
env.Append(CFLAGS= " -g ")
env.Append(CXXFLAGS= " -g ")
env.Append(LINKFLAGS= " -g ")
env.Append(SHLINKFLAGS=" -g ")
else:
env.Append(CFLAGS=" -O ")
env.Append(CXXFLAGS=" -O ")
env.Append(LINKFLAGS=" -O ")
env.Append(CFLAGS= " -O ")
env.Append(CXXFLAGS= " -O ")
env.Append(LINKFLAGS= " -O ")
env.Append(SHLINKFLAGS=" -O ")
......
#/bin/bash
export PS_PATH=git.zephyr-software.com:4567/opensrc/irdb-sdk/
export PS_TAG=zipr-dev:latest
export DOCKER_PS=${PS_PATH}${PS_TAG}
do_docker_clean()
{
if [[ $CICD_WEEKLY == 1 ]]; then
docker system prune -a -f
fi
}
do_login()
{
# login to gitlab's docker registry as gitlab-user
docker login $PS_PATH -u gitlab-runner -p 84MyuSuDo4kQat4GZ_Zs 2> /dev/null
}
do_build_image()
{
#
# Re-install peasoup without ida.
#
# cd the docker dir so we can move files
cd docker-zipr-dev
# copyo irdb-sdk and cookbook_home (excluding git and testing files, etc)
time rsync -a --exclude='.git' --exclude 'cicd_testing' $PEASOUP_HOME/irdb-sdk .
time rsync -a --exclude='.git' --exclude 'cicd_testing' --exclude '*.exe' --exclude '*.so' $COOKBOOK_HOME/ irdb-cookbook-examples
# if we fail here, continue on so we put "install" back in the right place.
# the test should stop this
ls -lF
docker build -t $DOCKER_PS . || true
}
do_push()
{
if [[ $CICD_WEEKLY == 1 ]]; then
docker push ${DOCKER_PS}
fi
}
do_logout()
{
docker logout $PS_PATH
}
main()
{
if [[ -z $PEASOUP_HOME ]]; then
cd /tmp/peasoup_test
source set_env_vars
fi
set -e
if [[ -z $PEASOUP_HOME ]] ; then
cd $CICD_MODULE_WORK_DIR/cookbook_test
source set_env_vars
cd /tmp/cookbook_tmp
source set_env_vars
cd cicd_testing
fi
do_docker_clean
do_login
do_build_image
do_push
do_logout
}
main "$@"
FROM git.zephyr-software.com:4567/opensrc/irdb-sdk/zipr-bin:latest
RUN sudo apt-get update && sudo apt install scons -y
COPY irdb-sdk /home/zuser/irdb-sdk
COPY irdb-cookbook-examples /home/zuser/irdb-cookbook-examples
RUN sudo chown zuser:zuser -R irdb*
ENV IRDB_LIBS=/opt/ps_zipr/irdb-libs/lib
ENV IRDB_SDK=/home/zuser/irdb-sdk
ENV COOKBOOK_HOME=/home/zuser/irdb-cookbook-examples
ENV PSPATH=/opt/ps_zipr/irdb-libs/plugins_install:/home/zuser/irdb-cookbook-examples/plugins_install
#/bin/bash
print_usage()
{
echo ""
echo " This docker container is made available to the public by Zephyr Software "
echo " (contact: jwd@zephyr-software.com) under the Creative Commons Attribution- "
echo " NonCommercial license (CC BY-NC). "
echo ""
echo " Linux, Gcc, and other relevant open source projects are licensed under their "
echo " own license and are exempt from this license statement. "
echo ""
echo "IRDB toolchain subcommands:"
echo ""
echo " iagree Accept the creative commons non-commercial license and login."
echo " help Print this menu."
echo ""
}
function is_in_activation
{
service "$1" status
activation=$(service "$1" status | grep "Active: activation" )
if [ -z "$activation" ]; then
true;
else
false;
fi
return $?;
}
main()
{
local res=0
export USER=root;
cd /opt/ps_zipr
source ./set_env_vars
cd /home/zuser
subcommand=$1
shift
echo "Arguments are: $@"
case "$subcommand" in
iagree)
echo
echo Welcome to the IRDB toolchain docker image!
echo
echo "Setting up postgres..."
echo
service postgresql start
echo
echo 'The IRDB toolchain is setup and ready to run.'
echo 'You could start your first experiment with:'
echo
echo 'zuser@a3fc1666aaa4:~$ pszr /bin/ls ./ls.p1 -c p1transform'
echo 'Using Zipr backend.'
echo 'Detected ELF shared object.'
echo 'Performing step rida [dependencies=mandatory] ...Done. Successful.'
echo 'Performing step pdb_register [dependencies=mandatory] ...Done. Successful.'
echo 'Performing step fill_in_cfg [dependencies=unknown] ...Done. Successful.'
echo 'Performing step fill_in_indtargs [dependencies=unknown] ...Done. Successful.'
echo 'Performing step fix_calls [dependencies=unknown] ...Done. Successful.'
echo 'Performing step p1transform [dependencies=unknown] ...Done. Successful.'
echo 'Performing step zipr [dependencies=none] ...Done. Successful.'
echo 'zuser@a3fc1666aaa4:~$ ./ls.p1 -l '
echo ' < ls output > '
echo 'zuser@a3fc1666aaa4:~$ readelf -l /bin/ls ./ls.p1 '
echo
bash
res=0
;;
help)
print_usage
exit 0
;;
*)
print_usage
echo
echo "Unknown subcommand: '$subcommand'"
echo
exit 1
;;
esac
if [[ $res != 0 ]]; then
echo
echo Subcommand failed. Logs were printed.
exit 1
fi
exit 0
}
main "$@"
......@@ -19,11 +19,15 @@
Import('irdb_env')
myenv=irdb_env.Clone()
#
# set input files and output program name
#
files=Glob( Dir('.').srcnode().abspath+"/*.cpp" )
pgm_name="initialize_stack.exe"
#
# build, install and return the program by default.
#
pgm=irdb_env.Program(pgm_name, files)
install=myenv.Install("$INSTALL_PATH/", pgm)
Default(install)
......
......@@ -59,17 +59,24 @@ InitStack_t::InitStack_t(FileIR_t *p_variantIR, const string& p_functions_filena
//
void InitStack_t::readFunctionsFromFile(const string &p_filename)
{
// get all functions for readability of the rest of the code
const auto &all_funcs=getFileIR()->getFunctions();
// open input file and check for successful open
ifstream functionsFile(p_filename); // can't use auto decl here because of lack of copy constructor in ifstream class
if (!functionsFile.is_open()) throw runtime_error("Cannot open "+p_filename);
// read each line of the input file.
auto line = string();
while(functionsFile >> line)
{
// locate a function with the name read from the file.
const auto func_it=find_if(ALLOF(all_funcs), [&](const Function_t* f)
{
return f->getName() == line;
});
// if found, log and insert it into the set to transform
if(func_it!=end(all_funcs))
{
auto f=*func_it;
......@@ -92,7 +99,7 @@ bool InitStack_t::execute()
for(auto f : m_funcs_to_init)
{
// todo: remove this
// if (f->getName().substr(0, 1) == ".") continue; /* anh: ??? */
// if (f->getName().substr(0, 1) == ".") continue;
initStack(f);
}
......
......@@ -41,10 +41,10 @@ namespace InitStack
public:
// construct an object
InitStack_t(
FileIR_t *p_variantIR, // the FileIR object to transform
FileIR_t *p_variantIR, // the FileIR object to transform
const string& p_function_filename, // the name of a file with functions to transform. "" -> no file and transform all functions
int init_value = 0, // the value to write when initializing the stack
bool p_verbose = false // use verbose logging?
bool p_verbose = false // use verbose logging?
);
// execute the transform
......@@ -67,10 +67,10 @@ namespace InitStack
void initStack(Function_t* f);
// data
set<Function_t*> m_funcs_to_init; // the functions whose stacks this object should initialize
int m_init_value; // the value with which to init the stack.
bool m_verbose; // do verbose logging
int m_num_transformed; // stats about how many functions that this object has transformed
set<Function_t*> m_funcs_to_init; // the functions whose stacks this object should initialize
int m_init_value; // the value with which to init the stack.
bool m_verbose; // do verbose logging
int m_num_transformed; // stats about how many functions that this object has transformed
};
}
......
......@@ -37,8 +37,8 @@ void usage(char* p_name)
//
// The entry point for a stand-alone executable transform.
// Note: Thanos-enabled transforms are easier to write, faster to execute, and generally preferred,
// but stand-alone transforms may be useful if the transform has issues with memory leaks and/or memory errors.
// Note: Thanos-enabled transforms are easier to write, faster to execute, and generally preferred.
// Stand-alone transforms may be useful if the transform has issues with memory leaks and/or memory errors.
// Memory issues in a stand alone transform cannot affect correctness of other transforms.
//
int main(int argc, char **argv)
......@@ -120,7 +120,7 @@ int main(int argc, char **argv)
try
{
// Create and download the file's IR.
// Note: this is done differently than with thanos-enabled plugins
// Note: this is achieved differently with thanos-enabled plugins
auto firp = FileIR_t::factory(pidp.get(), this_file);
// sanity
......@@ -140,6 +140,8 @@ int main(int argc, char **argv)
// Stand alone trnasforms must manually write the IR back to the IRDB and commit the transactions
firp->writeToDB();
// and commit the the transaction to postgres
pqxx_interface->commit();
}
else
......
......@@ -23,7 +23,7 @@ using namespace std;
// constructor
KillDeads::KillDeads(FileIR_t *p_variantIR)
:
Transform_t(p_variantIR) // init transform class for insertAssembly and getFileIR
Transform_t(p_variantIR) // init Transform_t class for insertAssembly and getFileIR
{
// no other setup needed
}
......@@ -67,10 +67,10 @@ bool KillDeads::execute()
// find the dead registers for the instruction
const auto &regset=reg_map[insn];
// for each register that's dead
// for each register that is dead
for(auto reg : regset)
{
// if it's the x86 eflags register, kill it with a cmp instruction
// if it is the x86 eflags register, kill it with a cmp instruction
if (reg==rn_EFLAGS)
{
// for flags, do a random compare to change them
......@@ -81,7 +81,7 @@ bool KillDeads::execute()
killed_flags++;
}
// if it's an integer register
// if it is an integer register
if(is64bitRegister(reg) || is32bitRegister(reg) || is16bitRegister(reg) || is8bitRegister(reg))
{
// integer registers can be killed with a mov instruction
......
......@@ -32,7 +32,11 @@
class KillDeads : protected IRDB_SDK::Transform_t
{
public:
// construct the object, basically no parameters other than the IR to transform
KillDeads(IRDB_SDK::FileIR_t *p_variantIR);
// actually perform the transform
bool execute();
private:
......
......@@ -93,8 +93,10 @@ class KillDeadsDriver_t : public IRDB_SDK::TransformStep_t
}
private:
const string program_name = string("kill_deads");
int variantID = BaseObj_t::NOT_IN_DATABASE;
// data
const string program_name = string("kill_deads"); // constant program name
// methods
//
// optional: print using info for this transform.
......
......@@ -61,8 +61,8 @@ static Relocation_t* findRelocation(Instruction_t* insn, const string& type)
StackStamp_t::StackStamp_t(FileIR_t *p_variantIR, StampValue_t sv, bool p_verbose)
:
Transform_t(p_variantIR),
stamp_value(sv),
verbose(p_verbose)
m_stamp_value(sv),
m_verbose(p_verbose)
{
}
......@@ -71,7 +71,7 @@ StackStamp_t::StackStamp_t(FileIR_t *p_variantIR, StampValue_t sv, bool p_verbos
//
StampValue_t StackStamp_t::get_stamp(Function_t* f)
{
return stamp_value;
return m_stamp_value;
}
//
......@@ -99,7 +99,6 @@ bool StackStamp_t::can_stamp(Function_t* f)
const auto target=insn->getTarget();
const auto icfs=insn->getIBTargets();
//
// Check to see if this is a "fixed" call. A fixed call is an x86 call instruction that's been split into a
// push/jmp pair. The push/jmp pair can be relocated to any address without changing the value pushed on the stack.
......@@ -150,12 +149,12 @@ bool StackStamp_t::can_stamp(Function_t* f)
// So, let's check for those
// find a target that leaves
const auto leaver=find_if(icfs->begin(), icfs->end(), [&](Instruction_t* target){
const auto leaver=find_if(ALLOF(*icfs), [&](Instruction_t* target){
return target->getFunction()!=f;
});
// find a target that stays
const auto stayer=find_if(icfs->begin(), icfs->end(), [&](Instruction_t* target){
const auto stayer=find_if(ALLOF(*icfs), [&](Instruction_t* target){
return target->getFunction()==f;
});
......@@ -183,7 +182,6 @@ bool StackStamp_t::can_stamp(Function_t* f)
}
};
//
// Hack alert!
// The yylex function is a problem in AWS so we return false. No additional detail is available.
......@@ -222,14 +220,14 @@ Instruction_t* StackStamp_t::stamp(Function_t* f, Instruction_t* i)
(void)insertAssemblyBefore(i, assembly.str());
// logging
if (verbose)
if (m_verbose)
{
cout << "\tAdding: " << assembly.str() << " before : " << hex<<i->getBaseID()<<":"<<i->getDisassembly()
<< "@0x"<<i->getAddress()->getVirtualOffset()<<endl;
}
// update stats
instructions_added++;
m_instructions_added++;
return i;
}
......@@ -384,8 +382,8 @@ void StackStamp_t::stamp(Function_t* f)
if(!can_stamp(f))
{
// No, record stats.
cout<<"Skipping "<<dec<<functions_transformed<<": "<<f->getName()<<endl;
functions_not_transformed++;
cout<<"Skipping "<<dec<<m_functions_transformed<<": "<<f->getName()<<endl;
m_functions_not_transformed++;
// and exit.
return;
......@@ -395,8 +393,8 @@ void StackStamp_t::stamp(Function_t* f)
assert(f->getEntryPoint());
// Yes, we can stamp. Do log/stats.
cout<<"Doing "<<dec<<functions_transformed<<": "<<f->getName()<<endl;
functions_transformed++;
cout<<"Doing "<<dec<<m_functions_transformed<<": "<<f->getName()<<endl;
m_functions_transformed++;
const auto fix_call_fallthrough_string=string("fix_call_fallthrough");
......@@ -416,7 +414,7 @@ void StackStamp_t::stamp(Function_t* f)
// stamp all returns
if(di->isReturn())
{
if(verbose) cout<<"Stamping return"<<endl;
if(m_verbose) cout<<"Stamping return"<<endl;
stamp(f,insn);
}
// check for calls specially.
......@@ -432,7 +430,7 @@ void StackStamp_t::stamp(Function_t* f)
// not handled yet as we have to instrument oddly.
assert(!insn->getFallthrough());
if(verbose) cout<<"Stamping with target!=function"<<endl;
if(m_verbose) cout<<"Stamping with target!=function"<<endl;
stamp(f,insn);
}
else if(di->isUnconditionalBranch() && icfs)
......@@ -440,7 +438,6 @@ void StackStamp_t::stamp(Function_t* f)
// jump with IB targets are likely switches.
assert(!insn->getFallthrough());
// This is similar to the logic in can_stamp. We should isolate into a function/method that
// avoids the code duplication. For now, though....
......@@ -448,12 +445,12 @@ void StackStamp_t::stamp(Function_t* f)
assert(icfs->size() != 0);
// find a target that leaves
auto leaver=find_if(icfs->begin(), icfs->end(), [&](Instruction_t* target){
auto leaver=find_if(ALLOF(*icfs), [&](Instruction_t* target){
return target->getFunction()!=f;
});
// find a target that stays
auto stayer=find_if(icfs->begin(), icfs->end(), [&](Instruction_t* target){
auto stayer=find_if(ALLOF(*icfs), [&](Instruction_t* target){
return target->getFunction()==f;
});
......@@ -467,7 +464,7 @@ void StackStamp_t::stamp(Function_t* f)
// an indirect jump at a function entry needs a stamp
if(insn==f->getEntryPoint())
{
if(verbose) cout << "Stamping IB at entry of function" << endl;
if(m_verbose) cout << "Stamping IB at entry of function" << endl;
stamp(f,insn);
}
// stamp if we definitely are leaving this function.
......@@ -475,8 +472,8 @@ void StackStamp_t::stamp(Function_t* f)
// this is probably a tail jump that leaves the func, or a plt entry
else if(definitely_leaves || (might_leave && !icfs->isComplete()))
{
if(verbose && definitely_leaves ) cout << "Stamping IB because definitely_leaves " << endl;
else if(verbose) cout << "Stamping IB because might_leave && icfs->isComplete() " << endl;
if(m_verbose && definitely_leaves ) cout << "Stamping IB because definitely_leaves " << endl;
else if(m_verbose) cout << "Stamping IB because might_leave && icfs->isComplete() " << endl;
stamp(f,insn);
}
}
......@@ -532,13 +529,13 @@ bool StackStamp_t::execute()
const auto ss_max_do_transform = getenv("SS_MAX_DO_TRANSFORM");
// let's sort the functions so the order of xform is deterministic.
const auto sorted_funcs = set<Function_t*, nameSorter> (getFileIR()->getFunctions().begin(), getFileIR()->getFunctions().end());
const auto sorted_funcs = set<Function_t*, nameSorter> (ALLOF(getFileIR()->getFunctions()));
// try to stamp functions one at a time, in the sorted order
for(auto func : sorted_funcs)
{
// check to see if we've transformed everything we want already.
if (ss_max_do_transform && functions_transformed > atoi(ss_max_do_transform))
if (ss_max_do_transform && m_functions_transformed > atoi(ss_max_do_transform))
{
continue;
}
......@@ -551,23 +548,23 @@ bool StackStamp_t::execute()
cleanup_eh_pgms();
// calculate and output stats
const auto pct_transformed=((double)functions_transformed/(double)((functions_transformed+functions_not_transformed)))*100.00;
const auto pct_not_transformed=((double)functions_not_transformed/(double)(functions_transformed+functions_not_transformed))*100.00;
const auto pct_transformed=((double)m_functions_transformed/(double)((m_functions_transformed+m_functions_not_transformed)))*100.00;
const auto pct_not_transformed=((double)m_functions_not_transformed/(double)(m_functions_transformed+m_functions_not_transformed))*100.00;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Instructions_added=" << dec << instructions_added << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Total_number_of_functions=" << dec << functions_transformed+functions_not_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Functions_Transformed=" << dec << functions_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Functions_Not_Transformed=" << dec << functions_not_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Instructions_added=" << dec << m_instructions_added << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Total_number_of_functions=" << dec << m_functions_transformed+m_functions_not_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Functions_Transformed=" << dec << m_functions_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Functions_Not_Transformed=" << dec << m_functions_not_transformed << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Percent_Functions_Transformed=" << fixed << setprecision(1) << pct_transformed << "%" << endl;
cout << "# ATTRIBUTE ASSURANCE_Stack_Stamping::Percent_Functions_Not_Transformed=" << fixed << setprecision(1) << pct_not_transformed << "%" << endl;
// used in testing harness to verify that the stats are correct.
assert(getenv("SELF_VALIDATE")==nullptr || instructions_added > 10);
assert(getenv("SELF_VALIDATE")==nullptr || pct_transformed > 20); // can be kind of low for small files
assert(getenv("SELF_VALIDATE")==nullptr || functions_transformed > 5 );
assert(getenv("SELF_VALIDATE")==nullptr || m_instructions_added > 10);
assert(getenv("SELF_VALIDATE")==nullptr || pct_transformed > 20); // can be kind of low for small files
assert(getenv("SELF_VALIDATE")==