mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
487 lines
17 KiB
Groovy
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|