ghidra/Ghidra/Features/Decompiler/build.gradle

487 lines
17 KiB
Groovy

/* ###
* IP: GHIDRA
*
* 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.
*/
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 from: "$rootProject.projectDir/gradle/javadoc.gradle"
apply from: "buildNatives.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Features Decompiler'
dependencies {
api project(':Base')
api project(':SoftwareModeling')
// include Base src/test/resources when running decompiler integration tests (uses defaultTools)
integrationTestImplementation project(path: ':Base', configuration: 'testArtifacts')
integrationTestImplementation project(path: ':SoftwareModeling', configuration: 'testArtifacts')
}
// Include buildable native source in distribution
rootProject.assembleDistribution {
from (this.project.projectDir.toString()) {
include "src/decompile/**"
into { getZipPath(this.project) }
}
}
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);
Task t2 = createBisonTask("grammar", "decompile", false);
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); // also produces slghparse.hh header file
Task t2 = createBisonTask("pcodeparse", "sleigh", false);
Task t3 = createBisonTask("xml", "sleigh", false);
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"
def defaultStylePoint = "$rootDir/Ghidra/Framework/Help/src/main/resources/help/shared/DefaultStyle.css"
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" commonprofile.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 '/DefaultStyle.css/ { p; sQhref=".*"Qhref="../../shared/languages.css"Q; }' ${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 (executionResult.get().getExitValue()) {
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 ${executionResult.get().getExitValue()}; 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'
outputs.dir "$workingDir/$buildDir/pdf"
outputs.file "$workingDir/$buildDir/pdf/decompileplugin.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/pdf/images 2>&1
cp $installHelpPoint/topics/DecompilePlugin/images/*.png $buildDir/pdf/images 2>&1
cp ../resources/images/decompileFunction.gif $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Help/src/main/resources/help/shared/warning.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Help/src/main/resources/help/shared/tip.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Help/src/main/resources/help/shared/note.png $buildDir/pdf/images 2>&1
cp ../../../../../Features/Base/src/main/resources/images/camera-photo.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Gui/src/main/resources/images/openFolder.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Gui/src/main/resources/images/reload3.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Gui/src/main/resources/images/page_white_copy.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Docking/src/main/resources/images/document-properties.png $buildDir/pdf/images 2>&1
cp ../../../../../Framework/Project/src/main/resources/images/page_edit.png $buildDir/pdf/images 2>&1
echo '** Building decompileplugin.fo **'
xsltproc --output $buildDir/pdf/decompileplugin_withscaling.xml --stringparam profile.condition "withscaling" commonprofile.xsl decompileplugin.xml 2>&1
xsltproc --output $buildDir/pdf/decompileplugin.fo decompileplugin_pdf.xsl $buildDir/pdf/decompileplugin_withscaling.xml 2>&1
echo '** Building decompileplugin.pdf **'
fop $buildDir/pdf/decompileplugin.fo $buildDir/pdf/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 (executionResult.get().getExitValue()) {
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 (!(isCurrentLinux() || isCurrentMac())) {
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 (executionResult.get().getExitValue()) {
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 '/DefaultStyle.css/ { p; sQhref=".*"Qhref="../../shared/languages.css"Q; }' $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
cp $defaultStylePoint $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 '/DefaultStyle.css/ { p; sQhref=".*"Qhref="languages.css"Q; }' $buildDir/html/*.html
cp $defaultStylePoint $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 (executionResult.get().getExitValue()) {
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 ${executionResult.get().getExitValue()}; see task output for details." )
)
}
}
}
// 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) {
def yaccFile = "${filename}.y"
def ccFile = "${filename}.cc"
def headerFile = "${filename}.hh"
return task("bison_${binaryName}_$filename", type: Exec) {
inputs.file "${cppSourceDir}/${yaccFile}"
outputs.file "${cppSourceDir}/${ccFile}"
if (generateHeader) {
outputs.file "${headerFile}"
}
workingDir "${cppSourceDir}"
executable 'bison' // use bison program to process yacc files
// specify the bison's output file and that no #line directives should be generated
args "-l", "-o", "${ccFile}"
// 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 lexFile = "${filename}.l"
def ccFile = "${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 "${cppSourceDir}/${lexFile}"
outputs.files "${cppSourceDir}/${ccFile}"
workingDir "${cppSourceDir}"
executable 'flex' // the program to execute
// tell flex where to put the output and not to generate #line directives
args "-L", "-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
}
}
}
}
}
}