#!/usr/bin/env python3 """ Improved Binary to ELF Converter Handles platform-specific binary formats for ESP32, Uno, and other platforms. """ import json import subprocess from pathlib import Path def _run_command(command: list[str] | str, show_output: bool = False) -> str: """ Run a command using subprocess and capture the output. Args: command (list or str): Command to run. show_output (bool): Print command and its output if True. Returns: str: Standard output of the command. Raises: RuntimeError: If the command fails. """ if isinstance(command, str): command = [command] if show_output: print(f"Running command: {' '.join(command)}") result = subprocess.run(command, capture_output=True, text=True) if result.returncode != 0: if show_output: print(f"Command failed: {' '.join(command)}") print(f"Error: {result.stderr}") raise RuntimeError(f"Command failed: {' '.join(command)}\n{result.stderr}") if show_output and result.stdout: print(f"Command output: {result.stdout}") return result.stdout def _detect_platform_from_paths(as_path: Path, ld_path: Path): """ Detect the target platform from toolchain paths. Returns: str: Platform name (esp32, avr, arm, etc.) """ path_str = str(as_path).lower() if "xtensa-esp32" in path_str or "esp32" in path_str: return "esp32" elif "avr" in path_str: return "avr" elif "arm" in path_str: return "arm" else: return "unknown" def _analyze_binary_structure(bin_file: Path, platform: str): """ Analyze binary structure based on platform. Args: bin_file (Path): Path to binary file platform (str): Target platform Returns: dict: Binary analysis information """ print(f"Analyzing {platform} binary: {bin_file}") if not bin_file.exists(): raise RuntimeError(f"Binary file not found: {bin_file}") with open(bin_file, "rb") as f: data = f.read() analysis: dict[str, str | int | bytes | dict[str, str | int]] = { "platform": platform, "size": len(data), "header": data[:32] if len(data) >= 32 else data, } if platform == "esp32": # ESP32 binary format analysis print(f"ESP32 binary size: {len(data)} bytes") if len(data) >= 32: # ESP32 image header magic = data[0] segments = data[1] flash_mode = data[2] flash_size_freq = data[3] analysis["esp32"] = { "magic": hex(magic), "segments": segments, "flash_mode": flash_mode, "flash_size_freq": flash_size_freq, } print(f"ESP32 image - Magic: {hex(magic)}, Segments: {segments}") elif platform == "avr": # AVR/Arduino binary format (Intel HEX) print(f"AVR binary size: {len(data)} bytes") if bin_file.suffix.lower() == ".hex": print("Intel HEX format detected") analysis["format"] = "intel_hex" else: analysis["format"] = "binary" # Print hex dump of first 64 bytes for analysis print(f"\nFirst 64 bytes of {platform} binary (hex):") for i in range(0, min(64, len(data)), 16): hex_bytes = " ".join(f"{b:02x}" for b in data[i : i + 16]) ascii_chars = "".join( chr(b) if 32 <= b < 127 else "." for b in data[i : i + 16] ) print(f"{i:08x}: {hex_bytes:<48} {ascii_chars}") return analysis def _generate_platform_linker_script(map_file: Path, platform: str) -> Path: """ Generate a platform-specific linker script. Args: map_file (Path): Path to the map file. platform (str): Target platform. Returns: Path: Path to the generated linker script. """ print(f"Generating {platform} linker script...") if platform == "esp32": # ESP32 memory layout linker_script_content = """ MEMORY { iram0_0_seg : org = 0x40080000, len = 0x20000 dram0_0_seg : org = 0x3FFB0000, len = 0x50000 flash_seg : org = 0x400D0020, len = 0x330000 } SECTIONS { .iram0.text : { *(.iram0.literal .iram0.text) } > iram0_0_seg .flash.text : { *(.literal .text .literal.* .text.*) } > flash_seg .flash.rodata : { *(.rodata .rodata.*) } > flash_seg .dram0.data : { *(.data .data.*) } > dram0_0_seg .dram0.bss : { *(.bss .bss.*) } > dram0_0_seg } """ elif platform == "avr": # AVR memory layout linker_script_content = """ MEMORY { text (rx) : ORIGIN = 0x0000, LENGTH = 0x8000 data (rw!x) : ORIGIN = 0x800100, LENGTH = 0x800 eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 0x400 } SECTIONS { .text : { *(.text) *(.text.*) } > text .data : { *(.data) *(.data.*) } > data .bss : { *(.bss) *(.bss.*) } > data } """ else: # Generic linker script linker_script_content = """ SECTIONS { .text 0x00000000 : { *(.text) *(.text.*) } .data : { *(.data) *(.data.*) } .bss : { *(.bss) *(.bss.*) } } """ linker_script_path = map_file.with_suffix(f".{platform}.ld") linker_script_path.write_text(linker_script_content) print(f"Generated {platform} linker script at: {linker_script_path}") return linker_script_path def _create_platform_object_file(as_path: Path, dummy_obj_path: Path, platform: str): """ Create a platform-specific dummy object file. Args: as_path (Path): Path to the assembler executable. dummy_obj_path (Path): Path to the dummy object file to be created. platform (str): Target platform. """ print(f"Creating {platform} dummy object file...") if platform == "esp32": # ESP32/Xtensa assembly assembly_code = """ .section .text .global _start .type _start, @function _start: nop j _start """ elif platform == "avr": # AVR assembly assembly_code = """ .section .text .global _start _start: nop rjmp _start """ else: # Generic assembly assembly_code = """ .section .text .global _start _start: nop """ asm_file = dummy_obj_path.with_suffix(".s") asm_file.write_text(assembly_code) command = [str(as_path), "-o", str(dummy_obj_path), str(asm_file)] print(f"Creating {platform} dummy object file: {dummy_obj_path}") _run_command(command, show_output=True) # Clean up assembly file if asm_file.exists(): asm_file.unlink() def _create_dummy_elf( ld_path: Path, linker_script: Path, dummy_obj: Path, output_elf: Path, platform: str ): """ Create a dummy ELF file using the specified linker script and dummy object file. Args: ld_path (Path): Path to the ld executable. linker_script (Path): Path to the linker script. dummy_obj (Path): Path to the dummy object file. output_elf (Path): Path to the output ELF file. platform (str): Target platform. """ print(f"Creating {platform} dummy ELF file...") command = [ str(ld_path), str(dummy_obj), "-T", str(linker_script), "-o", str(output_elf), ] print(f"Creating {platform} dummy ELF file: {output_elf}") _run_command(command, show_output=True) def _update_elf_sections( objcopy_path: Path, bin_file: Path, elf_file: Path, platform: str ): """ Update the ELF file sections with binary data (platform-specific). Args: objcopy_path (Path): Path to the objcopy executable. bin_file (Path): Path to the binary file. elf_file (Path): Path to the ELF file. platform (str): Target platform. """ print(f"Updating {platform} ELF sections...") # Platform-specific section names section_mapping = {"esp32": ".flash.text", "avr": ".text", "arm": ".text"} section_name = section_mapping.get(platform, ".text") try: command = [ str(objcopy_path), "--update-section", f"{section_name}={bin_file}", str(elf_file), ] print( f"Updating {platform} ELF file '{elf_file}' section '{section_name}' with binary file '{bin_file}'" ) _run_command(command, show_output=True) print(f"Successfully updated {section_name} section") except RuntimeError as e: print(f"Warning: Could not update {section_name} section: {e}") # Try alternative section names alternative_sections = [".text", ".data", ".rodata"] for alt_section in alternative_sections: if alt_section != section_name: try: command = [ str(objcopy_path), "--update-section", f"{alt_section}={bin_file}", str(elf_file), ] print(f"Trying alternative section: {alt_section}") _run_command(command, show_output=True) print(f"Successfully updated {alt_section} section") break except RuntimeError: continue def bin_to_elf( bin_file: Path, map_file: Path, as_path: Path, ld_path: Path, objcopy_path: Path, output_elf: Path, ): """ Convert a binary file to ELF format with platform detection and analysis. Args: bin_file (Path): Path to the input binary file. map_file (Path): Path to the map file. as_path (Path): Path to the assembler executable. ld_path (Path): Path to the linker executable. objcopy_path (Path): Path to the objcopy executable. output_elf (Path): Path to the output ELF file. Returns: Path: Path to the generated ELF file. """ print("=" * 80) print("IMPROVED BINARY TO ELF CONVERTER") print("=" * 80) # Detect platform from toolchain platform = _detect_platform_from_paths(as_path, ld_path) print(f"Detected platform: {platform}") # Analyze binary structure binary_analysis = _analyze_binary_structure(bin_file, platform) # Generate platform-specific linker script linker_script = _generate_platform_linker_script(map_file, platform) # Create platform-specific dummy object file dummy_obj_path = bin_file.with_name(f"dummy_{platform}.o") _create_platform_object_file(as_path, dummy_obj_path, platform) # Create a dummy ELF file using the generated linker script _create_dummy_elf(ld_path, linker_script, dummy_obj_path, output_elf, platform) # Update the ELF sections with binary data _update_elf_sections(objcopy_path, bin_file, output_elf, platform) # Clean up temporary files if dummy_obj_path.exists(): dummy_obj_path.unlink() print(f"Cleaned up dummy object file: {dummy_obj_path}") if linker_script.exists(): linker_script.unlink() print(f"Cleaned up linker script: {linker_script}") print(f"\nāœ… Successfully created {platform} ELF file: {output_elf}") # Save analysis results analysis_file = output_elf.with_suffix(".analysis.json") with open(analysis_file, "w") as f: json.dump(binary_analysis, f, indent=2, default=str) print(f"šŸ“Š Binary analysis saved to: {analysis_file}") return output_elf # For backward compatibility, keep the old function name def _generate_linker_script(map_file: Path) -> Path: """Legacy function for backward compatibility.""" return _generate_platform_linker_script(map_file, "generic")