;; This is the 2nd stage of the hex compiler. ;; It still requires hex input, but it can automatically calculate rip-relative offsets to labels ;; we need the following variables: ;; +0 = buf: [u8; 0x1000] ;; +0x1000 = write_buf: [u8; 0x4000] ;; +0x5000 = read_cursor: u32 ;; +0x5004 write_cursor: u32 ;; +0x5008 read_end: u32 ;; +0x500c file_offset: u32 (the offset of the current file in the overall input, used for calculating label offsets) ;; +0x5010 labels: [(u64, u32); 340] ;; +0x6000 num_labels: u32 ;; +0x6004 scratch space ;; exits the program panic: mov rax, 60 ; syscall: exit mov rdi, 1 ; status: 1 syscall ;; exits the program die: mov rax, 60 ; syscall: exit mov rdi, 0 ; status: 0 syscall ;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length) .fashhash_exit: ret fasthash: mov eax, 0x811c9dc5 ; FNV offset basis .fashhash_loop: test rsi, rsi jz .fashhash_done movzx ecx, byte ptr [rdi] ; get next byte xor eax, ecx imul eax, 0x1000193 ; FNV prime inc rdi dec rsi jmp .fashhash_loop ;; push the label pointed at by rdi with length rsi, and the current file offset onto the labels array. push_lbl: mov rax, num_labels cmp rax, 340 jge panic add rax, rax ; rax = num_labels * 2 add rax, num_labels ; rax = num_labels * 3 sal rax, 2 ; rax = num_labels * 12 (size of each label entry) push rax call fasthash pop rdx ; rdx = offset of label entry in labels array mov [labels + rdx], rax ; store hash in labels array mov eax, file_offset mov [labels + rdx + 8], eax ; store file offset in labels array inc num_labels ret ;; searches for the offset of the label with the name pointed at by rdi with length rsi, and returns it in rax. If the label is not found, dies. .find_lbl_not_found: call panic .find_lbl_found: mov rax, [rdx + 8] ; get file offset of label ret find_lbl: call fasthash mov rbx, num_labels lea rdx, [labels] ; rdx = pointer to start of labels array .find_lbl_loop: test rbx, rbx jz .find_lbl_not_found dec rbx mov rcx, [rdx] ; get hash of label at rdx cmp rcx, rax je .find_lbl_found add rdx, 12 ; move to next label entry jmp .find_lbl_loop die_jmp_start: sub rsp, 8 mov qword ptr [rsp], '\0\0trats_' mov rdi, rsp mov rsi, 6 call find_lbl neg eax mov byte ptr [write_buf + write_cursor], 0xe9 ; write the opcode for jmp rel32 add write_cursor, 1 mov dword ptr [write_buf + write_cursor], eax ; write the offset of the label reference add write_cursor, 4 add offset 5 call die ;; reads up to 0x1000 bytes from stdin into buf, and sets read_end to the number of bytes read, and resets read_cursor to 0 ;; dies if no bytes were read (if it returns, there were bytes read) rd_stdin: mov rax, 0 ; syscall: read mov rdi, 0 ; fd: stdin mov rsi, buf ; buf: pointer to buffer mov rdx, 0x1000 ; count: size of buffer syscall mov read_end, rax mov read_cursor, 0 cmp read_end, 0 je die_jmp_start ret ;; write up to write_cursor to stdout, and then reset the write cursor to 0 wrt_stdout: mov rax, 1 ; syscall: write mov rdi, 1 ; fd: stdout mov rsi, write_buf ; buf: pointer to buffer mov rdx, write_cursor ; count: number of bytes to write syscall mov write_cursor, 0 ret ;; return the next byte from the input, or 0 if we are at the end of the input ;; automatically writes to stdout and reads from stdin if we are at the end of the buffer .rd_buf_end: mov al, byte ptr [buf + read_cursor] ; get byte at read_cursor inc read_cursor ret rd_buf: cmp read_cursor, read_end jl .rd_buf_end call wrt_stdout call rd_stdin jmp .rd_buf_end ;; The grammar is slightly updated from hex0, as follows: ;; program = (label-def / instruction-seq / comment)* ;; comment = ";" [^\n]* "\n" ;; label-def = "@" label-name ":" comment? ;; instruction-seq = instruction+ comment? ;; instruction = label-ref / opcode ;; label-ref = "'" label-name ;; opcode = hex hex ;; label-name = [a-zA-Z0-9_-]+ ;; hex = [0-9a-f][0-9a-f] ;; ;; whitespaces may occur between any tokens and are ignored. ;; whitespace = [ \t\n]+ .skip_line_read: call rd_buf skip_line: cmp al, '\n' jne .skip_line_read call rd_buf ret .skip_whitespace_read: call rd_buf skip_whitespaces: cmp al, ' ' je .skip_whitespace_read cmp al, '\t' je .skip_whitespace_read cmp al, '\n' je .skip_whitespace_read ret .skip_comment_ret: ret .skip_comment_read: call rd_buf skip_comment: cmp al, '\;' jne .skip_comment_ret call skip_line ret write_u32le: mov dword ptr [write_buf + write_cursor], edi add write_cursor, 4 ret .get_next_token_skip: call skip_whitespaces call skip_comment get_next_token: cmp al, '\;' je .get_next_token_skip cmp al, ' ' je .get_next_token_skip cmp al, '\t' je .get_next_token_skip cmp al, '\n' je .get_next_token_skip ret ;; read the label into scratch, call push_lbl .label_ret lea rdi, [r15 + 0x6004] call push_lbl ret label: lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch xor rsi, rsi cmp al, ':' je .label_ret mov byte ptr [rdi + rsi], al inc rsi call rd_buf jmp label .label_ref_ret: lea rdi, [r15 + 0x6004] call find_lbl add file_offset, 4 sub eax, file_offset mov dword ptr [write_buf + write_cursor], eax ; write the offset of the label reference add write_cursor, 4 ret label_ref: lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch xor rsi, rsi call rd_buf cmp al, ' ' je .label_ref_ret cmp al, '\t' je .label_ref_ret cmp al, '\n' je .label_ref_ret mov byte ptr [rdi + rsi], al inc rsi jmp label_ref .invalid_hex: call panic .is_digit: sub al, '0' ret .is_lower_hex: sub al, 'a' - 10 ret .is_upper_hex: sub al, 'A' - 10 ret char_to_u4: cmp al, '0' jb .invalid_hex cmp al, '9' jbe .is_digit cmp al, 'a' jb .invalid_hex cmp al, 'f' jbe .is_lower_hex cmp al, 'A' jb .invalid_hex cmp al, 'F' jbe .is_upper_hex jmp .invalid_hex octet: sub rsp, 8 call char_to_u4 shl rax, 4 mov byte ptr [rsp], al call rd_buf call char_to_u4 or byte ptr [rsp], al mov al, byte ptr [rsp] add rsp, 8 inc file_offset ret _start: push rbp mov rbp, rsp sub rsp, 0x8000 ; allocate space for our variables + some scratch space mov r15, rsp ; r15 will point to the start of our variables .loop: call get_next_token cmp al, '@' ;; label call label cmp al, '\'' ;; label-ref call label_ref ;; else opcode call octet jmp .loop