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 }