1 //: The bedrock level 1 of abstraction is now done, and we're going to start 2 //: building levels above it that make programming in x86 machine code a 3 //: little more ergonomic. 4 //: 5 //: All levels will be "pass through by default". Whatever they don't 6 //: understand they will silently pass through to lower levels. 7 //: 8 //: Since raw hex bytes of machine code are always possible to inject, SubX is 9 //: not a language, and we aren't building a compiler. This is something 10 //: deliberately leakier. Levels are more for improving auditing, checks and 11 //: error messages rather than for hiding low-level details. 12 13 //: Translator workflow: read 'source' file. Run a series of transforms on it, 14 //: each passing through what it doesn't understand. The final program should 15 //: be just machine code, suitable to write to an ELF binary. 16 //: 17 //: Higher levels usually transform code on the basis of metadata. 18 19 :(before "End Main") 20 if (is_equal(argv[1], "translate")) { 21 START_TRACING_UNTIL_END_OF_SCOPE; 22 assert(argc > 3); 23 program p; 24 ifstream fin(argv[2]); 25 if (!fin) { 26 cerr << "could not open " << argv[2] << '\n'; 27 return 1; 28 } 29 parse(fin, p); 30 if (trace_contains_errors()) return 1; 31 transform(p); 32 if (trace_contains_errors()) return 1; 33 save_elf(p, argv[3]); 34 if (trace_contains_errors()) unlink(argv[3]); 35 return 0; 36 } 37 38 //: Ordering transforms is a well-known hard problem when building compilers. 39 //: In our case we also have the additional notion of layers. The ordering of 40 //: layers can have nothing in common with the ordering of transforms when 41 //: SubX is tangled and run. This can be confusing for readers, particularly 42 //: if later layers start inserting transforms at arbitrary points between 43 //: transforms introduced earlier. Over time adding transforms can get harder 44 //: and harder, having to meet the constraints of everything that's come 45 //: before. It's worth thinking about organization up-front so the ordering is 46 //: easy to hold in our heads, and it's obvious where to add a new transform. 47 //: Some constraints: 48 //: 49 //: 1. Layers force us to build SubX bottom-up; since we want to be able to 50 //: build and run SubX after stopping loading at any layer, the overall 51 //: organization has to be to introduce primitives before we start using 52 //: them. 53 //: 54 //: 2. Transforms usually need to be run top-down, converting high-level 55 //: representations to low-level ones so that low-level layers can be 56 //: oblivious to them. 57 //: 58 //: 3. When running we'd often like new representations to be checked before 59 //: they are transformed away. The whole reason for new representations is 60 //: often to add new kinds of automatic checking for our machine code 61 //: programs. 62 //: 63 //: Putting these constraints together, we'll use the following broad 64 //: organization: 65 //: 66 //: a) We'll divide up our transforms into "levels", each level consisting 67 //: of multiple transforms, and dealing in some new set of representational 68 //: ideas. Levels will be added in reverse order to the one their transforms 69 //: will be run in. 70 //: 71 //: To run all transforms: 72 //: Load transforms for level n 73 //: Load transforms for level n-1 74 //: ... 75 //: Load transforms for level 2 76 //: Run code at level 1 77 //: 78 //: b) *Within* a level we'll usually introduce transforms in the order 79 //: they're run in. 80 //: 81 //: To run transforms for level n: 82 //: Perform transform of layer l 83 //: Perform transform of layer l+1 84 //: ... 85 //: 86 //: c) Within a level it's often most natural to introduce a new 87 //: representation by showing how it's transformed to the level below. To 88 //: make such exceptions more obvious checks usually won't be first-class 89 //: transforms; instead code that keeps the program unmodified will run 90 //: within transforms before they mutate the program. 91 //: 92 //: Level l transforms programs 93 //: Level l+1 inserts checks to run *before* the transform of level l runs 94 //: 95 //: This may all seem abstract, but will hopefully make sense over time. The 96 //: goals are basically to always have a working program after any layer, to 97 //: have the order of layers make narrative sense, and to order transforms 98 //: correctly at runtime. 99 :(before "End One-time Setup") 100 // Begin Transforms 101 // End Transforms 102 103 :(code) 104 // write out a program to a bare-bones ELF file 105 void save_elf(const program& p, const char* filename) { 106 ofstream out(filename, ios::binary); 107 write_elf_header(out, p); 108 for (size_t i = 0; i < p.segments.size(); ++i) 109 write_segment(p.segments.at(i), out); 110 out.close(); 111 } 112 113 void write_elf_header(ostream& out, const program& p) { 114 char c = '\0'; 115 #define O(X) c = (X); out.write(&c, sizeof(c)) 116 // host is required to be little-endian 117 #define emit(X) out.write(reinterpret_cast<const char*>(&X), sizeof(X)) 118 //// ehdr 119 // e_ident 120 O(0x7f); O(/*E*/0x45); O(/*L*/0x4c); O(/*F*/0x46); 121 O(0x1); // 32-bit format 122 O(0x1); // little-endian 123 O(0x1); O(0x0); 124 for (size_t i = 0; i < 8; ++i) { O(0x0); } 125 // e_type 126 O(0x02); O(0x00); 127 // e_machine 128 O(0x03); O(0x00); 129 // e_version 130 O(0x01); O(0x00); O(0x00); O(0x00); 131 // e_entry 132 int e_entry = p.segments.at(0).start; // convention 133 emit(e_entry); 134 // e_phoff -- immediately after ELF header 135 int e_phoff = 0x34; 136 emit(e_phoff); 137 // e_shoff; unused 138 int dummy32 = 0; 139 emit(dummy32); 140 // e_flags; unused 141 emit(dummy32); 142 // e_ehsize 143 uint16_t e_ehsize = 0x34; 144 emit(e_ehsize); 145 // e_phentsize 146 uint16_t e_phentsize = 0x20; 147 emit(e_phentsize); 148 // e_phnum 149 uint16_t e_phnum = SIZE(p.segments); 150 emit(e_phnum); 151 // e_shentsize 152 uint16_t dummy16 = 0x0; 153 emit(dummy16); 154 // e_shnum 155 emit(dummy16); 156 // e_shstrndx 157 emit(dummy16); 158 159 uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/; 160 for (int i = 0; i < SIZE(p.segments); ++i) { 161 //// phdr 162 // p_type 163 uint32_t p_type = 0x1; 164 emit(p_type); 165 // p_offset 166 emit(p_offset); 167 // p_vaddr 168 emit(p.segments.at(i).start); 169 // p_paddr 170 emit(p.segments.at(i).start); 171 // p_filesz 172 uint32_t size = size_of(p.segments.at(i)); 173 assert(size < SEGMENT_SIZE); 174 emit(size); 175 // p_memsz 176 emit(size); 177 // p_flags 178 uint32_t p_flags = (i == 0) ? /*r-x*/0x5 : /*rw-*/0x6; // convention: only first segment is code 179 emit(p_flags); 180 181 // p_align 182 // "As the system creates or augments a process image, it logically copies 183 // a file's segment to a virtual memory segment. When—and if— the system 184 // physically reads the file depends on the program's execution behavior, 185 // system load, and so on. A process does not require a physical page 186 // unless it references the logical page during execution, and processes 187 // commonly leave many pages unreferenced. Therefore delaying physical 188 // reads frequently obviates them, improving system performance. To obtain 189 // this efficiency in practice, executable and shared object files must 190 // have segment images whose file offsets and virtual addresses are 191 // congruent, modulo the page size." -- http://refspecs.linuxbase.org/elf/elf.pdf (page 95) 192 uint32_t p_align = 0x1000; // default page size on linux 193 emit(p_align); 194 if (p_offset % p_align != p.segments.at(i).start % p_align) { 195 raise << "segment starting at 0x" << HEXWORD << p.segments.at(i).start << " is improperly aligned; alignment for p_offset " << p_offset << " should be " << (p_offset % p_align) << " but is " << (p.segments.at(i).start % p_align) << '\n' << end(); 196 return; 197 } 198 199 // prepare for next segment 200 p_offset += size; 201 } 202 #undef O 203 #undef emit 204 } 205 206 void write_segment(const segment& s, ostream& out) { 207 for (int i = 0; i < SIZE(s.lines); ++i) { 208 const vector<word>& w = s.lines.at(i).words; 209 for (int j = 0; j < SIZE(w); ++j) { 210 uint8_t x = hex_byte(w.at(j).data); // we're done with metadata by this point 211 out.write(reinterpret_cast<const char*>(&x), /*sizeof(byte)*/1); 212 } 213 } 214 } 215 216 uint32_t size_of(const segment& s) { 217 uint32_t sum = 0; 218 for (int i = 0; i < SIZE(s.lines); ++i) 219 sum += SIZE(s.lines.at(i).words); 220 return sum; 221 } 222 223 :(before "End Includes") 224 using std::ios;