Skip to content

Instantly share code, notes, and snippets.

@nift4
Created August 1, 2025 12:26
Show Gist options
  • Save nift4/f9e411efed17fd431859b154708fda27 to your computer and use it in GitHub Desktop.
Save nift4/f9e411efed17fd431859b154708fda27 to your computer and use it in GitHub Desktop.
import linux kernel headers into ghidra
From 4b5e234b24d85eccc12978bee09b4e71b4974827 Mon Sep 17 00:00:00 2001
From: nift4 <[email protected]>
Date: Sun, 19 Nov 2023 09:56:28 +0100
Subject: [PATCH] fixes for ghidra
---
Makefile | 2 +-
include/linux/percpu-refcount.h | 4 ++--
include/linux/workqueue.h | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile
index fe56427c6a65..c23ee685059c 100644
--- a/Makefile
+++ b/Makefile
@@ -636,7 +636,7 @@ endif
# Defaults to vmlinux, but the arch makefile usually adds further targets
all: vmlinux
-KBUILD_CFLAGS += $(call cc-option,-fno-PIE)
+KBUILD_CFLAGS += $(call cc-option,-fno-PIE) -save-temps=obj
KBUILD_AFLAGS += $(call cc-option,-fno-PIE)
CFLAGS_GCOV := -fprofile-arcs -ftest-coverage -fno-tree-loop-im $(call cc-disable-warning,maybe-uninitialized,)
CFLAGS_KCOV := $(call cc-option,-fsanitize-coverage=trace-pc,)
diff --git a/include/linux/percpu-refcount.h b/include/linux/percpu-refcount.h
index 3a481a49546e..dc24e735d50a 100644
--- a/include/linux/percpu-refcount.h
+++ b/include/linux/percpu-refcount.h
@@ -56,8 +56,8 @@ typedef void (percpu_ref_func_t)(struct percpu_ref *);
/* flags set in the lower bits of percpu_ref->percpu_count_ptr */
enum {
- __PERCPU_REF_ATOMIC = 1LU << 0, /* operating in atomic mode */
- __PERCPU_REF_DEAD = 1LU << 1, /* (being) killed */
+ __PERCPU_REF_ATOMIC = 1UL << 0, /* operating in atomic mode */
+ __PERCPU_REF_DEAD = 1UL << 1, /* (being) killed */
__PERCPU_REF_ATOMIC_DEAD = __PERCPU_REF_ATOMIC | __PERCPU_REF_DEAD,
__PERCPU_REF_FLAG_BITS = 2,
diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
index b95c511139b9..4141c00f2387 100644
--- a/include/linux/workqueue.h
+++ b/include/linux/workqueue.h
@@ -82,7 +82,7 @@ enum {
WORK_OFFQ_POOL_SHIFT = WORK_OFFQ_FLAG_BASE + WORK_OFFQ_FLAG_BITS,
WORK_OFFQ_LEFT = BITS_PER_LONG - WORK_OFFQ_POOL_SHIFT,
WORK_OFFQ_POOL_BITS = WORK_OFFQ_LEFT <= 31 ? WORK_OFFQ_LEFT : 31,
- WORK_OFFQ_POOL_NONE = (1LU << WORK_OFFQ_POOL_BITS) - 1,
+ WORK_OFFQ_POOL_NONE = (1UL << WORK_OFFQ_POOL_BITS) - 1,
/* convenience constants */
WORK_STRUCT_FLAG_MASK = (1UL << WORK_STRUCT_FLAG_BITS) - 1,
--
2.42.0
#!/usr/bin/env kotlin -howtorun .main.kts
@file:Repository("https://repo1.maven.org/maven2/")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta")
@file:DependsOn("org.json:json:20240303")
import kotlinx.coroutines.*
import org.json.JSONObject
import java.io.File
import java.nio.file.LinkOption
import java.nio.file.Path
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.*
val taskCount = 16
val tmpDir = File("/home/nick/tmp")
val ctags = "ctags"
val debug = true
suspend inline fun <A, B> Iterable<A>.forEachParallel(crossinline f: suspend (A) -> B) = coroutineScope {
map { async { f(it) } }.awaitAll()
}
inline fun <A, B> A.mapForEach(collection: Collection<B>, block: A.(B) -> A): A {
var a = this
collection.forEach { a = a.block(it) }
return a
}
val context = Executors.newFixedThreadPool(taskCount).asCoroutineDispatcher()
@OptIn(ExperimentalPathApi::class)
fun main(args: Array<String>) {
println("i2ghidra 1.0.0 by https://github.com/nift4")
runBlocking {
val versionString = listOf(ctags, "--version").runCommand(stderr = true).waitForAndGetStdout()
if (versionString.contains("Universal Ctags 5.")) {
// debian's universal-ctags breaks parsing of gro_receive_t which in turn breaks removal of call_gro_receive
println("Your version of Universal Ctags is too old. There are known bugs that impact this script in " +
"this version. Please strongly consider updating.")
} else if (!versionString.contains("Universal Ctags")) {
println("Please consider using Universal Ctags. This script was only tested with Universal Ctags.")
}
}
if (args.size != 2) {
println("Wrong number of arguments!")
println("Usage:")
println("kotlinc -script i2ghidra.main.kts INPUT_FOLDER OUTPUT_FOLDER")
return
}
val rootTree = Path(args[0]).absolute()
if (!rootTree.exists()) {
println("Input folder ${args[0]} does not exist")
return
}
if (!rootTree.isDirectory() || !rootTree.isReadable()) {
println("Input folder ${args[0]} is not a readable directory")
return
}
val outDir = Path(args[1]).absolute()
outDir.deleteRecursively()
outDir.createDirectories()
if (!outDir.exists()) {
println("Output folder ${args[1]} does not exist and cannot be created")
return
}
if (!outDir.isDirectory() || !outDir.isReadable() || !outDir.isWritable()) {
println("Output folder ${args[1]} is not a readable and writeable directory")
return
}
if (outDir.contains(rootTree)) {
println("Output folder must not contain input folder")
return
}
print("Currently scanning input folder... ")
val files = rootTree.walk().filter {
it.isRegularFile(LinkOption.NOFOLLOW_LINKS) && !outDir.contains(it)
&& it.extension == "i" && it.name.startsWith(".tmp_")
}.toList()
println("Done!")
runBlocking {
val counter = AtomicInteger()
val job = launch(context) {
files.forEachParallel {
val abs = it.absolute()
val fileInOutDir = outDir.resolve(abs.relativeTo(rootTree))
processFile(abs, fileInOutDir.parent.resolve(abs.name.substring(".tmp_".length)))
counter.incrementAndGet()
}
}
val jobCount = "Currently processing ${files.size} files with $taskCount threads... "
val padLen = files.size.toString().length
while (job.isActive) {
print(jobCount + "Completed: ${counter.get().toString().padStart(padLen, '0')}\r")
try {
withTimeout(100L) {
job.join()
}
} catch (_: TimeoutCancellationException) {
}
}
job.join()
println(jobCount + "Done!")
}
println("Done! You can now import the generated .h files into Ghidra.")
}
val commonFixes = listOf(
Pair("extern __typeof__(unsigned long [16384/sizeof(long)]) irq_stack;", "extern unsigned long irq_stack[16384/sizeof(long)];"),
Pair("[] = \"\\\"\" \"i2c-%d #%u a=%03x f=%04x l=%u [%*phD]\" \"\\\", \" \"REC->adapter_nr, REC->msg_nr, REC->addr, REC->flags, REC->len, REC->len, __get_dynamic_array(buf)\";", "[] = \"\\\"i2c-%d #%u a=%03x f=%04x l=%u [%*phD]\\\", REC->adapter_nr, REC->msg_nr, REC->addr, REC->flags, REC->len, REC->len, __get_dynamic_array(buf)\";"),
Pair("[] = \"\\\"\" \"i2c-%d #%u a=%03x f=%04x l=%u\" \"\\\", \" \"REC->adapter_nr, REC->msg_nr, REC->addr, REC->flags, REC->len\";", "[] = \"\\\"i2c-%d #%u a=%03x f=%04x l=%u\\\", REC->adapter_nr, REC->msg_nr, REC->addr, REC->flags, REC->len\";"),
Pair("[] = \"\\\"\" \"i2c-%d n=%u ret=%d\" \"\\\", \" \"REC->adapter_nr, REC->nr_msgs, REC->ret\";", "[] = \"\\\"i2c-%d n=%u ret=%d\\\", REC->adapter_nr, REC->nr_msgs, REC->ret\";"),
Pair("[] = \"\\\"\" \"i2c-%d a=%03x f=%04x c=%x %s l=%u [%*phD]\" \"\\\", \" \"REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" }), REC->len, REC->len, REC->buf\";", "[] = \"\\\"i2c-%d a=%03x f=%04x c=%x %s l=%u [%*phD]\\\", REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" }), REC->len, REC->len, REC->buf\";"),
Pair("[] = \"\\\"\" \"i2c-%d a=%03x f=%04x c=%x %s\" \"\\\", \" \"REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" })\";", "[] = \"\\\"i2c-%d a=%03x f=%04x c=%x %s\\\", REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" })\";"),
Pair("[] = \"\\\"\" \"i2c-%d a=%03x f=%04x c=%x %s %s res=%d\" \"\\\", \" \"REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" }), REC->read_write == 0 ? \\\"wr\\\" : \\\"rd\\\", REC->res\";", "[] = \"\\\"i2c-%d a=%03x f=%04x c=%x %s %s res=%d\\\", REC->adapter_nr, REC->addr, REC->flags, REC->command, __print_symbolic(REC->protocol, { 0, \\\"QUICK\\\" }, { 1, \\\"BYTE\\\" }, { 2, \\\"BYTE_DATA\\\" }, { 3, \\\"WORD_DATA\\\" }, { 4, \\\"PROC_CALL\\\" }, { 5, \\\"BLOCK_DATA\\\" }, { 6, \\\"I2C_BLOCK_BROKEN\\\" }, { 7, \\\"BLOCK_PROC_CALL\\\" }, { 8, \\\"I2C_BLOCK_DATA\\\" }), REC->read_write == 0 ? \\\"wr\\\" : \\\"rd\\\", REC->res\";")
)
val prefix = "#define __typeof__(a) a\n#define _Static_assert(a)\ntypedef unsigned long long __uint128_t;\n"
private suspend fun processFile(file: Path, outFile: Path) {
var text = file.readText(Charsets.UTF_8)
text = removeComments(text)
text = removeAttributes(text) // TODO try using #define instead of this
// If your source is formatted in a way that ctags is unable to properly inspect, please enable one of these proper
// formatting tools and disable the (in comparison very performant) DIY formatter made to fit linux kernel style.
//text = runAstyle(text)
//text = runClangFormat(text)
text = text.replace("{ ", "{\n").replace("} ", "}\n").replace("; ", ";\n")
text = removeStrayCommas(text)
text = runDiyFormatter(text)
text = removeTypeofSelf(text)
if (debug) {
val outFile2 = outFile.resolveSibling(outFile.name + ".DEBUG")
val tw = text
coroutineScope {
launch(Dispatchers.IO) {
outFile2.createParentDirectories()
outFile2.writeText(tw, Charsets.UTF_8)
}
}
}
val tags = runCtags(text)
text = removeMethodBodiesUsingCtags(text, tags)
text = text.mapForEach(commonFixes) { replace(it.first, it.second) }
text = prefix + text
withContext(Dispatchers.IO) {
outFile.createParentDirectories()
outFile.writeText(text, Charsets.UTF_8)
}
}
private fun removeComments(text: String): String {
return text.split('\n').filter { !it.startsWith('#') }.joinToString("\n")
}
val attr = "__attribute__"
val relevantChars = charArrayOf('(', ')')
private fun removeAttributes(text: String): String {
return text.split(';').mapIndexed { linePos, inLine ->
var attrPos = inLine.indexOf(attr)
if (attrPos == -1) return@mapIndexed inLine
var line = inLine
var relevantCharPos = line.indexOfAny(relevantChars)
var pos = relevantCharPos.coerceAtMost(attrPos)
var lastAttrStartPos = -1
var initiallyOpen = 0
var open = 0
while ((lastAttrStartPos != -1 || attrPos != -1) && pos != -1 && pos < line.length) {
val char = line[pos]
if (char == '(') {
open++
pos++
} else if (char == ')') {
open--
if (lastAttrStartPos != -1 && open == initiallyOpen) {
line = line.removeRange(lastAttrStartPos..pos)
// if we remove 2 items, we need to move pos back by 2 to stay at the same character
pos = lastAttrStartPos
attrPos = line.indexOf(attr, startIndex = pos)
relevantCharPos = -1
lastAttrStartPos = -1
} else pos++
} else if (char == '_') {
if (pos == attrPos) {
lastAttrStartPos = pos
pos += attr.length
attrPos = line.indexOf(attr, startIndex = pos)
initiallyOpen = open
} else pos++
} else throw IllegalStateException("internal error in removeAttributes line $linePos, got $char")
if (relevantCharPos <= pos) { // if attrPos was before ( last time, no need to recalculate
relevantCharPos = line.indexOfAny(relevantChars, startIndex = pos)
}
pos = if (attrPos != -1) relevantCharPos.coerceAtMost(attrPos) else relevantCharPos
}
line
}.joinToString(";")
}
private fun List<String>.runCommand(stdout: Boolean = false, stderr: Boolean = false,
workingDir: File = Path("").absolute().toFile()): Process {
return ProcessBuilder(this)
.directory(workingDir)
.redirectOutput(if (stdout) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE)
.redirectError(if (stderr) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE)
.start()
}
private suspend fun Process.waitForAndGetStdout(): String {
val stdout = inputReader().readText()
runInterruptible {
waitFor()
}
if (isAlive) {
destroyForcibly()
}
return stdout
}
private fun mktemp(text: String): File {
return File.createTempFile("tags_", ".h", tmpDir).apply {
deleteOnExit()
writeText(text, charset = Charsets.UTF_8)
}
}
/*
private suspend fun runClangFormat(text: String): String {
val temp = mktemp(text)
return listOf(
"clang-format", "-style={BasedOnStyle: WebKit, " +
"PenaltyReturnTypeOnItsOwnLine: 0, " +
"AlwaysBreakAfterReturnType: None, " +
"AllowShortFunctionsOnASingleLine: None, " +
"AllowShortLambdasOnASingleLine: None, " +
"BreakBeforeBraces: Allman}", temp.absolutePath
).runCommand(stderr = true).waitForAndGetStdout()
}
private suspend fun runAstyle(text: String): String {
return listOf("astyle", "-q").runCommand(stderr = true).apply {
outputWriter().apply {
write(text)
close()
}
}.waitForAndGetStdout()
}
*/
private suspend fun runCtags(text: String): List<JSONObject> {
val temp = mktemp(text)
val tags = listOf(
ctags, "--fields=+ne", "-o", "-", "--sort=no", "--c-types=fp", "--output-format=json",
"--language-force=c", temp.absolutePath
).runCommand(stderr = true).waitForAndGetStdout()
return tags.split('\n').filter { it.isNotBlank() }.map { JSONObject(it.trim()) }.toList()
}
private fun <T> MutableList<T>.rangeReplace(from: Int, to: Int, new: List<T>) {
subList(from, to).apply {
clear()
addAll(new)
}
}
// ctags does not seem to support attributes such as "inline" and return type on another line than the function's name
// this heuristic works around this limitation
private fun runDiyFormatter(text: String): String {
val lines = text.split('\n').toMutableList()
var i = 0
while (i < lines.size) {
val line = lines[i]
if (!line.contains(inlineRegex) || line.contains('{') || line.contains(';')) {
i++
continue
}
val s = i
var newText = lines[i]
i++
while (i < lines.size) {
newText += " " + lines[i]
if (newText.contains('{') || newText.contains(';')) break
i++
}
lines.rangeReplace(s, i + 1, listOf(newText))
i = s + 1
}
return lines.joinToString("\n")
}
val inlineRegex = Regex("\\sinline\\s")
private fun removeMethodBodiesUsingCtags(inText: String, tags: List<JSONObject>): String {
var diff = 0
return LinkedList(inText.split('\n')).mapForEach(tags) {
if (it.has("scope") && it.getString("scope").contains("anon")) return@mapForEach this
val start = (it.getInt("line") - 1) // one line above {
val end = it.getInt("end") // line of }
val pattern = Regex(it.getString("pattern").mapForEach(listOf("*", "{", "(", ")", "[", "]", "+")) {
c -> replace(c, "\\$c") }.run { substring(1, length - 1) },
setOf(RegexOption.MULTILINE, RegexOption.UNIX_LINES))
val method = subList(start - diff, end - diff).joinToString("\n")
val pos = pattern.find(method)?.range
?: throw IllegalStateException("didn't find ctags pattern $pattern in string, why?")
val prefix = method.substring(0, pos.first)
val sp = method.substring(pos.first).split('{', limit = 2)
val header = sp[0]
val isInline = header.contains(inlineRegex)
if (sp.size <= 1 && !isInline) {
return@mapForEach this // not inline, no method body, no need to change anything
}
val new1 = if (isInline) "" /* remove inline method */ else /* remove method body */ "$header;"
val new = (prefix + new1).split("\n")
rangeReplace(start - diff, end - diff, new)
diff += ((end - start) - new.size)
return@mapForEach this
}.joinToString("\n")
}
val strayCommaRegex = Regex(",(\\s*\\})", setOf(RegexOption.MULTILINE, RegexOption.UNIX_LINES))
private fun removeStrayCommas(text: String): String {
return text.replace(strayCommaRegex) { it.groupValues[1] }
}
val typeofSelfRegex = Regex("extern typeof\\((\\S+)\\) \\1;", setOf(RegexOption.CANON_EQ))
private fun removeTypeofSelf(text: String): String {
return text.replace(typeofSelfRegex, "")
}
context.use {
main(args)
}
//TODO write a description for this script
//stolen from reddit, idk where from sorry, but it ain't mine
//@author
//@category _NEW_
//@keybinding
//@menupath
//@toolbar
import ghidra.app.script.GhidraScript;
import ghidra.app.cmd.function.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.util.*;
import ghidra.program.model.reloc.*;
import ghidra.program.model.data.*;
import ghidra.program.model.block.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.scalar.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.address.*;
public class MatchExportsToHeader extends GhidraScript {
public void run() throws Exception {
var dmgr = currentProgram.getDataTypeManager();
var symTable = currentProgram.getSymbolTable();
var iter = dmgr.getAllDataTypes();
while (iter.hasNext()) {
var type = iter.next();
if (!(type instanceof FunctionDefinition))
continue;
// println(type.getName());
var matches = symTable.getGlobalSymbols(type.getName());
println("match found: " + type.getName());
for (var sym : matches) {
ApplyFunctionSignatureCmd cmd = new ApplyFunctionSignatureCmd(sym.getAddress(),
(FunctionDefinition) type, SourceType.IMPORTED);
cmd.applyTo(currentProgram);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment