Files
2026-02-12 00:45:31 -08:00

427 lines
12 KiB
Python

#!/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")