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 //: Higher levels will be in later layers. Since we can stop at any layer, we
  6 //: can execute levels from bedrock up to any level.
  7 //:
  8 //: All levels will be "pass through by default". Whatever they don't
  9 //: understand they will silently pass through to lower levels.
 10 //:
 11 //: Since raw hex bytes of machine code are always possible to inject, SubX is
 12 //: not a language, and we aren't building a compiler. This is something
 13 //: deliberately leakier. Levels are more for improving auditing, checks and
 14 //: error messages rather than for hiding low-level details.
 15 
 16 //: Translator workflow: read 'source' file. Run a series of transforms on it,
 17 //: each passing through what it doesn't understand. The final program should
 18 //: be just machine code, suitable to write to an ELF binary.
 19 //:
 20 //: Higher levels usually transform code on the basis of metadata.
 21 
 22 :(before "End Main")
 23 if (is_equal(argv[1], "translate")) {
 24   START_TRACING_UNTIL_END_OF_SCOPE;
 25   assert(argc > 3);
 26   program p;
 27   ifstream fin(argv[2]);
 28   if (!fin) {
 29     cerr << "could not open " << argv[2] << '\n';
 30     return 1;
 31   }
 32   parse(fin, p);
 33   if (trace_contains_errors()) return 1;
 34   transform(p);
 35   if (trace_contains_errors()) return 1;
 36   save_elf(p, argv[3]);
 37   if (trace_contains_errors()) unlink(argv[3]);
 38   return 0;
 39 }
 40 
 41 :(code)
 42 // write out a program to a bare-bones ELF file
 43 void save_elf(const program& p, const char* filename) {
 44   ofstream out(filename, ios::binary);
 45   write_elf_header(out, p);
 46   for (size_t i = 0;  i < p.segments.size();  ++i)
 47     write_segment(p.segments.at(i), out);
 48   out.close();
 49 }
 50 
 51 void write_elf_header(ostream& out, const program& p) {
 52   char c = '\0';
 53 #define O(X)  c = (X); out.write(&c, sizeof(c))
 54 // host is required to be little-endian
 55 #define emit(X)  out.write(reinterpret_cast<const char*>(&X), sizeof(X))
 56   //// ehdr
 57   // e_ident
 58   O(0x7f); O(/*E*/0x45); O(/*L*/0x4c); O(/*F*/0x46);
 59     O(0x1);  // 32-bit format
 60     O(0x1);  // little-endian
 61     O(0x1); O(0x0);
 62   for (size_t i = 0;  i < 8;  ++i) { O(0x0); }
 63   // e_type
 64   O(0x02); O(0x00);
 65   // e_machine
 66   O(0x03); O(0x00);
 67   // e_version
 68   O(0x01); O(0x00); O(0x00); O(0x00);
 69   // e_entry
 70   int e_entry = p.segments.at(0).start;  // convention
 71   emit(e_entry);
 72   // e_phoff -- immediately after ELF header
 73   int e_phoff = 0x34;
 74   emit(e_phoff);
 75   // e_shoff; unused
 76   int dummy32 = 0;
 77   emit(dummy32);
 78   // e_flags; unused
 79   emit(dummy32);
 80   // e_ehsize
 81   uint16_t e_ehsize = 0x34;
 82   emit(e_ehsize);
 83   // e_phentsize
 84   uint16_t e_phentsize = 0x20;
 85   emit(e_phentsize);
 86   // e_phnum
 87   uint16_t e_phnum = SIZE(p.segments);
 88   emit(e_phnum);
 89   // e_shentsize
 90   uint16_t dummy16 = 0x0;
 91   emit(dummy16);
 92   // e_shnum
 93   emit(dummy16);
 94   // e_shstrndx
 95   emit(dummy16);
 96 
 97   uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/;
 98   for (int i = 0;  i < SIZE(p.segments);  ++i) {
 99     //// phdr
100     // p_type
101     uint32_t p_type = 0x1;
102     emit(p_type);
103     // p_offset
104     emit(p_offset);
105     // p_vaddr
106     emit(p.segments.at(i).start);
107     // p_paddr
108     emit(p.segments.at(i).start);
109     // p_filesz
110     uint32_t size = size_of(p.segments.at(i));
111     assert(size < SEGMENT_SIZE);
112     emit(size);
113     // p_memsz
114     emit(size);
115     // p_flags
116     uint32_t p_flags = (i == 0) ? /*r-x*/0x5 : /*rw-*/0x6;  // convention: only first segment is code
117     emit(p_flags);
118 
119     // p_align
120     // "As the system creates or augments a process image, it logically copies
121     // a file's segment to a virtual memory segment.  When—and if— the system
122     // physically reads the file depends on the program's execution behavior,
123     // system load, and so on.  A process does not require a physical page
124     // unless it references the logical page during execution, and processes
125     // commonly leave many pages unreferenced. Therefore delaying physical
126     // reads frequently obviates them, improving system performance. To obtain
127     // this efficiency in practice, executable and shared object files must
128     // have segment images whose file offsets and virtual addresses are
129     // congruent, modulo the page size." -- http://refspecs.linuxbase.org/elf/elf.pdf (page 95)
130     uint32_t p_align = 0x1000;  // default page size on linux
131     emit(p_align);
132     if (p_offset % p_align != p.segments.at(i).start % p_align) {
133       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();
134       return;
135     }
136 
137     // prepare for next segment
138     p_offset += size;
139   }
140 #undef O
141 #undef emit
142 }
143 
144 void write_segment(const segment& s, ostream& out) {
145   for (int i = 0;  i < SIZE(s.lines);  ++i) {
146     const vector<word>& w = s.lines.at(i).words;
147     for (int j = 0;  j < SIZE(w);  ++j) {
148       uint8_t x = hex_byte(w.at(j).data);  // we're done with metadata by this point
149       out.write(reinterpret_cast<const char*>(&x), /*sizeof(byte)*/1);
150     }
151   }
152 }
153 
154 uint32_t size_of(const segment& s) {
155   uint32_t sum = 0;
156   for (int i = 0;  i < SIZE(s.lines);  ++i)
157     sum += SIZE(s.lines.at(i).words);
158   return sum;
159 }
160 
161 :(before "End Includes")
162 using std::ios;