Brainfuck compiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gdb-experiment.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // Non-optimizing Brainfuck compiler generating binaries for Linux on x86-64
  2. // with debugging information mapping instructions onto an IR dump.
  3. // gofmt has been tried, with disappointing results.
  4. // codegen{} is also pretty ugly in the way it works but damn convenient.
  5. package main
  6. import (
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "io/ioutil"
  11. "log"
  12. "os"
  13. "strconv"
  14. // Let's not repeat all those constants here onstants
  15. "debug/dwarf"
  16. "debug/elf"
  17. )
  18. const ( RIGHT = iota; LEFT; INC; DEC; IN; OUT; BEGIN; END )
  19. var info = []struct {
  20. grouped bool
  21. name string
  22. }{
  23. {true, "RIGHT"},
  24. {true, "LEFT"},
  25. {true, "INC"},
  26. {true, "DEC"},
  27. {false, "IN"},
  28. {false, "OUT"},
  29. {false, "BEGIN"},
  30. {false, "END"},
  31. }
  32. type instruction struct {
  33. command int
  34. arg int
  35. }
  36. // Dump internal representation to a file for debugging purposes
  37. func dump(filename string, irb []instruction) error {
  38. out, err := os.Create(filename)
  39. if err != nil {
  40. return err
  41. }
  42. indent := 0
  43. for _, x := range irb {
  44. if x.command == END {
  45. indent--
  46. }
  47. for i := 0; i < indent; i++ {
  48. out.WriteString(" ")
  49. }
  50. out.WriteString(info[x.command].name)
  51. if info[x.command].grouped {
  52. fmt.Fprintf(out, " %d", x.arg)
  53. }
  54. out.WriteString("\n")
  55. if x.command == BEGIN {
  56. indent++
  57. }
  58. }
  59. if err = out.Close(); err != nil {
  60. return err
  61. }
  62. return nil
  63. }
  64. // Decode a Brainfuck program into internal representation,
  65. // coalescing identical commands together as the most basic optimization
  66. func decode(program []byte) (irb []instruction) {
  67. for _, c := range program {
  68. var command int
  69. switch c {
  70. case '>': command = RIGHT
  71. case '<': command = LEFT
  72. case '+': command = INC
  73. case '-': command = DEC
  74. case '.': command = OUT
  75. case ',': command = IN
  76. case '[': command = BEGIN
  77. case ']': command = END
  78. default: continue
  79. }
  80. if len(irb) == 0 || !info[command].grouped ||
  81. irb[len(irb)-1].command != command {
  82. irb = append(irb, instruction{command, 1})
  83. } else {
  84. irb[len(irb)-1].arg++
  85. }
  86. }
  87. return
  88. }
  89. // Match loop commands so that we know where to jump
  90. func pairLoops(irb []instruction) error {
  91. nesting := 0
  92. stack := make([]int, len(irb))
  93. for i, x := range irb {
  94. switch x.command {
  95. case BEGIN:
  96. stack[nesting] = i
  97. nesting++
  98. case END:
  99. if nesting <= 0 {
  100. return errors.New("unbalanced loops")
  101. }
  102. nesting--
  103. irb[stack[nesting]].arg = i + 1
  104. irb[i].arg = stack[nesting] + 1
  105. }
  106. }
  107. if nesting != 0 {
  108. return errors.New("unbalanced loops")
  109. }
  110. return nil
  111. }
  112. // --- Code generation ---------------------------------------------------------
  113. type codegen struct {
  114. buf []byte
  115. }
  116. // Convert an arbitrary integral value up to 8 bytes long to little endian
  117. func le(unknown interface{}) []byte {
  118. // Trying hard to avoid reflect.Value.Int/Uint
  119. formatted := fmt.Sprintf("%d", unknown)
  120. b := make([]byte, 8)
  121. if unsigned, err := strconv.ParseUint(formatted, 10, 64); err == nil {
  122. binary.LittleEndian.PutUint64(b, unsigned)
  123. } else if signed, err := strconv.ParseInt(formatted, 10, 64); err == nil {
  124. binary.LittleEndian.PutUint64(b, uint64(signed))
  125. } else {
  126. panic("cannot convert to number")
  127. }
  128. return b
  129. }
  130. func (a *codegen) append(v []byte) { a.buf = append(a.buf, v...) }
  131. func (a *codegen) code(v string) *codegen { a.append([]byte(v)); return a }
  132. func (a *codegen) db(v interface{}) *codegen { a.append(le(v)[:1]); return a }
  133. func (a *codegen) dw(v interface{}) *codegen { a.append(le(v)[:2]); return a }
  134. func (a *codegen) dd(v interface{}) *codegen { a.append(le(v)[:4]); return a }
  135. func (a *codegen) dq(v interface{}) *codegen { a.append(le(v)[:8]); return a }
  136. const (
  137. ElfCodeAddr = 0x400000 // Where the code is loaded in memory
  138. ElfDataAddr = 0x800000 // Where the tape is placed in memory
  139. )
  140. const (
  141. SYS_READ = 0
  142. SYS_WRITE = 1
  143. SYS_EXIT = 60
  144. )
  145. func codegenAmd64(irb []instruction) (code []byte, offsets []int) {
  146. offsets = make([]int, len(irb)+1)
  147. a := codegen{}
  148. a.code("\xB8").dd(ElfDataAddr) // mov rax, "ElfCodeAddr"
  149. a.code("\x30\xDB") // xor bl, bl
  150. for i, x := range irb {
  151. offsets[i] = len(a.buf)
  152. if x.command == LEFT || x.command == RIGHT {
  153. a.code("\x88\x18") // mov [rax], bl
  154. }
  155. switch x.command {
  156. case RIGHT: a.code("\x48\x05").dd(x.arg) // add rax, "arg"
  157. case LEFT: a.code("\x48\x2D").dd(x.arg) // sub rax, "arg"
  158. case INC: a.code("\x80\xC3").db(x.arg) // add bl, "arg"
  159. case DEC: a.code("\x80\xEB").db(x.arg) // sub bl, "arg"
  160. case OUT: a.code("\xE8").dd(0) // call "write"
  161. case IN: a.code("\xE8").dd(0) // call "read"
  162. case BEGIN:
  163. // test bl, bl; jz "offsets[arg]"
  164. a.code("\x84\xDB" + "\x0F\x84").dd(0)
  165. case END:
  166. // test bl, bl; jnz "offsets[arg]"
  167. a.code("\x84\xDB" + "\x0F\x85").dd(0)
  168. }
  169. if x.command == LEFT || x.command == RIGHT {
  170. a.code("\x8A\x18") // mov bl, [rax]
  171. }
  172. }
  173. // When there is a loop at the end we need to be able to jump past it
  174. offsets[len(irb)] = len(a.buf)
  175. // Write an epilog which handles all the OS interfacing
  176. //
  177. // System V x86-64 ABI:
  178. // rax <-> both syscall number and return value
  179. // args -> rdi, rsi, rdx, r10, r8, r9
  180. // trashed <- rcx, r11
  181. a.code("\xB8").dd(SYS_EXIT) // mov eax, 0x3c
  182. a.code("\x48\x31\xFF") // xor rdi, rdi
  183. a.code("\x0F\x05") // syscall
  184. fatal := len(a.buf)
  185. a.code("\x48\x89\xF7") // mov rdi, rsi -- use the string in rsi
  186. a.code("\x30\xC0") // xor al, al -- look for the nil byte
  187. a.code("\x48\x31\xC9") // xor rcx, rcx
  188. a.code("\x48\xF7\xD1") // not rcx -- start from -1
  189. a.code("\xFC" + "\xF2\xAE") // cld; repne scasb -- decrement until found
  190. a.code("\x48\xF7\xD1") // not rcx
  191. a.code("\x48\x8D\x51\xFF") // lea rdx, [rcx-1] -- save length in rdx
  192. a.code("\xB8").dd(SYS_WRITE) // mov eax, "SYS_WRITE"
  193. a.code("\xBF").dd(2) // mov edi, "STDERR_FILENO"
  194. a.code("\x0F\x05") // syscall
  195. a.code("\xB8").dd(SYS_EXIT) // mov eax, "SYS_EXIT"
  196. a.code("\xBF").dd(1) // mov edi, "EXIT_FAILURE"
  197. a.code("\x0F\x05") // syscall
  198. read := len(a.buf)
  199. a.code("\x50") // push rax -- save tape position
  200. a.code("\xB8").dd(SYS_READ) // mov eax, "SYS_READ"
  201. a.code("\x48\x89\xC7") // mov rdi, rax -- STDIN_FILENO
  202. a.code("\x66\x6A\x00") // push word 0 -- the default value for EOF
  203. a.code("\x48\x89\xE6") // mov rsi, rsp -- the char starts at rsp
  204. a.code("\xBA").dd(1) // mov edx, 1 -- count
  205. a.code("\x0F\x05") // syscall
  206. a.code("\x66\x5B") // pop bx
  207. a.code("\x48\x83\xF8\x00") // cmp rax, 0
  208. a.code("\x48\x8D\x35").dd(4) // lea rsi, [rel read_message]
  209. a.code("\x7C") // jl "fatal_offset" -- write failure message
  210. a.db(fatal - len(a.buf) - 1)
  211. a.code("\x58") // pop rax -- restore tape position
  212. a.code("\xC3") // ret
  213. a.code("fatal: read failed\n\x00")
  214. write := len(a.buf)
  215. a.code("\x50") // push rax -- save tape position
  216. a.code("\xB8").dd(SYS_WRITE) // mov eax, "SYS_WRITE"
  217. a.code("\x48\x89\xC7") // mov rdi, rax -- STDOUT_FILENO
  218. a.code("\x66\x53") // push bx
  219. a.code("\x48\x89\xE6") // mov rsi, rsp -- the char starts at rsp
  220. a.code("\xBA").dd(1) // mov edx, 1 -- count
  221. a.code("\x0F\x05") // syscall
  222. a.code("\x66\x5B") // pop bx
  223. a.code("\x48\x83\xF8\x00") // cmp rax, 0
  224. a.code("\x48\x8D\x35").dd(4) // lea rsi, [rel write_message]
  225. a.code("\x7C") // jl "fatal_offset" -- write failure message
  226. a.db(fatal - len(a.buf) - 1)
  227. a.code("\x58") // pop rax -- restore tape position
  228. a.code("\xC3") // ret
  229. a.code("fatal: write failed\n\x00")
  230. // Now that we know where each instruction is, fill in relative jumps
  231. for i, x := range irb {
  232. // This must accurately reflect the code generators
  233. target, fixup := 0, offsets[i]
  234. if x.command == BEGIN || x.command == END {
  235. fixup += 4
  236. target = offsets[x.arg]
  237. } else if x.command == IN {
  238. fixup += 1
  239. target = read
  240. } else if x.command == OUT {
  241. fixup += 1
  242. target = write
  243. } else {
  244. continue
  245. }
  246. copy(a.buf[fixup:], le(target - fixup - 4)[:4])
  247. }
  248. return a.buf, offsets
  249. }
  250. // --- Main --------------------------------------------------------------------
  251. func main() {
  252. var err error
  253. if len(os.Args) > 3 {
  254. log.Fatalf("usage: %s [INPUT-FILE] [OUTPUT-FILE]", os.Args[0])
  255. }
  256. input := os.Stdin
  257. if len(os.Args) > 1 {
  258. if input, err = os.Open(os.Args[1]); err != nil {
  259. log.Fatalf("%s", err)
  260. }
  261. }
  262. outputPath := "a.out"
  263. if len(os.Args) > 2 {
  264. outputPath = os.Args[2]
  265. }
  266. program, err := ioutil.ReadAll(input)
  267. input.Close()
  268. if err != nil {
  269. log.Fatalf("can't read program: %s", err)
  270. }
  271. irb := decode(program)
  272. // ... various optimizations could be performed here if we give up brevity
  273. pairLoops(irb)
  274. dump("ir-dump.txt", irb)
  275. code, offsets := codegenAmd64(irb)
  276. // - - ELF generation - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  277. // Now that we know how long the machine code is, we can write the header.
  278. // Note that for PIE we would need to depend on the dynamic linker, so no.
  279. //
  280. // Recommended reading:
  281. // http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
  282. // man 5 elf
  283. //
  284. // In case of unexpected gdb problems, also see:
  285. // DWARF4.pdf
  286. // https://sourceware.org/elfutils/DwarfLint
  287. // http://wiki.osdev.org/DWARF
  288. const (
  289. ElfHeaderSize = 64 // Size of the ELF header
  290. ElfProgramEntrySize = 56 // Size of a program header
  291. ElfSectionEntrySize = 64 // Size of a section header
  292. )
  293. // - - Program headers - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  294. ph := codegen{}
  295. phCount := 2
  296. codeOffset := ElfHeaderSize + phCount*ElfProgramEntrySize
  297. codeEndOffset := codeOffset + len(code)
  298. // Program header for code
  299. // The entry point address seems to require alignment, so map start of file
  300. ph.dd(elf.PT_LOAD).dd(elf.PF_R | elf.PF_X)
  301. ph.dq(0) // Offset within the file
  302. ph.dq(ElfCodeAddr) // Address in virtual memory
  303. ph.dq(ElfCodeAddr) // Address in physical memory
  304. ph.dq(codeEndOffset) // Length within the file
  305. ph.dq(codeEndOffset) // Length within memory
  306. ph.dq(4096) // Segment alignment
  307. // Program header for the tape
  308. ph.dd(elf.PT_LOAD).dd(elf.PF_R | elf.PF_W)
  309. ph.dq(0) // Offset within the file
  310. ph.dq(ElfDataAddr) // Address in virtual memory
  311. ph.dq(ElfDataAddr) // Address in physical memory
  312. ph.dq(0) // Length within the file
  313. ph.dq(1 << 20) // One megabyte of memory
  314. ph.dq(4096) // Segment alignment
  315. // Now that the rigid part has been generated, we can append sections
  316. pieces := [][]byte{ph.buf, code}
  317. position := codeEndOffset
  318. // - - Sections - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  319. sh := codegen{}
  320. shCount := 0
  321. // This section is created on the go as we need to name other sections
  322. stringTable := codegen{}
  323. // - - Text - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  324. sh.dd(len(stringTable.buf)) // Index for the name of the section
  325. stringTable.code(".text\x00")
  326. sh.dd(elf.SHT_PROGBITS)
  327. sh.dq(elf.SHF_ALLOC | elf.SHF_EXECINSTR)
  328. sh.dq(ElfCodeAddr + codeOffset) // Memory address
  329. sh.dq(codeOffset) // Byte offset
  330. sh.dq(len(code) - codeOffset) // Byte size
  331. sh.dd(0).dd(0) // No link, no info
  332. sh.dq(0).dq(0) // No alignment, no entry size
  333. shCount++
  334. // - - Debug line - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  335. const (
  336. opcodeBase = 13 // Offset by DWARF4 standard opcodes
  337. lineBase = 0 // We don't need negative line indexes
  338. lineRange = 2 // Either we advance a line or not (we always do)
  339. )
  340. // FIXME: we use db() a lot instead of a proper un/signed LEB128 encoder;
  341. // that means that values > 127/63 or < 0 would break it;
  342. // see Appendix C to DWARF4.pdf for an algorithm
  343. lineProgram := codegen{}
  344. // Extended opcode DW_LNE_set_address to reset the PC to the start of code
  345. lineProgram.db(0).db(1 + 8).db(2).dq(ElfCodeAddr + codeOffset)
  346. if len(irb) > 0 {
  347. lineProgram.db(opcodeBase + offsets[0] * lineRange)
  348. }
  349. // The epilog, which is at the very end of the offset array, is included
  350. for i := 1; i <= len(irb); i++ {
  351. size := offsets[i] - offsets[i - 1]
  352. lineProgram.db(opcodeBase + (1 - lineBase) + size * lineRange)
  353. }
  354. // Extended opcode DW_LNE_end_sequence is mandatory at the end
  355. lineProgram.db(0).db(1).db(1)
  356. lineHeader := codegen{}
  357. lineHeader.db(1) // Minimum instruction length
  358. lineHeader.db(1) // Maximum operations per instruction
  359. lineHeader.db(1) // default_is_stmt
  360. lineHeader.db(lineBase)
  361. lineHeader.db(lineRange)
  362. lineHeader.db(opcodeBase)
  363. // Number of operands for all standard opcodes (1..opcodeBase-1)
  364. opcodeLengths := []byte{0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}
  365. lineHeader.buf = append(lineHeader.buf, opcodeLengths...)
  366. // include_directories []string \x00
  367. lineHeader.db(0)
  368. // file_names []struct{base string; dir u8; modified u8; length u8} \x00
  369. lineHeader.code("ir-dump.txt\x00").db(0).db(0).db(0).db(0)
  370. lineEntry := codegen{}
  371. lineEntry.dw(4) // .debug_line version number
  372. lineEntry.dd(len(lineHeader.buf))
  373. lineEntry.buf = append(lineEntry.buf, lineHeader.buf...)
  374. lineEntry.buf = append(lineEntry.buf, lineProgram.buf...)
  375. debugLine := codegen{}
  376. debugLine.dd(len(lineEntry.buf))
  377. debugLine.buf = append(debugLine.buf, lineEntry.buf...)
  378. sh.dd(len(stringTable.buf)) // Index for the name of the section
  379. stringTable.code(".debug_line\x00")
  380. sh.dd(elf.SHT_PROGBITS).dq(0).dq(0) // Type, no flags, no memory address
  381. sh.dq(position) // Byte offset
  382. sh.dq(len(debugLine.buf)) // Byte size
  383. sh.dd(0).dd(0) // No link, no info
  384. sh.dq(0).dq(0) // No alignment, no entry size
  385. shCount++
  386. pieces = append(pieces, debugLine.buf)
  387. position += len(debugLine.buf)
  388. // - - Debug abbreviations - - - - - - - - - - - - - - - - - - - - - - - - - - -
  389. const (
  390. formAddr = 0x01 // Pointer size
  391. formSecOffset = 0x17 // DWARF size
  392. )
  393. debugAbbrev := codegen{}
  394. debugAbbrev.db(1) // Our abbreviation code
  395. debugAbbrev.db(dwarf.TagCompileUnit)
  396. debugAbbrev.db(0) // DW_CHILDREN_no
  397. debugAbbrev.db(dwarf.AttrLowpc).db(formAddr)
  398. debugAbbrev.db(dwarf.AttrHighpc).db(formAddr)
  399. debugAbbrev.db(dwarf.AttrStmtList).db(formSecOffset)
  400. debugAbbrev.db(0).db(0) // End of attributes
  401. debugAbbrev.db(0) // End of abbreviations
  402. sh.dd(len(stringTable.buf)) // Index for the name of the section
  403. stringTable.code(".debug_abbrev\x00")
  404. sh.dd(elf.SHT_PROGBITS).dq(0).dq(0) // Type, no flags, no memory address
  405. sh.dq(position) // Byte offset
  406. sh.dq(len(debugAbbrev.buf)) // Byte size
  407. sh.dd(0).dd(0) // No link, no info
  408. sh.dq(0).dq(0) // No alignment, no entry size
  409. shCount++
  410. pieces = append(pieces, debugAbbrev.buf)
  411. position += len(debugAbbrev.buf)
  412. // - - Debug info - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  413. cuEntry := codegen{}
  414. cuEntry.dw(4) // .debug_info version number
  415. cuEntry.dd(0) // Offset into .debug_abbrev
  416. cuEntry.db(8) // Pointer size
  417. // Single compile unit as per .debug_abbrev
  418. cuEntry.db(1)
  419. cuEntry.dq(ElfCodeAddr + codeOffset)
  420. cuEntry.dq(ElfCodeAddr + codeEndOffset)
  421. cuEntry.dd(0)
  422. debugInfo := codegen{}
  423. debugInfo.dd(len(cuEntry.buf))
  424. debugInfo.buf = append(debugInfo.buf, cuEntry.buf...)
  425. sh.dd(len(stringTable.buf)) // Index for the name of the section
  426. stringTable.code(".debug_info\x00")
  427. sh.dd(elf.SHT_PROGBITS).dq(0).dq(0) // Type, no flags, no memory address
  428. sh.dq(position) // Byte offset
  429. sh.dq(len(debugInfo.buf)) // Byte size
  430. sh.dd(0).dd(0) // No link, no info
  431. sh.dq(0).dq(0) // No alignment, no entry size
  432. shCount++
  433. pieces = append(pieces, debugInfo.buf)
  434. position += len(debugInfo.buf)
  435. // - - Section names and section table - - - - - - - - - - - - - - - - - - - - -
  436. sh.dd(len(stringTable.buf)) // Index for the name of the section
  437. stringTable.code(".shstrtab\x00")
  438. sh.dd(elf.SHT_STRTAB).dq(0).dq(0) // Type, no flags, no memory address
  439. sh.dq(position) // Byte offset
  440. sh.dq(len(stringTable.buf)) // Byte size
  441. sh.dd(0).dd(0) // No link, no info
  442. sh.dq(0).dq(0) // No alignment, no entry size
  443. shCount++
  444. pieces = append(pieces, stringTable.buf)
  445. position += len(stringTable.buf)
  446. pieces = append(pieces, sh.buf)
  447. // Don't increment the position, we want to know where section headers start
  448. // - - Final assembly of parts - - - - - - - - - - - - - - - - - - - - - - - - -
  449. bin := codegen{}
  450. // ELF header
  451. bin.code("\x7FELF\x02\x01\x01") // ELF, 64-bit, little endian, v1
  452. // Unix System V ABI, v0, padding
  453. bin.code("\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00")
  454. bin.dw(elf.ET_EXEC).dw(elf.EM_X86_64).dd(elf.EV_CURRENT)
  455. bin.dq(ElfCodeAddr + codeOffset) // Entry point address
  456. bin.dq(ElfHeaderSize) // Program header offset
  457. bin.dq(position) // Section header offset
  458. bin.dd(0) // No processor-specific flags
  459. bin.dw(ElfHeaderSize) // ELF header size
  460. bin.dw(ElfProgramEntrySize) // Program header table entry size
  461. bin.dw(phCount) // Program header table entry count
  462. bin.dw(ElfSectionEntrySize) // Section header table entry size
  463. bin.dw(shCount) // Section header table entry count
  464. bin.dw(shCount - 1) // Section index for strings
  465. for _, x := range pieces {
  466. bin.buf = append(bin.buf, x...)
  467. }
  468. if err = ioutil.WriteFile(outputPath, bin.buf, 0777); err != nil {
  469. log.Fatalf("%s", err)
  470. }
  471. }