2012-09-26 11 views
15

Niedawno napotkałem problem w niestandardowym sterowniku jądra systemu Linux (2.6.31.5, x86), w którym copy_to_user okresowo nie kopiował żadnych bajtów do przestrzeni użytkownika. Zwróci liczbę bajtów przekazanych do niej, wskazując, że nie skopiowała niczego. Po sprawdzeniu kodu stwierdziliśmy, że kod blokował przerwania podczas wywoływania copy_to_user, co narusza jego umowę. Po rozwiązaniu tego problemu problem przestał występować. Ponieważ problem zdarzał się tak rzadko, muszę udowodnić, że wyłączenie przerw spowodowało problem.Co się dzieje, gdy instrukcja mov powoduje błąd strony z przerwaniami wyłączonymi na x86?

Jeśli spojrzysz na fragment kodu poniżej z arch/x86/lib/usercopy_32.c rep; movsl kopiuje słowa do przestrzeni użytkownika przez liczbę w CX. Rozmiar jest aktualizowany przy pomocy CX przy wyjściu. CX będzie 0, jeśli movsl wykona poprawnie. Ponieważ CX nie wynosi zero, movs? instrukcje nie mogły zostać wykonane, aby pasowały do ​​definicji copy_to_user i obserwowanego zachowania.

/* Generic arbitrary sized copy. */ 
#define __copy_user(to, from, size)     \ 
do {         \ 
    int __d0, __d1, __d2;      \ 
    __asm__ __volatile__(      \ 
     " cmp $7,%0\n"     \ 
     " jbe 1f\n"     \ 
     " movl %1,%0\n"     \ 
     " negl %0\n"     \ 
     " andl $7,%0\n"     \ 
     " subl %0,%3\n"     \ 
     "4: rep; movsb\n"     \ 
     " movl %3,%0\n"     \ 
     " shrl $2,%0\n"     \ 
     " andl $3,%3\n"     \ 
     " .align 2,0x90\n"    \ 
     "0: rep; movsl\n"     \ 
     " movl %3,%0\n"     \ 
     "1: rep; movsb\n"     \ 
     "2:\n"       \ 
     ".section .fixup,\"ax\"\n"    \ 
     "5: addl %3,%0\n"     \ 
     " jmp 2b\n"     \ 
     "3: lea 0(%3,%0,4),%0\n"    \ 
     " jmp 2b\n"     \ 
     ".previous\n"      \ 
     ".section __ex_table,\"a\"\n"    \ 
     " .align 4\n"     \ 
     " .long 4b,5b\n"     \ 
     " .long 0b,3b\n"     \ 
     " .long 1b,2b\n"     \ 
     ".previous"      \ 
     : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2) \ 
     : "3"(size), "0"(size), "1"(to), "2"(from)  \ 
     : "memory");      \ 
} while (0) 

W 2 pomysły, które mam są:

  1. gdy przerwania są wyłączone, usterka nie wystąpi i następnie rep; movs? jest pomijany bez robienia czegokolwiek. Wartość zwracana byłaby wówczas wartością CX lub wartością, która nie została skopiowana do przestrzeni użytkownika, zgodnie z definicją i zaobserwowanym zachowaniem.
  2. Błąd strony występuje, ale linux nie może go przetworzyć, ponieważ przerwania są wyłączone, więc procedura obsługi błędów strony pomija tę instrukcję, chociaż nie wiem, w jaki sposób mógłby to zrobić program obsługi błędów strony. Ponownie, w tym przypadku CX pozostałaby niezmodyfikowana, a wartość zwracana byłaby poprawna.

Czy ktoś może wskazać mi sekcje w podręcznikach Intela, które określają to zachowanie, lub wskazać mi jakieś dodatkowe źródło linuksowe, które mogłoby być pomocne?

+0

wspominasz, że "kod blokował przerwania". Czy możesz opracować, które przerwania i jak? ... – TheCodeArtist

+0

@ TheCodeArtist: write_lock_bh(); odbyło się, co moim zdaniem wyłącza przerywanie oprogramowania. – Edward

+0

@ TheCodeArtist: Dzięki! Twój komentarz sprawił, że zajrzałam do write_lock_bh() znacznie bliżej, pokazując mi drogę! – Edward

Odpowiedz

7

znalazłem odpowiedź. Moja sugestia nr 2 była poprawna, a mechanizm był tuż przed moją twarzą. Błąd strony się nie dzieje, ale mechanizm fixup_exception służy do zapewnienia mechanizmu wyjątku/kontynuacji. Sekcja ta dodaje wpisy do tablicy procedury obsługi wyjątku:

".section __ex_table,\"a\"\n"    \ 
    " .align 4\n"     \ 
    " .long 4b,5b\n"     \ 
    " .long 0b,3b\n"     \ 
    " .long 1b,6b\n"     \ 
    ".previous"      \ 

mówi to: jeśli adres IP jest pierwszy wpis i wyjątek jest spotykane w procedury obsługi błędów, a następnie ustawić adres IP do drugiego adresu i kontynuować.

Jeśli więc wyjątek ma miejsce przy "4:", przejdź do "5:". Jeśli wyjątek ma miejsce w "0:", przejdź do "3:", a jeśli wyjątek ma miejsce w "1:", przejdź do "6:".

brakujące ogniwo jest w do_page_fault() w arch/x86/mm/fault.c:

/* 
* If we're in an interrupt, have no user context or are running 
* in an atomic region then we must not take the fault: 
*/ 
if (unlikely(in_atomic() || !mm)) { 
    bad_area_nosemaphore(regs, error_code, address); 
    return; 
} 

in_atomic powrócił prawdą, ponieważ jesteśmy w write_lock_bh() zamka! bad_area_nosemaphore ostatecznie rozwiązuje problem.

Jeśli wystąpiłby błąd page_fault (co było mało prawdopodobne, ze względu na koncepcję przestrzeni roboczej), wówczas wywołanie funkcji zakończy się niepowodzeniem i wyskoczy z makra __copy_user, z nieokreślonymi bajtami ustawionymi na rozmiar, ponieważ wyłączono wyłączenie.

4

Błędy strony nie są przerywanymi maskami. W gruncie rzeczy nie są to technicznie przerywacze - ale raczej wyjątki, chociaż zgadzam się, że różnica jest bardziej semantyczna.

Powód niepowodzenia operacji copy_to_user podczas wywoływania go w kontekście atomowym z wyłączonymi przerwaniami wynika z tego, że kod zawiera wyraźną kontrolę.

Zobacz http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L575

+0

Dzięki za odpowiedź. Połączenie działało przez większość czasu. Bardzo rzadko się zdarzało. Gdyby to było z powodu kontekstu atomowego, oczekiwałbym, że zawsze zawodzi. Warunek ten nie powinien zostać wykonany na pentium. [Według Linusa] (http://answers.softpicks.net/answers/topic/-BUG-__copy_to_user_inatomic-broken-on-non-Pentium-machines-2056019-1.htm) boot_cpu_data.wp_works_ok powinien == 0 na wszystko większe niż 386. – Edward

Powiązane problemy