Jak sprawdzić, który limit został przekroczony? (Proces zakończony z powodu ulimit.)

11

Załóżmy, że proces działa w środowisku ograniczonym:

(
ulimit  ... -v ... -t ... -x 0 ...
./program
)

Program został zakończony.

Może być wiele przyczyn: przekroczono limit pamięci / czasu / pliku; po prostu segfault; lub nawet normalne zakończenie z kodem powrotu 0.

Jak sprawdzić, jaki był powód zakończenia programu bez modyfikacji programu?

PS Mam na myśli „gdy podano binarny”. Może jakieś opakowanie (ptrace-ing itp.) Może pomóc?

Grzegorz Wierzowiecki
źródło

Odpowiedzi:

6

Ogólnie rzecz biorąc, nie sądzę, że możesz niestety. (Niektóre systemy operacyjne mogą to zapewniać, ale nie znam tych, które znam.)

Dokument referencyjny dotyczący limitów zasobów: getrlimitz POSIX 2008.

Weźmy na przykład limit procesora RLIMIT_CPU.

  • Jeśli proces przekroczy miękki limit, zostanie wysłany SIGXCPU
  • Jeśli proces przekroczy twardy limit, otrzyma zwykły SIGKILL

Jeśli potrafisz wait()w swoim programie, możesz stwierdzić, czy został zabity SIGXCPU. Ale nie można było odróżnić SIGKILLwysłanego za złamanie twardego limitu od zwykłego starego zabójstwa z zewnątrz. Co więcej, jeśli program obsługuje XCPU, nie zobaczysz tego nawet z zewnątrz.

To samo dotyczy RLIMIT_FSIZE. Widać SIGXFSZod wait()stanu, jeśli program nie poradzić. Ale gdy limit rozmiaru pliku zostanie przekroczony, jedyną rzeczą, która się zdarza, jest to, że kolejne operacje we / wy, które spróbują ponownie przetestować ten limit, po prostu otrzymają EFBIG- zostanie to obsłużone (lub niestety) wewnętrznie przez program. Jeśli program działa SIGXFSZtak samo jak powyżej - nie będziesz o tym wiedział.

RLIMIT_NOFILE? Cóż, nawet nie dostajesz sygnału. openi przyjaciele właśnie wracają EMFILEdo programu. Nie przeszkadza to w inny sposób, więc zawiedzie (lub nie) w jakikolwiek sposób, aby zakodować go w takiej sytuacji.

RLIMIT_STACK? Dobry stary SIGSEGV, nie można go odróżnić od wyniku z innych powodów, aby go dostarczyć. (Będziesz wiedział, że to właśnie zabiło proces, ze waitstatusu).

RLIMIT_ASi RLIMIT_DATApo prostu zrobi, malloc()a kilka innych zacznie się nie powieść (lub otrzyma, SIGSEGVjeśli limit AS zostanie przekroczony podczas próby rozszerzenia stosu w systemie Linux). O ile program nie jest bardzo dobrze napisany, prawdopodobnie w tym momencie prawdopodobnie losowo się nie powiedzie.

W skrócie, ogólnie mówiąc, awarie albo nie różnią się wyraźnie od innych przyczyn śmierci procesowej, więc nie możesz być pewien, albo można je całkowicie obsłużyć z programu, w którym to przypadku decyduje, czy / kiedy / jak będzie przebiegać, nie ty z zewnątrz.

O ile mi wiadomo, możesz napisać trochę kodu, który rozwidla twój program, czeka na niego i:

  • sprawdź status wyjścia w celu wykrycia SIGXCPUi SIGXFSZ(AFAIK, sygnały te będą generowane przez system operacyjny tylko w przypadku problemów z ograniczeniem zasobów). W zależności od swoich potrzeb, można założyć, że SIGKILLi SIGSEGVbyły również związane z ograniczeniami zasobów, ale to jest trochę odcinku.
  • spójrz na to, co możesz uzyskać ze getrusage(RUSAGE_CHILDREN,...)swojej implementacji, aby uzyskać wskazówki na temat innych.

Mogą istnieć narzędzia specyficzne dla systemu operacyjnego, które mogą w tym pomóc (być może rzeczy takie jak ptraceLinux lub Solaris dtrace), lub ewentualnie techniki typu debuggera, ale będzie to jeszcze bardziej związane z konkretną implementacją.


(Mam nadzieję, że ktoś inny odpowie magiczną rzeczą, której jestem całkowicie nieświadomy.)

Mata
źródło
Ok. A co z tymi trzema: (Mem) przekroczenie limitu pamięci, (Time) limit czasu, (Err) inny błąd? Wiem o robieniu otoki, mallocale niestety nie rozwiązuje to ogólnie problemu z pamięcią, ponieważ ogólnie chodzi o wywołanie systemowe brk(mam rację?).
Grzegorz Wierzowiecki,
1
Zawijanie malloc nie pomoże, jeśli nie kontrolujesz programu. Jeśli mówimy o hacki jak LD_PRELOADing to granicę dla „nie modyfikujących proces” przymusu, a to pomoże trochę, ale nie naprawdę - malloc, brk, sbrki mmapnie uda się ENOMEMdokładnie tak, jakby naprawdę byli w niskim sytuacji pamięci (ale znacznie poniżej limitów pamięci). Limit czasu jest taki RLIMIT_CPU, że nie znam limitu czasu naściennego.
Mat
Dzięki za zapewnienie mi o brk. Jak widzę, wymaganie „program nie obsługuje sygnałów X, Y, Z ...” rozwiąże problemy SIGXCPU, SIGXFSZ, SIGSEGV, dzięki waitpid (Jeśli się mylę, proszę mnie poprawić).
Grzegorz Wierzowiecki
1
SIGSEGV może zostać podniesiony w sytuacjach, które nie są przekroczeniami limitu zasobów (zerowa wskazówka wskaźnika jest najczęstszą rzeczą, która go podnosi) - nie możesz być pewien, że powoduje to ulimit.
Mat.
Dzięki za zapewnienie mi o brk. Jak widzę, wymaganie „program nie obsługuje sygnałów X, Y, Z ...” rozwiąże problemy SIGXCPU, SIGXFSZ, SIGSEGV, dzięki waitpid. Czy mam rację?
Grzegorz Wierzowiecki
3

Obecnie pracuję nad tym samym zagadnieniem. Byłem w stanie znaleźć częściowe rozwiązanie tego problemu. Użyłem susbsystemu audytu . Możesz śledzić pracę w [1].

[1] https://github.com/PaulDaviesC/Logging-limits.conf

PaulDaviesC
źródło