Извлекать значения из пользовательского стека внутри обработчика прерываний

Я пытаюсь реализовать обработчик системных вызовов в Pintos. Перед вызовом прерывания аргументы для системных вызовов помещаются следующим образом:

/* Invokes syscall NUMBER, passing argument ARG0, and returns the
   return value as an `int'. */
#define syscall1(NUMBER, ARG0)                                           \
        ({                                                               \
          int retval;                                                    \
          asm volatile                                                   \
            ("pushl %[arg0]; pushl %[number]; int $0x30; addl $8, %%esp" \
               : "=a" (retval)                                           \
               : [number] "i" (NUMBER),                                  \
                 [arg0] "g" (ARG0)                                       \
               : "memory");                                              \
          retval;                                                        \
        })

/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and
   returns the return value as an `int'. */
#define syscall2(NUMBER, ARG0, ARG1)                            \
        ({                                                      \
          int retval;                                           \
          asm volatile                                          \
            ("pushl %[arg1]; pushl %[arg0]; "                   \
             "pushl %[number]; int $0x30; addl $12, %%esp"      \
               : "=a" (retval)                                  \
               : [number] "i" (NUMBER),                         \
                 [arg0] "g" (ARG0),                             \
                 [arg1] "g" (ARG1)                              \
               : "memory");                                     \
          retval;                                               \
        })

/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and
   ARG2, and returns the return value as an `int'. */
#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;                                               \
        })

У меня есть структура, содержащая все отправленные регистры, а также указатель на стек пользовательского уровня (в который были помещены номер системного вызова и аргументы).

/* Interrupt stack frame. */
struct intr_frame
  {
    /* Pushed by intr_entry in intr-stubs.S.
       These are the interrupted task's saved registers. */
    uint32_t edi;               /* Saved EDI. */
    uint32_t esi;               /* Saved ESI. */
    uint32_t ebp;               /* Saved EBP. */
    uint32_t esp_dummy;         /* Not used. */
    uint32_t ebx;               /* Saved EBX. */
    uint32_t edx;               /* Saved EDX. */
    uint32_t ecx;               /* Saved ECX. */
    uint32_t eax;               /* Saved EAX. */
    uint16_t gs, :16;           /* Saved GS segment register. */
    uint16_t fs, :16;           /* Saved FS segment register. */
    uint16_t es, :16;           /* Saved ES segment register. */
    uint16_t ds, :16;           /* Saved DS segment register. */

    /* Pushed by intrNN_stub in intr-stubs.S. */
    uint32_t vec_no;            /* Interrupt vector number. */

    /* Sometimes pushed by the CPU,
       otherwise for consistency pushed as 0 by intrNN_stub.
       The CPU puts it just under `eip', but we move it here. */
    uint32_t error_code;        /* Error code. */

    /* Pushed by intrNN_stub in intr-stubs.S.
       This frame pointer eases interpretation of backtraces. */
    void *frame_pointer;        /* Saved EBP (frame pointer). */

    /* Pushed by the CPU.
       These are the interrupted task's saved registers. */
    void (*eip) (void);         /* Next instruction to execute. */
    uint16_t cs, :16;           /* Code segment for eip. */
    uint32_t eflags;            /* Saved CPU flags. */
    void *esp;                  /* Saved stack pointer. */
    uint16_t ss, :16;           /* Data segment for esp. */
  };

Теперь я хочу получить эти аргументы. Все указатели в стеке имеют размер 4 байта, поэтому я подумал, что могу просто привести аргумент (разыменованный указатель) к соответствующему типу, затем увеличить указатель стека на 4 и привести следующий указатель.

У меня следующий вопрос:

Инструкция pushl правильно помещает значения в стек? Итак, я должен иметь возможность получить эти значения, просто разыменовав указатель на стек? Например, чтобы получить первый аргумент (при условии, что это int), я бы использовал (int) *(f->esp + 4), где f — указатель на структуру intr_frame, и я добавляю 4, потому что номер системного вызова является первым элемент в стеке. Теперь проблема в том, что арифметика указателей на пустых указателях не разрешена в C, и аргументы могут быть разных типов, поэтому может ли кто-нибудь дать какие-либо предложения о том, как вытолкнуть эти аргументы из стека?


person eager2learn    schedule 03.09.2019    source источник
comment
Ваш код не работает, потому что ограничение g допускает esp относительных операндов, что было бы явно неправильным из-за ваших push инструкций, изменяющих esp до того, как все операнды будут обработаны.   -  person Jester    schedule 03.09.2019


Ответы (1)


Да, вы можете получить значения параметров, разыменовав пользовательский esp. Как и в случае с любым типом void *, вы должны привести его к подходящему типу указателя, прежде чем разыменовывать его или индексировать. В этом случае uint32_t * будет уместным, поэтому вы должны использовать

 *(((uint32_t *)f->esp) + 1)

Обратите внимание на +1 вместо +4, поскольку индекс масштабируется по размеру объекта, на который указывает указатель. Если вы хотите использовать фактическое смещение байтов, вам потребуется два приведения

*(uint32_t *)(((uint8_t *)f->esp) + 4)
person prl    schedule 03.09.2019
comment
Слепое разыменование указателя, предоставленного кодом непривилегированного пользователя, было бы дырой в безопасности. - person Timothy Baldwin; 10.09.2019
comment
@ Тимоти, да, хороший момент — пользовательский код мог загрузить что угодно в esp до системного вызова. - person prl; 10.09.2019