/* ### * 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') helpPath project(path: ":Base", configuration: 'helpPath') } // 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, 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" 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 '/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 (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' // '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 $buildDir/decompileplugin.fo $buildDir/decompileplugin.pdf $buildDir/decompileplugin_withscaling.xml 2>&1 rm -rf $buildDir/images 2>&1 mkdir -p $buildDir/images 2>&1 cp $installHelpPoint/topics/DecompilePlugin/images/*.png $buildDir/images 2>&1 cp $installHelpPoint/topics/DecompilePlugin/images/*.gif $buildDir/images 2>&1 cp $installHelpPoint/shared/*.png $buildDir/images 2>&1 echo '** Building decompileplugin.fo **' xsltproc --output $buildDir/decompileplugin_withscaling.xml --stringparam profile.condition "withscaling" commonprofile.xsl decompileplugin.xml 2>&1 xsltproc --output $buildDir/decompileplugin.fo decompileplugin_pdf.xsl $buildDir/decompileplugin_withscaling.xml 2>&1 echo '** Building decompileplugin.pdf **' fop $buildDir/decompileplugin.fo $buildDir/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 '/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 (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, 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 } } } } } }