1 // zem - makes an ELF file - which we can look at with mez
2
3 const std = @import("std");
4
5 const Bytes = struct {
6 mem: [0x2000]u8 = undefined,
7 pos: usize = 0,
8
9 pub fn writeNum(self: *Bytes, num: anytype) void {
10 const T = @TypeOf(num);
11 std.mem.writeIntSliceNative(T, self.mem[self.pos..], num);
12 // std.debug.print("Writing {x} ({s})\n", .{num, @typeName(@TypeOf(num))});
13 self.pos += @sizeOf(T);
14 }
15
16 pub fn writeBytes(self: *Bytes, source_bytes: []const u8) void {
17 // std.debug.print("Writing {d} bytes\n", .{source_bytes.len});
18 for (source_bytes) |b| {
19 // std.debug.print(" 0x{x}: {x}\n", .{self.pos, b});
20 self.mem[self.pos] = b;
21 self.pos += 1;
22 }
23 }
24
25 pub fn padTo(self: *Bytes, addr: usize) void {
26 while (self.pos < addr) {
27 self.mem[self.pos] = 0;
28 self.pos += 1;
29 }
30 }
31
32 pub fn print(self: Bytes) void {
33 for(self.mem[0..self.pos], 0..self.pos) |b,a| {
34 std.debug.print("\x1b[90m{x}:\x1b[39m{x} ", .{a,b});
35 }
36 std.debug.print("\n", .{});
37 }
38
39 pub fn get(self: Bytes) []const u8 {
40 return self.mem[0..self.pos];
41 }
42 };
43
44 pub fn main() !void {
45 // var bytes: [100]u8 = undefined;
46 // var pos: usize = 0;
47
48 var bytes = Bytes{};
49
50 // hard-coding for ease!
51 const segment_count = 2;
52 const program_mem_addr: u32 = 0x8040000;
53 const data_mem_addr: u32 = 0x8041000;
54 const ph_size = 32; // program header size (bytes)
55 const elf_size = 52; // main elf header size (bytes)
56 const total_header = elf_size + (ph_size * segment_count);
57 const entry_addr = program_mem_addr + total_header;
58
59 std.debug.print("total header: {d} bytes\n", .{total_header});
60
61 bytes.writeBytes(&[_]u8{ 0x7F, 'E', 'L', 'F' }); // magic
62 bytes.writeNum(@as(u8, 1)); // 32-bit 0x04
63 bytes.writeNum(@as(u8, 1)); // little endian 0x05
64 bytes.writeNum(@as(u8, 1)); // elf version 0x06
65 bytes.writeNum(@as(u8, 0)); // systemV os abi 0x07
66 bytes.writeNum(@as(u8, 0)); // abi version 0x08
67 bytes.writeBytes(&[_]u8{
68 0, // pad 0x09
69 0, // pad 0x0a
70 0, // pad 0x0b
71 0, // pad 0x0c (7 bytes padding)
72 0, // pad 0x0d
73 0, // pad 0x0e
74 0, // pad 0x0f
75 });
76 bytes.writeNum(@as(u16, 2)); // executable 0x10
77 bytes.writeNum(@as(u16, 3)); // arch intel 80386 0x12
78 bytes.writeNum(@as(u32, 1)); // ELF version 0x14
79 bytes.writeNum(entry_addr); // entry address! 0x18
80
81 // next is elf_size because program headers start right after
82 // main elf header.
83 bytes.writeNum(@as(u32, elf_size)); // e_phoff 0x1C
84
85 // Stuff
86 bytes.writeNum(@as(u32, 0)); // e_shoff ignore! 0x20
87 bytes.writeNum(@as(u32, 0)); // e_flags 0x24
88 bytes.writeNum(@as(u16, elf_size)); // e_hsize 0x28
89
90 // Program header size and count
91 bytes.writeNum(@as(u16, 32)); // ph size (each) 0x2A
92 bytes.writeNum(@as(u16, 2)); // ph count 0x2C
93
94 // More section stuff to ignore:
95 bytes.writeNum(@as(u16, 0)); // sh size 0x2E
96 bytes.writeNum(@as(u16, 0)); // sh count 0x30
97 bytes.writeNum(@as(u16, 0)); // sh str idx 0x32
98
99 // Program Header 1
100 // ================================================
101 bytes.writeNum(@as(u32, 1)); // ptype (1=LOAD)
102 bytes.writeNum(@as(u32, 0)); // file offset to load!
103 bytes.writeNum(@as(u32, program_mem_addr)); // write to mem!
104 bytes.writeNum(@as(u32, program_mem_addr)); // same (physical)
105 bytes.writeNum(@as(u32, 0x1000)); // bytes to write
106 bytes.writeNum(@as(u32, 0x1000)); // memory to alloc
107 bytes.writeNum(@as(u32, 5)); // flags (RX)
108 bytes.writeNum(@as(u32, 0x1000)); // alignment
109
110 // Program Header 2
111 // ================================================
112 bytes.writeNum(@as(u32, 1)); // ptype (1=LOAD)
113 bytes.writeNum(@as(u32, 0x1000)); // file offset to load!
114 bytes.writeNum(@as(u32, data_mem_addr)); // write to mem!
115 bytes.writeNum(@as(u32, data_mem_addr)); // same (physical)
116 bytes.writeNum(@as(u32, 13)); // bytes to write
117 bytes.writeNum(@as(u32, 13)); // memory to alloc
118 bytes.writeNum(@as(u32, 6)); // flags (RW)
119 bytes.writeNum(@as(u32, 0x1000)); // alignment
120
121 // Write program (0x22 bytes)
122 // ================================================
123 bytes.writeBytes(&[_]u8{
124 // comes from hello.asm - then hand-edited address to
125 // point to my data segment: 0x8041000
126 0xba, 0x0d, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x10, 0x04, 0x08,
127 0xbb, 0x01, 0x00, 0x00, 0x00, 0xb8, 0x04, 0x00, 0x00, 0x00,
128 0xcd, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x00,
129 0x00, 0x00, 0xcd, 0x80,
130 });
131
132 // Pad to next 0x1000, then write string data
133 // ================================================
134 bytes.padTo(0x1000);
135 bytes.writeBytes("Hello world.\x0a");
136
137
138 // Print what we're gonna write to compare with mez
139 // LOL - not now that there's 1kb padding!
140 //bytes.print();
141
142 const file = try std.fs.cwd().createFile("foo", .{
143 .mode = 0o777,
144 });
145 defer file.close();
146 _ = try file.write(bytes.get());
147 }