diff options
Diffstat (limited to 'sh.asm')
| -rw-r--r-- | sh.asm | 465 |
1 files changed, 465 insertions, 0 deletions
@@ -0,0 +1,465 @@ +section .data + sigs dd 0x2 + cpid dd 0x0 + eol db `\n` + eol_mask db `\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n` + env_str db "/etc/environment", 0x0 + + prompt_str db "[0xdeadbeef] " + prompt_str_len equ $-prompt_str + + not_func_str db "Error: program not found.", `\n` + not_func_str_len equ $-not_func_str + + no_dir_str db "cd: Unknown directory.",`\n` + no_dir_str_len equ $-no_dir_str + + invalid_int_str db "exit: Invalid integer.",`\n` + invalid_int_str_len equ $-invalid_int_str + ;Flag indicating ability to execute + X_OK equ 0x1 + + ;Indicates that a file can be accessed in the way specified + F_OK equ 0x0 + + ;option for waitid + P_PGID equ 2 + + ;Special file descriptors + fd_stdin equ 0x00 + fd_stdout equ 0x01 + fd_stderr equ 0x02 + + ;Syscall constants + sys_read equ 0x00 + sys_write equ 0x01 + sys_open equ 0x02 + sys_close equ 0x03 + sys_access equ 0x15 + stub_fork equ 0x39 + stub_execve equ 0x3b + sys_exit equ 0x3c + sys_wait4 equ 0x3d + sys_kill equ 0x3e + sys_chdir equ 0x50 + sys_waitid equ 0xf7 + +section .bss + ;These must all be divisible by 16! + input_buffer_size equ 4096 + input_buffer resb input_buffer_size + + path_buffer_size equ 4096 + path_buffer resb path_buffer_size + + concat_buffer_size equ 8192 + concat_buffer resb concat_buffer_size ;Double so the path and input can never overflow concat_buffer when combined + +section .text + global _start + +_start: + ; TODO: handle interrupt signal + + mov r15, input_buffer ;stdin input + mov r9, path_buffer ;PATH variable reader + mov rbx, concat_buffer ;Concatenated directory and command name + + ; input: none + ; output: r9 (path elements), r10 (length) + ; clobbered: r8, r12 + ; other: r15 (buffer) + _parse_path: + mov rax, sys_open + mov rdi, env_str + xor rsi, rsi + syscall + test rax, rax ;check if rax is 0 + jl _quit + + call _bzero + + _parse_path2: + mov rdi, rax + mov rax, sys_read + mov rsi, r9 + mov rdx, path_buffer_size + syscall + test rax, rax + jl _quit + mov r8, r9 + dec r8 + + ; Reads the PATH variable + mov r10, `\0\0\0PATH=` + _pathfinder: + inc r8 + + ;Check that we've found the PATH variable + mov rax, [r8] + shl rax, 24 + cmp rax, r10 + jne _pathfinder ;if we haven't, let's move right one and see if it's there + cmp byte [r8], `"` + jne _pathfinder_continue + inc r8 + cmp byte [r8], 0 + jz _quit + _pathfinder_continue: + add r8, 0x5 + mov r10, 0x1 + push 0x0 + push r8 + dec r8 + + _pathender: + inc r8 + mov al, [r8] + cmp al, `:` + je _pathender1 + cmp al, `"` + je _pathender_exit + cmp al, `\n` + je _pathender_exit + cmp al, 0 + jz _pathender_exit + jmp _pathender + _pathender_exit: + mov byte [r8], 0x0 + mov r9, rsp + jmp _read_loop + + _pathender1: + inc r10 + mov byte [r8], 0x0 + lea rcx, [r8+0x1] + push rcx + jmp _pathender + +_read_loop: + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, prompt_str + mov rdx, prompt_str_len + syscall + + call _bzero + +_read_loopr: + mov rax, sys_read + xor rdi, rdi ;stdin + mov rsi, r15 + mov rdx, input_buffer_size + syscall + + ; check for _quit + mov r12, _quit + cmp byte [r15], 0x0 + je _weol + ;Start using r12 to hold the number of args + xor r12,r12 + + ; call _parse + jmp _parse + +_exec: + ; fork so we don't hurt ourselves + mov rax, stub_fork + syscall + mov dword [cpid], eax + test eax, eax ;checks if cpid is 0 + jne _wait_for_proc + + ; now that we know what to execute, do so + mov rax, stub_execve + mov rdi, r13 ;Filename + mov rsi, r14 ;argv + xor rdx, rdx ;envp + syscall + + ; and now kill the child process + jmp _quit + +;waits for the process to close +_wait_for_proc: + mov r12, r10 ;backup + + mov rsi, rax + mov rax, sys_waitid + xor rdi, rdi + xor rdx, rdx + mov r10, P_PGID + xor r8, r8 + syscall + + mov r10, r12 ;restore + + mov dword [cpid], -0x1 + jmp _read_loop + +;Writes 0s in buffer r15 until r8 reaches input_buffer_size +;input: r15 (buffer) +;output: none +_bzero: + xor r8, r8 + _bzero_main: + movups [r15+r8], xmm0 + add r8, 16 + cmp r8, input_buffer_size + jl _bzero_main + ret + +;input: r15 (buffer) +;output: r13 (path), r14 (arguments) +;clobbered: r8, rax, rbx, rcx +_parse: + ;syscall 80 is chdir, so do that first, it's easiest + ;It takes a path string. + ;also needs pipes (|, >, <) + xor r13, r13 ;for _strlen + push r13 ;push 0 + call _strlen + +;r8 holds strlen so we start at the end and parse backwards +; go through and sub/push +_parse1: + cmp byte [r15+r8], ` ` + je _subzp + cmp byte [r15+r8], `\n` + je _subz + cmp byte [r15+r8], `/` + je _sabsf + cmp byte [r15+r8], `.` + je _sabsf + +;Controls the parsing of the directory, keeps parsing each bit of the dir until +;the end of the string or directory name +_parse1r: + dec r8 + jl _parse2 + jmp _parse1 + +_sabsf: + cmp r8, 1 + jg _parse1r + mov r13, 1 + jmp _parse1r + +_subz: + mov byte [r8+r15], 0x0 + jmp _parse1 + +;Splits at the current position and puts the address of the argument on the stack +_subzp: + mov byte [r8+r15], 0x0 + lea rax, [r8+r15+1] + push rax + inc r12 + jmp _parse1 + +_parse2: + push rbx + mov r14, rsp + test r13, r13 + jg _parse7 + mov r8, -0x1 + +_parse3: + inc r8 + cmp r8, r10 + je _parse4 + ; Builtins to add: export, eval + ;r15 holds the name that the user entered, so we'll use that for shell builtins + mov rcx,`\0\0\0exit\0` + mov r13, [r15] + shl r13, 24 ;Remove the last character in r13, because it's an arg not the name of it + cmp r13, rcx + je _builtin_exit + mov rcx,`\0\0\0\0\0cd\0` + shl r13, 16 ;Remove the extra characters for cd, note how we're doing the + ;longest function names first then the shorter later + cmp r13, rcx + je _builtin_cd + ;END OF BUILTINS + ;mov r13, r15 + mov rdi, [r9+r8*8] + push r8 + ; input: rdi (a), r15 (b) + ; output: rbx (string) + ; clobbered: r8, r11 + _concat_dir: + xor r8,r8 ;new string's length (and for _dirty_strcpy_1, also the + ;initial string length) + ;Copies the contents of rdi to rbx in blocks of + ;16, doesn't bother to clean up stuff past the null + ;so moves that too, but it doesn't matter, just make + ;sure the buffer is big enough + _dirty_strcpy_1: + movDqU xmm2, [rdi+r8] + movDqU [rbx+r8],xmm2 + + pcmpistri xmm1, xmm2, 122 + jz _dirty_strcpy_1_exit + + PcmpIstrI xmm0, xmm2, 122 ;if there was a null in the + ;copied string exit loop + jz _dirty_strcpy_1_exit + add r8,15 + jmp _dirty_strcpy_1 + _dirty_strcpy_1_exit: + add r8,rcx + inc r8 + mov byte [rbx+r8], '/' + inc r8 + xor r11, r11 + _dirty_strcpy_2: + movdqu xmm2, [r15+r11] + movdqu [rbx+r8], xmm2 + + pcmpistri xmm1, xmm2, 122 + jz _parse5 + + pcmpistri xmm0, xmm2, 122 + jz _parse5 + + add r8, 15 + add r11, 15 + jmp _dirty_strcpy_2 + +_parse4: ; Function not found + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, not_func_str + mov rdx, not_func_str_len + syscall + jmp _read_loop + +; Checks that the program rbx can be executed +_parse5: + mov rax, sys_access + mov rdi, rbx + mov rsi, X_OK + syscall + + pop r8 + ;Check if it can be executed + cmp rax, F_OK + jl _parse3 + +; We'll fall through to here if the file is accessible, therefore we should execute it +_parse6: + mov r13, rbx + jmp _exec + +_parse7: ; absolute path + mov rax, sys_access + mov rdi, r15 + mov rsi, X_OK + syscall + + cmp rax, F_OK + jl _parse4 + + mov r13, r15 + jmp _exec + + +;input: r15 (buffer) +;Clobbered: rsi +;output: r8 (length) +_strlen: + xor r8, r8 + xchg rsi, rcx ;bacup rcx, let rsi get clobbered + MovDqU xmm1, [eol_mask] + _strlen1: + PcmpIstrI xmm1, [r15+r8], 122 + jz _strlen_end + PcmpIstrI xmm0, [r15+r8], 122 + jz _strlen_end + add r8, 15 + jmp _strlen1 + _strlen_end: + inc rcx + add r8, rcx + xchg rsi,rcx + ret + +; interrupt signal handler +_sigint: + cmp dword [cpid], 0x0 + jl _sigint_nc + +__sigint_c: ; kill the child + mov rax, sys_kill + mov rdi, [cpid] + mov rsi, 0x2 + syscall + +_sigint_nc: + mov r12, _read_loop + jmp _weol + +; input: r12 (return address) +; output: none +; Writes end of line to the terminal +_weol: + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, eol + mov rdx, 0x1 + syscall + jmp r12 + +_quit: + mov rax, sys_exit + xor rdi, rdi + syscall + +;cd [dir (rax)] +;changes to the given directory +_builtin_cd: + mov rdi,rax + mov rax, sys_chdir + syscall + test rax, rax + jz _read_loop + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, no_dir_str + mov rdx, no_dir_str_len + syscall + jmp _read_loop +;usage: exit [status] +;exits with the given status or 0 if one hasn't been provided +_builtin_exit: + ;convert first arg to an int if present + ;exit with that code + _string_to_int: + xor rdi, rdi + ;accumulate in rdi since it's also the register used to provide the status + ;to sys_exit + xor rdx, rdx + test r12, r12 + jz _string_to_int_end + _string_to_int_loop: + mov dl, [rax] ; Convert this to a number + test dl, dl ;Checks for NULL + jz _string_to_int_end + sub dl, `0` ;sub because we'll need to do this anyway + jl _invalid_int + cmp dl, 9 ;since we've already subtracted we can just compase with 9 + jg _invalid_int + imul rdi, 10 + add rdi, rdx + inc rax + jmp _string_to_int_loop + _string_to_int_end: + mov rax, sys_exit + syscall +_invalid_int: + mov rax,sys_write + mov rdi, fd_stdout + mov rsi, invalid_int_str + mov rdx, invalid_int_str_len + syscall + jmp _read_loop |
