ghidra/Ghidra/Features/Decompiler/build.gradle

679 lines
23 KiB
Groovy

apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Features Decompiler'
dependencies {
compile project(':Base')
compile project(':SoftwareModeling')
// include Base src/test/resources when running decompiler integration tests (uses defaultTools)
integrationTestRuntime project(path: ':Base', configuration: 'testArtifacts')
testCompile "org.jmockit:jmockit:1.44"
helpPath project(path: ":Base", configuration: 'helpPath')
}
ext.cppSourceDir = "src/decompile/cpp"
/**
* The bison and flex generated files are maintained in source control and
* do not need regeneration unless changes have been made to the yacc (*.y),
* lex (*.l) or any critical header file. This stand-alone task should be
* executed as needed prior to building the decompiler or sleigh native
* executables.
*/
task generateParsers {
dependsOn "lexSleigh", "yaccDecompiler"
}
/**
* This task calls bison to process the grammar.y and xml.y files (yacc) to
* generate the corresponding *.cc files that will then be compiled with the rest
* of the decompiler code.
*/
task yaccDecompiler {
Task t1 = createBisonTask("xml", "decompile", false, true);
Task t2 = createBisonTask("grammar", "decompile", false, true);
if (t1 != null) {
dependsOn t1, t2
}
}
/**
* This task calls bison to process the slghparse.y, pcodeparse.y and xml.y
* files (yacc) to generate the corresponding *.cc files that will then be compiled
* with the rest of the sleigh code.
*/
task yaccSleigh {
Task t1 = createBisonTask("slghparse", "sleigh", true, false); // also produces slghparse.hh header file
Task t2 = createBisonTask("pcodeparse", "sleigh", false, true);
Task t3 = createBisonTask("xml", "sleigh", false, true);
if (t1 != null) {
dependsOn t1,t2,t3
}
}
/**
* This task calls flex to process the slghscan.l file (lex) into a cpp file that will then
* be compiled with the rest of the sleigh code.
*/
task lexSleigh {
dependsOn "yaccSleigh"
Task t1 = createLexTask("slghscan", "sleighlex")
if (t1 != null) {
// TODO: add missing task input file dependencies
dependsOn t1
}
}
def buildDir = "../../../build" // Relative to the 'workingDir' Exec task property.
def installPoint = "$rootDir/GhidraDocs/languages/html"
def installHelpPoint = "../help/help"
task buildDecompilerHelpHtml(type: Exec) {
workingDir 'src/main/doc'
// 'which' returns the number of failed arguments
// Using the 'which' command first will allow the task to fail if the required
// executables are not installed.
//
// The bash commands end with "2>&1" to redirect stderr to stdout and have all
// messages print in sequence
//
// 'commandLine' takes one command, so wrap multiple commands in bash.
commandLine 'bash', '-e', '-c', """
echo '** Checking if required executables are installed. **'
which xsltproc
which sed
echo '** Removing older html files installed under '$installHelpPoint' **'
rm -f $installHelpPoint/topics/DecompilePlugin/*.html
echo '** Building html files **'
xsltproc --output $buildDir/decomp_noscaling.xml --stringparam profile.condition "noscaling" /usr/share/sgml/docbook/xsl-stylesheets/profiling/profile.xsl decompileplugin.xml 2>&1
xsltproc --stringparam base.dir ${installHelpPoint}/topics/DecompilePlugin/ --stringparam root.filename Decompiler decompileplugin_html.xsl $buildDir/decomp_noscaling.xml 2>&1
rm ${installHelpPoint}/topics/DecompilePlugin/Decompiler.html
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' ${installHelpPoint}/topics/DecompilePlugin/*.html 2>&1
echo '** Done. **'
"""
// Allows doLast block regardless of exit value.
ignoreExitValue true
// Store the output instead of printing to the console.
standardOutput = new ByteArrayOutputStream()
ext.output = { standardOutput.toString() }
ext.errorOutput = { standardOutput.toString() }
// Check the OS before executing command.
doFirst {
if (!getCurrentPlatformName().startsWith("linux")) {
throw new TaskExecutionException( it, new Exception("The '$it.name' task only works on Linux."))
}
}
// Print the output of the commands and check the return value.
doLast {
println output()
if (execResult.exitValue) {
logger.error("$it.name: An error occurred. Here is the output:\n" + output())
throw new TaskExecutionException( it, new Exception("'$it.name': The command: '${commandLine.join(' ')}'" +
" task \nfailed with exit code $execResult.exitValue; see task output for details."))
}
}
}
task buildDecompilerHelpPdf(type: Exec) {
// Check the OS before enabling task.
if (!(org.gradle.internal.os.OperatingSystem.current().isLinux()
|| org.gradle.internal.os.OperatingSystem.current().isMacOsX())) {
it.enabled = false
}
workingDir 'src/main/doc'
// 'which' returns the number of failed arguments
// Using 'which' first will allow the entire command to fail if the required
// executables are not installed.
//
// The bash commands end with "2>&1" to redirect stderr to stdout and have all
// messages print in sequence
//
// 'commandLine' takes one command, so wrap multiple commands in bash.
commandLine 'bash', '-e', '-c', """
echo '** Checking if required executables are installed. **'
which fop 2>&1
which xsltproc 2>&1
rm -f decompileplugin.fo decompileplugin.pdf decompileplugin_withscaling.xml 2>&1
rm -rf ./images 2>&1
mkdir -p ./images 2>&1
cp $installHelpPoint/topics/DecompilePlugin/images/*.png ./images 2>&1
cp $installHelpPoint/topics/DecompilePlugin/images/*.gif ./images 2>&1
cp $installHelpPoint/shared/*.png ./images 2>&1
echo '** Building decompileplugin.fo **'
xsltproc --output ./decompileplugin_withscaling.xml --stringparam profile.condition "withscaling" /usr/share/sgml/docbook/xsl-stylesheets/profiling/profile.xsl decompileplugin.xml 2>&1
xsltproc --output ./decompileplugin.fo decompileplugin_pdf.xsl decompileplugin_withscaling.xml 2>&1
echo '** Building decompileplugin.pdf **'
fop decompileplugin.fo decompileplugin.pdf 2>&1
echo '** Done. **'
"""
// Allows doLast block regardless of exit value. Task does not fail if bash command fails.
ignoreExitValue true
// Store the output instead of printing to the console.
standardOutput = new ByteArrayOutputStream()
ext.output = { standardOutput.toString() }
ext.errorOutput = { standardOutput.toString() }
// Print the output of the commands and check the return value.
doLast {
println output()
if (execResult.exitValue) {
println "$it.name: An error occurred with this task. Here is the output:\n" + output()
println "Skipping task $it.name\n"
}
}
}
/**
* Build the pdfs docs for the decompiler and place them in the '$buildDir' directory.
* A build (ex: 'gradle buildGhidra') will place the pdfs in the distribution zip file.
* This task will fail gracefully and allow any distribution task (like buildGhidra) to continue,
* without pdfs in the distribution zip.
* There is an associated, auto-generated clean task.
*/
task buildDecompilerDocumentationPdfs(type: Exec) {
// Check the OS before enabling task.
if (!(org.gradle.internal.os.OperatingSystem.current().isLinux()
|| org.gradle.internal.os.OperatingSystem.current().isMacOsX())) {
it.enabled = false
}
workingDir 'src/main/doc'
// Gradle will provide a cleanBuildDecompilerDocumentationPdfs task that will remove these
// declared outputs.
outputs.file "$workingDir/$buildDir/pcoderef.fo"
outputs.file "$workingDir/$buildDir/pcoderef.pdf"
outputs.file "$workingDir/$buildDir/sleigh.fo"
outputs.file "$workingDir/$buildDir/sleigh.pdf"
// 'which' returns the number of failed arguments
// Using 'which' first will allow the entire command to fail if the required
// executables are not installed.
//
// The bash commands end with "2>&1" to redirect stderr to stdout and have all
// messages print in sequence
//
// 'commandLine' takes one command, so wrap multiple commands in bash.
commandLine 'bash', '-e', '-c', """
echo '** Checking if required executables are installed. **'
which fop 2>&1
which xsltproc 2>&1
mkdir -p $buildDir 2>&1
cp $installPoint/Diagram*.png $buildDir 2>&1
echo '** Building sleigh.fo **'
xsltproc --output $buildDir/sleigh.fo sleigh_pdf.xsl sleigh.xml 2>&1
echo '** Building sleigh.pdf **'
fop $buildDir/sleigh.fo $buildDir/sleigh.pdf 2>&1
echo '** Building pcoderef.fo **'
xsltproc --output $buildDir/pcoderef.fo pcoderef_pdf.xsl pcoderef.xml 2>&1
echo '** Building pcoderef.pdf **'
fop $buildDir/pcoderef.fo $buildDir/pcoderef.pdf 2>&1
echo '** Done. **'
"""
// Allows doLast block regardless of exit value. Task does not fail if bash command fails.
ignoreExitValue true
// Store the output instead of printing to the console.
standardOutput = new ByteArrayOutputStream()
ext.output = { standardOutput.toString() }
ext.errorOutput = { standardOutput.toString() }
// Print the output of the commands and check the return value.
doLast {
println output()
if (execResult.exitValue) {
println "$it.name: An error occurred with this task. Here is the output:\n" + output()
println "Skipping task $it.name\n"
}
}
}
/**
* Build the html docs for the decompiler and place them in the '$installPoint' directory.
* This gradle task will overwrite the html docs currently in the git repo.
* A build (ex: 'gradle buildGhidra') will place the html files in the distribution, but buildGhidra
* does not depend on buildDecompilerDocumentationHtml.
* There is an associated, auto-generated clean task.
**/
task buildDecompilerDocumentationHtml(type: Exec) {
workingDir 'src/main/doc'
// Gradle will provide a cleanBuildDecompilerDocumentationHtml task that will remove these
// declared outputs.
outputs.file "$workingDir/$buildDir/index.html"
outputs.dir "$workingDir/$buildDir/html"
// 'which' returns the number of failed arguments
// Using the 'which' command first will allow the task to fail if the required
// executables are not installed.
//
// The bash commands end with "2>&1" to redirect stderr to stdout and have all
// messages print in sequence
//
// 'commandLine' takes one command, so wrap multiple commands in bash.
commandLine 'bash', '-e', '-c', """
echo '** Checking if required executables are installed. **'
which sed 2>&1
which xsltproc 2>&1
echo -e '** Building index.html **'
xsltproc --output $buildDir/index.html main_html.xsl main.xml 2>&1
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/index.html
echo '** Building html/sleigh.html **'
xsltproc --stringparam base.dir $buildDir/html/ --stringparam root.filename sleigh sleigh_html.xsl sleigh.xml 2>&1
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/html/sleigh*.html
cp $installPoint/Frontpage.css $buildDir/html 2>&1
cp $installPoint/languages.css $buildDir/html
cp $installPoint/Diagram1.png $buildDir/html
cp $installPoint/Diagram2.png $buildDir/html
cp $installPoint/Diagram3.png $buildDir/html
echo '** Building html/pcoderef.html **'
xsltproc --stringparam base.dir $buildDir/html/ --stringparam root.filename pcoderef pcoderef_html.xsl pcoderef.xml 2>&1
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/html/pcoderef.html
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/html/pcodedescription.html
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/html/pseudo-ops.html
sed -i -e '/Frontpage.css/ { p; s/Frontpage.css/languages.css/; }' $buildDir/html/reference.html
cp $installPoint/Frontpage.css $buildDir/html
cp $installPoint/languages.css $buildDir/html
echo '** Installing html documentation. **'
cp $buildDir/index.html $installPoint/index.html
rm $installPoint/*.html $installPoint/*.png
cp $buildDir/html/*.html $buildDir/html/*.png $installPoint/
echo '** Done. **'
"""
// Allows doLast block regardless of exit value.
ignoreExitValue true
// Store the output instead of printing to the console.
standardOutput = new ByteArrayOutputStream()
ext.output = { standardOutput.toString() }
ext.errorOutput = { standardOutput.toString() }
// Check the OS before executing command.
doFirst {
if ( !(org.gradle.internal.os.OperatingSystem.current().isLinux()
|| org.gradle.internal.os.OperatingSystem.current().isMacOsX())) {
throw new TaskExecutionException( it,
new Exception( "The '$it.name' task only works on Linux or Mac Os X" ))
}
}
// Print the output of the commands and check the return value.
doLast {
println output()
if (execResult.exitValue) {
logger.error("$it.name: An error occurred. Here is the output:\n" + output())
throw new TaskExecutionException( it,
new Exception( "$it.name: The command: '${commandLine.join(' ')}'" +
"\nfailed with exit code $execResult.exitValue; see task output for details." )
)
}
}
}
/**
* Define the "native build model" for building the decompiler executables.
*/
model {
// Define the source files that are compiled and linked to become the decompiler.
// The decompiler source is a bit weird in that all the cpp and headers all live in
// the same directory with other files that are not used by the decompiler.
// That is why we have to list every cpp file that makes up the decomplier.
components {
decompile(NativeExecutableSpec) {
baseName "decompile"
// these tell gradle for which platforms to build a decompiler executable.
targetPlatform "win64"
targetPlatform "linux64"
targetPlatform "osx64"
sources {
cpp {
// NOTE: The bison/flex generated files are assumed to be up-to-date.
// The task `generateParsers` should be executed if needed.
// builtBy yaccDecompiler
source {
srcDir "src/decompile/cpp"
include "space.cc"
include "float.cc"
include "address.cc"
include "pcoderaw.cc"
include "translate.cc"
include "opcodes.cc"
include "globalcontext.cc"
include "capability.cc"
include "architecture.cc"
include "options.cc"
include "graph.cc"
include "cover.cc"
include "block.cc"
include "cast.cc"
include "typeop.cc"
include "database.cc"
include "cpool.cc"
include "comment.cc"
include "stringmanage.cc"
include "fspec.cc"
include "action.cc"
include "loadimage.cc"
include "varnode.cc"
include "op.cc"
include "type.cc"
include "variable.cc"
include "varmap.cc"
include "jumptable.cc"
include "emulate.cc"
include "emulateutil.cc"
include "flow.cc"
include "userop.cc"
include "funcdata.cc"
include "funcdata_block.cc"
include "funcdata_varnode.cc"
include "funcdata_op.cc"
include "pcodeinject.cc"
include "heritage.cc"
include "prefersplit.cc"
include "rangeutil.cc"
include "ruleaction.cc"
include "subflow.cc"
include "transform.cc"
include "blockaction.cc"
include "merge.cc"
include "double.cc"
include "coreaction.cc"
include "condexe.cc"
include "override.cc"
include "dynamic.cc"
include "crc32.cc"
include "prettyprint.cc"
include "printlanguage.cc"
include "printc.cc"
include "printjava.cc"
include "memstate.cc"
include "opbehavior.cc"
include "paramid.cc"
include "ghidra_arch.cc"
include "inject_ghidra.cc"
include "ghidra_translate.cc"
include "loadimage_ghidra.cc"
include "typegrp_ghidra.cc"
include "database_ghidra.cc"
include "ghidra_context.cc"
include "cpool_ghidra.cc"
include "ghidra_process.cc"
include "comment_ghidra.cc"
include "string_ghidra.cc"
// include "callgraph.cc" // uncomment for debug
// include "ifacedecomp.cc" // uncomment for debug
// include "ifaceterm.cc" // uncomment for debug
// include "interface.cc" // uncomment for debug
// generated source files
include "xml.cc"
// include "grammar.cc" // used by diagnostic console mode
}
exportedHeaders {
srcDir "src/decompile/cpp"
}
} // end cpp
} // end sources
} // end decompile
sleigh(NativeExecutableSpec) {
targetPlatform "win64"
targetPlatform "linux64"
targetPlatform "osx64"
sources {
cpp {
// NOTE: The bison/flex generated files are assumed to be up-to-date.
// The task `generateParsers` should be executed if needed.
// builtBy lexSleigh
source {
srcDir "src/decompile/cpp"
include "space.cc"
include "float.cc"
include "address.cc"
include "pcoderaw.cc"
include "translate.cc"
include "opcodes.cc"
include "globalcontext.cc"
include "sleigh.cc"
include "pcodecompile.cc"
include "sleighbase.cc"
include "slghsymbol.cc"
include "slghpatexpress.cc"
include "slghpattern.cc"
include "semantics.cc"
include "context.cc"
include "filemanage.cc"
include "slgh_compile.cc"
// generated source files
include "xml.cc"
include "pcodeparse.cc"
include "slghparse.cc"
include "slghscan.cc"
}
exportedHeaders {
srcDir "src/decompile/cpp"
}
} // end cpp
} // end sources (sleigh)
} // end sleigh
} // end components
binaries {
all{ b ->
if (b.toolChain in Gcc) {
b.cppCompiler.args "-Wall"
b.cppCompiler.args "-O2" // for DEBUG, comment this line out
// b.cppCompiler.args "-g" // for DEBUG, uncomment this line
b.cppCompiler.args "-Wno-sign-compare"
if (b.targetPlatform.operatingSystem.linux) {
// b.linker.args "-static"
b.cppCompiler.define "LINUX"
b.cppCompiler.define "_LINUX"
}
}
else if (b.toolChain in VisualCpp) {
b.cppCompiler.args "/EHsc"
b.cppCompiler.define "_SECURE_SCL=0"
b.cppCompiler.define "_HAS_ITERATOR_DEBUGGING=0"
// b.cppCompiler.args "/Zi" // for DEBUG, uncomment this line
// b.cppCompiler.args "/FS" // for DEBUG, uncomment this line
// b.linker.args "/DEBUG" // for DEBUG, uncomment this line
if (b.targetPlatform.operatingSystem.windows) {
b.cppCompiler.define "WINDOWS"
b.cppCompiler.define "_WINDOWS"
b.cppCompiler.define "WIN32"
b.cppCompiler.define "_WIN32"
if (b.targetPlatform.name == "win64") {
b.cppCompiler.define "WIN64"
b.cppCompiler.define "_WIN64"
}
}
}
else if (b.toolChain in Clang) {
b.cppCompiler.args "-Wall"
b.cppCompiler.args "-O2" // for DEBUG, comment this line out
// b.cppCompiler.args "-g" // for DEBUG, uncomment this line
b.cppCompiler.args "-Wno-sign-compare"
b.cppCompiler.args "-w"
if (b.targetPlatform.operatingSystem.linux) {
// b.linker.args "-static"
}
}
}
}
} // end model
// Perform simple dependency change detection for generated files
// produced by bison or flex. A 5-second tolerance is used
// to avoid arbitrary last-modified times which may be produced
// by a git checkout.
boolean isUpToDate(File srcFile, File resultFile) {
long resultLm = resultFile.lastModified()
if (resultLm == 0) {
return true
}
long srcLm = srcFile.lastModified()
long elapsedTime = srcLm - resultLm
if (elapsedTime < 5000) {
println "Is UpToDate: ${resultFile}"
return true;
}
return false;
}
/**
* Create a bison task to compile a yacc file (*.y) for the sleigh/decompiler
*/
Task createBisonTask(String filename, String binaryName, boolean generateHeader, boolean qualifyVariables) {
def outputCppDir = "${cppSourceDir}"
def outputHeadersDir = "${cppSourceDir}"
def yaccFile = "${cppSourceDir}/${filename}.y"
def ccFile = "${outputCppDir}/${filename}.cc"
def headerFile = "${outputCppDir}/${filename}.hh"
return task("bison_${binaryName}_$filename", type: Exec) {
inputs.file "${yaccFile}"
outputs.file "${ccFile}"
if (generateHeader) {
outputs.file "${headerFile}"
}
// doFirst {
// file(outputCppDir).mkdirs()
// file(outputHeadersDir)mkdirs()
// }
executable 'bison' // use bison program to process yacc files
// specify the bison's output file
args "-o", "${ccFile}"
// most of the yacc files should be compiled with a variable qualifyer to avoid dupes.
// Unfortunately there is one (slghparse) that can't use a qualifyer because it
// declares a variable used by other files.
if (qualifyVariables) {
args "-p", filename
}
// tell bison where to put the hh file.
if (generateHeader) {
args "--defines=${headerFile}"
}
// tell bison the file to compile
args "${yaccFile}"
}
}
/**
* Create a flex task to compile a lex file (*.l) for sleigh.
*/
Task createLexTask(String filename, String binaryName) {
def outputCppDir = "${cppSourceDir}"
def lexFile = "${cppSourceDir}/${filename}.l"
def ccFile = "${outputCppDir}/${filename}.cc"
return task("lex_${binaryName}_$filename", type: Exec) {
// set up inputs and outputs so that gradle knows when this needs to be rebuilt
inputs.file "${lexFile}"
outputs.files "${ccFile}"
// doFirst {
// file(outputCppDir).mkdirs();
// }
executable 'flex' // the program to execute
// tell flex where to put the output
args "-o", "${ccFile}"
// tell flex the input file
args "${lexFile}"
}
}
rootProject.createInstallationZip {
dependsOn buildDecompilerDocumentationPdfs
def decompilerPdfZipPath = rootProject.ext.ZIP_DIR_PREFIX + "/docs/languages/"
// Add decompiler pdf files to zip. If the pdf files do not exist during execution time
// (if there was an error or wrong platform), the zip task will move on.
buildDecompilerDocumentationPdfs.outputs.each { output ->
output.files.each { file ->
if (file.name.endsWith("pdf")) {
logger.debug("$project.name: Adding Decompiler documentation (if it exists) $file.name to $decompilerPdfZipPath")
rootProject.createInstallationZip.from (file) {
into {
decompilerPdfZipPath
}
}
}
}
}
}