Pintos - syscalls project 2

1.4k views Asked by At

I am doing the Pintos project on the side to learn more about operating systems. I finished Project 1 and have started the second project. I already have setup stack verified and working (via hex_dump). Right now I am having issues getting the correct syscall arguments.

In user/syscall.c there are 4 assembly stubs(0 - 4 stubs) that the user syscall wrap.

 #define syscall3(NUMBER, ARG0, ARG1, ARG2)                      \
    ({                                                      \
      int retval;                                           \
      asm volatile                                          \
        ("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; "    \
         "pushl %[number]; int $0x30; addl $16, %%esp"      \
           : "=a" (retval)                                  \
           : [number] "i" (NUMBER),                         \
             [arg0] "g" (ARG0),                             \
             [arg1] "g" (ARG1),                             \
             [arg2] "g" (ARG2)                              \
           : "memory");                                     \
      retval;                                               \
    }) (this code is given to us)

I have some code inside of my syscall_handler that calls the correct function inside of the kernel.

static void syscall_handler (struct intr_frame *f) {

  uint32_t *args = f->esp;
  if (args[0] == SYS_WRITE) {
    f->eax = write(args);
  }

Inside of my write function I am printing out the FD and Size

int sysCallNumber = (int) args[0];
  int fd = (int) args[1];
  const char *buffer = (char *) args[2];
  unsigned size = (unsigned) args[3];

  printf("FD is %d\n", fd);
  printf("Size is %d\n", size);

Running 'echo hello stack overflow 1 22 333' will yield the result below. Note I added the notes in parentheses. () <- Something is getting screwed up and FD is getting overridden with the size (including null terminator)

FD is 6    (hello)
Size is 6
FD is 6     (stack)
Size is 6
FD is 9    (overflow)
Size is 9
FD is 2    (1)
Size is 2
FD is 3    (22)
Size is 3
FD is 4    (333)
Size is 4
FD is 1   (this is from the printf("\n") in echo.c)
Size is 1

Ive ran this with GDB setting breakpoints and dumping frames and have not been able to figure it out. Has anyone encountered anything similar? If so how did you fix it?

Thanks!

1

There are 1 answers

3
Charles Celerier On

I discovered a bug recently in user/syscall.c that could be affecting you. Try applying this patch to user/syscall.c:

diff --git a/src/lib/user/syscall.c b/src/lib/user/syscall.c
index a9c5bc8..efeb38c 100644
--- a/src/lib/user/syscall.c
+++ b/src/lib/user/syscall.c
@@ -10,7 +10,7 @@
             ("pushl %[number]; int $0x30; addl $4, %%esp"       \
                : "=a" (retval)                                  \
                : [number] "i" (NUMBER)                          \
-               : "memory");                                     \
+               : "memory", "esp");                              \
           retval;                                               \
         })

@@ -24,7 +24,7 @@
                : "=a" (retval)                                           \
                : [number] "i" (NUMBER),                                  \
                  [arg0] "g" (ARG0)                                       \
-               : "memory");                                              \
+               : "memory", "esp");                                       \
           retval;                                                        \
         })

@@ -40,7 +40,7 @@
                : [number] "i" (NUMBER),                         \
                  [arg0] "g" (ARG0),                             \
                  [arg1] "g" (ARG1)                              \
-               : "memory");                                     \
+               : "memory", "esp");                              \
           retval;                                               \
         })

@@ -57,7 +57,7 @@
                  [arg0] "g" (ARG0),                             \
                  [arg1] "g" (ARG1),                             \
                  [arg2] "g" (ARG2)                              \
-               : "memory");                                     \
+               : "memory", "esp" );                             \
           retval;                                               \
         })

Here's the long story...

In the macro syscall3 for example, the expected assembly code to be generated looks something like this

pushl ARG2
pushl ARG1
pushl ARG0
pushl NUMBER
int  $0x30
addl $16, %esp

This should work as long as the stack looks exactly as expected just push the interrupt instruction. Here is what the disassembled write function looks like in of the test programs (generated by objdump -d pintos/src/userprog/build/tests/userprog/args-many):

0804a1a5 <write>:
 804a1a5:       ff 74 24 0c             pushl  0xc(%esp)
 804a1a9:       ff 74 24 08             pushl  0x8(%esp)
 804a1ad:       ff 74 24 04             pushl  0x4(%esp)
 804a1b1:       6a 09                   push   $0x9
 804a1b3:       cd 30                   int    $0x30
 804a1b5:       83 c4 10                add    $0x10,%esp
 804a1b8:       c3                      ret

There is an enormous problem with these instructions. Because the arguments are being pushed onto the stack relative to the stack pointer %esp, the wrong arguments are pushed onto the stack! This is because %esp changes after every pushl instruction.

After doing some digging on the extended asm syntax for gcc, I think I found the right fix. The %esp register needs to be added to the Clobbers list in the extended asm instruction for each syscall*. This is because each pushl instruction modifies %esp indirectly, so we need to inform gcc of that modification so that it does not use %esp improperly in the generated instructions.