Skip to content

Commit bf6e22e

Browse files
committed
feat: add header file export
1 parent 2337476 commit bf6e22e

5 files changed

Lines changed: 574 additions & 106 deletions

File tree

ghidra_common.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
Note: This module runs in Ghidra's Jython environment and uses Ghidra's API.
1111
"""
1212

13+
import os
1314
import re
1415

1516
# Ghidra undefined type to standard C type mapping
@@ -443,3 +444,175 @@ def write_file_header(f, module_name, func_count, program_name=None):
443444
f.write("#include <stdint.h>\n")
444445
f.write("#include <stdbool.h>\n")
445446
f.write("#include <stddef.h>\n\n")
447+
448+
449+
# ============================================================
450+
# Header File Generation Functions
451+
# ============================================================
452+
453+
454+
def extract_function_signature(decompiled_code):
455+
"""
456+
Extract function signature from decompiled code.
457+
Returns the normalized function signature string or None if extraction fails.
458+
"""
459+
if not decompiled_code:
460+
return None
461+
462+
# Find the first function definition (before the opening brace)
463+
lines = decompiled_code.strip().split("\n")
464+
signature_lines = []
465+
466+
for line in lines:
467+
if "{" in line:
468+
# Include part before the brace
469+
signature_lines.append(line.split("{")[0].strip())
470+
break
471+
signature_lines.append(line.strip())
472+
473+
if not signature_lines:
474+
return None
475+
476+
signature = " ".join(signature_lines).strip()
477+
478+
# Clean up the signature
479+
signature = re.sub(r"\s+", " ", signature)
480+
481+
# Skip if it looks like a variable declaration or empty
482+
if not signature or signature.endswith(";"):
483+
return None
484+
485+
# Normalize types in the signature
486+
signature = normalize_code_types(signature)
487+
488+
return signature
489+
490+
491+
def generate_header_file(
492+
output_dir, module_name, func_signatures, source_type="decompilation"
493+
):
494+
"""
495+
Generate a header file for a module with function declarations.
496+
497+
Args:
498+
output_dir: Directory to write the header file
499+
module_name: Name of the module
500+
func_signatures: List of (func_name, signature) tuples
501+
source_type: Description of the source (e.g., "ELF decompilation", "library decompilation")
502+
503+
Returns:
504+
Path to the generated header file
505+
"""
506+
safe_name = sanitize_filename(module_name)
507+
header_file = os.path.join(output_dir, "{}.h".format(safe_name))
508+
509+
guard_name = "_{}_H_".format(safe_name.upper())
510+
511+
with open(header_file, "w") as f:
512+
f.write("/**\n")
513+
f.write(" * Header: {}.h\n".format(safe_name))
514+
f.write(" * Module: {}\n".format(module_name))
515+
f.write(" * Functions: {}\n".format(len(func_signatures)))
516+
f.write(" * \n")
517+
f.write(" * Auto-generated by LibSurgeon from {}\n".format(source_type))
518+
f.write(" */\n\n")
519+
520+
f.write("#ifndef {}\n".format(guard_name))
521+
f.write("#define {}\n\n".format(guard_name))
522+
523+
f.write("#include <stdint.h>\n")
524+
f.write("#include <stdbool.h>\n")
525+
f.write("#include <stddef.h>\n")
526+
f.write('#include "_types.h"\n\n')
527+
528+
f.write("#ifdef __cplusplus\n")
529+
f.write('extern "C" {\n')
530+
f.write("#endif\n\n")
531+
532+
# Write function declarations
533+
f.write("/* Function Declarations */\n\n")
534+
for func_name, signature in sorted(func_signatures, key=lambda x: x[0]):
535+
if signature:
536+
f.write("/* {} */\n".format(func_name))
537+
f.write("{};\n\n".format(signature))
538+
539+
f.write("#ifdef __cplusplus\n")
540+
f.write("}\n")
541+
f.write("#endif\n\n")
542+
543+
f.write("#endif /* {} */\n".format(guard_name))
544+
545+
return header_file
546+
547+
548+
def generate_master_header(output_dir, module_names, program_name):
549+
"""
550+
Generate a master header file that includes all module headers.
551+
552+
Args:
553+
output_dir: Directory to write the header file
554+
module_names: Iterable of module names
555+
program_name: Name of the source program
556+
557+
Returns:
558+
Path to the generated master header file
559+
"""
560+
header_file = os.path.join(output_dir, "_all_headers.h")
561+
562+
with open(header_file, "w") as f:
563+
f.write("/**\n")
564+
f.write(" * Master Header File\n")
565+
f.write(" * Source: {}\n".format(program_name))
566+
f.write(" * Modules: {}\n".format(len(list(module_names))))
567+
f.write(" * \n")
568+
f.write(" * Auto-generated by LibSurgeon\n")
569+
f.write(" * Include this file to get all function declarations.\n")
570+
f.write(" */\n\n")
571+
572+
f.write("#ifndef _ALL_HEADERS_H_\n")
573+
f.write("#define _ALL_HEADERS_H_\n\n")
574+
575+
f.write('#include "_types.h"\n\n')
576+
577+
for module_name in sorted(module_names):
578+
safe_name = sanitize_filename(module_name)
579+
f.write('#include "{}.h"\n'.format(safe_name))
580+
581+
f.write("\n#endif /* _ALL_HEADERS_H_ */\n")
582+
583+
return header_file
584+
585+
586+
def generate_types_header(output_dir):
587+
"""
588+
Generate _types.h header with type definitions.
589+
590+
Args:
591+
output_dir: Directory to write the header file
592+
593+
Returns:
594+
Path to the generated types header file
595+
"""
596+
types_file = os.path.join(output_dir, "_types.h")
597+
598+
with open(types_file, "w") as f:
599+
f.write("/**\n")
600+
f.write(" * Type Definitions for Decompiled Code\n")
601+
f.write(" * \n")
602+
f.write(" * This file contains typedef mappings for Ghidra-generated types.\n")
603+
f.write(" * Auto-generated by LibSurgeon\n")
604+
f.write(" */\n\n")
605+
606+
f.write("#ifndef _LIBSURGEON_TYPES_H_\n")
607+
f.write("#define _LIBSURGEON_TYPES_H_\n\n")
608+
609+
f.write("#include <stdint.h>\n")
610+
f.write("#include <stdbool.h>\n\n")
611+
612+
f.write("/* Unknown type definitions (signedness uncertain) */\n")
613+
f.write(UNKNOWN_TYPE_DEFS)
614+
f.write("\n")
615+
616+
f.write("#endif /* _LIBSURGEON_TYPES_H_ */\n")
617+
618+
return types_file

ghidra_decompile_elf.py

Lines changed: 11 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@
5252
demangle_cpp_name,
5353
enhance_decompiled_code,
5454
extract_class_from_method,
55+
extract_function_signature,
5556
extract_namespace,
57+
generate_header_file,
58+
generate_master_header,
59+
generate_types_header,
5660
normalize_code_types,
5761
normalize_ghidra_type,
5862
sanitize_filename,
@@ -495,109 +499,6 @@ def write_file_header(f, module_name, func_count):
495499
f.write('#include "../include/_types.h"\n\n')
496500

497501

498-
def extract_function_signature(decompiled_code):
499-
"""
500-
Extract function signature from decompiled code.
501-
Returns the normalized function signature string or None if extraction fails.
502-
"""
503-
if not decompiled_code:
504-
return None
505-
506-
# Find the first function definition (before the opening brace)
507-
lines = decompiled_code.strip().split("\n")
508-
signature_lines = []
509-
510-
for line in lines:
511-
if "{" in line:
512-
# Include part before the brace
513-
signature_lines.append(line.split("{")[0].strip())
514-
break
515-
signature_lines.append(line.strip())
516-
517-
if not signature_lines:
518-
return None
519-
520-
signature = " ".join(signature_lines).strip()
521-
522-
# Clean up the signature
523-
signature = re.sub(r"\s+", " ", signature)
524-
525-
# Normalize types in the signature (already done in decompiled code, but just in case)
526-
signature = normalize_code_types(signature)
527-
528-
return signature
529-
530-
531-
def generate_header_file(output_dir, module_name, func_signatures):
532-
"""Generate a header file for a module with function declarations."""
533-
safe_name = sanitize_filename(module_name)
534-
header_file = os.path.join(output_dir, "{}.h".format(safe_name))
535-
536-
guard_name = "_{}_H_".format(safe_name.upper())
537-
538-
with open(header_file, "w") as f:
539-
f.write("/**\n")
540-
f.write(" * Header: {}.h\n".format(safe_name))
541-
f.write(" * Module: {}\n".format(module_name))
542-
f.write(" * Functions: {}\n".format(len(func_signatures)))
543-
f.write(" * \n")
544-
f.write(" * Auto-generated by LibSurgeon from ELF decompilation\n")
545-
f.write(" */\n\n")
546-
547-
f.write("#ifndef {}\n".format(guard_name))
548-
f.write("#define {}\n\n".format(guard_name))
549-
550-
f.write("#include <stdint.h>\n")
551-
f.write("#include <stdbool.h>\n")
552-
f.write("#include <stddef.h>\n")
553-
f.write('#include "_types.h"\n\n')
554-
555-
f.write("#ifdef __cplusplus\n")
556-
f.write('extern "C" {\n')
557-
f.write("#endif\n\n")
558-
559-
# Write function declarations
560-
f.write("/* Function Declarations */\n\n")
561-
for func_name, signature in sorted(func_signatures, key=lambda x: x[0]):
562-
if signature:
563-
f.write("/* {} */\n".format(func_name))
564-
f.write("{};\n\n".format(signature))
565-
566-
f.write("#ifdef __cplusplus\n")
567-
f.write("}\n")
568-
f.write("#endif\n\n")
569-
570-
f.write("#endif /* {} */\n".format(guard_name))
571-
572-
return header_file
573-
574-
575-
def generate_master_header(output_dir, module_names, program_name):
576-
"""Generate a master header file that includes all module headers."""
577-
header_file = os.path.join(output_dir, "_all_headers.h")
578-
579-
with open(header_file, "w") as f:
580-
f.write("/**\n")
581-
f.write(" * Master Header File\n")
582-
f.write(" * Source: {}\n".format(program_name))
583-
f.write(" * Modules: {}\n".format(len(module_names)))
584-
f.write(" * \n")
585-
f.write(" * Auto-generated by LibSurgeon from ELF decompilation\n")
586-
f.write(" * Include this file to get all function declarations.\n")
587-
f.write(" */\n\n")
588-
589-
f.write("#ifndef _ALL_HEADERS_H_\n")
590-
f.write("#define _ALL_HEADERS_H_\n\n")
591-
592-
for module_name in sorted(module_names):
593-
safe_name = sanitize_filename(module_name)
594-
f.write('#include "{}.h"\n'.format(safe_name))
595-
596-
f.write("\n#endif /* _ALL_HEADERS_H_ */\n")
597-
598-
return header_file
599-
600-
601502
def format_data_type(dt, indent=0):
602503
"""Format a Ghidra DataType to C code string"""
603504
if dt is None:
@@ -1106,10 +1007,16 @@ def main():
11061007
for module_name in sorted(module_signatures.keys()):
11071008
signatures = module_signatures[module_name]
11081009
if signatures:
1109-
generate_header_file(include_dir, module_name, signatures)
1010+
generate_header_file(
1011+
include_dir, module_name, signatures, "ELF decompilation"
1012+
)
11101013
header_count += 1
11111014
total_signatures += len(signatures)
11121015

1016+
# Generate types header
1017+
if header_count > 0:
1018+
generate_types_header(include_dir)
1019+
11131020
# Generate master header
11141021
if header_count > 0:
11151022
generate_master_header(include_dir, module_signatures.keys(), program_name)

0 commit comments

Comments
 (0)