1 # Assign addresses (co-ordinates) to instructions (landmarks) in a program 2 # (landscape). 3 # Use the addresses assigned to: 4 # a) replace labels 5 # b) add segment headers with addresses and offsets correctly filled in 6 # 7 # To build: 8 # $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/survey.subx -o apps/survey 9 # 10 # The expected input is a stream of bytes with segment headers, comments and 11 # some interspersed labels. 12 # $ cat x 13 # == code 0x1 14 # l1: 15 # aa bb l1/imm8 16 # cc dd l2/disp32 17 # l2: 18 # ee foo/imm32 19 # == data 0x10 20 # foo: 21 # 00 22 # 23 # The output is the stream of bytes without segment headers or label definitions, 24 # and with label references replaced with numeric values/displacements. 25 # 26 # $ cat x |./subx run apps/survey 27 # ...ELF header bytes... 28 # # ELF header above will specify that code segment begins at this offset 29 # aa bb nn # some computed address 30 # cc dd nn nn nn nn # some computed displacement 31 # ee nn nn nn nn # some computed address 32 # # ELF header above will specify that data segment begins at this offset 33 # 00 34 # 35 # The ELF format has some persnickety constraints on the starting addresses of 36 # segments, so input headers are treated as guidelines and adjusted in the 37 # output. 38 39 == code 40 # instruction effective address register displacement immediate 41 # . op subop mod rm32 base index scale r32 42 # . 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes 43 44 Entry: # run tests if necessary, convert stdin if not 45 # . prologue 46 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 47 48 # Heap = new-segment(Heap-size) 49 # . . push args 50 68/push Heap/imm32 51 ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Heap-size/disp32 # push *Heap-size 52 # . . call 53 e8/call new-segment/disp32 54 # . . discard args 55 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 56 # initialize-trace-stream(Trace-size) 57 # . . push args 58 ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Trace-size/disp32 # push *Heap-size 59 # . . call 60 e8/call initialize-trace-stream/disp32 61 # . . discard args 62 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 63 64 # - if argc > 1 and argv[1] == "test", then return run_tests() 65 # if (argc <= 1) goto interactive 66 81 7/subop/compare 1/mod/*+disp8 5/rm32/ebp . . . . 0/disp8 1/imm32 # compare *ebp 67 7e/jump-if-lesser-or-equal $subx-survey-main:interactive/disp8 68 # if (!kernel-string-equal?(argv[1], "test")) goto interactive 69 # . eax = kernel-string-equal?(argv[1], "test") 70 # . . push args 71 68/push "test"/imm32 72 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8) 73 # . . call 74 e8/call kernel-string-equal?/disp32 75 # . . discard args 76 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 77 # . if (eax == false) goto interactive 78 3d/compare-eax-and 0/imm32/false 79 74/jump-if-equal $subx-survey-main:interactive/disp8 80 # run-tests() 81 e8/call run-tests/disp32 82 # syscall(exit, *Num-test-failures) 83 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/ebx Num-test-failures/disp32 # copy *Num-test-failures to ebx 84 eb/jump $subx-survey-main:end/disp8 85 $subx-survey-main:interactive: 86 # - otherwise convert stdin 87 # subx-survey(Stdin, Stdout) 88 # . . push args 89 68/push Stdout/imm32 90 68/push Stdin/imm32 91 # . . call 92 e8/call subx-survey/disp32 93 # . . discard args 94 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 95 #? # . write-stream(2/stderr, Trace-stream) 96 #? # . . push args 97 #? ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Trace-stream/disp32 # push *Trace-stream 98 #? 68/push 2/imm32/stderr 99 #? # . . call 100 #? e8/call write-stream/disp32 101 #? # . . discard args 102 #? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 103 # syscall(exit, 0) 104 bb/copy-to-ebx 0/imm32 105 $subx-survey-main:end: 106 b8/copy-to-eax 1/imm32/exit 107 cd/syscall 0x80/imm8 108 109 # data structures: 110 # segment-info: {addr, file-offset, size} (12 bytes) 111 # segments: (addr stream {string, segment-info}) (16 bytes per row) 112 # label-info: {segment-name, segment-offset, addr} (12 bytes) 113 # labels: (addr stream {string, label-info}) (16 bytes per row) 114 # these are all inefficient; use sequential scans for lookups 115 116 subx-survey: # infile : (addr buffered-file), out : (addr buffered-file) 117 # pseudocode 118 # var in : (ref stream byte 4096) 119 # slurp(infile, in) 120 # var segments : (stream segment-info) 121 # var labels : (stream label-info Max-labels) 122 # compute-offsets(in, segments, labels) 123 # compute-addresses(segments, labels) 124 # rewind-stream(in) 125 # emit-output(in, out, segments, labels) 126 # 127 # . prologue 128 55/push-ebp 129 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 130 # . save registers 131 51/push-ecx 132 52/push-edx 133 56/push-esi 134 # var segments/ecx : (ref stream {string, segment-info} 160) # 10 rows * 16 bytes/row 135 81 5/subop/subtract 3/mod/direct 4/rm32/esp . . . . . 0xa0/imm32 # subtract from esp 136 68/push 0xa0/imm32/length 137 68/push 0/imm32/read 138 68/push 0/imm32/write 139 89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx 140 # var labels/edx : (ref stream label-info Max-labels*16) 141 # . data 142 2b/subtract 0/mod/indirect 5/rm32/.disp32 . . 4/r32/esp Max-labels/disp32 # subtract *Max-labels from esp 143 # . length 144 ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Max-labels/disp32 # push *Max-labels 145 # . read 146 68/push 0/imm32/read 147 # . write 148 68/push 0/imm32/write 149 89/copy 3/mod/direct 2/rm32/edx . . . 4/r32/esp . . # copy esp to edx 150 # var in/esi : (ref stream byte Input-size) 151 # . data 152 2b/subtract 0/mod/indirect 5/rm32/.disp32 . . 4/r32/esp Input-size/disp32 # subtract *Input-size from esp 153 # . length 154 ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Input-size/disp32 # push *Input-size 155 # . read 156 68/push 0/imm32/read 157 # . write 158 68/push 0/imm32/write 159 89/copy 3/mod/direct 6/rm32/esi . . . 4/r32/esp . . # copy esp to esi 160 +-- 41 lines: #? # dump labels->write -------------------------------------------------------------------------------------------------------------------- 201 +-- 9 lines: #? # write(2/stderr, "slurp in\n") --------------------------------------------------------------------------------------------------------- 210 # slurp(infile, in) 211 # . . push args 212 56/push-esi 213 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8) 214 # . . call 215 e8/call slurp/disp32 216 # . . discard args 217 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 218 +-- 40 lines: #? # dump labels->write -------------------------------------------------------------------------------------------------------------------- 258 +-- 33 lines: #? # dump in ------------------------------------------------------------------------------------------------------------------------------- 291 +-- 9 lines: #? # write(2/stderr, "compute-offsets\n") -------------------------------------------------------------------------------------------------- 300 # compute-offsets(in, segments, labels) 301 # . . push args 302 52/push-edx 303 51/push-ecx 304 56/push-esi 305 # . . call 306 e8/call compute-offsets/disp32 307 # . . discard args 308 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 309 +-- 9 lines: #? # write(2/stderr, "compute-addresses\n") ------------------------------------------------------------------------------------------------ 318 # compute-addresses(segments, labels) 319 # . . push args 320 52/push-edx 321 51/push-ecx 322 # . . call 323 e8/call compute-addresses/disp32 324 # . . discard args 325 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 326 # rewind-stream(in) 327 # . . push args 328 56/push-esi 329 # . . call 330 e8/call rewind-stream/disp32 331 # . . discard args 332 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 333 +-- 9 lines: #? # write(2/stderr, "emit-output\n") ------------------------------------------------------------------------------------------------------ 342 +-- 26 lines: #? # dump *Trace-stream -------------------------------------------------------------------------------------------------------------------- 368 +-- 40 lines: #? # dump labels->write -------------------------------------------------------------------------------------------------------------------- 408 # emit-output(in, out, segments, labels) 409 # . . push args 410 52/push-edx 411 51/push-ecx 412 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12) 413 56/push-esi 414 # . . call 415 e8/call emit-output/disp32 416 # . . discard args 417 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x10/imm32 # add to esp 418 # flush(out) 419 # . . push args 420 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12) 421 # . . call 422 e8/call flush/disp32 423 # . . discard args 424 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 425 $subx-survey:end: 426 # . reclaim locals 427 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x30a0/imm32 # add to esp 428 # . restore registers 429 5e/pop-to-esi 430 5a/pop-to-edx 431 59/pop-to-ecx 432 # . epilogue 433 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp 434 5d/pop-to-ebp 435 c3/return 436 437 test-subx-survey-computes-addresses: 438 # input: 439 # == code 0x1 440 # Entry: 441 # ab x/imm32 442 # == data 0x1000 443 # x: 444 # 01 445 # 446 # trace contains (in any order): 447 # label x is at address 0x1079 448 # segment code starts at address 0x74 449 # segment code has size 5 450 # segment data starts at address 0x1079 451 # 452 # . prologue 453 55/push-ebp 454 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 455 # setup 456 # . clear-stream(_test-input-stream) 457 # . . push args 458 68/push _test-input-stream/imm32 459 # . . call 460 e8/call clear-stream/disp32 461 # . . discard args 462 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 463 # . clear-stream($_test-input-buffered-file->buffer) 464 # . . push args 465 68/push $_test-input-buffered-file->buffer/imm32 466 # . . call 467 e8/call clear-stream/disp32 468 # . . discard args 469 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 470 # . clear-stream(_test-output-stream) 471 # . . push args 472 68/push _test-output-stream/imm32 473 # . . call 474 e8/call clear-stream/disp32 475 # . . discard args 476 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 477 # . clear-stream($_test-output-buffered-file->buffer) 478 # . . push args 479 68/push $_test-output-buffered-file->buffer/imm32 480 # . . call 481 e8/call clear-stream/disp32 482 # . . discard args 483 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 484 # initialize input 485 # . write(_test-input-stream, "== code 0x1\n") 486 # . . push args 487 68/push "== code 0x1\n"/imm32 488 68/push _test-input-stream/imm32 489 # . . call 490 e8/call write/disp32 491 # . . discard args 492 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 493 # . write(_test-input-stream, "Entry:\n") 494 # . . push args 495 68/push "Entry:\n"/imm32 496 68/push _test-input-stream/imm32 497 # . . call 498 e8/call write/disp32 499 # . . discard args 500 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 501 # . write(_test-input-stream, "ab x/imm32\n") 502 # . . push args 503 68/push "ab x/imm32\n"/imm32 504 68/push _test-input-stream/imm32 505 # . . call 506 e8/call write/disp32 507 # . . discard args 508 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 509 # . write(_test-input-stream, "== data 0x1000\n") 510 # . . push args 511 68/push "== data 0x1000\n"/imm32 512 68/push _test-input-stream/imm32 513 # . . call 514 e8/call write/disp32 515 # . . discard args 516 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 517 # . write(_test-input-stream, "x:\n") 518 # . . push args 519 68/push "x:\n"/imm32 520 68/push _test-input-stream/imm32 521 # . . call 522 e8/call write/disp32 523 # . . discard args 524 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 525 # . write(_test-input-stream, "01\n") 526 # . . push args 527 68/push "01\n"/imm32 528 68/push _test-input-stream/imm32 529 # . . call 530 e8/call write/disp32 531 # . . discard args 532 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 533 # subx-survey(_test-input-buffered-file, _test-output-buffered-file) 534 # . . push args 535 68/push _test-output-buffered-file/imm32 536 68/push _test-input-buffered-file/imm32 537 # . . call 538 e8/call subx-survey/disp32 539 # . . discard args 540 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 541 # check trace 542 +-- 26 lines: #? # dump *Trace-stream -------------------------------------------------------------------------------------------------------------------- 568 # . check-trace-contains("label 'x' is at address 0x00001079.", msg) 569 # . . push args 570 68/push "F - test-subx-survey-computes-addresses/0"/imm32 571 68/push "label 'x' is at address 0x00001079."/imm32 572 # . . call 573 e8/call check-trace-contains/disp32 574 # . . discard args 575 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 576 # . check-trace-contains("segment 'code' starts at address 0x00000074.", msg) 577 # . . push args 578 68/push "F - test-subx-survey-computes-addresses/1"/imm32 579 68/push "segment 'code' starts at address 0x00000074."/imm32 580 # . . call 581 e8/call check-trace-contains/disp32 582 # . . discard args 583 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 584 # . check-trace-contains("segment 'code' has size 0x00000005.", msg) 585 # . . push args 586 68/push "F - test-subx-survey-computes-addresses/2"/imm32 587 68/push "segment 'code' has size 0x00000005."/imm32 588 # . . call 589 e8/call check-trace-contains/disp32 590 # . . discard args 591 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 592 # . check-trace-contains("segment 'data' starts at address 0x00001079.", msg) 593 # . . push args 594 68/push "F - test-subx-survey-computes-addresses/3"/imm32 595 68/push "segment 'data' starts at address 0x00001079."/imm32 596 # . . call 597 e8/call check-trace-contains/disp32 598 # . . discard args 599 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 600 # . epilogue 601 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp 602 5d/pop-to-ebp 603 c3/return 604 605 # global scratch space for compute-offsets in the data segment 606 == data 607 608 compute-offsets:file-offset: # int 609 0/imm32 610 compute-offsets:segment-offset: # int 611 0/imm32 612 compute-offsets:word-slice: 613 0/imm32/start 614 0/imm32/end 615 compute-offsets:segment-tmp: # slice 616 0/imm32/start 617 0/imm32/end 618 619 == code 620 621 compute-offsets: # in : (addr stream byte), segments : (addr stream {string, segment-info}), labels : (addr stream {string, label-info}) 622 # skeleton: 623 # for lines in 'in' 624 # for words in line 625 # switch word 626 # case 1 627 # case 2 628 # ... 629 # default 630 # 631 # pseudocode: 632 # curr-segment-name : (addr string) = 0 633 # var line : (stream byte 512) 634 # while true # line loop 635 # clear-stream(line) 636 # read-line(in, line) 637 # if (line->write == 0) break # end of file 638 # while true # word loop 639 # word-slice = next-word(line) 640 # if slice-empty?(word-slice) # end of line 641 # break 642 # else if slice-starts-with?(word-slice, "#") # comment 643 # break # end of line 644 # else if slice-equal?(word-slice, "==") 645 # if curr-segment-name != 0 646 # seg = get-or-insert(segments, curr-segment-name) 647 # seg->size = *file-offset - seg->file-offset 648 # trace("segment '", curr-segment-name, "' has size ", seg->size) 649 # segment-tmp = next-word(line) 650 # curr-segment-name = slice-to-string(segment-tmp) 651 # if empty?(curr-segment-name) 652 # abort 653 # segment-tmp = next-word(line) 654 # if slice-empty?(segment-tmp) 655 # abort 656 # seg = get-or-insert(segments, curr-segment-name) 657 # seg->starting-address = parse-hex-int(segment-tmp) 658 # seg->file-offset = *file-offset 659 # trace("segment '", curr-segment-name, "' is at file offset ", seg->file-offset) 660 # segment-offset = 0 661 # break (next line) 662 # else if is-label?(word-slice) 663 # strip trailing ':' from word-slice 664 # x : (addr label-info) = get-or-insert(labels, name) 665 # x->segment-name = curr-segment-name 666 # trace("label '", word-slice, "' is in segment '", curr-segment-name, "'.") 667 # x->segment-offset = segment-offset 668 # trace("label '", word-slice, "' is at segment offset ", segment-offset, ".") 669 # # labels occupy no space, so no need to increment offsets 670 # else 671 # width = compute-width-of-slice(word-slice) 672 # *segment-offset += width 673 # *file-offset += width 674 # 675 # . prologue 676 55/push-ebp 677 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 678 # . save registers 679 50/push-eax 680 51/push-ecx 681 52/push-edx 682 53/push-ebx 683 56/push-esi 684 57/push-edi 685 # curr-segment-name/esi = 0 686 31/xor 3/mod/direct 6/rm32/esi . . . 6/r32/esi . . # clear esi 687 # file-offset = 0 688 c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . compute-offsets:file-offset/disp32 0/imm32 # copy to *compute-offsets:word-slice 689 # segment-offset = 0 690 c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . compute-offsets:segment-offset/disp32 0/imm32 # copy to *compute-offsets:word-slice 691 # var line/ecx : (stream byte 512) 692 81 5/subop/subtract 3/mod/direct 4/rm32/esp . . . . . 0x200/imm32 # subtract from esp 693 68/push 0x200/imm32/length 694 68/push 0/imm32/read 695 68/push 0/imm32/write 696 89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx 697 $compute-offsets:line-loop: 698 # clear-stream(line) 699 51/push-ecx 700 e8/call clear-stream/disp32 701 # . discard args 702 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 703 # read-line(in, line) 704 51/push-ecx 705 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8) 706 e8/call read-line/disp32 707 # . discard args 708 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 709 # if (line->write == 0) break 710 8b/copy 0/mod/indirect 1/rm32/ecx . . . 0/r32/eax . . # copy *ecx to eax 711 3d/compare-eax-and 0/imm32 712 0f 84/jump-if-equal $compute-offsets:break-line-loop/disp32 713 +-- 33 lines: #? # dump line ----------------------------------------------------------------------------------------------------------------------------- 746 $compute-offsets:word-loop: 747 # edx = word-slice 748 ba/copy-to-edx compute-offsets:word-slice/imm32 749 # next-word(line, word-slice/edx) 750 52/push-edx 751 51/push-ecx 752 e8/call next-word/disp32 753 # . discard args 754 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 755 +-- 74 lines: #? # dump word-slice and maybe curr-segment-name ------------------------------------------------------------------------------------------- 829 $compute-offsets:case-empty: 830 # if slice-empty?(word/edx) break 831 # . eax = slice-empty?(word/edx) 832 52/push-edx 833 e8/call slice-empty?/disp32 834 # . . discard args 835 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 836 # . if (eax != false) break 837 3d/compare-eax-and 0/imm32/false 838 0f 85/jump-if-not-equal $compute-offsets:line-loop/disp32 839 $compute-offsets:case-comment: 840 # if slice-starts-with?(word-slice, "#") continue 841 68/push "#"/imm32 842 52/push-edx 843 e8/call slice-starts-with?/disp32 844 # . . discard args 845 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 846 # . if (eax != false) break 847 3d/compare-eax-and 0/imm32/false 848 0f 85/jump-if-not-equal $compute-offsets:line-loop/disp32 849 $compute-offsets:case-segment-header: 850 # if (!slice-equal?(word-slice/edx, "==")) goto next case 851 # . eax = slice-equal?(word-slice/edx, "==") 852 68/push "=="/imm32 853 52/push-edx 854 e8/call slice-equal?/disp32 855 # . . discard args 856 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 857 # . if (eax == false) goto next case 858 3d/compare-eax-and 0/imm32/false 859 0f 84/jump-if-equal $compute-offsets:case-label/disp32 860 # if (curr-segment-name == 0) goto construct-next-segment 861 81 7/subop/compare 3/mod/direct 6/rm32/esi . . . . . 0/imm32 # compare esi 862 74/jump-if-equal $compute-offsets:construct-next-segment/disp8 863 # seg/eax = get-or-insert(segments, curr-segment-name, row-size=16) 864 # . . push args 865 68/push 0x10/imm32/row-size 866 56/push-esi 867 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12) 868 # . . call 869 e8/call get-or-insert/disp32 870 # . . discard args 871 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 872 # seg->size = file-offset - seg->file-offset 873 # . save ecx 874 51/push-ecx 875 # . ebx = *file-offset 876 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/ebx compute-offsets:file-offset/disp32 # copy *file-offset to ebx 877 # . ecx = seg->file-offset 878 8b/copy 1/mod/*+disp8 0/rm32/eax . . . 1/r32/ecx 4/disp8 . # copy *(eax+4) to ecx 879 # . ebx -= ecx 880 29/subtract 3/mod/direct 3/rm32/ebx . . . 1/r32/ecx . . # subtract ecx from ebx 881 # . seg->size = ebx 882 89/copy 1/mod/*+disp8 0/rm32/eax . . . 3/r32/ebx 8/disp8 . # copy ebx to *(eax+8) 883 # . restore ecx 884 59/pop-to-ecx 885 # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".") 886 # . . push args 887 68/push "."/imm32 888 53/push-ebx 889 68/push "' has size "/imm32 890 56/push-esi 891 68/push "segment '"/imm32 892 # . . call 893 e8/call trace-sssns/disp32 894 # . . discard args 895 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x14/imm32 # add to esp 896 $compute-offsets:construct-next-segment: 897 # next-word(line, segment-tmp) 898 68/push compute-offsets:segment-tmp/imm32 899 51/push-ecx 900 e8/call next-word/disp32 901 # . discard args 902 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 903 +-- 45 lines: #? # dump curr-segment-name if not null (clobbering eax) ----------------------------------------------------------------------------------- 948 $compute-offsets:update-curr-segment-name: 949 # curr-segment-name = slice-to-string(segment-tmp) 950 # . eax = slice-to-string(Heap, segment-tmp) 951 # . . push args 952 68/push compute-offsets:segment-tmp/imm32 953 68/push Heap/imm32 954 # . . call 955 e8/call slice-to-string/disp32 956 # . . discard args 957 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 958 # . curr-segment-name = eax 959 89/copy 3/mod/direct 6/rm32/esi . . . 0/r32/eax . . # copy eax to esi 960 # if empty?(curr-segment-name) abort 961 # . if (eax == 0) abort 962 3d/compare-eax-and 0/imm32 963 0f 84/jump-if-equal $compute-offsets:abort/disp32 964 # next-word(line, segment-tmp) 965 68/push compute-offsets:segment-tmp/imm32 966 51/push-ecx 967 e8/call next-word/disp32 968 # . discard args 969 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 970 # if slice-empty?(segment-tmp) abort 971 # . eax = slice-empty?(segment-tmp) 972 68/push compute-offsets:segment-tmp/imm32 973 e8/call slice-empty?/disp32 974 # . . discard args 975 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 976 # . if (eax != false) abort 977 3d/compare-eax-and 0/imm32/false 978 0f 85/jump-if-not-equal $compute-offsets:abort/disp32 979 # seg/ebx = get-or-insert(segments, curr-segment-name, row-size=16) 980 # . . push args 981 68/push 0x10/imm32/row-size 982 56/push-esi 983 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12) 984 # . . call 985 e8/call get-or-insert/disp32 986 # . . discard args 987 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 988 # . ebx = eax 989 89/copy 3/mod/direct 3/rm32/ebx . . . 0/r32/eax . . # copy eax to ebx 990 # seg->address = parse-hex-int(segment-tmp) 991 # . eax = parse-hex-int(segment-tmp) 992 68/push compute-offsets:segment-tmp/imm32 993 e8/call parse-hex-int/disp32 994 # . . discard args 995 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 996 # . seg->address = eax 997 89/copy 0/mod/indirect 3/rm32/ebx . . . 0/r32/eax . . # copy eax to *ebx 998 # seg->file-offset = *file-offset 999 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 0/r32/eax compute-offsets:file-offset/disp32 # copy *file-offset to eax 1000 89/copy 1/mod/*+disp8 3/rm32/ebx . . . 0/r32/eax 4/disp8 . # copy eax to *(ebx+4) 1001 # trace-sssns("segment '", curr-segment-name, "' is at file offset ", seg->file-offset, "") 1002 # . . push args 1003 68/push "."/imm32 1004 50/push-eax 1005 68/push "' is at file offset "/imm32 1006 56/push-esi 1007 68/push "segment '"/imm32 1008 # . . call 1009 e8/call trace-sssns/disp32 1010 # . . discard args 1011 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x14/imm32 # add to esp 1012 # segment-offset = 0 1013 c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . compute-offsets:segment-offset/disp32 0/imm32 # copy to *segment-offset 1014 # break 1015 e9/jump $compute-offsets:line-loop/disp32 1016 $compute-offsets:case-label: 1017 # if (!is-label?(word-slice/edx)) goto next case 1018 # . eax = is-label?(word-slice/edx) 1019 # . . push args 1020 52/push-edx 1021 # . . call 1022 e8/call is-label?/disp32 1023 # . . discard args 1024 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 1025 # . if (eax == false) goto next case 1026 3d/compare-eax-and 0/imm32/false 1027 74/jump-if-equal $compute-offsets:case-default/disp8 1028 # strip trailing ':' from word-slice 1029 ff 1/subop/decrement 1/mod/*+disp8 2/rm32/edx . . . . 4/disp8 . # decrement *(edx+4) 1030 # x/eax = leaky-get-or-insert-slice(labels, word-slice, row-size=16) 1031 # . . push args 1032 68/push 0x10/imm32/row-size 1033 52/push-edx 1034 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0x10/disp8 . # push *(ebp+16) 1035 # . . call 1036 e8/call leaky-get-or-insert-slice/disp32 1037 # . . discard args 1038 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 1039 $compute-offsets:save-label-offset: 1040 # x->segment-name = curr-segment-name 1041 89/copy 0/mod/indirect 0/rm32/eax . . . 6/r32/esi . . # copy esi to *eax 1042 # trace-slsss("label '" word-slice/edx "' is in segment '" current-segment-name "'.") 1043 # . . push args 1044 68/push "'."/imm32 1045 56/push-esi 1046 68/push "' is in segment '"/imm32 1047 52/push-edx 1048 68/push "label '"/imm32 1049 # . . call 1050 e8/call trace-slsss/disp32 1051 # . . discard args 1052 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x14/imm32 # add to esp 1053 # x->segment-offset = segment-offset 1054 # . ebx = segment-offset 1055 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/ebx compute-offsets:segment-offset/disp32 # copy *segment-offset to ebx 1056 # . x->segment-offset = ebx 1057 89/copy 1/mod/*+disp8 0/rm32/eax . . . 3/r32/ebx 4/disp8 . # copy ebx to *(eax+4) 1058 # trace-slsns("label '" word-slice/edx "' is at segment offset " *segment-offset/eax ".") 1059 # . . eax = file-offset 1060 b8/copy-to-eax compute-offsets:segment-offset/imm32 1061 # . . eax = *file-offset/eax 1062 8b/copy 0/mod/indirect 0/rm32/eax . . . 0/r32/eax . . # copy *eax to eax 1063 # . . push args 1064 68/push "."/imm32 1065 50/push-eax 1066 68/push "' is at segment offset "/imm32 1067 52/push-edx 1068 68/push "label '"/imm32 1069 # . . call 1070 e8/call trace-slsns/disp32 1071 # . . discard args 1072 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x14/imm32 # add to esp 1073 # continue 1074 e9/jump $compute-offsets:word-loop/disp32 1075 $compute-offsets:case-default: 1076 # width/eax = compute-width-of-slice(word-slice) 1077 # . . push args 1078 52/push-edx 1079 # . . call 1080 e8/call compute-width-of-slice/disp32 1081 # . . discard args 1082 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 1083 # segment-offset += width 1084 01/add 0/mod/indirect 5/rm32/.disp32 . . 0/r32/eax compute-offsets:segment-offset/disp32 # add eax to *segment-offset 1085 # file-offset += width 1086 01/add 0/mod/indirect 5/rm32/.disp32 . . 0/r32/eax compute-offsets:file-offset/disp32 # add eax to *file-offset 1087 +-- 41 lines: #? # dump segment-offset ------------------------------------------------------------------------------------------------------------------- 1128 e9/jump $compute-offsets:word-loop/disp32 1129 $compute-offsets:break-line-loop: 1130 # seg/eax = get-or-insert(segments, curr-segment-name, row-size=16) 1131 # . . push args 1132 68/push 0x10/imm32/row-size 1133 56/push-esi 1134 ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12) 1135 # . . call 1136 e8/call get-or-insert/disp32 1137 # . . discard args 1138 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 1139 # seg->size = file-offset - seg->file-offset 1140 # . save ecx 1141 51/push-ecx 1142 # . ebx = *file-offset 1143 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/ebx compute-offsets:file-offset/disp32 # copy *file-offset to ebx 1144 # . ecx = seg->file-offset 1145 8b/copy 1/mod/*+disp8 0/rm32/eax . . . 1/r32/ecx 4/disp8 . # copy *(eax+4) to ecx 1146 # . ebx -= ecx 1147 29/subtract 3/mod/direct 3/rm32/ebx . . . 1/r32/ecx . . # subtract ecx from ebx 1148 # . seg->size = ebx 1149 89/copy 1/mod/*+disp8 0/rm32/eax . . . 3/r32/ebx 8/disp8 . # copy ebx to *(eax+8) 1150 # . restore ecx 1151 59/pop-to-ecx 1152 # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".") 1153 # . trace-sssns("segment '", curr-segment-name, "' has size ", ebx, ".") 1154 # . . push args 1155 68/push "."/imm32 1156 53/push-ebx 1157 68/push "' has size "/imm32 1158 56/push-esi 1159 68/push "segment '"/imm32 1160 # . . call 1161 e8/call trace-sssns/disp32 1162 # . . discard args 1163 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x14/imm32 # add to esp 1164 $compute-offsets:end: 1165 # . reclaim locals 1166 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x20c/imm32 # add to esp 1167 # . restore registers 1168 5f/pop-to-edi 1169 5e/pop-to-esi 1170 5b/pop-to-ebx 1171 5a/pop-to-edx 1172 59/pop-to-ecx 1173 58/pop-to-eax 1174 # . epilogue 1175 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp 1176 5d/pop-to-ebp 1177 c3/return 1178 1179 $compute-offsets:abort: 1180 # . _write(2/stderr, error) 1181 # . . push args 1182 68/push "'==' must be followed by segment name and segment-start\n"/imm32 1183 68/push 2/imm32/stderr 1184 # . . call 1185 e8/call _write/disp32 1186 # . . discard args 1187 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1188 # . syscall(exit, 1) 1189 bb/copy-to-ebx 1/imm32 1190 b8/copy-to-eax 1/imm32/exit 1191 cd/syscall 0x80/imm8 1192 # never gets here 1193 1194 test-compute-offsets: 1195 # input: 1196 # == code 0x1 1197 # ab x/imm32 # skip comment 1198 # == data 0x1000 1199 # 00 1200 # x: 1201 # 34 1202 # 1203 # trace contains (in any order): 1204 # segment 'code' is at file offset 0x0. 1205 # segment 'code' has size 0x5. 1206 # segment 'data' is at file offset 0x5. 1207 # segment 'data' has size 0x2. 1208 # label 'x' is in segment 'data'. 1209 # label 'x' is at segment offset 0x1. 1210 # 1211 # . prologue 1212 55/push-ebp 1213 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 1214 # setup 1215 # . clear-stream(_test-input-stream) 1216 # . . push args 1217 68/push _test-input-stream/imm32 1218 # . . call 1219 e8/call clear-stream/disp32 1220 # . . discard args 1221 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 1222 # var segments/ecx : (ref stream byte 2*16) 1223 81 5/subop/subtract 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # subtract from esp 1224 68/push 0x20/imm32/length 1225 68/push 0/imm32/read 1226 68/push 0/imm32/write 1227 89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx 1228 # var labels/edx : (ref stream byte 2*16) 1229 81 5/subop/subtract 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # subtract from esp 1230 68/push 0x20/imm32/length 1231 68/push 0/imm32/read 1232 68/push 0/imm32/write 1233 89/copy 3/mod/direct 2/rm32/edx . . . 4/r32/esp . . # copy esp to edx 1234 # initialize input 1235 # . write(_test-input-stream, "== code 0x1\n") 1236 # . . push args 1237 68/push "== code 0x1\n"/imm32 1238 68/push _test-input-stream/imm32 1239 # . . call 1240 e8/call write/disp32 1241 # . . discard args 1242 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1243 # . write(_test-input-stream, "ab x/imm32 # skip comment\n") 1244 # . . push args 1245 68/push "ab x/imm32 # skip comment\n"/imm32 1246 68/push _test-input-stream/imm32 1247 # . . call 1248 e8/call write/disp32 1249 # . . discard args 1250 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1251 # . write(_test-input-stream, "== data 0x1000\n") 1252 # . . push args 1253 68/push "== data 0x1000\n"/imm32 1254 68/push _test-input-stream/imm32 1255 # . . call 1256 e8/call write/disp32 1257 # . . discard args 1258 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1259 # . write(_test-input-stream, "00\n") 1260 # . . push args 1261 68/push "00\n"/imm32 1262 68/push _test-input-stream/imm32 1263 # . . call 1264 e8/call write/disp32 1265 # . . discard args 1266 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1267 # . write(_test-input-stream, "x:\n") 1268 # . . push args 1269 68/push "x:\n"/imm32 1270 68/push _test-input-stream/imm32 1271 # . . call 1272 e8/call write/disp32 1273 # . . discard args 1274 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1275 # . write(_test-input-stream, "34\n") 1276 # . . push args 1277 68/push "34\n"/imm32 1278 68/push _test-input-stream/imm32 1279 # . . call 1280 e8/call write/disp32 1281 # . . discard args 1282 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1283 # compute-offsets(_test-input-stream, segments, labels) 1284 # . . push args 1285 52/push-edx 1286 51/push-ecx 1287 68/push _test-input-stream/imm32 1288 # . . call 1289 e8/call compute-offsets/disp32 1290 # . . discard args 1291 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 1292 +-- 26 lines: #? # dump *Trace-stream -------------------------------------------------------------------------------------------------------------------- 1318 # check trace 1319 # . check-trace-contains("segment 'code' is at file offset 0x00000000.", msg) 1320 # . . push args 1321 68/push "F - test-compute-offsets/0"/imm32 1322 68/push "segment 'code' is at file offset 0x00000000."/imm32 1323 # . . call 1324 e8/call check-trace-contains/disp32 1325 # . . discard args 1326 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1327 # . check-trace-contains("segment 'code' has size 0x00000005", msg) 1328 # . . push args 1329 68/push "F - test-compute-offsets/1"/imm32 1330 68/push "segment 'code' has size 0x00000005."/imm32 1331 # . . call 1332 e8/call check-trace-contains/disp32 1333 # . . discard args 1334 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1335 # . check-trace-contains("segment 'data' is at file offset 0x00000005.", msg) 1336 # . . push args 1337 68/push "F - test-compute-offsets/2"/imm32 1338 68/push "segment 'data' is at file offset 0x00000005."/imm32 1339 # . . call 1340 e8/call check-trace-contains/disp32 1341 # . . discard args 1342 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1343 # . check-trace-contains("segment 'data' has size 0x00000002.", msg) 1344 # . . push args 1345 68/push "F - test-compute-offsets/3"/imm32 1346 68/push "segment 'data' has size 0x00000002."/imm32 1347 # . . call 1348 e8/call check-trace-contains/disp32 1349 # . . discard args 1350 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1351 # . check-trace-contains("label 'x' is in segment 'data'.", msg) 1352 # . . push args 1353 68/push "F - test-compute-offsets/4"/imm32 1354 68/push "label 'x' is in segment 'data'."/imm32 1355 # . . call 1356 e8/call check-trace-contains/disp32 1357 # . . discard args 1358 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1359 # . check-trace-contains("label 'x' is at segment offset 0x00000001.", msg) 1360 # . . push args 1361 68/push "F - test-compute-offsets/5"/imm32 1362 68/push "label 'x' is at segment offset 0x00000001."/imm32 1363 # . . call 1364 e8/call check-trace-contains/disp32 1365 # . . discard args 1366 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 1367 # . check-ints-equal(labels->write, 0x10, msg) 1368 # . . push args 1369 68/push "F - test-compute-offsets-maintains-labels-write-index"/imm32 1370 68/push 0x10/imm32/1-entry 1371 ff 6/subop/push 0/mod/indirect 2/rm32/edx . . . . . . # push *edx 1372 # . . call 1373 e8/call check-ints-equal/disp32 1374 # . . discard args 1375 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 1376 # . epilogue 1377 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp 1378 5d/pop-to-ebp 1379 c3/return 1380 1381 compute-addresses: # segments : (addr stream {string, segment-info}), labels : (addr stream {string, label-info}) 1382 # pseudocode: 1383 # srow : (addr segment-info) = segments->data 1384 # max = &segments->data[segments->write] 1385 # num-segments = segments->write / 16 1386 # starting-offset = 0x34 + (num-segments * 0x20) 1387 # while true 1388 # if (srow >= max) break 1389 # s->file-offset += starting-offset 1390 # s->address &= 0xfffff000 # clear last 12 bits for p_align 1391 # s->address += (s->file-offset & 0x00000fff) 1392 # trace-sssns("segment " s->key " starts at address " s->address) 1393 # srow += 16 # row-size 1394 # lrow : (addr label-info) = labels->data 1395 # max = &labels->data[labels->write] 1396 # while true 1397 # if (lrow >= max) break 1398 # seg-name : (addr string) = lrow->segment-name 1399 # label-seg : (addr segment-info) = get(segments, seg-name) 1400 # lrow->address = label-seg->address + lrow->segment-offset 1401 # trace-sssns("label " lrow->key " is at address " lrow->address) 1402 # lrow += 16 # row-size 1403 # 1404 # . prologue 1405 55/push-ebp 1406 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 1407 # . save registers 1408 50/push-eax 1409 51/push-ecx 1410 52/push-edx 1411 53/push-ebx 1412 56/push-esi 1413 57/push-edi 1414 # esi = segments 1415 8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 6/r32/esi 8/disp8 . # copy *(ebp+8) to esi 1416 # starting-offset/edi = 0x34 + (num-segments * 0x20) # make room for ELF headers 1417 # . edi = segments->write / 16 (row-size) 1418 8b/copy 0/mod/indirect 6/rm32/esi . . . 7/r32/edi . . # copy *esi to edi 1419 c1/shift 5/subop/logic-right 3/mod/direct 7/rm32/edi . . . . . 4/imm8 # shift edi right by 4 bits, while padding zeroes 1420 # . edi = (edi * 0x20) + 0x34 1421 c1/shift 4/subop/left 3/mod/direct 7/rm32/edi . . . . . 5/imm8 # shift edi left by 5 bits 1422 81 0/subop/add 3/mod/direct 7/rm32/edi . . . . . 0x34/imm32 # add to edi 1423 # srow/eax = segments->data 1424 8d/copy-address 1/mod/*+disp8 6/rm32/esi . . . 0/r32/eax 0xc/disp8 . # copy esi+12 to eax 1425 # max/ecx = &segments->data[segments->write] 1426 8b/copy 0/mod/indirect 6/rm32/esi . . . 1/r32/ecx . . # copy *esi to ecx 1427 01/add 3/mod/direct 1/rm32/ecx . . . 6/r32/esi . . # add esi to ecx 1428 $compute-addresses:segment-loop: 1429 # if (srow >= max) break 1430 39/compare 3/mod/direct 0/rm32/eax . . . 1/r32/ecx . . # compare eax with ecx 1431 73/jump-if-greater-or-equal-unsigned $compute-addresses:segment-break/disp8 1432 # srow->file-offset += starting-offset 1433 01/add 1/mod/*+disp8 0/rm32/eax . . . 7/r32/edi 8/disp8 . # add edi to *(eax+8) 1434 # clear last 12 bits of srow->address for p_align=0x1000 1435 # . edx = srow->address 1436 8b/copy 1/mod/*+disp8 0/rm32/eax . . . 2/r32/edx 4/disp8 . # copy *(eax+4) to edx 1437 # . edx &= 0xfffff000 1438 81 4/subop/and 3/mod/direct 2/rm32/edx . . . . . 0xfffff000/imm32 # bitwise and of edx 1439 # update last 12 bits from srow->file-offset 1440 # . ebx = srow->file-offset 1441 8b/copy 1/mod/*+disp8 0/rm32/eax . . . 3/r32/ebx 8/disp8 . # copy *(eax+8) to ebx 1442 # . ebx &= 0xfff 1443 81 4/subop/and 3/mod/direct 3/rm32/ebx . . . . . 0x00000fff/imm32 # bitwise and of ebx 1444 # . srow->address = edx | ebx 1445 09/or 3/mod/direct 2/rm32/edx . . . 3/r32/ebx . . # edx = bitwise OR with ebx 1446 89/copy 1/mod/*+disp8 0/rm32/eax . . . 2/r32/edx 4/disp8 . # copy edx to *(eax+4) 1447 # trace-sssns("segment " srow " starts at address " srow->address ".") 1448 # . . push args 1449 68/push "."/imm32 1450 52/push-edx 1451 68/push "' starts at address "/imm32 1452 ff 6/subop/push 0/mod/indirect 0/rm32/eax . . . . . . # push *eax 1453 68/push "segment '"/imm32 1454 # . . call 1455 e8/call trace-sssns/disp32 1456 # . . discard args 1457 81 0/subop/add 3/mo;;; init-minibuffer.el --- Minibuffer Completion Configuration File -*- lexical-binding: t -*- ;;; Commentary: ;; Config for completion etc in the minibuffer (vertico, embark, consult, etc) ;; Most of it is taken from the READMEs and wikis of those packages. ;; Relies on orderless config in init-completion.el ;;; Code: (use-package vertico :ensure (vertico :files (:defaults "extensions/*")) :hook (elpaca-after-init . vertico-mode) :custom (vertico-cycle t) :config ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (setq enable-recursive-minibuffers t) (minibuffer-depth-indicate-mode t) ;; Add prompt indicator to `completing-read-multiple'. ;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma. (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Emacs 28: Hide commands in M-x which do not work in the current mode. ;; Vertico commands are hidden in normal buffers. (setq read-extended-command-predicate #'command-completion-default-include-p) (advice-add #'vertico--format-candidate :around (lambda (orig cand prefix suffix index start) (setq cand (funcall orig cand prefix suffix index start)) (concat (if (= vertico--index index) (propertize "» " 'face 'vertico-current) " ") cand))) (defun down-from-outside () "Move to next candidate in minibuffer, even when minibuffer isn't selected." (interactive) (with-selected-window (active-minibuffer-window) (execute-kbd-macro [down]))) (defun up-from-outside () "Move to previous candidate in minibuffer, even when minibuffer isn't selected." (interactive) (with-selected-window (active-minibuffer-window) (execute-kbd-macro [up]))) (defun preview-from-outside () "Preview the selected candidate, even when minibuffer isn't selected." (interactive) (with-selected-window (active-minibuffer-window) (execute-kbd-macro (kbd "M-.")))) (defun to-and-fro-minibuffer () "Go back and forth between minibuffer and other window." (interactive) (if (window-minibuffer-p (selected-window)) (select-window (minibuffer-selected-window)) (select-window (active-minibuffer-window)))) (defun minibuffer-really-quit () "Quit minibuffer session, even if it is not the selected window." (interactive) (with-selected-window (active-minibuffer-window) (minibuffer-keyboard-quit))) :bind (("C-M-<" . up-from-outside) ("C-M->" . down-from-outside) ("C-M-+" . preview-from-outside) ("M-X" . to-and-fro-minibuffer) ("C-M-S-g" . minibuffer-really-quit) (:map vertico-map ("M-RET" . minibuffer-force-complete-and-exit)))) (use-extension vertico vertico-directory :config (defvar switching-project nil) (defun vertico-directory-enter-or-select-project () "vertico-directory-enter wrapper that plays nicely with selecting new projects." (interactive) ;; When selecting a project, use this to return, instead of entering the directory (if switching-project (vertico-exit) (vertico-directory-enter))) (defun vertico-directory-slash () (interactive) (if (and (>= vertico--index 0) (string-suffix-p "/" (vertico--candidate)) (eq 'file (vertico--metadata-get 'category))) (vertico-insert) (insert "/"))) (defun vertico-directory-home () (interactive) (if (and (string-suffix-p "/" (vertico--candidate)) (eq 'file (vertico--metadata-get 'category))) (insert "~/") (insert "~"))) (defun read-project (orig &rest args) (let ((switching-project t)) (apply orig args))) (advice-add 'project-prompt-project-dir :around 'read-project) ;; TODO this should be part of the vertico config (defun define-vertico-key (key &rest defs) "Define KEY conditionally in the vertico keymap. DEFS is a plist associating completion categories to commands." (let ((default-command (lookup-key vertico-map (kbd key)))) (define-key vertico-map (kbd key) (list 'menu-item nil defs :filter (lambda (d) (or (plist-get d (completion-metadata-get (completion-metadata (minibuffer-contents) minibuffer-completion-table minibuffer-completion-predicate) 'category)) default-command)))))) (define-vertico-key "/" 'file #'vertico-directory-slash 'project-file #'vertico-directory-slash) (define-vertico-key "RET" 'file #'vertico-directory-enter-or-select-project 'project-file #'vertico-directory-enter) (define-vertico-key "~" 'file #'vertico-directory-home) (define-vertico-key "DEL" 'file #'vertico-directory-delete-char 'project-file #'vertico-directory-delete-char) (define-vertico-key "M-DEL" 'file #'vertico-directory-delete-word 'project-file #'vertico-directory-delete-word) :commands (vertico-directory-enter vertico-directory-delete-word vertico-directory-delete-char) ;; Tidy shadowed file names :hook (rfn-eshadow-update-overlay . vertico-directory-tidy)) (use-extension vertico vertico-repeat :after savehist :bind ("C-\\" . vertico-repeat) ("C-|" . vertico-repeat-select) :hook (minibuffer-setup . vertico-repeat-save) :config (add-to-list 'savehist-additional-variables 'vertico-repeat-history)) (use-extension vertico vertico-indexed :config (defmacro define-choose (n) `(defun ,(intern (format "vertico-indexed-choose-%s" n)) () ,(format "Exit minibuffer with candidate %s." n) (interactive) (let ((vertico--index ,n)) (funcall-interactively 'vertico-exit)))) (defmacro define-insert (n) `(defun ,(intern (format "vertico-indexed-insert-%s" n)) () ,(format "Insert candidate %s in minibuffer." n) (interactive) (let ((vertico--index ,n)) (funcall-interactively 'vertico-insert)))) (dotimes (n 10) (eval `(define-choose ,n)) (eval `(define-insert ,n)) (define-key vertico-map (kbd (format "C-%s" n)) (intern (format "vertico-indexed-choose-%s" n))) (define-key vertico-map (kbd (format "M-%s" n)) (intern (format "vertico-indexed-insert-%s" n)))) (vertico-indexed-mode 1)) (use-extension vertico vertico-quick :bind (:map vertico-map ("M-;" . vertico-quick-insert) ("M-'" . vertico-quick-exit))) (use-package consult :bind (;; C-c bindings (mode-specific-map) ("C-c h" . consult-history) ("C-c m" . consult-mode-command) ("C-c b" . consult-bookmark) ("C-c k" . consult-kmacro) ;; C-x bindings (ctl-x-map) ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command ("C-x b" . consult-buffer) ;; orig. switch-to-buffer ("C-x B" . consult-buffer-no-preview) ;; orig. switch-to-buffer ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame ;; Custom M-# bindings for fast register access ("M-#" . consult-register-load) ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) ("C-M-#" . consult-register) ;; Other custom bindings ("C-S-s" . consult-line) ("M-*" . consult-line-thing-at-point) ("C-c f" . consult-recent-file) ("C-c r" . consult-ripgrep) ;; TODO find an alternative to C-c c? ("C-c c r" . consult-ripgrep-auto-preview) ("C-c c s" . consult-ripgrep-case-sensitive) ("C-c c z" . consult-z-ripgrep) ("C-c C-*" . consult-ripgrep-thing-at-point) ("C-c C-^" . consult-ripgrep-parent) ("M-y" . consult-yank-pop) ;; orig. yank-pop ("<help> a" . consult-apropos) ;; orig. apropos-command ;; M-g bindings (goto-map) ("M-g e" . consult-compile-error) ("M-g f" . consult-flycheck) ("M-g g" . consult-goto-line) ;; orig. goto-line ("M-g M-g" . consult-goto-line) ;; orig. goto-line ("M-g o" . consult-outline) ;; Alternative: consult-org-heading ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) (:map isearch-mode-map ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s l" . consult-line)) ;; needed by consult-line to detect isearch (:map search-map ("f" . consult-fd) ("F" . consult-find) ("M-f" . consult-locate) ("g" . consult-grep) ("G" . consult-git-grep) ("r" . consult-ripgrep) ("R" . consult-ripgrep) ;; can't use r in isearch-mode, so add R too ("u" . consult-ripgrep-unrestricted) ("*" . consult-ripgrep-thing-at-point) ("z" . consult-z-ripgrep) ("^" . consult-ripgrep-parent) ("l" . consult-line) ("L" . consult-line-multi) ("m" . consult-multi-occur) ("k" . consult-keep-lines) ("C-f" . consult-focus-lines) ("e" . consult-isearch-history)) (:map vertico-map ;; These are used for previewing with some consult commands (see consult-customize call below) ("C-S-p" . vertico-previous) ("C-S-n" . vertico-next) ;; Toggle preview on/off without changing preview-key ("M-P" . consult-toggle-preview) ("C-x C-M-x" . remove-leading-hash))) :init ;; Use Consult to select xref locations with preview (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) :config ;; Optionally configure the register formatting. This improves the register ;; preview for `consult-register', `consult-register-load', ;; `consult-register-store' and the Emacs built-ins. (setq register-preview-delay 0 register-preview-function #'consult-register-format) ;; Optionally tweak the register preview window. ;; This adds thin lines, sorting and hides the mode line of the window. (advice-add #'register-preview :override #'consult-register-window) (add-to-list 'consult-mode-histories '(cider-repl-mode cider-repl-input-history)) (defun consult-ripgrep-auto-preview (&optional dir initial) (interactive "P") (consult-ripgrep dir initial)) (defun consult-ripgrep-unrestricted (&optional dir initial) (interactive "P") (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-uu ." consult-ripgrep-args))) (consult-ripgrep dir initial))) (defun consult-z-ripgrep (&optional dir initial) (interactive "P") (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-z ." consult-ripgrep-args))) (consult-ripgrep dir initial))) (defun consult-ripgrep-case-sensitive (&optional dir initial) (interactive "P") (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-s ." consult-ripgrep-args))) (consult-ripgrep dir initial))) (defun consult-buffer-no-preview () (interactive) (consult-buffer)) (defun consult-ripgrep-parent (&optional initial) (interactive "P") (consult-ripgrep (file-name-directory (directory-file-name (persp-current-project-root))) initial)) (defalias 'consult-line-thing-at-point 'consult-line) (defalias 'consult-ripgrep-thing-at-point 'consult-ripgrep) (consult-customize consult-theme :preview-key '(:debounce 0.2 any) ;; For these commands we can use C-N/C-P to scroll and preview, or M-. to preview consult-git-grep consult-grep consult-ripgrep-parent consult-ripgrep consult-ripgrep-case-sensitive consult-ripgrep-unrestricted consult-z-ripgrep consult-ripgrep-thing-at-point consult-bookmark consult-recent-file consult-xref consult-buffer-no-preview consult--source-recent-file consult--source-project-recent-file consult--source-bookmark :preview-key '("M-." :debounce 0.2 "C-S-n" :debounce 0.2 "C-S-p") consult-ripgrep-thing-at-point :initial (concat "#" (thing-at-point 'symbol)) consult-line-thing-at-point :initial (thing-at-point 'symbol)) (defvar-local consult-toggle-preview-orig nil) (defun consult-toggle-preview () "Command to enable/disable preview." (interactive) (if consult-toggle-preview-orig (setq consult--preview-function consult-toggle-preview-orig consult-toggle-preview-orig nil) (setq consult-toggle-preview-orig consult--preview-function consult--preview-function #'ignore))) (setq consult-narrow-key "<") (append-to-list* 'consult-buffer-filter "^\\*helpful" "^\\*Warnings\\*" "^\\*cider-test-report\\*" "^\\*cider-error\\*" "^\\*cider-inspect\\*") (setq consult-project-function (lambda (_) (persp-current-project-root))) ;; Switches perspective if we select a buffer from another perspective, but note that previewing ;; a buffer adds it to the current perspective, so preview should be disabled before removing ;; perspective narrowing (defun consult--persp-buffer-action (orig &rest args) (when (not (cdr args)) ;; (cdr args) is norecord, which should distinguish preview/non-preview (let ((buffer (window-normalize-buffer-to-switch-to (car args)))) (unless (persp-is-current-buffer buffer) (let ((other-persp (persp-buffer-in-other-p buffer))) (when (eq (car-safe other-persp) (selected-frame)) (persp-switch (cdr other-persp))))))) (apply orig args)) (advice-add 'consult--buffer-action :around 'consult--persp-buffer-action) (defvar consult-initial-narrow-config '((consult-buffer . ?x) (consult-buffer-no-preview . ?x) (consult-buffer-other-window . ?x) (consult-project-extra-find . ?f))) ;; Add initial narrowing hook (defun consult-initial-narrow () (when-let (key (alist-get this-command consult-initial-narrow-config)) (setq unread-command-events (append unread-command-events (list key 32))))) (add-hook 'minibuffer-setup-hook #'consult-initial-narrow) (defvar consult--source-perspective-buffer `(:name "Perspective Buffer" :narrow (?x . "Perspective") :hidden t :category buffer :face consult-buffer :history buffer-name-history :state ,#'consult--buffer-state :enabled ,(lambda () persp-mode) :items ,(lambda () (consult--buffer-query :sort 'visibility :predicate #'persp-is-current-buffer :as #'buffer-name))) "Perspective buffer candidate source for `consult-buffer'.") (add-to-list 'consult-buffer-sources 'consult--source-perspective-buffer t) ;; Copy of consult--source-project-file to use with perspective narrowing (identical except for narrowing key) ;; Put before consult--source-project-file so we get recentf behaviour here (defvar consult--source-perspective-files (plist-put (plist-put (copy-sequence consult--source-project-recent-file) :name "Project File") :narrow '(?x . "Perspective"))) (add-to-list 'consult-buffer-sources 'consult--source-perspective-files t) ;; Versions of consult--source-project-buffer and consult--source-project-file for use by consult-project-buffer ;; They allow narrowing with b, f and a (instead of p) ;; f is the recentf version provided by consult ;; a is an "all files" version based on fd (respecting .gitignore, hidden by default) (defvar consult--project-source-project-buffer (plist-put (plist-put (copy-sequence consult--source-project-buffer) :hidden nil) :narrow '(?b . "Buffer"))) (defvar consult--project-source-project-file-recentf (plist-put (plist-put (copy-sequence consult--source-project-recent-file) :hidden nil) :narrow '(?f . "File (Recentf)"))) (defvar consult--project-source-project-file-all (plist-put (plist-put (copy-sequence consult--source-project-recent-file) :narrow '(?a . "File (All)")) :items '(lambda () (when (eq 0 (call-process-shell-command "fd")) (when-let (root (consult--project-root)) (let ((len (length root)) (inv-root (propertize root 'invisible t))) (mapcar (lambda (x) (concat inv-root (substring x len))) (split-string (shell-command-to-string (format "fd --color never -t f -0 . %s" root)) "\0" t)))))))) (defun remove-leading-hash () "Remove a # character from the beginning of the current line. Designed to be used for consult commands that automatically add a # at the beginning of the minibuffer. See `+become' and the functions that call it (e.g. `+become-consult-line')." (interactive) (save-excursion (beginning-of-line) (when (= ?# (char-after)) (delete-forward-char 1)))) (defun consult-project-buffer () "Version of `consult-buffer' that only uses project-related sources." (interactive) (let ((consult-buffer-sources '(consult--project-source-project-buffer consult--project-source-project-file-recentf consult--project-source-project-file-all))) (consult-buffer)))) (use-package consult-flycheck) (use-package consult-lsp :bind (:map lsp-mode-map ([remap xref-find-apropos] . consult-lsp-symbols))) (use-package consult-dir :bind (("C-x C-d" . consult-dir) :map vertico-map ("C-x C-d" . consult-dir) ("C-x C-j" . consult-dir-jump-file))) (use-package consult-git-log-grep :bind ("C-c g l" . consult-git-log-grep) :custom (consult-git-log-grep-open-function #'magit-show-commit)) (use-package consult-ls-git :bind ("C-c g f" . consult-ls-git)) (use-package consult-project-extra) (use-package marginalia :hook (elpaca-after-init . marginalia-mode) :config ;; crux-recentf-find-file (add-to-list 'marginalia-prompt-categories '("Choose recent file" . file))) (use-package embark :bind (("C-." . embark-act) ([remap xref-find-definitions] . embark-dwim) ("C-c C-o" . embark-export) ("C-h b" . embark-bindings) ("C-h B" . describe-bindings) (:map minibuffer-local-map ("M-." . embark-preview) ("C-," . embark-become)) (:map embark-become-file+buffer-map ("e" . consult-project-extra-find) ("E" . project-switch-consult-project-extra-find))) :custom (prefix-help-command 'embark-prefix-help-command) :config (defun embark-preview () "Previews candidate in vertico buffer, unless it's a consult command" (interactive) (unless (bound-and-true-p consult--preview-function) (save-selected-window (let ((embark-quit-after-action nil)) (embark-dwim))))) ;; Hide the mode line of the Embark live/completions buffers (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) (use-package embark-consult :after (embark consult) ;; demand, combined with after means that this will load after embark and consult ;; See https://github.com/oantolin/embark/commit/47daded610b245caf01a97d74c940aff91fe14e2#r46010972 :demand t :config (defun +become (fn) "Remove the leading # from the minibuffer, and call `FN'. Useful with embark-become, when changing from a command that uses # as a separator, to one that doesn't." (interactive) (setq unread-command-events (listify-key-sequence "\C-x\C-\M-x")) (funcall-interactively fn)) (defun +become-consult-line () "A version of `consult-line', designed for use with `embark-become'. The leading # added by other consult commands is removed." (interactive) (+become #'consult-line)) (defun +become-consult-line () "A version of `consult-imenu', designed for use with `embark-become'. The leading # added by other consult commands is removed." (interactive) (+become #'consult-imenu)) :bind (:map embark-consult-async-search-map ("l" . +become-consult-line) ("f" . consult-focus-lines) ("i" . +become-consult-imenu) ("^" . consult-ripgrep-parent) ("u" . consult-ripgrep-unrestricted) ("c" . consult-ripgrep-case-sensitive) ("z" . consult-z-ripgrep)) :hook (embark-collect-mode . consult-preview-at-point-mode)) (use-package consult-todo ;; TODO use consult-todo-project when it works :bind ("C-c c t t" . consult-todo)) (provide 'init-minibuffer) ;;; init-minibuffer.el ends here