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;