Author Topic: Writing a Linux shell using assembly  (Read 12813 times)

Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Writing a Linux shell using assembly
« on: September 23, 2017, 01:19:42 AM »
I'm trying to finish this program which forks a command line shell, however, it's not quite working properly. Anything stick out to you?

Code: [Select]
; nasm -f elf32 -g assn6.asm -o assn6.o
; ld -m elf_i386 assn6.o -o assn6
; A forked command shell written in nasm x86 assembly language
;
; --------------------

bits 32

global _start

input_buf_size  equ 1024

section .bss

byte_buf        resb 1
input_buf       resb 1024               ; input lines will contain no more than 1024 characters
buflen          resb 1024


section .data

prompt          db "$ "
exit_typed      db "exit", 10
cmd_not_found   db ": command not found", 10



section .text


_start:

        push ebp                ; stack prologue
        mov ebp, esp


setup_cl_prompt:

        xor eax, eax                    ; clear eax
        mov ebx, dword [byte_buf]       ; store typed buffer into ebx


clear_cmd:

        cmp eax, ebx                    ; have we gone through all characters yet?
        jge clear_cmd_line              ; if yes, display prompt
        mov byte [input_buf + eax], 0   ; clear the buffer
        inc eax                         ; evaluate next char
        jmp clear_cmd                   ; continue


clear_cmd_line:
        mov eax, 4                      ; write syscall
        mov ebx, 1                      ; write to stdout
        mov ecx, prompt                 ; chars to write
        mov edx, 2                      ; size_t= 2 chars (bytes) to write
        int 0x80


cmd_line_input:

        mov eax, 3                      ; syscall read
        mov ebx, 0                      ; read from stdin (keyboard)
        mov ecx, input_buf              ; input_buf stores characters read from keyboard
        mov edx, input_buf_size         ; size_t (1024 bytes)
        int 0x80

        mov dword [byte_buf], eax       ; eax returns # bytes read (characters typed by user): stored into byte_buf

        cmp eax, 0                      ; if nothing was typed, go back to prompt
        je setup_cl_prompt

        cmp eax, 1                      ; if 1 byte read, must be return key
        je setup_cl_prompt

        cmp eax, 5                      ; if 5 bytes read, could be "exit\n"
        je user_exit
     
        jmp back_to_shell


;return_check:

;        cmp byte [input_buf], 10        ; is return?
;        je setup_cl_prompt              ; back to command line if yes


back_to_shell:

        ;mov eax, dword [byte_buf]        ; restores number of bytes read into eax
        xor ebx, ebx            ; ebx used as counter
        xor ecx, ecx            ; so that ch can be used to store a character later for comparison


is_space_or_ret:

        cmp ebx, eax                    ; if number of bytes read = counter, all characters have been evaluated, jump
        je get_cmd_name                ; XXXXXXXXXXXX JE TO JGE XXXXXXXXXXXXXX

        mov ch, byte [input_buf + ebx]  ; move [count] byte into ch
        cmp byte ch, 0x20               ; is [count] byte a space?
        je fix_char                     ; get rid of space if yes
        cmp byte ch, 10                 ; is [count] byte return key?
        je fix_char                     ; null out return key if yes

        inc ebx                         ; proceed to next byte in keyboard input
        jmp is_space_or_ret

fix_char:

        mov byte ch, 0
        mov byte [input_buf + ebx], ch  ; place null where space/ ret was (space/ ret = different commands)
        inc ebx                         ; proceed to next byte in keyboard input
        jmp is_space_or_ret             
       

get_cmd_name:

        mov esi, input_buf              ; esi stores user typed buffer
        sub eax, 2                      ; subtract null and last character so that only command name can be evaluated
        add esi, eax                    ; esi now points to the last character of the command (minus return and null)


find_cmd_name:

        cmp eax, 0                      ; test for no more chars left to examine
        je cmd_name                    ; XXXXXXX JE TO JLE XXXXXXX now we have the name of command user typed

        cmp byte [esi], 0               ; null?
        je out_cmd_name

        dec esi                         ; "next" character (pointing backwards)
        dec eax                         ; decrease counter

        jmp find_cmd_name


out_cmd_name:

        inc esi                         ; move past null
        push esi                        ; string pushed to stack
        sub esi, 2                         ; back to typed character before null
        dec eax                         ; decrease counter
        jmp find_cmd_name


cmd_name:

        push esi                        ; command name typed by user pushed onto stack


cmd_fork:

        mov eax, 2                      ; fork syscall
        int 0x80

        cmp eax, 0                      ; if 0, fork success, -1= error
        jl fork_error
        je child_process
        jg parent_process               ; pid of child process returned in parent


child_process:

        mov eax, 11                     ; execve syscall for executing the user typed command
        mov ebx, input_buf              ; what was typed (argv[])
        mov ecx, esp                    ; put envp variables on stack
        lea edx, [ebp + 16]             ; start at first arg
        int 0x80

        jmp setup_cl_prompt             ; XXXXXXXXXXXXXXXXXXXXXXXXXXX


fork_error:

        mov eax, 4                      ; write syscall for printing error to user
        mov ebx, 1                      ; print to stdout
        mov ecx, input_buf              ; print the user input first per program directions
        mov edx, [byte_buf]             ; size_t
        int 0x80

        mov eax, 4                      ; write syscall
        mov ebx, 1                      ; write to screen
        mov ecx, cmd_not_found          ; generic error message
        mov edx, 20                     ; size_t of generic error message
        int 0x80

        jmp exit_shell


parent_process:

        mov ebx, eax                    ; pid of child
        mov eax, 7                      ; wait syscall (so that parent can wait for child to execve)
        mov ecx, buflen                 ; storage space for wait status
        mov edx, 0                      ; no options set

        mov esp, ebp                    ; stack epilogue
        jmp setup_cl_prompt


user_exit:
       
        cld                     ; clear direction flag
        mov ecx, 5              ; 5 bytes (characters) for "exit\n", count used for "rep"
        mov edi, exit_typed
        mov esi, input_buf      ; to compare if "exit\n" has been typed by the user
        repe cmpsb              ; compare string bytes- are they both "exit\n" ? If yes --> exit
        jne back_to_shell       ; if "exit\n" not typed, we go back to shell 
        je exit_shell     


exit_child:
        mov esp, ebp            ; stack epilogue

        mov eax, 1              ; an extra sys exit to kill the child
        int 0x80

exit_shell:
        mov esp, ebp            ; stack epilogue

        mov eax, 1              ; syscall exit
        int 0x80

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing a Linux shell using assembly
« Reply #1 on: September 23, 2017, 03:53:57 AM »
Yes. "byte_buf" is resb 1, but you use it as a dword. Changing that to resd 1 seems to help a bit. There may be more. "not quite working properly" isn't much to go on.

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing a Linux shell using assembly
« Reply #2 on: September 23, 2017, 04:02:24 AM »
of course it was something simple like that. bleh! thanks!

Do you know how I could use "dup" to create a "pipe" ? I'm gonna work on that for a bit..

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing a Linux shell using assembly
« Reply #3 on: September 23, 2017, 04:23:13 AM »
I don't think I've ever used "dup". Reading the manual, it looks like "dup2" might be what you want. I'm not sure.

Best,
Frank