LINUX.ORG.RU

Кажется я положил пых

 , , ,


0

2

Код вполне себе такой:

<?php

class a
{
    public function getNewB()
    {
        $b = new b();
        $b->a = $this;
        return $b;
    }
}
class b
{
    public $a = null;
    public $b = null;
}
class x
{
    public static $a = null;
    public static $b = null;
    public static function foo()
    {
        self::$a = new a();
        self::$b = self::$a->getNewB();
        self::bar(self::$b);
    }
    public static function bar($b)
    {
        $b->b = self::$a->getNewB();
        self::bar($b->b);
    }
}
x::foo();
Запуск:
~$ php test.php 
Segmentation fault
Т.е. не память, не время, а сразу — херак и всё.

Про окружение:

~$ php -v
PHP 5.6.22-0+deb8u1 (cli) (built: Jun 13 2016 07:55:54) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
32 бит если что.
~$ cat /etc/debian_version 
8.5
Подтверждаете? Или это платформо-версионно зависимое?

Kilte, KRoN73

Fedora 24, i686

> php test.php 
fish: “php test.php” terminated by signal SIGSEGV (Address boundary error)
> php -version
PHP 5.6.28 (cli) (built: Nov  9 2016 07:56:15) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
Ja-Ja-Hey-Ho ★★★★ ()

Нет пыха, ставить лень. Попробовал в оф docker образе:

root@01d8a5c05de6:/# php t.php

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 130968 bytes) in /t.php on line 29
root@01d8a5c05de6:/# php -v
PHP 5.6.28 (cli) (built: Dec  6 2016 22:12:29) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies

UPD: На 7.1 такая же фигня

Kilte ★★★★★ ()
Последнее исправление: Kilte (всего исправлений: 1 )
~$ php test.php
Ошибка сегментирования
~$ php -v
PHP 7.0.13-2 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.13-2, Copyright (c) 1999-2016, by Zend Technologies
Balantay ()

Изменил чутка метод:

    public static function bar($b, $i = 0)
    {
        if ($i == 15396) {
            return;
        }
        $b->b = self::$a->getNewB();
        self::bar($b->b, ++$i);
    }
Это максимальное число итераций на целевой машинке. После него — клеит ласты. Проблема конечно в выделении памяти. Но вот что в разных сборках и версиях по разному, где-то это проверяется, а где-то вообще кладут большой и толстый — это напрягает.

deep-purple ★★★★★ ()
$ php test.php 
Ошибка сегментирования (сделан дамп памяти)
$ php -v
PHP 7.0.8-0ubuntu0.16.04.3 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.8-0ubuntu0.16.04.3, Copyright (c) 1999-2016, by Zend Technologies
isildur ()
$ php -v
PHP 7.0.14 (cli) (built: Dec  7 2016 17:11:27) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
$ php test.php
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /tmp/test.php on line 29
surefire ★★★ ()
Ответ на: комментарий от NeOlip

Это не баг

Сегфолт приложения это не баг? Разупорись.

Выше я показал что проблема может вылезти сама собой только потому что кончилась память. А память может кончится гораздо быстрее если $a и $b будут не пустышками для теста, а толстыми моделями.

deep-purple ★★★★★ ()
Ответ на: комментарий от anonymous

обыкновенная рекурсия

С сегфолтом. Приложение было прибито осью за то что некорректно пишет в память. А так да — обыкновенная рекурсия ))

deep-purple ★★★★★ ()
Ответ на: комментарий от anonymous

Что ты хочешь этим сказать? Тут ничего не понятно ибо сборки разные.

deep-purple ★★★★★ ()
┌─( ✔ 16:32:13 +00:00:07.294):/tmp/111
└balancer@pfs─> php foo.php 
PHP Fatal error:  Maximum function nesting level of '256' reached, aborting! in /tmp/111/foo.php on line 5
PHP Stack trace:
PHP   1. {main}() /tmp/111/foo.php:0
PHP   2. x::foo() /tmp/111/foo.php:36
PHP   3. x::bar() /tmp/111/foo.php:27
PHP   4. x::bar() /tmp/111/foo.php:32
PHP   5. x::bar() /tmp/111/foo.php:32
PHP   6. x::bar() /tmp/111/foo.php:32
PHP   7. x::bar() /tmp/111/foo.php:32
...
PHP 255. x::bar() /tmp/111/foo.php:32
PHP 256. x::bar() /tmp/111/foo.php:32

php-7.0 какой-то, xdebug.

KRoN73 ★★★★★ ()

This is a well-known issue, we probably have 100s of duplicates about this. E.g. #72568 for interaction with pthreads, but there's many others around for specific cases. This usually occurs either with magic methods or if you have extensions loaded that hook into the function call process.

We know this issue exists and there is no concrete plan to fix it.

Ыыыы )) Ну хоть честно.

deep-purple ★★★★★ ()

пыхтёры должны страдать!

anonymous ()
Ответ на: комментарий от deep-purple

А без хердебага?

А без него PHP не контролирует переполнение стека. Можно «положить» и без таких наворотов, как у тебя:

┌─( ✔ 18:03:08 +00:00:01.577):~
└balancer@pfs─> php -r 'function x() { return x(); } x();'
Ошибка сегментирования

KRoN73 ★★★★★ ()
Последнее исправление: KRoN73 (всего исправлений: 1 )
Ответ на: комментарий от KRoN73

У меня в сообщении выше — ~15к итераций. Проблема в том, что это не очень большое число даже для таких легких тестов. На реальных же данных это может выпасть гораздо раньше. А если вспомнить что на серверах разные ограничения, то вообще не понятно как это отлавливать. в одном месте оно работать будет, а в другом нет. И известно это станет только тогда, когда ты его там запустишь. Да и то, сегодня это один объем данных, а завтра другой. Искать методом тыка (для каждого сервера индивидуально) сколько элементов позволять загружать за раз — вообще не вариант.

deep-purple ★★★★★ ()
Последнее исправление: deep-purple (всего исправлений: 1 )
Ответ на: комментарий от deep-purple

У меня в сообщении выше — ~15к итераций

1. Это не итерации, а глубина стека.

2. Какое отношение глубина стека имеет к бесконечной рекурсии, вызванной ошибкой программирования? :)

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

А глубина стека ни в одном языке на уровне языка не оговаривается. _Некоторые_ позволяют выставить такую опцию на уровне среды исполнения или в опциях компиляции. Но это бывает очень редко. Т.к. при нормальном программировании переполнение стека — большая редкость. В 99.9% случаев это обычная ошибка.

KRoN73 ★★★★★ ()
Ответ на: комментарий от KRoN73

Итераций ака кол-во погружений.

Если я разверну это в цикл — то сегфолт не случится.

Возникает вопрос — какого хрена выделение памяти на внутренние нужды логики скрипта не учитываются при выполнении рекурсии? Ведь это просто расход памяти. Я, как пользователь, должен видеть сообщение о неудавшейся попытке выделения памяти во время работы, а не получить тупой сегфолт.

Все тесты выше — CLI режим, где обычно нет ограничения по памяти. Но я напоролся на эту херь запуская скрипт в апаче — И ТАМ УСТАНОВЛЕНО ОГРАНИЧЕНИЕ: memory_limit = 128M. И сегфолт пыха под апачем свидетельствует о некорректной работе с указателями и памятью внутри самого пхп в момент достижения лимита из конфига.

Поэтому — какого хрена я получаю сегфолт?

deep-purple ★★★★★ ()
Ответ на: комментарий от deep-purple

Это не динамическая память, а стек, который ограничен ОС ( ulimit -s ). memory_limit и динамическая память тут вообще никаким боком.

Потому ты и получаешь сегфолт, что хрен его знает как это корректно обработать, с учётом того, что стек может выжрать не только сам PHP, но и какое-нибудь левое расширение (о чём вполне доступно написали в багтрекере). По крайней мере мои крайне ограниченные познания в C/C++ не позволяют найти корректное решение данной проблемы.

NeOlip ★★ ()
Ответ на: комментарий от deep-purple

Итераций ака кол-во погружений

Это абсолютно разные и прямо не коррелируемые понятия.

Если я разверну это в цикл — то сегфолт не случится.

Да. Просто с бесконечным циклом ты получишь зависание программы :) Рекурсия у тебя бесконечная.

какого хрена выделение памяти на внутренние нужды логики скрипта не учитываются при выполнении рекурсии?

На бесконечную рекурсию нужно бесконечное количество памяти.

CLI режим, где обычно нет ограничения по памяти

Ограничение по памяти выставляет ОС.

Поэтому — какого хрена я получаю сегфолт?

Потому что организуешь бесконечную рекурсию. С бесконечной глубиной.

KRoN73 ★★★★★ ()
Ответ на: комментарий от NeOlip

Давай пусть так: память ос я буду называть «осмем», память пыха, которая не относится напрямую к скриптовым данным «пыхмем», а память для данных скрипта «скриптмем».

Заранее известно что осмем больше чем 128м лимита в конфиге пыха.

Когда скрипт распарсен, то внутри пыхмем лежит некое дерево, по которому уже в дальнейшем идет работа.

Хочу обратить внимание что в моем примере есть динамическое создание экземпляров, что должно повлечь выделение памяти (и пометкой её как скриптмем) в рантайме, и память выделяется, но она не влияет на конечный результат (сегфолт).

В случае с рекурсией пых почему-то:

а) либо упирается в пыхмем+скриптмем=лимит и некорректно ведет себя в этом момент пытаясь записать что-то не туда, на что получает пинок от оси.

б) либо, игнорируя лимит из конфига, пытается записать данные по указателю, который был выделен, но писать туда уже не получится, ибо ось сейчас скажет — сдохни тварь.

Я не смотрел сорцы, просто предполагаю исходя из поведения пыха:

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

б) либо рекурсия как ветвь все же конечна и зациклена, но именно во время её обработки пых пишет что-то не туда и получает пинок оси.

Да, я считаю что это баг.

deep-purple ★★★★★ ()
Ответ на: комментарий от KRoN73

На бесконечную рекурсию нужно бесконечное количество памяти

И срать на лимиты в конфиге, ага. Причем не забывай — некоторые сборки и версии работают корректно. Возможно там GC шалит кстати.

deep-purple ★★★★★ ()
Ответ на: комментарий от KRoN73

Ага! Поймал!

NeOlip

Сегфолт выпадает когда лимит в конфиге выше чем может успешно выделить ось. На 1 метре в конфиге даже твоя «function x() { return x(); } x();» отвечает как не могу выделить еще памяти.

Тогда все еще более странно, т.к. 128 метров в апаче вызывают сегфолт. У меня ось что, не может дать 128 метров? Смотрю в хтоп и там еще достаточно ресурсов, да и некоторые другие приложения вполне себе схавали больше чем 128 метров.

deep-purple ★★★★★ ()
Ответ на: комментарий от anonymous

Парни, почему вы до сих пор пользуетесь 32 битами?

Как бы очевидно, что бесконечная рекурсия сожрёт хоть память адресуемую 64 битами, хоть 1024 битами :)

KRoN73 ★★★★★ ()
Ответ на: комментарий от deep-purple

Повторяю ещё раз: заканчивается не динамическая память, а стек, который ограничен ОС ( ulimit -s ). Стек, @#$%, а не динамическая память! Нет никаких проблем с выделением памяти или её адресацией, в данном случае. Понятно?

Подними размер стека на достаточно большую величину (или уменьши memory_limit) и упрёшься в лимит памяти до того, как закончится стек. И соответственно получишь

PHP Fatal error: Allowed memory size of…

который ты наблюдаешь, выставив memory_limit=1M. А при установленном xdebug

Fatal error: Maximum function nesting level…

Не знаю точно как это происходит (копать исходники php лень, да и не факт, что осилю), но рекурсия в php рано или поздно забивает стек с закономерным результатом в виде segmentation fault. К примеру python работает аналогично, но там глубина рекурсии ограничена по умолчанию и нужно изменить это ограничение, чтобы получить сегфолт.

NeOlip ★★ ()

На работе у меня debian стоит, такой вот код вызывал сегфолт:

php -r 'function test() {test();}; test();'

При том что вот сейчас на арче как и положено, виртуальная машина убивает себя по достижению лимита памяти.

shooter93 ★★ ()
Ответ на: комментарий от deep-purple
(gdb) run -n -r 'function x() { return x(); } x();'     
Starting program: /usr/bin/php7.0 -n -r 'function x() { return x(); } x();'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x00005555557a2a89 in dtrace_execute_ex ()
(gdb) bt
#0  0x00005555557a2a89 in dtrace_execute_ex ()
#1  0x0000555555837a7d in ?? ()
#2  0x00005555557f2f8b in execute_ex ()
#3  0x00005555557a2b31 in dtrace_execute_ex ()
...
#51938 0x00005555557f2f8b in execute_ex ()
#51939 0x00005555557a2b31 in dtrace_execute_ex ()
#51940 0x0000555555837a7d in ?? ()
...
(gdb) bt -1
#112235 0x00005555556380a4 in main ()

Т.е. здесь php упирается не в memory_limit, а в ulimit -s

sjinks ★★★ ()
Последнее исправление: sjinks (всего исправлений: 1 )
Ответ на: комментарий от NeOlip

Не знаю точно как это происходит

Там всё просто: каждому вызову функции соответствует вызов zend_execute(). Установить максимальную глубину стека вызовов достаточно тривиально: достаточно повесить свой обработчик на zend_execute_internal, при входе в обработчик увеличивать внутренний счётчик вложенности, при выходе — уменьшать. При достижении лимита — вызывать zend_error(E_ERROR, «Maximum function nesting level reached»).

sjinks ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.