1 const std = @import("std");
2 const stdout = std.io.getStdOut().writer();
3
4 // ANSI colors!
5 const cdone = "\x1b[0m";
6 const red = "\x1b[31m";
7 const green = "\x1b[32m";
8 const yellow = "\x1b[33m";
9 const magenta = "\x1b[35m";
10 const cyan = "\x1b[36m";
11 const gray = "\x1b[37m";
12 const gray2 = "\x1b[90m";
13 const bright_magenta = "\x1b[95m";
14 const bright_yellow = "\x1b[93m";
15 const bright_cyan = "\x1b[96m";
16 const bgmagenta = "\x1b[30;105m";
17
18 // A global slice will "point" to the data buffer
19 // so functions can access it directly.
20 var buffer: []u8 = undefined;
21
22 // Gets a bit of buffer as an integer of given type
23 fn get(addr: usize, T: anytype) T {
24 return std.mem.readIntSliceNative(T, buffer[addr..]);
25 }
26
27 // Verifies that we found what we expected at the given address.
28 fn expect(addr: usize, expected: anytype, desc: []const u8) void {
29 var val = get(addr, @TypeOf(expected));
30
31 if (expected != val) {
32 stdout.print("\n{s}At 0x{x} expected '{x}', but found '{x}' ({s}){s}\n", .{red, addr, expected, val, desc, cdone})
33 catch unreachable;
34 std.os.exit(1);
35 }
36 }
37
38 fn printByte(addr: usize) void {
39 stdout.print("{X:0>2} ", .{buffer[addr]})
40 catch unreachable;
41 }
42
43 fn printByteColor(addr: usize, color: []const u8) void {
44 stdout.print("{s}{X:0>2}{s} ", .{color, buffer[addr], cdone})
45 catch unreachable;
46 }
47
48 fn printBytes(addr: usize, count: usize) void {
49 for (0..count) |i| {
50 printByte(addr+i);
51 }
52 }
53
54 fn printBytesColor(addr: usize, count: usize, color: []const u8) void {
55 for (0..count) |i| {
56 printByteColor(addr+i, color);
57 }
58 }
59
60 fn printCharColor(addr: usize, color: []const u8) void {
61 stdout.print("{s}{c}{s} ", .{color, buffer[addr], cdone})
62 catch unreachable;
63 }
64
65 pub fn main() !void {
66 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
67 defer arena.deinit();
68 const allocator = arena.allocator();
69
70 const args = try std.process.argsAlloc(allocator);
71 if(args.len < 2){
72 std.debug.print("Usage: mez <ELF BINARY>\n", .{});
73 std.os.exit(1);
74 }
75
76 var file = try std.fs.cwd().openFile(args[1], .{});
77 defer file.close();
78
79 const file_size = (try file.stat()).size;
80 buffer = try allocator.alloc(u8, file_size);
81 // var actual_buffer = try allocator.alloc(u8, file_size);
82
83 // Set global slice "pointer"
84 // buffer = actual_buffer;
85
86 try file.reader().readNoEof(buffer);
87
88 // ===========================
89 // || Print Main ELF Header ||
90 // ===========================
91 try stdout.print("+-[ELF Header]------------------------------------------+\n", .{});
92
93 // ROW 1
94 // ===========================
95 try stdout.print("| ", .{});
96
97 printByteColor(0x00, green);
98 expect(0x00, @as(u8, 0x7F), "Magic number 0x7F");
99
100 printCharColor(0x01, green);
101 expect(0x01, @as(u8, 'E'), "Magic number 'E'");
102
103 printCharColor(0x02, green);
104 expect(0x02, @as(u8, 'L'), "Magic number 'L'");
105
106 printCharColor(0x03, green);
107 expect(0x03, @as(u8, 'F'), "Magic number 'F'");
108
109 printByte(0x04);
110 expect(0x04, @as(u8, 1), "1=32 bit arch");
111
112 printByte(0x05);
113 expect(0x05, @as(u8, 1), "1=little endian");
114
115 printByte(0x06);
116 expect(0x06, @as(u8, 1), "1=ELF version is current");
117
118 printByte(0x07);
119 expect(0x07, @as(u8, 0), "0=System V ABI");
120
121 inline for(0x08..0x10) |addr| {
122 // print padding zeroes as gray to de-emph
123 printByteColor(addr, gray);
124 expect(addr, @as(u8, 0), "Padding 0s");
125 }
126
127 printBytes(0x10, 2);
128 expect(0x10, @as(u16, 2), "2=Executable file");
129 try stdout.print("|\n", .{});
130
131 // ROW 2
132 // ===========================
133 try stdout.print("| ", .{});
134
135 printBytes(0x12, 2);
136 expect(0x12, @as(u16, 3), "3=x386 ISA (arch)");
137
138 printBytes(0x14, 4);
139 expect(0x14, @as(u32, 1), "1=ELF version again");
140
141 // e_entry - entry address (in memory)
142 printBytesColor(0x18, 4, bright_magenta);
143 const e_entry = get(0x18, u32);
144
145 // e_phoff - program header offset (in file)
146 printBytesColor(0x1C, 4, bright_yellow);
147 const e_phoff = get(0x1C, u32);
148
149 // e_shoff - section header offset (in file), ignore
150 printBytesColor(0x20, 4, gray);
151 try stdout.print("|\n", .{});
152
153 // ROW 3
154 // ===========================
155 try stdout.print("| ", .{});
156
157 // e_flags - totally ignoring
158 printBytesColor(0x24, 4, gray);
159
160 // e_hsize - don't need to display, but will check
161 printBytes(0x28, 2);
162 expect(0x28, @as(u16, 52), "header size should be 52 bytes (0x34) for 32-bit");
163
164 // e_phentsize - the size of program header entries
165 printBytesColor(0x2A, 2, bright_yellow);
166 expect(0x2A, @as(u16, 32), "expecting program headers to be 32 bytes (0x20)");
167 const e_phentsize = 32;
168
169 // e_phnum - the count of program header entries
170 printBytesColor(0x2C, 2, bright_yellow);
171 const e_phnum = get(0x2C, u16);
172
173 // e_shentsize - section header size, ignoring
174 printBytesColor(0x2E, 2, gray);
175
176 // e_shnum - section header count, ignoring
177 printBytesColor(0x30, 2, gray);
178
179 // e_shstrndx - section header string table offset, ignoring
180 printBytesColor(0x32, 2, gray);
181 try stdout.print("/-----+\n", .{});
182 try stdout.print("+--+---------------------------------------------/\n", .{});
183
184 // Main ELF Header Summary (decoded)
185 // =================================
186 try stdout.print(" +-- Entry point address: {s}0x{X:0>8}{s}\n", .{bright_magenta, e_entry, cdone});
187 try stdout.print(" +-- Program header file offset: {s}0x{X}{s}\n", .{bright_yellow, e_phoff, cdone});
188 try stdout.print(" +-- Program header size: {s}{d} (0x{x}){s}\n", .{bright_yellow, e_phentsize, e_phentsize, cdone});
189 try stdout.print(" \\-- Program header count: {s}{d}{s}\n", .{bright_yellow, e_phnum, cdone});
190 try stdout.print(" |\n", .{});
191 for (0..e_phnum) |ph_num| {
192 const ph_offset: usize = e_phoff + (ph_num * e_phentsize);
193 const ph_type = switch(get(ph_offset, u32)){
194 0 => cyan ++ "PT_NULL" ++ cdone,
195 1 => bright_cyan ++ "PT_LOAD" ++ cdone,
196 2 => cyan ++ "PT_DYNAMIC" ++ cdone,
197 3 => cyan ++ "PT_INTERP" ++ cdone,
198 else => red ++ "PT_WTF (a surprise)" ++ cdone,
199 };
200
201 try stdout.print(" +-- Program Header {d} at 0x{x}, type: {s}\n", .{ph_num, ph_offset,ph_type});
202 }
203
204 // =========================================
205 // || Print all LOAD-type program headers ||
206 // =========================================
207 for (0..e_phnum) |ph_num| {
208 const ph_offset: usize = e_phoff + (ph_num * e_phentsize);
209 if (get(ph_offset, u32) != 1) {
210 continue;
211 }
212
213 // Program Header Row 1
214 // =========================
215 try stdout.print("\n+-[{s}Program Header {d}{s}]------------------------------+\n", .{bright_yellow, ph_num, cdone});
216 try stdout.print("| ", .{});
217
218 // p_type (decoded in summary above)
219 printBytesColor(ph_offset, 4, bright_cyan);
220
221 // p_offset - file offset of data image to load
222 printBytesColor(ph_offset + 0x04, 4, yellow);
223 const p_offset = get(ph_offset + 0x04, u32);
224
225 // p_vaddr - memory segment start address
226 printBytesColor(ph_offset + 0x08, 4, magenta);
227 const p_vaddr = get(ph_offset + 0x08, u32);
228
229 // p_paddr - physical address, ignore
230 printBytesColor(ph_offset + 0x0C, 4, gray);
231 try stdout.print("|\n", .{});
232
233 // Program Header Row 2
234 // =========================
235 try stdout.print("| ", .{});
236
237 // p_filesz - data image size to load from file
238 printBytesColor(ph_offset + 0x10, 4, yellow);
239 const p_filesz = get(ph_offset + 0x10, u32);
240
241 // p_memsz - memory segment size
242 printBytesColor(ph_offset + 0x14, 4, magenta);
243 const p_memsz = get(ph_offset + 0x14, u32);
244
245 const p_vaddr_end = p_vaddr + p_memsz;
246
247 // p_flags - memory segment flags
248 printBytesColor(ph_offset + 0x18, 4, magenta);
249 const p_flags = get(ph_offset + 0x18, u32);
250
251 const rwx_decode = switch(p_flags) {
252 1 => "X",
253 2 => "W",
254 3 => "W+X",
255 4 => "R",
256 5 => "R+X",
257 6 => "R+W",
258 7 => "R+W+X",
259 else => "WTF",
260 };
261
262 // p_align - memory segment alignment
263 printBytesColor(ph_offset + 0x1C, 4, gray);
264 try stdout.print("|\n", .{});
265
266 // Program Header Summary (decoded)
267 // ================================
268 try stdout.print("+--+----------------------------------------------+\n", .{});
269 try stdout.print(" +-- File data start offset: {s}0x{x}{s}\n", .{yellow, p_offset, cdone});
270 try stdout.print(" +-- File data bytes to load: {s}{d} (0x{x}){s}\n", .{yellow, p_filesz, p_filesz, cdone});
271 try stdout.print(" +-- Memory segment start addr: {s}0x{x:0>8}{s}\n", .{magenta, p_vaddr, cdone});
272 try stdout.print(" +-- Memory segment byte size: {s}{d} (0x{x}){s}\n", .{magenta, p_memsz, p_memsz, cdone});
273 try stdout.print(" +-- Memory segment flags: {s}{s} (0x{x}){s}\n", .{magenta, rwx_decode, p_flags, cdone});
274
275 // If we contain the entry point address, display it!
276 var need_to_print_entry = (e_entry >= p_vaddr and e_entry < p_vaddr_end);
277 if (need_to_print_entry) {
278 try stdout.print(" +-- {s}Contains entry point 0x{X:0>8}{s}\n", .{bright_magenta, e_entry, cdone});
279 }
280
281 // Hex Dump data!
282 // ================================
283 const dump_lines = 4;
284 const dump_columns = 12;
285 var data_start = p_offset;
286 var data_end = p_offset + p_filesz;
287 var mem_start = p_vaddr;
288 var line: usize = 0;
289 try stdout.print("from {x} to {x}...\n", .{mem_start, mem_start+p_filesz});
290
291 while (line < dump_lines) {
292
293 var mybyte = data_start + (line * dump_columns);
294 var myaddr = mem_start + (line * dump_columns);
295
296 // We've printed it all!
297 if (mybyte >= data_end) {
298 break;
299 }
300
301 // Memory address at start of line
302 try stdout.print("0x{x:0>8} ", .{myaddr});
303
304 // Print columns of bytes
305 try stdout.print("{s}", .{gray});
306 for (0..dump_columns) |i| {
307 if (mybyte + i >= data_end) {
308 // Print spaces to complete row
309 try stdout.print(" ", .{});
310 continue;
311 }
312
313 if(myaddr + i == e_entry) {
314 try stdout.print("{s}{x:0>2}{s} ", .{bgmagenta, buffer[mybyte + i], cdone});
315 need_to_print_entry = false;
316 continue;
317 }
318
319 try stdout.print("{x:0>2} ", .{buffer[mybyte + i]});
320 }
321 try stdout.print("{s}", .{cdone});
322
323 // Print columns of ASCII
324 for (0..dump_columns) |i| {
325 if (mybyte + i >= data_end) {
326 break;
327 }
328
329 const m = buffer[mybyte + i];
330
331 if(m > 32 and m < 127){
332 try stdout.print("{s}{c}{s}", .{cyan, m, cdone});
333 }
334 else {
335 try stdout.print(".", .{});
336 }
337 }
338
339 line += 1;
340
341 // If that was the last line and we need to print
342 // the entry address, reset lines and set the start
343 // address so it'll start with the entry's line.
344 if (line == dump_lines and need_to_print_entry){
345 var skip = e_entry - p_vaddr;
346 skip -= skip % dump_columns;
347 try stdout.print("\n ...Skipping to entry point...", .{});
348 data_start = p_offset + skip;
349 mem_start = p_vaddr + skip;
350 line = 0;
351 }
352
353 try stdout.print("\n", .{});
354 }
355
356 // Did we print it all? If not, show how much 'til end
357 const displayed_to = data_start + dump_lines * dump_columns;
358 if (displayed_to < data_end) {
359 try stdout.print(" ...{d} more bytes to load...\n", .{data_end - displayed_to});
360 }
361 }
362 }