draugr.git


draugr

~ single ELF file analysis tool
git clone https://github.com/djnnvx/draugr.git
log | files | refs | archive | README

bin_diff.odin (5229B)

      1 package analysis
      2 
      3 import "core:fmt"
      4 import "core:os"
      5 import "core:slice"
      6 import "core:strings"
      7 
      8 import "../elf"
      9 
     10 Section_Delta :: struct {
     11 	name  : string,
     12 	a     : u64,
     13 	b     : u64,
     14 	delta : i64,
     15 }
     16 
     17 bin_diff_output :: proc(data: []u8, info: ^elf.ELF_Info, ref_path: string, out_path: string) -> string {
     18 	ref_data, read_err := os.read_entire_file_from_path(ref_path, context.allocator)
     19 	if read_err != nil {
     20 		return fmt.tprintf("failed to read reference file: %s", ref_path)
     21 	}
     22 	defer delete(ref_data)
     23 
     24 	if !elf.probe(ref_data) {
     25 		return fmt.tprintf("reference is not an ELF file: %s", ref_path)
     26 	}
     27 
     28 	ref, parse_err := elf.load(ref_data)
     29 	if parse_err != "" {
     30 		return fmt.tprintf("reference parse error: %s", parse_err)
     31 	}
     32 	defer elf.destroy(&ref)
     33 
     34 	f, err := open_out(out_path)
     35 	if err != "" {
     36 		return err
     37 	}
     38 	defer close_out(f, out_path)
     39 
     40 	fmt.fprintf(f, "Binary Diff\n")
     41 	fmt.fprintf(f, "===========\n\n")
     42 	fmt.fprintf(f, "reference: %s\n\n", ref_path)
     43 
     44 	diff_header(f, info, &ref, len(data), len(ref_data))
     45 	diff_segments(f, info, &ref)
     46 	diff_sections(f, info, &ref)
     47 	diff_needed(f, info, &ref)
     48 
     49 	return ""
     50 }
     51 
     52 diff_header :: proc(f: ^os.File, a: ^elf.ELF_Info, b: ^elf.ELF_Info, a_size: int, b_size: int) {
     53 	fmt.fprintf(f, "Header:\n")
     54 	row_str(f, "class", "ELF64" if a.is_64bit else "ELF32", "ELF64" if b.is_64bit else "ELF32")
     55 	row_str(f, "machine", elf.machine_to_str(a.header.machine), elf.machine_to_str(b.header.machine))
     56 	row_str(f, "type", elf.type_to_str(a.header.type), elf.type_to_str(b.header.type))
     57 	row_hex(f, "entry", a.header.entry, b.header.entry)
     58 	row_num(f, "file size", i64(a_size), i64(b_size))
     59 	fmt.fprintf(f, "\n")
     60 }
     61 
     62 diff_segments :: proc(f: ^os.File, a: ^elf.ELF_Info, b: ^elf.ELF_Info) {
     63 	fmt.fprintf(f, "Segments:\n")
     64 	row_num(f, "count", i64(len(a.program_hdrs)), i64(len(b.program_hdrs)))
     65 
     66 	n := min(len(a.program_hdrs), len(b.program_hdrs))
     67 	for i := 0; i < n; i += 1 {
     68 		pa, pb := a.program_hdrs[i], b.program_hdrs[i]
     69 		if pa.type == pb.type && pa.vaddr == pb.vaddr && pa.filesz == pb.filesz &&
     70 		   pa.memsz == pb.memsz && pa.flags == pb.flags {
     71 			continue
     72 		}
     73 		label := elf.segment_type_str(pa.type)
     74 		if pa.type != pb.type {
     75 			label = fmt.tprintf("%s -> %s", label, elf.segment_type_str(pb.type))
     76 		}
     77 		fmt.fprintf(f, "  ~ [%d] %s%s%s%s\n", i, label,
     78 			delta_hex("vaddr", pa.vaddr, pb.vaddr),
     79 			delta_hex("filesz", pa.filesz, pb.filesz),
     80 			delta_hex("memsz", pa.memsz, pb.memsz))
     81 	}
     82 	fmt.fprintf(f, "\n")
     83 }
     84 
     85 diff_sections :: proc(f: ^os.File, a: ^elf.ELF_Info, b: ^elf.ELF_Info) {
     86 	a_sizes := section_sizes(a)
     87 	defer delete(a_sizes)
     88 	b_sizes := section_sizes(b)
     89 	defer delete(b_sizes)
     90 
     91 	fmt.fprintf(f, "Sections:\n")
     92 
     93 	changed := make([dynamic]Section_Delta)
     94 	defer delete(changed)
     95 
     96 	for name, a_size in a_sizes {
     97 		b_size, in_b := b_sizes[name]
     98 		if !in_b {
     99 			fmt.fprintf(f, "  - %-24s 0x%x (removed)\n", name, a_size)
    100 			continue
    101 		}
    102 		if a_size != b_size {
    103 			append(&changed, Section_Delta{name, a_size, b_size, i64(b_size) - i64(a_size)})
    104 		}
    105 	}
    106 	for name, b_size in b_sizes {
    107 		if _, in_a := a_sizes[name]; !in_a {
    108 			fmt.fprintf(f, "  + %-24s 0x%x (added)\n", name, b_size)
    109 		}
    110 	}
    111 
    112 	slice.sort_by(changed[:], proc(x, y: Section_Delta) -> bool {
    113 		return abs_i64(x.delta) > abs_i64(y.delta)
    114 	})
    115 	for c in changed {
    116 		fmt.fprintf(f, "  ~ %-24s 0x%x -> 0x%x (%+d)\n", c.name, c.a, c.b, c.delta)
    117 	}
    118 
    119 	if len(changed) == 0 && len(a_sizes) == len(b_sizes) {
    120 		fmt.fprintf(f, "  no size changes\n")
    121 	}
    122 	fmt.fprintf(f, "\n")
    123 }
    124 
    125 section_sizes :: proc(info: ^elf.ELF_Info) -> map[string]u64 {
    126 	out := make(map[string]u64)
    127 	for shdr in info.section_hdrs {
    128 		name := elf.get_section_name(info, shdr)
    129 		if name == "" || shdr.type == elf.SHT_NULL {
    130 			continue
    131 		}
    132 		out[name] = shdr.size
    133 	}
    134 	return out
    135 }
    136 
    137 diff_needed :: proc(f: ^os.File, a: ^elf.ELF_Info, b: ^elf.ELF_Info) {
    138 	fmt.fprintf(f, "Dependencies:\n")
    139 	changed := false
    140 	for lib in a.needed {
    141 		if !slice.contains(b.needed[:], lib) {
    142 			fmt.fprintf(f, "  - %s\n", lib)
    143 			changed = true
    144 		}
    145 	}
    146 	for lib in b.needed {
    147 		if !slice.contains(a.needed[:], lib) {
    148 			fmt.fprintf(f, "  + %s\n", lib)
    149 			changed = true
    150 		}
    151 	}
    152 	if a.runpath != b.runpath {
    153 		row_str(f, "runpath", a.runpath, b.runpath)
    154 		changed = true
    155 	}
    156 	if a.rpath != b.rpath {
    157 		row_str(f, "rpath", a.rpath, b.rpath)
    158 		changed = true
    159 	}
    160 	if !changed {
    161 		fmt.fprintf(f, "  unchanged\n")
    162 	}
    163 	fmt.fprintf(f, "\n")
    164 }
    165 
    166 delta_hex :: proc(label: string, a: u64, b: u64) -> string {
    167 	if a == b {
    168 		return ""
    169 	}
    170 	return fmt.tprintf("  %s 0x%x -> 0x%x", label, a, b)
    171 }
    172 
    173 abs_i64 :: proc(v: i64) -> i64 {
    174 	return -v if v < 0 else v
    175 }
    176 
    177 row_str :: proc(f: ^os.File, label: string, a: string, b: string) {
    178 	mark := " " if a == b else "~"
    179 	if a == b {
    180 		fmt.fprintf(f, "  %s %-12s %s\n", mark, label, a)
    181 	} else {
    182 		fmt.fprintf(f, "  %s %-12s %s -> %s\n", mark, label, a, b)
    183 	}
    184 }
    185 
    186 row_hex :: proc(f: ^os.File, label: string, a: u64, b: u64) {
    187 	if a == b {
    188 		fmt.fprintf(f, "    %-12s 0x%x\n", label, a)
    189 	} else {
    190 		fmt.fprintf(f, "  ~ %-12s 0x%x -> 0x%x\n", label, a, b)
    191 	}
    192 }
    193 
    194 row_num :: proc(f: ^os.File, label: string, a: i64, b: i64) {
    195 	if a == b {
    196 		fmt.fprintf(f, "    %-12s %d\n", label, a)
    197 	} else {
    198 		fmt.fprintf(f, "  ~ %-12s %d -> %d (%+d)\n", label, a, b, b - a)
    199 	}
    200 }