https://github.com/akkartik/mu/blob/master/subx/028translate.cc

      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   reset();
     23   // Begin subx translate
     24   program p;
     25   string output_filename;
     26   for (int i = /*skip 'subx translate'*/2;  i < argc;  ++i) {
     27     if (is_equal(argv[i], "-o")) {
     28       ++i;
     29       if (i >= argc) {
     30         print_translate_usage();
     31         cerr << "'-o' must be followed by a filename to write results to\n";
     32         exit(1);
     33       }
     34       output_filename = argv[i];
     35     }
     36     else {
     37       trace(2, "parse") << argv[i] << end();
     38       ifstream fin(argv[i]);
     39       if (!fin) {
     40         cerr << "could not open " << argv[i] << '\n';
     41         return 1;
     42       }
     43       parse(fin, p);
     44       if (trace_contains_errors()) return 1;
     45     }
     46   }
     47   if (p.segments.empty()) {
     48     print_translate_usage();
     49     cerr << "nothing to do; must provide at least one file to read\n";
     50     exit(1);
     51   }
     52   if (output_filename.empty()) {
     53     print_translate_usage();
     54     cerr << "must provide a filename to write to using '-o'\n";
     55     exit(1);
     56   }
     57   trace(2, "transform") << "begin" << end();
     58   transform(p);
     59   if (trace_contains_errors()) return 1;
     60   trace(2, "translate") << "begin" << end();
     61   save_elf(p, output_filename);
     62   if (trace_contains_errors()) {
     63     unlink(output_filename.c_str());
     64     return 1;
     65   }
     66   // End subx translate
     67   return 0;
     68 }
     69
     70 :(code)
     71 void print_translate_usage() {
     72   cerr << "Usage: subx translate file1 file2 ... -o output\n";
     73 }
     74
     75 // write out a program to a bare-bones ELF file
     76 void save_elf(const program& p, const string& filename) {
     77   ofstream out(filename.c_str(), ios::binary);
     78   write_elf_header(out, p);
     79   for (size_t i = 0;  i < p.segments.size();  ++i)
     80     write_segment(p.segments.at(i), out);
     81   out.close();
     82 }
     83
     84 void write_elf_header(ostream& out, const program& p) {
     85   char c = '\0';
     86 #define O(X)  c = (X); out.write(&c, sizeof(c))
     87 // host is required to be little-endian
     88 #define emit(X)  out.write(reinterpret_cast<const char*>(&X), sizeof(X))
     89   //// ehdr
     90   // e_ident
     91   O(0x7f); O(/*E*/0x45); O(/*L*/0x4c); O(/*F*/0x46);
     92     O(0x1);  // 32-bit format
     93     O(0x1);  // little-endian
     94     O(0x1); O(0x0);
     95   for (size_t i = 0;  i < 8;  ++i) { O(0x0); }
     96   // e_type
     97   O(0x02); O(0x00);
     98   // e_machine
     99   O(0x03); O(0x00);
    100   // e_version
    101   O(0x01); O(0x00); O(0x00); O(0x00);
    102   // e_entry
    103   uint32_t e_entry = p.segments.at(0).start;  // convention
    104   // Override e_entry
    105   emit(e_entry);
    106   // e_phoff -- immediately after ELF header
    107   uint32_t e_phoff = 0x34;
    108   emit(e_phoff);
    109   // e_shoff; unused
    110   uint32_t dummy32 = 0;
    111   emit(dummy32);
    112   // e_flags; unused
    113   emit(dummy32);
    114   // e_ehsize
    115   uint16_t e_ehsize = 0x34;
    116   emit(e_ehsize);
    117   // e_phentsize
    118   uint16_t e_phentsize = 0x20;
    119   emit(e_phentsize);
    120   // e_phnum
    121   uint16_t e_phnum = SIZE(p.segments);
    122   emit(e_phnum);
    123   // e_shentsize
    124   uint16_t dummy16 = 0x0;
    125   emit(dummy16);
    126   // e_shnum
    127   emit(dummy16);
    128   // e_shstrndx
    129   emit(dummy16);
    130
    131   uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/;
    132   for (int i = 0;  i < SIZE(p.segments);  ++i) {
    133     //// phdr
    134     // p_type
    135     uint32_t p_type = 0x1;
    136     emit(p_type);
    137     // p_offset
    138     emit(p_offset);
    139     // p_vaddr
    140     uint32_t p_start = p.segments.at(i).start;
    141     emit(p_start);
    142     // p_paddr
    143     emit(p_start);
    144     // p_filesz
    145     uint32_t size = num_words(p.segments.at(i));
    146     assert(p_offset + size < SEGMENT_ALIGNMENT);
    147     emit(size);
    148     // p_memsz
    149     emit(size);
    150     // p_flags
    151     uint32_t p_flags = (i == 0) ? /*r-x*/0x5 : /*rw-*/0x6;  // convention: only first segment is code
    152     emit(p_flags);
    153
    154     // p_align
    155     // "As the system creates or augments a process image, it logically copies
    156     // a file's segment to a virtual memory segment.  When—and if— the system
    157     // physically reads the file depends on the program's execution behavior,
    158     // system load, and so on.  A process does not require a physical page
    159     // unless it references the logical page during execution, and processes
    160     // commonly leave many pages unreferenced. Therefore delaying physical
    161     // reads frequently obviates them, improving system performance. To obtain
    162     // this efficiency in practice, executable and shared object files must
    163     // have segment images whose file offsets and virtual addresses are
    164     // congruent, modulo the page size." -- http://refspecs.linuxbase.org/elf/elf.pdf (page 95)
    165     uint32_t p_align = 0x1000;  // default page size on linux
    166     emit(p_align);
    167     if (p_offset % p_align != p_start % p_align) {
    168       raise << "segment starting at 0x" << HEXWORD << p_start << " is improperly aligned; alignment for p_offset " << p_offset << " should be " << (p_offset % p_align) << " but is " << (p_start % p_align) << '\n' << end();
    169       return;
    170     }
    171
    172     // prepare for next segment
    173     p_offset += size;
    174   }
    175 #undef O
    176 #undef emit
    177 }
    178
    179 void write_segment(const segment& s, ostream& out) {
    180   for (int i = 0;  i < SIZE(s.lines);  ++i) {
    181     const vector<word>& w = s.lines.at(i).words;
    182     for (int j = 0;  j < SIZE(w);  ++j) {
    183       uint8_t x = hex_byte(w.at(j).data);  // we're done with metadata by this point
    184       out.write(reinterpret_cast<const char*>(&x), /*sizeof(byte)*/1);
    185     }
    186   }
    187 }
    188
    189 uint32_t num_words(const segment& s) {
    190   uint32_t sum = 0;
    191   for (int i = 0;  i < SIZE(s.lines);  ++i)
    192     sum += SIZE(s.lines.at(i).words);
    193   return sum;
    194 }
    195
    196 :(before "End Includes")
    197 using std::ios;