COMP 3000 2026F Carleton University Assignment 1 Solutions ---------------------- 1. [2] How do you define a custom command for 3000menu without changing its code? What parts 3000menu implement custom command support? A: To create a custom command, you just need to sent the MENUCMD environment variable to the desired command. The first portion of main implements this functionality, from the getenv to the end of the first if-else clause (lines 70-77). 2. [2] Can menu commands include command line options? Why, or why not? A: Yes. Menu commands may include command line options, as they are executed by system() in run_program(), and system runs commands through a shell that parses the string. 3. [2] What compiler directive (an instruction starting with a period) is present at the start of every function? What is at the end of the function? Ignore directives that include the name of the function. A: Functions start with .cfi_startproc and end with .cfi_endproc. 4. [2] Which line(s) in 3000menu.s implement the if/else statements in run_program()? How do you know those are the correct lines? A: The run_program() if/else is implemented in assembly in three parts: 1) The definitions of the printf strings are on lines 6-9. 2) The logic of if is on lines 34 and 35 (test and je) 3) The printf's are lines 36 and 37 (true branch, failure) and 49 and 50 (false branch, success). Note that lines 39 through 46 implement the return and end of function logic (i.e., the clean up of the stack). You can tell these are the correct lines because of the references to the strings in the printf commands and the function calls (to system and puts, which is the optimized version of printf since these printf's have no variable arguments). 5. [2] What line(s) in 3000menu.s implement the call to getline() in choose_program()? How do you know those are the correct lines? A: Lines 108-111 implement the call to getline, as these are the lines after the _printf_chk that printed "Your choice?" (which we can tell from the referenced label .LC5 a few lines before) and go until the actual line that says "call getline@PLT". The lines after the call implement the logic of atoi(), which is turned into "call __isoc23_strtol@PLT". 6. [2] printf calls get translated into two different calls in assembly. What are those two calls? And why is each used (rather than the other)? A: printf() gets translated to __printf_chk or puts depending upon whether the printf has any variable arguments or not. (Note the @PLT is for code relocation purposes.) 7. [2] Does the code in 3000menu.s make any system calls directly? How do you know? Explain briefly. A: The code in 3000menu.s doesn't make any system calls directly because there are no syscall or other software interrupt calls in the code, there are only function calls (the call instructions). 8. [6] How could you modify 3000menu.c so that run_program() no longer used system() and instead used fork, execve, and any code from the first two tutorials except for execvp()? You may not use any other external code. Your modified version should have equivalent functionality to the original. A: The strategy for this question is to replace the system() call with code from 3000shell.c. To have equivalent functionality, you'll need to duplicate what system() does, which from the man page is calling "sh -c" with the given command. We don't need to search the path or parse the command line arguments, we can just pass them to the shell and it will do the work. So we can change the call to system() in run_program() to instead call my_system(), which we can define below: int my_system(char *command) { pid_t pid; int ret_status; char *args[4]; extern char **environ; pid = fork(); if (pid) { pid = wait(&ret_status); if (WIFEXITED(ret_status)) { return WEXITSTATUS(ret_status); } else { return -1; } } else { args[0] = "/bin/sh"; args[1] = "-c"; args[2] = command; args[3] = NULL; execve("/bin/sh", args, environ); return -1; } } Note this code is just the standard fork/execve pattern, but we can statically define the command line argument array because we're always calling /bin/sh. One tricky part is to pass in the environment variables; to preserve the API of system you'd need to assign the environment variables to a global variable, but the extern declaration of environ is the simplest. Alternately, you could do a minimal definition of an environment statically, but that could cause issues for some commands (but would get full credit here). Note that the real system() does more work in terms of setting up signal handlers and more elaborate error codes; such functionality is not needed, however, for the common case of executing a command.