Skip to content

Decompiler Explorer tool

Dinkydau edited this page Jan 8, 2024 · 3 revisions

image

purpose of this tool

  • to compare source code to what decompilers generate

how the program works

More details are explained in comments in the code. Some extra general information:

  • The tool starts compilation and decompilation processes in the background. While those processes take place, you can still edit the source code.
  • If you click "Compile and decompile", any existing processes are aborted and new ones are started.
  • You can hide panels by using the checkboxes. If you hide a panel, all related tasks will pause, so that no CPU time is wasted on tasks of which the result can not be seen anyway. This should all work as expected: if you unhide a hidden panel, its tasks will resume again.

how to run the tool

The main function is in https://github.com/reijer-dev/decompiler-benchmarking/blob/master/src/main/java/nl/ou/debm/devtools/explorer/Main.java . Run that file to start the tool.

todo: create a wiki that explains how to build the whole project (not just the explorer).

This project uses the build system gradle. Dependencies are listed in https://github.com/reijer-dev/decompiler-benchmarking/blob/master/build.gradle.kts The dependency implementation("com.fifesoft:rsyntaxtextarea:3.3.4") is used by decompiler explorer for code areas (with syntax highlighting etc.) If it weren't for RSyntaxTextArea, it wouldn't have been so beautiful, because it's a lot of work to create such nice code areas.

configuring the compiler

For the purpose of our project we only need clang as a compiler, because we use its LLVM IR for our analyses. Therefore decompiler explorer is also hardconfigured to use clang. The location of clang is determined here in Main.java:

if (compilerPath == "") {
    compilerPath = Misc.strGetExternalSoftwareLocation(ECompiler.CLANG.strProgramName());
}

This is a somewhat convoluted way of expressing that clang is searched for in your PATH environment variable. If needed you can insert a different hardcoded path here (for example c:\clang\clang.exe).

It should be possible to use a different compiler, but you may have to also change the way commandline parameters are generated. For example, in the controller::compile function, this is used to define the parameters for compilation to a binary:

var compilerParams = new ArrayList<String>();
compilerParams.add(compilerPath);
compilerParams.add(source_filePath());
compilerParams.add("-o"); compilerParams.add(compiled_binary_filePath());
compilerParams.addAll(extra_compilerParams); //extra parameters entered by the user in the GUI

configuring decompilers

Every "decompiler" is assumed to be an executable or script that accepts two parameters: (a path to) the program to be decompiled and the location of the decompiled C. Decompilers that work differently will need a wrapper script of this form.

You can have as many decompilers as you want. As long as they have the expected form, it will work. In Main.java, the location were the tool searches for decompilers is defined here:

public Controller() throws Exception {
    //Find the decompilers
    File dir = new File(Environment.decompilerPath);

This refers to the Environment class from the common package of this project. You can easily edit this to use a different path.

The same decompilerPath is also used in the creation of a temporary directory that is used by the tool to store its files (C source code, compiled binary, compiled LLVM IR and assembly, and decompiled C for each decompiler):

class Constants {
    public static String temp_dir = Environment.decompilerPath + "temp\\";

There are some example wrapper scripts in the scripts directory (of this project) that you will need to edit to refer to the right paths on your computer. For example, retdec is easy to use. It already accepts an executable path and an output path as parameters, so all that is needed to conform to what our project expects is the following script (on windows):

C:\software\RetDec-v5.0-Windows-Release\bin\retdec-decompiler.exe %1 -o %2

If you have retdec on a different path you need to edit that path.

Ghidra is more difficult to use. We currently have this wrapper script:

SET THIS_SCRIPT_DIR=%~dp0%
::Does string have a trailing slash? if so remove it 
IF %THIS_SCRIPT_DIR:~-1%==\ SET THIS_SCRIPT_DIR=%THIS_SCRIPT_DIR:~0,-1%
echo %THIS_SCRIPT_DIR%

SET EXECUTABLE_NAME=%~nx1%
echo %EXECUTABLE_NAME%
SET POSTSCRIPT_DIR=%THIS_SCRIPT_DIR%\resources
SET POSTSCRIPT_NAME=ghidra_decompile.java
SET GHIDRA_NAME=ghidra_11.0_PUBLIC
SET RUNFOLDER=C:\software\%GHIDRA_NAME%

echo Ik start Ghidra...
call "%RUNFOLDER%\support\analyzeHeadless.bat" "%THIS_SCRIPT_DIR%" tmp_ghidra_project -import %1 -scriptPath "%POSTSCRIPT_DIR%" -postscript "%POSTSCRIPT_NAME%" %2 -deleteProject

Ghidra needs to create a project, which is deleted after analysis (parameters -deleteProject). It also needs a "postscript" to postprocess the analysis results, which is a java script (not to be confused with javascript, the browser language!). The script looks like this:

// Decompilatie via Ghidra naar C

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

//import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.Option;
import ghidra.app.util.exporter.CppExporter;

public class ghidra_decompile extends ghidra.app.script.GhidraScript {
    @Override
    public void run() throws Exception {
        // haal parameters --> oftewel de uitvoerbestandsnaam
        String[] args = getScriptArgs();
        
        // exporteer naar uitvoerfile
        System.out.print("Exporteren naar: " + Arrays.toString(args) + "...");
        File outputFile = new File(args[0]);
        CppExporter cppExporter = new CppExporter();
		List<Option> options = new ArrayList<Option>();
		options.add(new Option(CppExporter.CREATE_HEADER_FILE, new Boolean(false)));
		cppExporter.setOptions(options);
		cppExporter.setExporterServiceProvider(state.getTool());
		cppExporter.export(outputFile, currentProgram, null, monitor);
        // geef weer
        System.out.println(" klaar.");
    }
}