Repository: mahaloz/decomp2dbg (language: python)

Issue:
Merge d2d-ghidra-plugin repo into this one



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ghidra-build.yml b/.github/workflows/ghidra-build.yml
new file mode 100644
index 0000000..9aeb4e9
--- /dev/null
+++ b/.github/workflows/ghidra-build.yml
@@ -0,0 +1,73 @@
+name: Ghidra Extension Build
+
+env:
+  ghidra-url: https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.3_build/ghidra_10.3_PUBLIC_20230510.zip
+  ghidra-zip-filename: ghidra_10.3_PUBLIC.zip
+  ghidra-directory: ghidra_10.3_PUBLIC
+
+on:
+  workflow_run:
+    workflows: [ "Release" ]
+    types:
+      - completed
+  pull_request:
+    branches: [ main ]
+    paths: ['decompilers/d2d_ghidra/**']
+  push:
+    branches: [ main ]
+    paths: ['decompilers/d2d_ghidra/**']
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Download Ghidra release
+      uses: carlosperate/download-file-action@v1.0.3
+      id: download-ghidra
+      with:
+        file-url: ${{ env.ghidra-url }}
+        file-name: ${{ env.ghidra-zip-filename }}
+
+    - name: Unzip Ghidra
+      uses: TonyBogdanov/zip@1.0
+      with:
+        args: unzip -qq ${{ steps.download-ghidra.outputs.file-path }} -d .
+    - uses: actions/setup-java@v1
+      with:
+        java-version: '17.0.6'
+
+    - name: Build extension
+      uses: eskatos/gradle-command-action@v1
+      with:
+        gradle-version: '7.3'
+        build-root-directory: ${{ github.workspace }}/decompilers/d2d_ghidra/
+        arguments: '-PGHIDRA_INSTALL_DIR=${{ github.workspace }}/${{ env.ghidra-directory }}'
+
+    - name: Rename extension
+      run: cd decompilers/d2d_ghidra/dist/ && mv *.zip d2d-ghidra-plugin.zip
+    - name: Upload built extension
+      uses: actions/upload-artifact@v2
+      with:
+        name: extension
+        path: decompilers/d2d_ghidra/dist/d2d-ghidra-plugin.zip
+
+  release:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_run' }}
+    needs: build
+
+    steps:
+    - name: Download built extension
+      uses: actions/download-artifact@v2
+      with:
+        name: extension
+    - name: Upload To Github Release
+      uses: xresloader/upload-to-github-release@v1.3.12
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        file: "*.zip"
+        update_latest_release: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9ebdbc0..4a81629 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,13 +13,10 @@ jobs:
     steps:
       - name: Checkout code
         uses: actions/checkout@v2
-      - name: Download Plugin
-        run: wget "https://github.com/mahaloz/d2d-ghidra-plugin/releases/latest/download/d2d-ghidra-plugin.zip"
       - name: Create Release
         uses: ncipollo/release-action@v1
         with:
           generateReleaseNotes: true
-          artifacts: "d2d-ghidra-plugin.zip"
 
   release-pypi:
     name: Release pypi package
diff --git a/.gitignore b/.gitignore
index 0ca1fae..99225cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 *.idea
 *.egg-info
-build*
+build/*
 *.pyc
 *.gdb_history
 decomp2dbg/decompilers
diff --git a/README.md b/README.md
index 6f8ba24..5170ab9 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ pip3 install decomp2dbg && decomp2dbg --install
 ```
 
 This will open a prompt where you be asked to input the path to your decompiler and debugger of choice. For Ghidra installs,
-you must follow the extra steps to enable extensions [here](https://github.com/mahaloz/d2d-ghidra-plugin).
+you must follow the extra steps to enable extensions [here](https://github.com/mahaloz/decomp2dbg/tree/main/decompilers/d2d_ghidra/README.md).
 If you installed the decompiler-side in the Binja Plugin Manager, you still need to install the debugger side with the above.
 
 **Note**: You may need to allow inbound connections on port 3662, or the port you use, for decomp2dbg to connect
diff --git a/decomp2dbg/__init__.py b/decomp2dbg/__init__.py
index 1f185fc..fd70f12 100644
--- a/decomp2dbg/__init__.py
+++ b/decomp2dbg/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "3.4.0"
+__version__ = "3.5.0"
 
 try:
     from .clients.client import DecompilerClient
diff --git a/decompilers/d2d_ghidra/Module.manifest b/decompilers/d2d_ghidra/Module.manifest
new file mode 100644
index 0000000..e69de29
diff --git a/decompilers/d2d_ghidra/README.md b/decompilers/d2d_ghidra/README.md
new file mode 100644
index 0000000..acdd860
--- /dev/null
+++ b/decompilers/d2d_ghidra/README.md
@@ -0,0 +1,18 @@
+# d2d-ghidra-plugin
+This is the plugin code for the Ghidra-side server of [decomp2dbg](https://github.com/mahaloz/decomp2dbg).
+Please look at the decomp2dbg project docs for more info. 
+
+## Installing
+1. Follow the install steps at [decomp2dbg#install](https://github.com/mahaloz/decomp2dbg#install) using the built-in script
+2. Start Ghidra
+3. On the Project Window, click the tab `File->Install Extensions...`
+![](./data/readme_0.png)
+
+4. Search in the filter for "d2d", and enable it by clickng on the box
+	- If you don't see the d2d plugin, download the latest release from [here](https://github.com/mahaloz/decomp2dbg/releases/latest/download/d2d-ghidra-plugin.zip) and add it manually to the Extensions/Ghidra folder in your install.
+
+5. Restart Ghidra
+6. Open a binary, now click `File->Configure...` tab and enable `decomp2dbg` plugin.
+
+You are done! Now you should be able to hit `Ctrl+Shift+D` to start the decomp2dbg server config. 
+You can also find it in the `Tools` tab when you have a binary open.  
diff --git a/decompilers/d2d_ghidra/build.gradle b/decompilers/d2d_ghidra/build.gradle
new file mode 100644
index 0000000..c6979ee
--- /dev/null
+++ b/decompilers/d2d_ghidra/build.gradle
@@ -0,0 +1,61 @@
+/* ###
+ * 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.
+ */
+// Builds a Ghidra Extension for a given Ghidra installation.
+//
+// An absolute path to the Ghidra installation directory must be supplied either by setting the 
+// GHIDRA_INSTALL_DIR environment variable or Gradle project property:
+//
+//     > export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra> 
+//     > gradle
+//
+//         or
+//
+//     > gradle -PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>
+//
+// Gradle should be invoked from the directory of the project to build.  Please see the
+// application.gradle.version property in <GHIDRA_INSTALL_DIR>/Ghidra/application.properties
+// for the correction version of Gradle to use for the Ghidra installation you specify.
+
+//----------------------START "DO NOT MODIFY" SECTION------------------------------
+def ghidraInstallDir
+
+if (System.env.GHIDRA_INSTALL_DIR) {
+	ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR
+}
+else if (project.hasProperty("GHIDRA_INSTALL_DIR")) {
+	ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR")
+}
+
+if (ghidraInstallDir) {
+	apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle"
+}
+else {
+	throw new GradleException("GHIDRA_INSTALL_DIR is not defined!")
+}
+//----------------------END "DO NOT MODIFY" SECTION-------------------------------
+
+repositories {
+	// Declare dependency repositories here.  This is not needed if dependencies are manually 
+	// dropped into the lib/ directory.
+	// See https://docs.gradle.org/current/userguide/declaring_repositories.html for more info.
+	// Ex: mavenCentral()
+}
+
+dependencies {
+	//implementation 'org.apache.xmlrpc:xmlrpc:3.1.3'
+	// Any external dependencies added here will automatically be copied to the lib/ directory when
+	// this extension is built.	
+}
diff --git a/decompilers/d2d_ghidra/data/.keep b/decompilers/d2d_ghidra/data/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/decompilers/d2d_ghidra/data/readme_0.png b/decompilers/d2d_ghidra/data/readme_0.png
new file mode 100644
index 0000000..6b0264a
Binary files /dev/null and b/decompilers/d2d_ghidra/data/readme_0.png differ
diff --git a/decompilers/d2d_ghidra/extension.properties b/decompilers/d2d_ghidra/extension.properties
new file mode 100644
index 0000000..cf2da71
--- /dev/null
+++ b/decompilers/d2d_ghidra/extension.properties
@@ -0,0 +1,5 @@
+name=@extname@
+description=decomp2dbg Ghidra interface for syncing symbols to decompiler 
+author=mahaloz
+createdOn=
+version=@extversion@
diff --git a/decompilers/d2d_ghidra/lib/README.txt b/decompilers/d2d_ghidra/lib/README.txt
new file mode 100644
index 0000000..528dbc6
--- /dev/null
+++ b/decompilers/d2d_ghidra/lib/README.txt
@@ -0,0 +1,3 @@
+The "lib" directory is intended to hold Jar files which this module is dependent upon.  Jar files 
+may be placed in this directory manually, or automatically by maven via the dependencies block
+of this module's build.gradle file.
\ No newline at end of file
diff --git a/decompilers/d2d_ghidra/lib/commons-logging-1.1.jar b/decompilers/d2d_ghidra/lib/commons-logging-1.1.jar
new file mode 100644
index 0000000..2ff9bbd
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/commons-logging-1.1.jar differ
diff --git a/decompilers/d2d_ghidra/lib/ws-commons-util-1.0.2.jar b/decompilers/d2d_ghidra/lib/ws-commons-util-1.0.2.jar
new file mode 100644
index 0000000..3fc364e
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/ws-commons-util-1.0.2.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-javadoc.jar b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-javadoc.jar
new file mode 100644
index 0000000..17f4d16
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-javadoc.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-sources.jar b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-sources.jar
new file mode 100644
index 0000000..f9d1277
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1-sources.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1.jar b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1.jar
new file mode 100644
index 0000000..87832a8
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-client-3.1.1.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-javadoc.jar b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-javadoc.jar
new file mode 100644
index 0000000..4650349
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-javadoc.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-sources.jar b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-sources.jar
new file mode 100644
index 0000000..b3a8ce4
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1-sources.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1.jar b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1.jar
new file mode 100644
index 0000000..6119617
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-common-3.1.1.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-javadoc.jar b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-javadoc.jar
new file mode 100644
index 0000000..5c2057e
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-javadoc.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-sources.jar b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-sources.jar
new file mode 100644
index 0000000..6332fcd
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1-sources.jar differ
diff --git a/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1.jar b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1.jar
new file mode 100644
index 0000000..1c17bd8
Binary files /dev/null and b/decompilers/d2d_ghidra/lib/xmlrpc-server-3.1.1.jar differ
diff --git a/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraProcessorFactoryFactory.java b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraProcessorFactoryFactory.java
new file mode 100644
index 0000000..e521e0b
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraProcessorFactoryFactory.java
@@ -0,0 +1,27 @@
+package decomp2dbg;
+
+import org.apache.xmlrpc.XmlRpcException;
+import org.apache.xmlrpc.XmlRpcRequest;
+import org.apache.xmlrpc.server.RequestProcessorFactoryFactory;
+
+public class D2DGhidraProcessorFactoryFactory implements RequestProcessorFactoryFactory {
+	private final RequestProcessorFactory factory = new D2DGhidraProcessorFactory();
+	private final D2DGhidraServerAPI api;
+
+	public D2DGhidraProcessorFactoryFactory(D2DGhidraServerAPI api) {
+		this.api = api;
+	}
+
+	public RequestProcessorFactory getRequestProcessorFactory(Class aClass) 
+			throws XmlRpcException {
+		return factory;
+	}
+
+	private class D2DGhidraProcessorFactory implements RequestProcessorFactory {
+		public Object getRequestProcessor(XmlRpcRequest xmlRpcRequest)
+				throws XmlRpcException {
+			return api;
+		}
+	}
+
+}
diff --git a/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServer.java b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServer.java
new file mode 100644
index 0000000..666e61f
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServer.java
@@ -0,0 +1,60 @@
+package decomp2dbg;
+
+import org.apache.xmlrpc.XmlRpcException;
+import org.apache.xmlrpc.server.PropertyHandlerMapping;
+import org.apache.xmlrpc.webserver.WebServer;
+
+
+public class D2DGhidraServer {
+    public D2DPlugin plugin;
+    public D2DGhidraServerAPI api;
+    private WebServer server;
+    public Boolean uiConfiguredCorrectly;
+    
+    public int port;
+    public String host;
+    
+    public D2DGhidraServer(String host, int port, D2DPlugin plugin)
+    {
+        this.server = new WebServer(port);
+    	this.plugin = plugin;
+    	this.uiConfiguredCorrectly = null;
+    	this.port = port;
+    	this.host = host;
+    	
+        PropertyHandlerMapping phm = new PropertyHandlerMapping();
+        api = new D2DGhidraServerAPI(this);
+        phm.setRequestProcessorFactoryFactory(new D2DGhidraProcessorFactoryFactory(api));
+        phm.setVoidMethodEnabled(true);
+        
+        try {
+			phm.addHandler("d2d", D2DGhidraServerAPI.class);
+			this.server.getXmlRpcServer().setHandlerMapping(phm);
+		} catch (XmlRpcException e) {
+    		System.out.println("Error in phm config: " + e);
+			this.server = null;
+		}
+    }
+    
+    public Boolean start_server() {
+    	if(this.server == null) {
+    		return false;
+    	}
+    	
+    	try {
+    		this.server.start();
+    		return true;
+    	} catch (Exception exception){
+    		System.out.println("Error starting Server: " + exception);
+    		return false;
+       }
+    }
+    
+    public Boolean stop_server() {
+    	if(this.server == null)
+    		return false;
+    	
+    	this.server.shutdown();
+    	return true;
+    }
+}
diff --git a/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServerAPI.java b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServerAPI.java
new file mode 100644
index 0000000..6333239
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DGhidraServerAPI.java
@@ -0,0 +1,156 @@
+package decomp2dbg;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import ghidra.app.decompiler.ClangLine;
+import ghidra.app.decompiler.PrettyPrinter;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolType;
+import ghidra.util.Msg;
+
+public class D2DGhidraServerAPI {
+    private D2DGhidraServer server;
+	
+	public D2DGhidraServerAPI(D2DGhidraServer server) {
+		this.server = server;
+	}
+	
+	/*
+	 * Server Manipulation API 
+	 */
+	
+	public Boolean ping() {
+		return true;
+	}
+	
+	public Boolean stop() {
+		this.server.stop_server();
+		return true;
+	}
+	
+	/*
+	 * 
+	 * Decompiler API
+	 *
+	 */
+	
+	public Map<String, Object> decompile(Integer addr) {
+		Map<String, Object> resp = new HashMap<>();
+		resp.put("decompilation", "");
+		resp.put("curr_line", -1);
+		resp.put("func_name", "");
+		
+		var rebasedAddr = this.server.plugin.rebaseAddr(addr, false);
+		var func = this.server.plugin.getNearestFunction(this.server.plugin.rebaseAddr(addr, false));
+		var rebasedAddrLong = rebasedAddr.getOffset();
+		
+		if(func == null) {
+			Msg.warn(server, "Failed to find a function by the address " + addr);
+			return resp;
+		}
+		
+		resp.put("func_name", func.getName());
+		
+		var dec = this.server.plugin.decompileFunc(func);
+		if(dec == null) {
+			Msg.warn(server, "Failed to decompile function by the address " + addr);
+			return resp;
+		}
+	    
+		// create a nice string
+	    var decLines = dec.getDecompiledFunction().getC().split("\n");
+	    resp.put("decompilation", decLines);
+		
+		PrettyPrinter pp = new PrettyPrinter(func, dec.getCCodeMarkup());
+	    ArrayList<ClangLine> lines = pp.getLines();
+	    
+	    // locate the decompilation line
+	    Boolean lineFound = false;
+	    Integer lineNumber = 0;
+	    for (ClangLine line : lines) {
+	    	for (int i = 0; i < line.getNumTokens(); i++) {
+				if (line.getToken(i).getMinAddress() == null) {
+					continue; 
+				}
+				long tokenMinAddr = line.getToken(i).getMinAddress().getOffset();
+				long tokenMaxAddr = line.getToken(i).getMaxAddress().getOffset();
+				if(tokenMinAddr == rebasedAddrLong || tokenMaxAddr == rebasedAddrLong || 
+						(rebasedAddrLong > tokenMinAddr && rebasedAddrLong < tokenMaxAddr)) {
+					lineFound = true;
+					lineNumber = line.getLineNumber();
+					break;
+				}
+	    	}
+	    	
+	    	if(lineFound)
+				break;
+	    }
+	    
+	    // unable to locate the decompilation line
+	    if(!lineFound)
+	    	return resp;
+	    
+	    resp.put("curr_line", lineNumber-1);
+		return resp;
+	}
+	
+	
+	public Map<String, Object> function_data(Integer addr) {
+		var cache = this.server.plugin.funcDataCache.get(addr);
+		if(cache != null)
+			return (Map<String, Object>) cache;
+		
+		var resp = this.server.plugin.getFuncData(addr);
+		this.server.plugin.funcDataCache.put(addr, resp);
+		return resp;
+	}
+
+	public Map<String, Object> function_headers() {
+		// always check cache first
+		if(!this.server.plugin.funcSymCache.isEmpty())
+			return this.server.plugin.funcSymCache;
+		
+		Map<String, Object> resp = new HashMap<>();
+		var program = this.server.plugin.getCurrentProgram();
+		var fm = program.getFunctionManager();
+		var functions = fm.getFunctions(true);
+		for (Function func : functions) {
+			Map<String, Object> funcInfo = new HashMap<>();
+			funcInfo.put("name", func.getName());
+			funcInfo.put("size", (int) func.getBody().getNumAddresses());
+			var rebasedAddr = this.server.plugin.rebaseAddr((int) func.getEntryPoint().getOffset(), true);
+			resp.put("0x" + rebasedAddr.toString(), funcInfo);
+			
+		}
+		
+		this.server.plugin.funcSymCache = resp;
+		return resp;
+	}
+	
+	public Map<String, Object> global_vars() {
+		// check the cache before doing hard work!
+		if(!this.server.plugin.gVarCache.isEmpty())
+			return this.server.plugin.gVarCache;
+		
+		// if we are here, this is first connection!
+		Map<String, Object> resp = new HashMap<>();
+		var symTab = this.server.plugin.getCurrentProgram().getSymbolTable();
+		for (Symbol sym: symTab.getAllSymbols(true)) {
+			if (sym.getSymbolType() != SymbolType.LABEL || !sym.isExternal())
+				continue;
+			
+			Map<String, Object> varInfo = new HashMap<>();
+			varInfo.put("name", sym.getName());
+			var rebasedAddr = this.server.plugin.rebaseAddr((int) sym.getAddress().getOffset(), true);
+			resp.put("0x" + rebasedAddr.toString(), varInfo);
+		}
+		
+		// cache it for next request
+		this.server.plugin.gVarCache = resp;
+		return resp;
+	}
+	
+}
diff --git a/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPlugin.java b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPlugin.java
new file mode 100644
index 0000000..a5c4a17
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPlugin.java
@@ -0,0 +1,335 @@
+package decomp2dbg;
+
+import java.awt.Event;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+
+import docking.ActionContext;
+import docking.action.DockingAction;
+import docking.action.KeyBindingData;
+import docking.action.MenuData;
+import ghidra.app.decompiler.DecompInterface;
+import ghidra.app.decompiler.DecompileOptions;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.ProgramPlugin;
+import ghidra.framework.model.DomainObjectChangeRecord;
+import ghidra.framework.model.DomainObjectChangedEvent;
+import ghidra.framework.model.DomainObjectListener;
+import ghidra.framework.plugintool.PluginInfo;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.program.database.symbol.CodeSymbol;
+import ghidra.program.database.symbol.FunctionSymbol;
+import ghidra.program.database.symbol.VariableSymbolDB;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.util.ChangeManager;
+import ghidra.program.util.ProgramChangeRecord;
+import ghidra.util.Msg;
+import ghidra.util.task.ConsoleTaskMonitor;
+
+@PluginInfo(
+	status = PluginStatus.RELEASED,
+	packageName = D2DPluginPackage.NAME,
+	category = PluginCategoryNames.DEBUGGER,
+	shortDescription = "Syncronize symbols to a debugger",
+	description = "This is the server for the decompiler side plugin of decomp2dbg "
+			+ "which connects the decompiler to a debugger and syncronizes symbols "
+			+ "and decompilation lines. See github.com/mahaloz/decomp2dbg for more info."
+)
+public class D2DPlugin extends ProgramPlugin implements DomainObjectListener {
+	private DockingAction configureD2DAction;
+	private D2DGhidraServer server;
+	public Map<Long, DecompileResults> decompileCache;
+	public Map<String, Object> gVarCache;
+	public Map<String, Object> funcSymCache;
+	public Map<Integer, Object> funcDataCache;
+	
+	public D2DPlugin(PluginTool tool) {
+		super(tool);
+		
+		// Add a d2d button to 'Tools' in GUI menu
+		configureD2DAction = this.createD2DMenuAction();
+		tool.addAction(configureD2DAction);
+		
+		// cache maps
+		decompileCache = new HashMap<>();
+		gVarCache = new HashMap<>();
+		funcSymCache = new HashMap<>();
+		funcDataCache = new HashMap<>();
+	}
+	
+	@Override
+	protected void programActivated(Program program) {
+		program.addListener(this);
+	}
+
+	@Override
+	protected void programDeactivated(Program program) {
+		program.removeListener(this);
+	}
+	
+	private DockingAction createD2DMenuAction() {
+		D2DPlugin plugin = this;
+		configureD2DAction = new DockingAction("decomp2dbg", getName()) {
+			@Override
+			public void actionPerformed(ActionContext context) {
+				plugin.configureD2DServer();
+			}
+		};
+		
+		configureD2DAction.setEnabled(true);
+		configureD2DAction.setMenuBarData(new MenuData(new String[] {"Tools", "Configure decomp2dbg..." }));
+		configureD2DAction.setKeyBindingData(new KeyBindingData(KeyStroke.getKeyStroke('D', Event.CTRL_MASK + Event.SHIFT_MASK)));
+		return configureD2DAction;
+	}
+	
+	private void configureD2DServer() {
+		Msg.info(this, "Configuring decomp2dbg...");
+		JTextField hostField = new JTextField("localhost");
+		JTextField portField = new JTextField("3662");
+		Object[] message = {
+		    "Host: ", hostField,
+		    "Port: ", portField
+		};
+
+		int option = JOptionPane.showConfirmDialog(null, message, "Login", JOptionPane.OK_CANCEL_OPTION);
+		String host = null;
+		int port = 0;
+		if (option == JOptionPane.OK_OPTION) {
+			host = hostField.getText();
+			try {
+				port = Integer.parseInt(portField.getText());
+			} catch(Exception e) {
+				JOptionPane.showMessageDialog(null, "Unable to parse port: " + e.toString());
+				return;
+			}
+		} else 
+			return;
+		
+		this.server = new D2DGhidraServer(host, port, this);
+		
+		try {
+			this.server.start_server();
+		} catch(Exception e) {
+			JOptionPane.showMessageDialog(null, "Encountered error: " + e.toString());
+			return;
+		}
+		
+		JOptionPane.showMessageDialog(null, "Sever configured and running!");
+	}
+	
+	/*
+	 * Decompiler Utils
+	 */
+
+	public Address strToAddr(String addrStr) {
+		return this.getCurrentProgram().getAddressFactory().getAddress(addrStr);
+	}
+	
+	public Address rebaseAddr(Integer addr, Boolean rebaseDown) {
+		var program = this.getCurrentProgram();
+		var base = (int) program.getImageBase().getOffset();
+		Integer rebasedAddr = addr;
+		if(rebaseDown) {
+			rebasedAddr -= base;
+		}
+		else if(addr < base) {
+			rebasedAddr += base;
+		}
+		
+		return this.strToAddr(Integer.toHexString(rebasedAddr));
+	}
+	
+	public Function getNearestFunction(Address addr) {
+		if(addr == null) {
+			Msg.warn(this, "Failed to parse Addr string earlier, got null addr.");
+			return null;
+		}
+		
+		var program = this.getCurrentProgram();
+		var funcManager = program.getFunctionManager();
+		var func =  funcManager.getFunctionContaining(addr);
+		
+		return func;
+	}
+	
+	public DecompileResults decompileFunc(Function func) {
+		var cacheRes = this.decompileCache.get(func.getEntryPoint().getOffset());
+		if(cacheRes != null) 
+			return (DecompileResults) cacheRes; 
+		
+		DecompInterface ifc = new DecompInterface();
+		ifc.setOptions(new DecompileOptions());
+		ifc.openProgram(this.getCurrentProgram());
+		DecompileResults res = ifc.decompileFunction(func, 10, new ConsoleTaskMonitor());
+		
+		// cache it!
+		this.decompileCache.put(func.getEntryPoint().getOffset(), res);
+		return res;
+	}
+	
+	public Map<String, Object> getFuncData(Integer addr) {
+		Map<String, Object> funcInfo = new HashMap<>();
+		funcInfo.put("stack_vars", new HashMap<>());
+		funcInfo.put("reg_vars", new HashMap<>());
+		var func = this.getNearestFunction(this.rebaseAddr(addr, false));
+		if(func == null) {
+			Msg.warn(server, "Failed to find a function by the address " + addr);
+			return funcInfo;
+		}
+		
+		var dec = this.decompileFunc(func);
+		if(dec == null) {
+			Msg.warn(server, "Failed to decompile function by the address " + addr);
+			return funcInfo;
+		}
+		
+		ArrayList<HighSymbol> symbols = new ArrayList<HighSymbol>();
+		Map<String, Object> regVars = new HashMap<>();
+		Map<String, Object> stackVars = new HashMap<>();
+		dec.getHighFunction().getLocalSymbolMap().getSymbols().forEachRemaining(symbols::add);
+		for (HighSymbol sym: symbols) {
+			if(sym.getStorage().isStackStorage()) {
+				Map<String, String> sv = new HashMap<>();
+				sv.put("name", sym.getName());
+				sv.put("type", sym.getDataType().toString());
+				stackVars.put(String.valueOf(sym.getStorage().getStackOffset()), sv);
+			}
+			else if(sym.getStorage().isRegisterStorage()) {
+				Map<String, String> rv = new HashMap<>();
+				rv.put("reg_name", sym.getStorage().getRegister().toString().toLowerCase());
+				rv.put("type", sym.getDataType().toString());
+				regVars.put(sym.getName(), rv);
+			}
+		}
+		funcInfo.put("stack_vars", stackVars);
+		funcInfo.put("reg_vars", regVars);
+		
+		return funcInfo;
+	}
+	
+	/*
+	 * Change Event Handler
+	 */
+	
+	@Override
+	public void domainObjectChanged(DomainObjectChangedEvent ev) {
+		// also look at:
+		// https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java
+		
+		ArrayList<Integer> funcEvents = new ArrayList<>(Arrays.asList(
+			ChangeManager.DOCR_FUNCTION_CHANGED,
+			ChangeManager.DOCR_FUNCTION_BODY_CHANGED,
+			ChangeManager.DOCR_VARIABLE_REFERENCE_ADDED,
+			ChangeManager.DOCR_VARIABLE_REFERENCE_REMOVED
+		));
+
+		ArrayList<Integer> symDelEvents = new ArrayList<>(Arrays.asList(
+			ChangeManager.DOCR_SYMBOL_REMOVED	
+		));
+		
+		ArrayList<Integer> symChgEvents = new ArrayList<>(Arrays.asList(
+			ChangeManager.DOCR_SYMBOL_ADDED,
+			ChangeManager.DOCR_SYMBOL_RENAMED,
+			ChangeManager.DOCR_SYMBOL_DATA_CHANGED
+		));
+		
+		
+		for (DomainObjectChangeRecord record : ev) {
+			// only analyze changes to the current program 
+			if( !(record instanceof ProgramChangeRecord) )
+				continue;
+			
+			int chgType = record.getEventType();
+			var pcr = (ProgramChangeRecord) record;
+			var obj = pcr.getObject();
+			var newVal = pcr.getNewValue();
+			
+			/*
+			 * Function Updates
+			 */
+			if(funcEvents.contains(chgType)) {
+				// use record.getSubEvent() when checking if a FUNCTION_CHANGED
+				// since it will be triggered if the signature of the function changes
+				var funcAddr = pcr.getStart().getOffset();
+				this.funcDataCache.put((int) funcAddr, null);
+				this.decompileCache.put(funcAddr, null);
+			}
+			
+			/*
+			 * Symbol Removed (global variable)
+			 */
+			else if (symDelEvents.contains(chgType)) {
+				var addr = pcr.getStart().getOffset();
+				var rebasedAddrStr = "0x" + this.rebaseAddr((int) addr, true).toString();
+				this.gVarCache.remove(rebasedAddrStr);
+			}
+			
+			/*
+			 * Symbol Updated or Created
+			 */
+			else if (symChgEvents.contains(chgType)) {
+				if (obj == null && newVal != null)
+					obj = newVal;
+				
+				/*
+				 * Stack Variable
+				 */
+				if (obj instanceof VariableSymbolDB) {
+					continue;
+				}
+				
+				/*
+				 * GlobalVar & Label
+				 */
+				else if(obj instanceof CodeSymbol) {
+					if(this.gVarCache.isEmpty())
+						continue;
+					
+					var sym = (CodeSymbol) obj;
+					var newName = sym.getName();
+					var addr = sym.getAddress().getOffset();
+					
+					Map<String, Object> varInfo = new HashMap<>();
+					varInfo.put("name", newName);
+					var rebasedAddr = this.rebaseAddr((int) addr, true);
+					this.gVarCache.put("0x" + rebasedAddr.toString(), varInfo);
+				}
+				
+				/*
+				 * Function Name
+				 */
+				else if(obj instanceof FunctionSymbol) {
+					if(this.funcSymCache.isEmpty())
+						continue;
+					
+					var sym = (FunctionSymbol) obj;
+					var newName = sym.getName();
+					var addr = sym.getAddress().getOffset();
+					
+					var func = this.getCurrentProgram().getFunctionManager().getFunction(addr);
+					Map<String, Object> funcInfo = new HashMap<>();
+					funcInfo.put("name", newName);
+					funcInfo.put("size", (int) func.getBody().getNumAddresses());
+					var rebasedAddr = this.rebaseAddr((int) addr, true);
+					this.funcSymCache.put("0x" + rebasedAddr.toString(), funcInfo);
+				}
+				else
+					continue;
+				
+				this.decompileCache.clear();
+			}
+		}
+	}
+	
+}
diff --git a/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPluginPackage.java b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPluginPackage.java
new file mode 100644
index 0000000..ef4da6e
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/java/decomp2dbg/D2DPluginPackage.java
@@ -0,0 +1,24 @@
+package decomp2dbg;
+
+import javax.swing.Icon;
+
+import ghidra.framework.plugintool.util.PluginPackage;
+import resources.ResourceManager;
+
+/**
+ * The {@link PluginPackage} for the {@value #NAME}
+ */
+public class D2DPluginPackage extends PluginPackage {
+	public static final String NAME = "decomp2dbg decompiler server";
+	public static final String DESCRIPTION = "These plugins are for connecting a debugger to the decompiler syms";
+	
+	/*
+	protected D2DPluginPackage(String name, Icon icon, String description, int priority) {
+		super(NAME, icon, DESCRIPTION, priority);
+	}
+	*/
+	
+	public D2DPluginPackage() {
+		super(NAME, ResourceManager.loadImage("data/d2d.png"), DESCRIPTION, DEVELOPER_PRIORITY);
+	}
+}
\ No newline at end of file
diff --git a/decompilers/d2d_ghidra/src/main/resources/images/README.txt b/decompilers/d2d_ghidra/src/main/resources/images/README.txt
new file mode 100644
index 0000000..f20ae77
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/main/resources/images/README.txt
@@ -0,0 +1,2 @@
+The "src/resources/images" directory is intended to hold all image/icon files used by
+this module.
diff --git a/decompilers/d2d_ghidra/src/main/resources/images/binsync.png b/decompilers/d2d_ghidra/src/main/resources/images/binsync.png
new file mode 100644
index 0000000..58d3603
Binary files /dev/null and b/decompilers/d2d_ghidra/src/main/resources/images/binsync.png differ


Test patch:
diff --git a/decompilers/d2d_ghidra/src/test/java/README.test.txt b/decompilers/d2d_ghidra/src/test/java/README.test.txt
new file mode 100644
index 0000000..11b8a4a
--- /dev/null
+++ b/decompilers/d2d_ghidra/src/test/java/README.test.txt
@@ -0,0 +1,2 @@
+The "test" directory is intended to hold unit test cases.  The package structure within
+this folder should correspond to that found in the "src" folder.