OSDev

для всех
Текущее время: 28 апр 2024, 09:21

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 160 ]  На страницу Пред.  1 ... 12, 13, 14, 15, 16  След.
Автор Сообщение
СообщениеДобавлено: 02 сен 2013, 22:10 

Зарегистрирован: 16 июл 2013, 00:56
Сообщения: 26
Цитата:
Тогда непонятно как технически, скажем один байт, из ВАП одного процесса попадает в ВАП другого? На примитивном уровне - один процесс куда-то этот байт кладет, другой его оттуда берет.

Программа пишет в регистры, дергает int (или что там на данной архитектуре), ядро переключает ВАПы не затирая те регистры, переходит на другую программу, все. По-моему это самый быстрый способ.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 02 сен 2013, 22:43 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
Решил сегодня важный вопрос - наладил автоматический возврат управления из незацикленной программы. До этого у меня были такие программы
Код:
int main(void)
{
      .
      .
      while (1)
      {
            ....
            /* Тут я что-то делаю */
            ....
      }
     
      return 0;
}

Конечно такие "приложения" никому не нужны. Если не зацикливать, то управление уходит в никуда - по случайному адресу, который лежит в стеке потока. Раньше, с потоками, я решал проблему возврата из функции потока путем добавления в его конец некой функции thread_exit(....) с указателем на описатель потока в качестве параметра. Эта функция удаляла структуры потока, убирала его из очереди и передавала управление планировщику.

Если с потоками такое ещё прокатывает - в линуксе есть подобные pthread_exit() функции, то с приложениями не хорошо - пользователь не обязна применять каких то мер по выходу из программы, кроме return. (Хотя в старых книгах по *nix кодингу встречал некий exit()...)

Решил пойти путем автоматизации - поместить адрес возврата в стек. В качестве адреса возврата указать ядреную функцию завершения задачи.

Снова взял в руки отладчик. Дизасемблерный код функции main() содержит такие пролог и эпилог
Код:
          main:
9000000d:   push %ebp
9000000e:   mov %esp,%ebp
90000010:   and $0xfffffff0,%esp
90000013:   sub $0x220,%esp
................................................
Тут мы что-то делаем
................................................
900000c5:   leave
900000c6:   ret

Тот leave который попортил мне крови... :) Так вот - в начале в стек проталкивается EBP, затем в него записывается значение ESP. Команда LEAVE делает наоборот - записывает в ESP значение сохраненное ранее в EBP. То есть в конце ESP указывает место в стеке, лежащее на 4 байта глубже той позиции, на которую указывал ESP после передачи управления на данную задачу. И если перед запуском задачи мы поместим в эту позицию адрес возврата, то при завершении функции потока уйдем контролируемо в нужное нам место ядра.

Для ядерный потоков и процессов можно сразу передать управление в функцию уничтожения потока/процесса, с последующим переходом на планировщик. Для прикладных потоков сначала надо перейти в Ring 0, ибо при обычном прыжке мы останемся в Ring 3. Для этого я использовал системный вызов такого вида
Код:
.global kernel_mode_switch

.extern destroy_proc

kernel_mode_switch:
   
    /* Формируем в стеке кадр: EFLAGS, CS, EIP - этого достаточно так как мы идем в Ring 0 */   
    pushf
    push    $0x8
    push    $destroy_proc
   
    /* И выходим из прерыывания */
    iret

В системный вызов это завернуть пришлось чтобы получить необходимые значения DS и SS, так как в Ring 3 он загружаться не захотел, вываливая #GP, из-за несоблюдения мною условия DPL >= max(RPL,CPL) (действительно DPL = 0, RPL = 0, CPL = 3, то есть max(RPL,CPL) = 3, DPL < CPL). А SS я явно и не загрузил бы, я это не учел, только сейчас вспомнил.

Обертка в системный вызов дает нам выполнить данный код в Ring 0, учитывая что CS указывает на сегмент с тем же уровнем привилегий что и текущий, IRET затаскивает по местам только три значения, а не пять, как при переходе Ring0 --> Ring 3.

После этого управление переходит к функции уничтожения потока/процесса.

Вот теперь у меня все приложения завершаются без вывала системы.
На скрине: Сообщение от завершенного пользовательского процесса, работающее приложение ring 0 и два потока ядра
Изображение


Последний раз редактировалось maisvendoo 02 сен 2013, 23:19, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 02 сен 2013, 23:01 

Зарегистрирован: 26 мар 2012, 17:32
Сообщения: 209
maisvendoo писал(а):
то с приложениями не хорошо - пользователь не обязна применять каких то мер по выходу из программы, кроме return
Это кто сказал? Точнее, смотря как посмотреть. Ведь main - обычно не точка входа в программу (особенно если используются всякие argv), а функция, вызываемая из libc (с которой линкуются программы) для твоей системы.
Собсно, в тех же линухах если не используешь libc, то нужно завершать программу с помощью системного вызова (того самого exit), ибо делать ret некуда.
Так что пихать на стек адрес возврата при создании процесса совсем не обязательно - просто сделай вызов exit (заодно сможешь реализовать всякие atexit) в своей libc. При создании потока - аналогично, просто вместо stdlib/libc говорим о libpthread (точнее, её аналоге в твоей системе).

Хотя с другой стороны, нынче модны всякие VDSO, так что организовывать выход из программы/треда как ret на адрес вызова соответствующего системного вызова - выглядит в чём-то даже и красиво.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 02 сен 2013, 23:14 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
Nable писал(а):
Собсно, в тех же линухах если не используешь libc, то нужно завершать программу с помощью системного вызова (того самого exit), ибо делать ret некуда.

С программами не сталкивался, а вот с потоками - без pthread_exit() вылезают всякие нехорошести и программа аварийно завершается. А вот в венде, насколько я помню, потоки завершаются автоматом - за счет библиотек тоже, получается?
Nable писал(а):
Собсно, в тех же линухах если не используешь libc

Да, без libc никогда не писал под линукс.
Nable писал(а):
Хотя с другой стороны, нынче модны всякие VDSO, так что организовывать выход из программы/треда как ret на адрес вызова соответствующего системного вызова - выглядит в чём-то даже и красиво.

Ценность этого хотя бы в получении полезного опыта разбора кода в отладчике и понимании происходящего :)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 00:56 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
vlad9486 писал(а):
Программа пишет в регистры, дергает int (или что там на данной архитектуре), ядро переключает ВАПы не затирая те регистры, переходит на другую программу, все. По-моему это самый быстрый способ.
В конечном счете все равно получится через память, причем скорее всего не самым простым путем.

maisvendoo писал(а):
Тот leave который попортил мне крови... :) Так вот - в начале в стек проталкивается EBP, затем в него записывается значение ESP. Команда LEAVE делает наоборот - записывает в ESP значение сохраненное ранее в EBP. То есть в конце ESP указывает место в стеке, лежащее на 4 байта глубже той позиции, на которую указывал ESP после передачи управления на данную задачу. И если перед запуском задачи мы поместим в эту позицию адрес возврата, то при завершении функции потока уйдем контролируемо в нужное нам место ядра.

Для ядерный потоков и процессов можно сразу передать управление в функцию уничтожения потока/процесса, с последующим переходом на планировщик.
Если по-простому: подложить в стек адрес возврата из функции потока ;) - я тебе давно говорил об этом способе - хорошо подходит для потоков ядра, позволяя сэкономить несколько байт на коде завершения основной функции потока.

Что такое ядерные процессы, я не совсем понимаю. У меня ядерный процесс только один - первичная задача. Кстати это единственный процесс, в контексте которого вызов exit не приводит к безусловному уничтожению процесса. Возможен и перезапуск исполняемого модуля в контексте этого же процесса. Естественно, код исполняемого модуля работает в user mode.

Цитата:
Для прикладных потоков сначала надо перейти в Ring 0, ибо при обычном прыжке мы останемся в Ring 3. Для этого я использовал системный вызов такого вида...
А я бы использовал такой:
Код:
.global kernel_mode_switch

kernel_mode_switch:
; destroy_proc:
Или в крайнем случае такой:
Код:
.global kernel_mode_switch

.extern destroy_proc

kernel_mode_switch:
  jmp destroy_proc


Цитата:
После этого управление переходит к функции уничтожения потока/процесса.
Вообще-то это две разные функции, хотя конечно можно договориться считать выход из первичного потока процесса завершением процесса, но зачем? Чтобы завершать процесс в контексте других его потоков через трехэтажные костыли что ли?

Естественно, exit(process) есть везде. У меня так вообще на самом первом месте (SI_EXIT=0).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 07:45 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
Или в крайнем случае такой:
Код:
.global kernel_mode_switch

.extern destroy_proc

kernel_mode_switch:
  jmp destroy_proc


Дальний переход, для загрузки CS? Да я так и хотел в начале, просто я запутался с привилегиями при загрузке DS (не мог понять почему его загрузка вызывает #GP, потом разобрался) и сделал так "академично" через iret
phantom-84 писал(а):
Вообще-то это две разные функциих

имел в виду как раз две разные функции - одна у меня thread_exit, другая destroy_proc.
phantom-84 писал(а):
Что такое ядерные процессы, я не совсем понимаю.

у меня есть возможность запустить процесс в ring 0. Осталась опционально после того как начал с ними работать, для тестирования оставил пока
phantom-84 писал(а):
Естественно, exit(process) есть везде.

Можно предусмотреть оба механизма, тем более что это место в стеке - куда помещается адрес перехода на destroy - всё равно пустует. Просто автоматическое завершение средствами системы - мне такой подход нравится.

P.S.: Переделал вот так (как и хотел с самого начала)
Код:
.global kernel_mode_switch

.extern destroy_proc

kernel_mode_switch:
       
    ljmp    $KERNEL_CS,$destroy_proc

просто я не учел, что без прерывания не смогу загрузить DS из Ring 3.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 10:09 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
maisvendoo писал(а):
Дальний переход, для загрузки CS? Да я так и хотел в начале, просто я запутался с привилегиями при загрузке DS (не мог понять почему его загрузка вызывает #GP, потом разобрался) и сделал так "академично" через iret
Что-то я запутался. Я думал, речь идет о реализации системного вызова (в смысле "системной функции") внутри ядра, поэтому использовал именно ближний переход. Ты же вроде писал, что используешь для входа в ядро int 50h. Использовать iret или прямой far call/far jump для этих целей нельзя, все вызовы нужно выполнять через шлюзы.

Цитата:
Можно предусмотреть оба механизма, тем более что это место в стеке - куда помещается адрес перехода на destroy - всё равно пустует.
Почему пустует? Для этого адреса нужно отдельно предусмотреть место в стеке: под вершиной стека будет располагаться адрес destroy, а ниже (стековый) стартовый кадр потока.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 11:30 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
Что-то я запутался. Я думал, речь идет о реализации системного вызова (в смысле "системной функции") внутри ядра, поэтому использовал именно ближний переход. Ты же вроде писал, что используешь для входа в ядро int 50h. Использовать iret или прямой far call/far jump для этих целей нельзя, все вызовы нужно выполнять через шлюзы.

Так и есть. Эта функция - kernel_mode_switch() - выполняется внутри обработчика int 50h. В стек помещается не её адрес, а адрес syscall_kernel_mode_switch() А первоначально я её просто пробовал выполнять, находясь в ring 3 сразу после возврата из завершаемого процесса.
У меня же большая часть ядра написана на C, ассемблерными выполнены только функции, критичные к чистоте кода (в смысле без лишних телодвижений типа leave).

Обработчик сделан так
Код:
/*-----------------------------------------------------------------------------
 *       System call handler
 *---------------------------------------------------------------------------*/
void syscall_handler(registers_t regs)
{
   if (regs.eax >= NUM_CALLS)
      return;

   void* syscall = calls_table[regs.eax];

   syscall_entry_call(syscall);
}

Таблица системных функций
Код:
/*-----------------------------------------------------------------------------
 *       System calls table
 *---------------------------------------------------------------------------*/
void* calls_table[NUM_CALLS] =
{
      /* System calls for test */
      &print_text,
      &print_dec_value,
      &vprint_text,
      &get_digit,


      &in_byte,
      &out_byte,
      &kernel_mode_switch
};

Функция, передающая управление на табличную
Код:
/*-----------------------------------------------------------------------------
/
/       Call system call function
/       (c) maisvendoo, 21.08.2013
/
/----------------------------------------------------------------------------*/
.global     syscall_entry_call

syscall_entry_call:

        push    %edi
        push    %esi
        push    %edx            /* Push params into stack */
        push    %ecx
        push    %ebx
       
        mov    24(%esp), %edx   /* entry_point --> EDX */
       
        call    *%edx           /* Call function at address in EDX  */
       
        add     $20, %esp       /* Clean stack */
       
        ret

Да собсна в блоге всё описано

P.S.: Я понял тебя - можно обойтись ближним переходом - мы же в предалах сегмента кода ядра, при вызове kernel_mode_switch()

P.P.S.: Пока читал лекцию, посетила идея как переделать механизм системных вызовов. Переделал - теперь код компонуемый с приложениями можно реально сократить в разы. Кроме того, наконец появилась возможность включить защиту памяти на уровне страниц - станицы ядра и видеопамять с атрибутами 0x03. А до этого всё было 0х07
phantom-84 писал(а):
Мне не нравятся твои cli/sti с достаточно большим объемом кода между ними

Убрал. Для большинства мест где это делается достаточно просто остановить планировщик
phantom-84 писал(а):
Я большую часть работы выполняю в контексте первичного потока нового процесса - не нужно делать лишние переключения ВАП.

Сделал и без лишних переключений. Теперь в стеке создаваемого потока адрес функции, которая сначала заряжает ELF в память, потом включает user mode с переходом на entrypoint программы. Вся работа с памятью процесса ведется в его контексте, загрузка CR3 производится только планировщиком, при переключении задач.

И, надо сказать, когда выполнялись эти лишние переключения каталога было невозможно запустить процесс из процесса - вылетал #PF. Теперь всё ок - убрал лишние переключения - дочерний процесс сразу взлетел


Последний раз редактировалось maisvendoo 03 сен 2013, 20:45, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 20:44 

Зарегистрирован: 10 май 2007, 11:33
Сообщения: 1206
По мелочам...

Функция вызова системного вызова - совершенно ненужное усложнение. Попробуй избавиться от лишних вложенных вызовов. Нет необходимости использовать промежуточный регистр, т.е. допустима команда call dword [esp+24]. Но а в общем лучше выйти на такую команду вызова: call [calls_table+eax*4].

У меня и с 0x07 страничная защита работает, т.к. пространство ядра защищается на уровне входов каталога страниц. Чем меньше различий в распределении прикладных и ядерных страниц, тем удобнее.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 03 сен 2013, 20:54 
Аватара пользователя

Зарегистрирован: 25 июл 2013, 08:45
Сообщения: 141
Откуда: Новочеркасск
phantom-84 писал(а):
Попробуй избавиться от лишних вложенных вызовов.

Попробую.

Кстати - офигенная идея запуска процесса в его же контексте. Ни прерывания выключать не надо, ни планировщик останавливать. Задача запускает сама себя


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 160 ]  На страницу Пред.  1 ... 12, 13, 14, 15, 16  След.

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 31


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
Создано на основе phpBB® Forum Software © phpBB Group
Русская поддержка phpBB