На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
  
> Что бы такое придумать, что бы определить причину зависания галвного потока?
    Есть большой проект. В нем есть и потоки, и COM-объекты.
    С этими ком объектами работает IE. Точнее не IE а сторонняя программа SiteKiosk, которая использует IE в качестве движка. Приложение exe, т.е. COM-сервер outproc.

    При переходе на Delphi 2010 появился странный баг. Некоторые, я подчеркиваю, некоторые, компьютеры (их много) работают нормально, а некоторые (их примерно столько же, сколько и первых) зависают через какое-то время работы. Особенно все усложняется тем, что к проблемным (да и не к проблемным) компам есть только удаленный доступ через RAdmin. Тестовые, которые есть рядом работают нормально.

    SiteKiosk (SK) это оболочка для интернет киосков, которая перехватывает управление показывает только html странички в полный экран. Это если очень грубо. Эти странички используют мои activex. Так же сам SK экспортирует COM-объекты, через которые им можно управлять программно. Что и делает мое приложение. В частнотси постоянно проверяет, на каком адресе находится SK.

    Эффект такой. SiteKiosk виснет. Если снять мое приложение, то SK отлипает и продолжает работать. Если снять SK мое приложение не восстанавливается.

    Искал дедлоки. Не нашел. Искал долго и тщательно. В обычных потоках дедлоков нет. Возможно они связаны с COM. Тут уже сложнее...

    Ничего общего между такими компьютерами не удалось найти. Искал в версиях windows, версиях dll (в том числе системных), сравнивал количество процессоров\ядер.
    В системном журнале ничего особенного то же нет.

    Ставил отладчик на "глючащий комп". Смотрел. Сначала показалось, что зависает внутри компонентов Indy. TidHTTP создавался в отдельном потоке и часто уничтожался и создавался заново. Вис внутри метода get. Заменил все Indy компоненты на рукописный клиент на голых сокетах. Стал сравнивать и смотреть под отладчиком. Оказалось, что не работает цикл выборки сообщений Application.Run. Т.е. в этом цикле никогда не срабатывают точки останова. Для проверки ставил точки останова в обработчики TTimer ов. В рабочем состоянии они срабатывают, в зависшем - нет.
    Стэк главного потока в общем ничего особого не показывает. Остальные потоки работают и выполняют свои функции.

    ProcessExplorer показывает это:
    Цитата

    ntkrnlpa.exe+0x6d98b
    ntkrnlpa.exe+0x2b2b6
    ntkrnlpa.exe+0x2bb35
    ntkrnlpa.exe+0x13d7e7
    ntkrnlpa.exe+0x6960c
    ntdll.dll!KiFastSystemCallRet
    kernel32.dll!Sleep+0xf
    borlndmm.dll!Borlndmm+0xaa


    Причем переключения на поток происходят.

    Стек из под отладчика главного потока приложения сейчас не могу точно привести, никак что-то зависание не поймаю на компе, где стоит отладчик но в нем примерно тоже самое,за исключением строк ntkrnlpa.exe.
    Т.е. три последние строки.

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

    Так же PrcessExplorer показывает почему-то две запущенные копии exe. С разными типами маппинга.
    Например зависший вариант:
    user posted image
    Работающий вариант:
    user posted image
    Что бы это могло значить? Откуда вторая копия?

    не знаю, может ли относиться к делу, но вот еще есть такой стек (ProcessExplorer) для потока "ole32.dll!CoGetMalloc + 0x147"
    Цитата

    ntkrnlpa.exe+0x6d98b
    ntkrnlpa.exe+0x2b2b6
    ntkrnlpa.exe+0x2bb35
    win32k.sys+0x2ec4
    win32k.sys+0x1aa8
    win32k.sys+0xf106
    ntkrnlpa.exe+0x6960c
    ntdll.dll!KiFastSystemCallRet
    ole32.dll!CoFreeUnusedLibrariesEx+0x214
    ole32.dll!CoGetObject+0x2ccd
    ole32.dll!CoGetObject+0x2c00
    ole32.dll!CoGetMalloc+0xfa
    kernel32.dll!GetModuleFileNameA+0x1b4


    В принципе вызов последних процедур у них совпадает. Хотя, практически у всех потов к конце эти строки есть. В той или иной комбинации...

    В общем я че-то даже не знаю, что еще можно придумать, что бы если не исправить, так хоть найти причину?
    Может кто чего дельного сможет посоветовать?
    Сообщение отредактировано: Felan -
    // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
      если честно - жуть какая-то.

      по поводу ole32.dll
      Есть приложение - делает заковыристый запрос в базу данных и выводит отчет в Excel через Ole Automation.
      Запрос выполняется долго (порядка 10-15 мин), поэтому все вынес в дополнительный поток.
      Провели недавно обновления офиса 2003 мелкомягких, чтоб понимал новый формат. Прога стала вылетать, причем без всяких ошибок. Версия библиотеки вроде бы и не поменялась :(

      может и эта капля информации поможет!
      "Помилуйте, королева, — прохрипел он, — разве я позволил бы себе налить даме водки? Это чистый спирт!"
        Цитата Light13 @
        если честно - жуть какая-то.

        Да не то слово! :)

        Офис вроде нигде не используется у меня... хотя, вроде там было что-то с акссесом... надо будет покопать.

        В продолжение темы.

        Нет никакой стабильной периодичности в зависаниях. Что наводит на мысль, что зависает в случае выполнение каких то действий. Вот как бы их поймать...
        Логировать все подряд уже пробовал. Пока не помогло.
        // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
          По первому стеку: вы попробуйте несколько раз подряд получить стек. Если в нём всегда будет borlndmm.dll!Borlndmm+0xaa (и почти всегда Sleep), то я даже знаю, что это такое :)

          Это - memory corruption. Ваш код повредил управляющие структуры менеджера памяти (установив признак "занят" на свободный блок памяти), и теперь менеджер памяти пытается вечно ожидать освобождения никем не занятого участка памяти.

          Как лечить. Кратко: для начала - использовать отладочный менеджер памяти.
          Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
            Цитата CodeMonkey @
            вы попробуйте несколько раз подряд получить стек. Если в нём всегда будет borlndmm.dll!Borlndmm+0xaa (и почти всегда Sleep)

            Вот стек с другой повисшей машины. Много раз его смотрел process eplorer'ом с различными промежутками времени... Вот небольшая выборка. Других значений вроде не было.
            Скрытый текст

            Цитата

            ntkrnlpa.exe+0x6997f
            ntkrnlpa.exe+0xf5770
            ntkrnlpa.exe+0x25fbf
            ntkrnlpa.exe+0x29198
            ntkrnlpa.exe+0x1342f5
            ntkrnlpa.exe+0x65808
            ntdll.dll!KiFastSystemCallRet
            kernel32.dll!Sleep+0xf
            borlndmm.dll!Borlndmm+0xb5


            ntkrnlpa.exe+0x6997f
            ntkrnlpa.exe+0xf5770
            ntkrnlpa.exe+0x25fbf
            ntkrnlpa.exe+0x29198
            ntkrnlpa.exe+0x1342f5
            ntkrnlpa.exe+0x65808
            ntdll.dll!KiFastSystemCallRet
            kernel32.dll!Sleep+0xf
            borlndmm.dll!Borlndmm+0xb5


            ntkrnlpa.exe+0x6997f
            ntkrnlpa.exe+0xf5770
            ntkrnlpa.exe+0x25fbf
            ntkrnlpa.exe+0x29198
            ntkrnlpa.exe+0x1342f5
            ntkrnlpa.exe+0x65808
            ntdll.dll!KiFastSystemCallRet
            kernel32.dll!Sleep+0xf
            borlndmm.dll!Borlndmm+0x9b


            ntkrnlpa.exe+0x6997f
            ntkrnlpa.exe+0xf5770
            ntkrnlpa.exe+0x25fbf
            ntkrnlpa.exe+0x29198
            ntkrnlpa.exe+0x1342f5
            ntkrnlpa.exe+0x65808
            ntdll.dll!KiFastSystemCallRet
            kernel32.dll!Sleep+0xf
            borlndmm.dll!Borlndmm+0x9b


            ntkrnlpa.exe+0x6997f
            ntkrnlpa.exe+0xf5770
            ntkrnlpa.exe+0x25fbf
            ntkrnlpa.exe+0x29198
            ntkrnlpa.exe+0x1342f5
            ntkrnlpa.exe+0x65808
            ntdll.dll!KiFastSystemCallRet
            kernel32.dll!Sleep+0xf
            borlndmm.dll!Borlndmm+0xb5


            Т.е. да. Всегда есть Sleep и всегда есть borlndmm.dll!Borlndmm.
            Но смещение иногда меняется. От чего это зависит?

            Еще не мог бы объяснить, на основании чего сделан вывод о том, что это memory corruption? Это я не к тому, что не верю, а к тому, что бы понимать на что смотреть, может мысль какая подвернется... да и на будущее...
            // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
              Ну это не однозначный вывод, конечно же! :D А предположение.

              Просто я такое уже не раз видел. И в 90% случаев предположение о порче памяти подтверждается.

              Просто, если вы посмотрите в исходники FastMM, то увидите такие строки:
              ExpandedWrap disabled
                procedure LockAllSmallBlockTypes;
                var
                  LInd: Cardinal;
                begin
                  if IsMultiThread then
                  begin
                    for LInd := 0 to NumSmallBlockTypes - 1 do
                    begin
                      while LockCmpxchg(0, 1, @SmallBlockTypes[LInd].BlockTypeLocked) <> 0 do
                      begin
                        if not NeverSleepOnMMThreadContention then
                        begin
                          Sleep(InitialSleepTime);
                          if LockCmpxchg(0, 1, @SmallBlockTypes[LInd].BlockTypeLocked) = 0 then
                            Break;
                          Sleep(AdditionalSleepTime);
                        end;
                      end;
                    end;
                  end;
                end;


              и такие:

              ExpandedWrap disabled
                    {Lock the block type}
                    if IsMultiThread then
                    begin
                      while True do
                      begin
                        {Try to lock the small block type}
                        if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
                          Break;
                        {Try the next block type}
                        Inc(Cardinal(LPSmallBlockType), SizeOf(TSmallBlockType));
                        if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
                          Break;
                        {Try up to two sizes past the requested size}
                        Inc(Cardinal(LPSmallBlockType), SizeOf(TSmallBlockType));
                        if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
                          Break;
                        {All three sizes locked - given up and sleep}
                        Dec(Cardinal(LPSmallBlockType), 2 * SizeOf(TSmallBlockType));
                        if not NeverSleepOnMMThreadContention then
                        begin
                          {Both this block type and the next is in use: sleep}
                          Sleep(InitialSleepTime);
                          {Try the lock again}
                          if LockCmpxchg(0, 1, @LPSmallBlockType.BlockTypeLocked) = 0 then
                            Break;
                          {Sleep longer}
                          Sleep(AdditionalSleepTime);
                        end;
                      end;
                    end;


              и такие:

              ExpandedWrap disabled
                  @LockBlockTypeLoop:
                  mov eax, $100
                  {Attempt to grab the block type}
                  lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
                  je @GotLockOnSmallBlockType
                  {Try the next size}
                  add ebx, Type(TSmallBlockType)
                  mov eax, $100
                  lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
                  je @GotLockOnSmallBlockType
                  {Try the next size (up to two sizes larger)}
                  add ebx, Type(TSmallBlockType)
                  mov eax, $100
                  lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
                  je @GotLockOnSmallBlockType
                  {Block type and two sizes larger are all locked - sleep and/or retry}
                  sub ebx, 2 * Type(TSmallBlockType)
                  {The pause instruction improves spinlock performance}
                  pause
                  {"Busy waiting" or "sleep and retry" strategy?}
                  cmp NeverSleepOnMMThreadContention, 0
                  jne @LockBlockTypeLoop
                  {Couldn't grab the block type - sleep and try again}
                  push InitialSleepTime
                  call Sleep
                  {Try again}
                  mov eax, $100
                  {Attempt to grab the block type}
                  lock cmpxchg TSmallBlockType([ebx]).BlockTypeLocked, ah
                  je @GotLockOnSmallBlockType
                  {Couldn't grab the block type - sleep and try again}
                  push AdditionalSleepTime
                  call Sleep
                  {Try again}
                  jmp @LockBlockTypeLoop


              И аналогичные...

              Как видите, во многих случаях FastMM работает по схеме (грубо):

              ExpandedWrap disabled
                while Busy do
                  Sleep;
                Busy := True;
                // Do the work
                Busy := False;


              Сделано это для быстроты работы, потому что ресурсы блокируются на очень короткий промежуток времени, и ждать их через объект ядра не имеет смысла.

              Откуда следует, что если ты видишь бесконечный цикл со Sleep, то это значит, что флаг, который проверяется, не соответствует действительности - испорчен. Произойти это может только в одном из двух случаев:

              1. Явная порча памяти по адресу флага. Переполнение буфера испортило флаг. Туда записали что-то, отличное от 0.
              2. Вылет функции менеджера памяти ранее. Поскольку в FastMM нет try/finally (видимо, опять же, для скорости), то выброс исключения на этапе "Do the work" приведёт к тому, что флаг не будет сброшен. Выброс исключения, понятно, не штатная ситуация и происходит в результате, опять-таки, повреждения памяти. В целом это приводит к тому, что следующий же вызов функции менеджера памяти, работающей с этим же флагом, приведёт к бесконечному ожиданию.

              Цитата Felan @
              От чего это зависит?

              От конкретного куска кода. Как видно из примеров выше, Sleep вызывается во многих местах - смотря какую блокировку запрашивает FastMM. Без исходников borlndmm сказать что-то более определённое нельзя. Можно пересобрать её, добавив отладочную информацию. А потом сконвертить map-файл в что-то, принимаемое Process Explorer-ом.
              Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                Вот, стек под отладчиком делфи для главного потока:

                Цитата
                :7c90eb94 ntdll.KiFastSystemCallRet
                :7c90d85c ntdll.ZwDelayExecution + 0xc
                :7c802451 kernel32.Sleep + 0f


                Всегда такой... Если заморозить все потоки кроме главного, то пошагово он крутиться в этих функциях...
                // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                  Че-то я похоже не совсем понимаю...
                  Почему-то не могу найти выполняющуюся процедуру в map файле по инфе от process explorer...

                  Делаю так:

                  Вижу в окне с длл-ками:

                  KioskFeedBackModule.dll бла-бла ImageBase: 0xA00000 Base: 0xA6B0000.

                  Т.е. моя длл-ка имеет базовый адрес $A00000, но загружена с адреса $A6B0000. Ладно. Хотя в принципе по идее это и не важно. Дальше то все равно идут смещения...

                  Открываю свойства процесса, закладку Threads. Вижу список потоков приложения. Около них находится смещение.
                  Типа KioskFeedBackModule.dll + 0x6144.
                  Т.е. это говорит о том, что поток выполняет команды в модуле MyModule, а точка входа в модуль имеет смещение $6144.

                  Хорошо.

                  Открываем окно Stack for thread (жмем кнопку Stack).

                  Цитата

                  ntoskrnl.exe+0x5d0d
                  ntoskrnl.exe+0x1615a9
                  ntoskrnl.exe+0x1649a
                  ntoskrnl.exe+0x164d9
                  ntoskrnl.exe+0x98efc
                  ntoskrnl.exe+0x6f0f
                  ntdll.dll!KiFastSystemCallRet
                  kernel32.dll!Sleep+0xf
                  borlndmm.dll!Borlndmm+0xb5
                  KioskService2.exe+0x150b6c
                  KioskFeedBackModule.dll+0x129b9d
                  KioskFeedBackModule.dll+0x128b82
                  KioskFeedBackModule.dll+0x12818d
                  KioskFeedBackModule.dll+0x45755
                  KioskFeedBackModule.dll+0x616e
                  kernel32.dll!GetModuleFileNameA+0x1b4


                  Видим список вызываемых модулей после входа в наш модуль и смещения, точек входа.
                  Напирмер KioskFeedBackModule.dll+0x129b9d (последняя перед входом в модуль KioskService2.exe).

                  Т.е. у нас получается если взять смещение $129b9d, то в подробном map файле можно найти функцию, которой соответствует эта строчка в стеке.

                  Но в map файле нет такого смещения. Так же там нет и смещения $6144 + $129b9d.

                  Что я не так считаю?
                  // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                    Так я и не разобрался, почему для exe есть две записи с различным типом мэппинга... и что это за типы...
                    // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                      Цитата Felan @
                      Т.е. у нас получается если взять смещение $129b9d, то в подробном map файле можно найти функцию, которой соответствует эта строчка в стеке.
                      Но в map файле нет такого смещения

                      Во-первых, ProcessExplorer выдает смещения относительно базового адреса загрузки dll, а map-файл относительно секции кода (CODE == .text), поэтому чтобы привести их в соотв-е нужно к смещением в мапе добавить смещение секции кода относительно базы (обычно $1000 на PE-заголовок).
                      Во-вторых, нужно учитывать, что смещения в стеке вызовов это точки возврата, находящиеся внутри каких-то функций, а не точки входа самих функций. И к тому же, раскрутка стека не всегда может выполняться правильно и после какого-то значения м.б. просто мусор. Ты же не думаешь, что все твои функции вызываются из kernel32.dll!GetModuleFileNameA ?! ;) Также и твоя dll может вызывать ф-ю из KioskService2.exe только в случае передачи в dll какой-то callback функции приложения (обработчика события или вызова метода класса\интерфейса) - это действительно так ?
                        Цитата leo @
                        Во-вторых, нужно учитывать, что смещения в стеке вызовов это точки возврата, находящиеся внутри каких-то функций, а не точки входа самих функций.

                        Не понял... Что с ними делать то тогда надо?


                        Цитата leo @
                        что все твои функции вызываются из kernel32.dll!GetModuleFileNameA ?! ;)

                        А я че-то удивлялся, откуда на взялась вообще... А как-то можно отделить мусор от не мусора? В том же Process Explorer?


                        Цитата leo @
                        Также и твоя dll может вызывать ф-ю из KioskService2.exe только в случае передачи в dll какой-то callback функции приложения (обработчика события или вызова метода класса\интерфейса) - это действительно так ?

                        Да, это так.
                        Из exe в dll передаются интерфейсы, методы которых вызываются из длл.
                        // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                          Felan, попробуй утилитку :)

                          Добавлено
                          И посмотри ещё вот это (сессия "How to find the exception source line").

                          Добавлено
                          Цитата Felan @
                          А я че-то удивлялся, откуда на взялась вообще... А как-то можно отделить мусор от не мусора? В том же Process Explorer?

                          Телепатией. Серьёзно.
                          Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                            Цитата CodeMonkey @
                            Felan, попробуй утилитку :)

                            Попробовал. В приложении ее вывод.
                            Честно говоря не совсем понял, куда смотреть. В отличии от process explorer даже не понятно, к какой длл относится стек... Ну или я недопонимаю чего-то...


                            Цитата CodeMonkey @
                            И посмотри ещё вот это (сессия "How to find the exception source line").

                            Посмотрел. В вроде ничего нового не открыл :( Хотя, хорошо, что теперь это есть явно прописанное в одно месте :)


                            Цитата CodeMonkey @
                            Телепатией. Серьёзно.

                            Этого я и боялся.
                            Прикреплённый файлПрикреплённый файлCallStack_v495_197.zip (6.1 Кбайт, скачиваний: 104)
                            // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                              Вот с другой машины. Подумал, надо бы сделать несколько дампов с небольшими промежутками...
                              Вот сделал три раза с разницей в несколько секунд... ну сильно примерно. Как рука взяла, короче...

                              Сравниваю файлы по содержимому, а они практически одинаковые. Т.е. насколько я понимаю, все прочно висит...
                              Прикреплённый файлПрикреплённый файлCallStack_v294_217.zip (18.25 Кбайт, скачиваний: 74)
                              // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                Ой как у тебя там страшно. Куча потоков, COM, WinSock, ... ужас :)

                                В общем, что я увидел: несколько потоков встали на @Borlndmm@SysFreeMem$qqrpv. Я уже предлагал погонять под FastMM в отладочном режиме. Чего не пробуем?

                                Второе: у главного потока есть ссылка на CheckIniChange и получение настроек форматирования. У второго потока - на WideFormatBuf. Вопрос: не может ли у тебя быть проблема с синхронизацией доступа к TFormatSettings?

                                Третье: что-то у тебя вообще отсутствуют номера строки. Ты включил бы генерацию map-файла-то. Да и "Use Debug DCUs" включенной не помешает. А вот пакеты лучше отключать (хотя ты их не используешь вроде).
                                Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                  Цитата CodeMonkey @
                                  Ой как у тебя там страшно. Куча потоков, COM, WinSock, ... ужас :)

                                  Не то слово :)
                                  Самое неприятное в этом то, что все это превратилось в некоторую кашу, благодаря решениям которые были "спущены сверху". Т.е. пока я единолично решал что и где должно быть, было попроще... А теперь я сам путаюсь, что куда идет.
                                  Кстати, потоков именно моих не много. Всего штук 6. Остальные это главный и еще какие-то... Откуда они берутся я че-то даже теряюсь...

                                  Цитата CodeMonkey @
                                  В общем, что я увидел: несколько потоков встали на @Borlndmm@SysFreeMem$qqrpv. Я уже предлагал погонять под FastMM в отладочном режиме. Чего не пробуем?

                                  Руки не дошли. + Никогда с ним не работал, поэтому как-то откладываю... Надо будет начать видимо. JCL подключил, но как-то не впечатлился. Ну или опять же по недопониманию...

                                  Цитата CodeMonkey @
                                  Второе: у главного потока есть ссылка на CheckIniChange и получение настроек форматирования. У второго потока - на WideFormatBuf. Вопрос: не может ли у тебя быть проблема с синхронизацией доступа к TFormatSettings?

                                  Возможно. То, что используется точно, но вроде только на чтение... Да и в хелпе написано "TFormatSettings defines a thread-safe string formatting context. "
                                  В общем проверю.

                                  Цитата CodeMonkey @
                                  Третье: что-то у тебя вообще отсутствуют номера строки. Ты включил бы генерацию map-файла-то. Да и "Use Debug DCUs" включенной не помешает. А вот пакеты лучше отключать (хотя ты их не используешь вроде).

                                  Слетели настройки. Я сделал отдельный билд конфиг, в котором добавляется jcl, используется дебаг-дку, генерится полный мэп, добавляется Debug information, но она иногда слетает (фиг знает почему) а я не замечаю. Последняя сборка получилась так...

                                  В общем наверно теперь только завтра получится посмотреть что будет показываться при правильном конфиге...
                                  // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                    Цитата Felan @
                                    Остальные это главный и еще какие-то...

                                    Ну по стеку видно, что не твои. Может COM насоздавал для внутренних целей, а может ещё кто. В общем, их пока можно игнорировать, сосредоточившись на наших.

                                    Цитата Felan @
                                    Руки не дошли.

                                    Вот надо в первую очередь сделать.

                                    Цитата Felan @
                                    Никогда с ним не работал

                                    Введение в отладочные менеджеры памяти.

                                    Добавлено
                                    Также полезно "Поиск места ошибки" в чтение баг-отчётов.

                                    Добавлено
                                    Держи ещё - только закончил писать :)
                                    Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                      Почитал. Качнул. Прикрутил. Пока пробую всякое...

                                      Но возникло пару глупых вопросов напрямую не относящихся к поиску проблемы:

                                      1. Всегда ли поток отладчика последний в списке? Т.е. когда в IDE жмешь паузу.
                                      2. Подключил FastMM в режиме шаринга памяти между приложением и длл. Все вроде нормально. Попробовал на тестовом приложении.
                                      Перенес на свое и сразу получил messagebox с ошибкой. Вопрос, пока это сообщение висит, программа работает? Судя по иконке в трее она работает.
                                      3. После отладки, и решения проблемы, насколько я понимаю, можно и не возвращаться к "родному" менеджеру памяти? А просто оставить этот, только убрать
                                      мессажебоксы и полную отладку?
                                      4. Каким образом FastMM работает в режиме разделения памяти для приложения и длл без собственной длл а-ля borlndMM.dll?
                                      5. В поставке ФастММ есть библиотека BorlndMM.dll, которая есть в двух версиях для ide и для приложения. Я не понял, для чего она вообще нужна и почему в двух версиях?
                                      // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                        Цитата Felan @
                                        1. Всегда ли поток отладчика последний в списке? Т.е. когда в IDE жмешь паузу.

                                        Понятия не имею. Поток отладчика легко отличить по стеку, в котором нет твоих вызовов, зато полно слов типа Debug.

                                        P.S. Кроме того, именовать потоки - полезная практика, которой не следует пренебрегать.

                                        Цитата Felan @
                                        2. Подключил FastMM в режиме шаринга памяти между приложением и длл. Все вроде нормально. Попробовал на тестовом приложении.
                                        Перенес на свое и сразу получил messagebox с ошибкой. Вопрос, пока это сообщение висит, программа работает? Судя по иконке в трее она работает.

                                        Ошибка показывается обычным модальным MessageBox. Т.е. поток, где это было обнаружено - ждёт. Остальные крутятся. Пока висит один MessageBox, остальные (если будут) не показываются.

                                        Цитата Felan @
                                        3. После отладки, и решения проблемы, насколько я понимаю, можно и не возвращаться к "родному" менеджеру памяти? А просто оставить этот, только убрать
                                        мессажебоксы и полную отладку?

                                        Можно и даже нужно. Ибо в FastMM есть слово Fast и оно там не просто так :)

                                        Цитата Felan @
                                        4. Каким образом FastMM работает в режиме разделения памяти для приложения и длл без собственной длл а-ля borlndMM.dll?

                                        На базе PID процесса создаётся кусок разделяемой памяти с именем, куда записывается структура TMemoryManager(Ex). Остальные модули по PID находят эту память, проецируют, читают структуру и передают в свой SetMemoryManager.

                                        Цитата Felan @
                                        5. В поставке ФастММ есть библиотека BorlndMM.dll, которая есть в двух версиях для ide и для приложения. Я не понял, для чего она вообще нужна и почему в двух версиях?

                                        Она оставлена по соображениям совместимости. Т.е. если нет возможности пересобрать модуль под FastMM, то можно подсунуть другую DLL, в которой вместо дефолтного зашит FastMM. Можете игнорировать.
                                        Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                          Цитата CodeMonkey @
                                          P.S. Кроме того, именовать потоки - полезная практика, которой не следует пренебрегать.

                                          Всегда так делаю.
                                          Но, во-первых, в данном конкретном случае есть куча потоков которые не мои, а во вторых, когда делаешь аттач к процессу, имена потоков не отображаются.

                                          Вобщем понятно.

                                          Седня сразу же нашел одину ошибку с GetMem/FreeMem... Завтра выложу версии на проблемные компы. Надеюсь это и была она :)
                                          // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                            И никогда не стоит сбрасывать со счетов, что ошибка всего одна =))

                                            Имеет смысл вставлять в программу простенький чекер, который отправит вам отчёт, если будет что-то не так. FastMM на эту роль не очень подходит, а вот EurekaLog - вполне.

                                            Добавлено
                                            Цитата Felan @
                                            когда делаешь аттач к процессу, имена потоков не отображаются

                                            Это да, к сожалению :(((

                                            Просто имена в программе нигде не хранятся, а установка имени - это просто отправка команды отладчику. Т.е. если отладчик не приаттачен, то и имя уплывёт.
                                            Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                              Цитата CodeMonkey @
                                              а вот EurekaLog - вполне

                                              Самореклама? :))))) шучу :))))) Кстати никогда не пользовался, надо попробовать.
                                              Honesta mors turpi vita potior
                                                Цитата Rennigth @
                                                Самореклама? :)))))

                                                Нет, я бы предложил, даже если бы там не работал. Как я это делал года два назад. Примерно как я рекомендую AQTime в качестве профайлера. Просто с другими решениями не знаком.

                                                А что, есть другие варианты? :)
                                                Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                                  Цитата Rennigth @
                                                  Самореклама? :))))) шучу :))))) Кстати никогда не пользовался, надо попробовать.


                                                  Он платный. А значит не для меня. :(
                                                  // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                                    Цитата Felan @
                                                    Он платный.

                                                    Эх... жаль. Лень руководство уговаривать на новые расходы. :)
                                                    Honesta mors turpi vita potior
                                                      Наверно можно считать вопрос решенным. Т.к. при помощи FastMM4 удалось найти два места, где неправильно освобождалась память из-за того, что раньше PChar был 1 байт, а в D2010 стал 2 байта.

                                                      Спасибо всем телепатирующим :)
                                                      Особенно CodeMonkey +
                                                      // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                                        Итак, подробный рассказ о том что же все-таки это было. Специально по просьбе CodeMonkey[/b] :)

                                                        Для представления общей картины напомню, что это огромный проект, который состоит из исполняемого файла и кучи различных dll, в том числе и сторонних. Некоторые dll представляют собой in-proc COM-сервера, а так же исполняемый файл содержит 7 COM-объектов. Все это удовольствие работает в многопоточном окружении, только своих потоком создается 13 штук. По предназначено для круглосуточной работы и мониторинга.

                                                        Проблема возникла, когда проект перевели с Turbo Delphi на Delphi 2010. Проявлятся стала стабильным зависанием через 1-2 часа работы на большей части компьютеров, в то время как некоторые работали нормально.

                                                        Подозрения на дедлоки не оправдались. Лечили пирогами, и блинами, и сушёными грибами, а так же подменяли TCriticalSection на собственный TCriticalSectionLog, которая писала в лог-файл все входы и выходы из критических секций. Так же в логи писалось все, что только можно, подробнейшим образом, что бы попытаться найти хоть примерно место зависания. Трехнедельный разбор гигабайтных логов показал, что с той долей вероятности, с которой можно разобрать логи такого объема - дедлоков связанных с собственной синхронизацией НЕТ.

                                                        Стали прикручивать удаленную отладку. В отличии от удаленного отладчика Turbo Delphi удаленный отладчик Delphi 2010 заработал после отключения антивирусов. Удаленная отладка не дала вразумительных результатов, кроме того, что зависания происходят где-то внутри системных вызовов. Все стэки вызвово заканчивались во внутренностях ntdll.dll.

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

                                                        Было принято решение, установить на один из виснущих компов Delphi и запускать все под отладчиков. Поскольку все это удовольствие происходило удаленно, приходилось ждать зависания, а потом аттачиться к процессу. Особого эффекта это тоже не дало. Теперь было видно, имена функций стеке вызовов, но зависания были каждый раз "в разных местах" и весело практически все, за исключением некоторых потоков.

                                                        В итоге, было сделано предположение (CodeMonkey), что завсание происходит внутри менеджера памяти borlndmm.dll, который использовался для того, что бы можно было передавать длинные строки через границы dll. По его же совету был подключен альтернативный менеджер памяти FastMM4, который был настроен на полный отладочный режим.

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

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

                                                        Поставив точку останова в этой функции и запустим под отладчиком, без труда удалось убедиться, что разрушение памяти происходит действительно в ней, т.е. при выходе из нее...

                                                        Вот как выглядела эта функция:

                                                        ExpandedWrap disabled
                                                          function THardwareInfo.getCompName: string;
                                                          var
                                                            comp_name: PChar;
                                                            name_size: Cardinal;
                                                          begin
                                                            name_size := MAX_COMPUTERNAME_LENGTH + 1;
                                                            {$WARNINGS OFF}
                                                            GetMem( comp_name, name_size );
                                                            {$WARNINGS ON}
                                                            try
                                                              if GetComputerName( comp_name, name_size ) then
                                                                Result := Copy( comp_name, 0, Length( comp_name ) )
                                                            finally
                                                              {$WARNINGS OFF}
                                                              FreeMem( comp_name, name_size );
                                                              {$WARNINGS ON}
                                                            end;
                                                          end;


                                                        Тут и раньше была ошибка, т.к. name_size изменяемый параметр, и зачем вообще мне понадобилось указывать длину освобождаемой памяти (второй параметр freemem) не понятно.
                                                        Но, таки в TD это работало. Хотя, и приводило к утечке памяти... небольшой такой... :)

                                                        При переходе на D2010 тип PChar превратился из PAnsiChar в PWideChar, т.е. из указателя на байт, превратился в указатель на 2 байта, ну грубо говоря :)
                                                        Поэтому тут памяти стало освобождаться больше, чем выделялось. Тут я честно говоря немного теряюсь, надеюсь на комментарий CodeMonkey Основная проблема в том, что память выделяется в байтах, а освобождается в Char'ах.

                                                        Мне вообще не понятно, как это я сделал эту функцию на Getmem\freemem, везде в других местах для этого используется статический массив :) Хотя, возможно это был не я...

                                                        После исправления функции:

                                                        ExpandedWrap disabled
                                                          class function THardwareInfo.getCompName: string;
                                                          var
                                                            comp_name: PChar;
                                                            name_size: Dword;
                                                          begin
                                                            name_size := MAX_COMPUTERNAME_LENGTH + 1;
                                                            {$WARNINGS OFF}
                                                            GetMem( comp_name, name_size * SizeOf( Char ) );
                                                            ZeroMemory( comp_name, name_size * SizeOf( Char ) );
                                                            {$WARNINGS ON}
                                                            try
                                                              if GetComputerName( comp_name, name_size ) then
                                                                Result := comp_name;
                                                            finally
                                                              {$WARNINGS OFF}
                                                              FreeMem( comp_name );
                                                              {$WARNINGS ON}
                                                            end;
                                                          end;


                                                        Ошибка пропала, и зависания прекратились.

                                                        Остается загадкой, почему некоторые компьютеры так ни разу и не зависли в течении почти месяца. В этой связи вспоминается такая история (рассказываю по памяти):

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

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

                                                        Понятно, что машина не может различать сорта мороженного... И вообще выглядит это все странно... Так или иначе, был послан эксперт для проверки.

                                                        И все оказалось действительно так. Когда хозяин с экспертом проезжал мимо магазина и покупал ванильное мороженное все было хорошо, а когда шоколадное - машина не заводилась... Эксперту ничего не оставалось кроме как признать наличие проблемы, но согласиться с тем, что машина не любит шоколадное мороженное он никак не мог :)

                                                        После тщательных исследований и замеров временных интервалов выяснилось в чем дело.

                                                        Ванильное мороженное, как самое ходовое, было рядом с выходом, а шоколадное в глубине магазина. Так, что на его покупку уходило примерно в два раза больше времени. А у машины был какой-то дефект (я уже не помню, рассказываю по памяти :)), связанный с тем, что если заглушить двигатель, и подождать чуть-чуть, то там что-то куда-то конденсировалось, и он не заводился, а если подождать подольше, то конденсат высыхал и машина заводилась нормально. :)
                                                        Сообщение отредактировано: Felan -
                                                        // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                                          Комментарии к сказанному.

                                                          Цитата Felan @
                                                          Поэтому тут памяти стало освобождаться больше, чем выделялось.

                                                          Тут сделан неверный вывод из имеющихся данных. FreeMem игнорирует второй параметр и освобождает ровно столько, сколько было выделено.

                                                          Проблема сидит в вызове GetComputerName, которой было сказано, что у неё есть буфер в name_size * 2 байт (потому что она меряет в символах, а не в байтах), хотя в действительности было выделено только name_size байт - т.е. вдвое меньше. Вот она-то и портила память, записывая через буфер (к примеру, обнуляя его). Понятно, что выделение нужного объёма (name_size * SizeOf(Char) = name_size * 2 = указанному в GetComputerName) решило проблему.

                                                          Цитата Felan @
                                                          Остается загадкой, почему некоторые компьютеры так ни разу и не зависли в течении почти месяца.

                                                          Потому что окружение программы не детерминировано. Так выпали кубики. Так совпало, что на этих машинах за буфером для GetComputerName никогда не было выделенных блоков памяти, поэтому GetComputerName могла портить память, сколько ей было угодно.
                                                          Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                                            Цитата CodeMonkey @
                                                            Проблема сидит в вызове GetComputerName, которой было сказано, что у неё есть буфер в name_size * 2 байт (потому что она меряет в символах, а не в байтах), хотя в действительности было выделено только name_size байт - т.е. вдвое меньше. Вот она-то и портила память, записывая через буфер (к примеру, обнуляя его). Понятно, что выделение нужного объёма (name_size * SizeOf(Char) = name_size * 2 = указанному в GetComputerName) решило проблему.

                                                            Блин, точно... временами я сам удивляюсь своим заключениям :(
                                                            // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                                              Цитата Felan @
                                                              После исправления функции:

                                                              Это не исправление, а замена одного изврата другим, более изощренным и продвинутым :)
                                                              И кому интересно вообще пришла в голову выделять память динамически под строку длиной 15-16 байт вместо того, чтобы по простому обявить comp_name как локальный массив
                                                              var comp_name: array[0..MAX_COMPUTERNAME_LENGTH ] of Char ?!
                                                              Воистину - горе от ума ! :D
                                                              Сообщение отредактировано: leo -
                                                                Цитата CodeMonkey @
                                                                Проблема сидит в вызове GetComputerName, которой было сказано, что у неё есть буфер в name_size * 2 байт (потому что она меряет в символах, а не в байтах), хотя в действительности было выделено только name_size байт - т.е. вдвое меньше.


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

                                                                Цитата leo @
                                                                Это не исправление, а замена одного изврата другим, более изощренным и продвинутым

                                                                Э-нет. Это исправление. Потому как до этого она работала не правильно, а теперь стала работать правильно. А то, что способ не самый хороший, это отдельны вопрос.


                                                                Цитата leo @
                                                                И кому интересно вообще пришла в голову выделять память динамически под строку длиной 15-16 байт вместо того, чтобы по простому обявить comp_name как локальный массив
                                                                var comp_name: array[0..MAX_COMPUTERNAME_LENGTH ] of Char ?!
                                                                Воистину - горе от ума !

                                                                Согласен. И даже написал об этом выше, что обычно делаю именно так... Откуда взялся этот вариант, черт его знает. Есть вероятность, что это делал не я... хотя скорее всего конечно я... в состоянии аффекта наверное :)
                                                                // Когда у оппонента кончаются аргументы, он начинает уточнять национальность.
                                                                  Цитата Felan @
                                                                  Кстати, я вот сейчас подумал, что если бы имя моего компа было достаточно коротким, то мог бы и не найти ошибку...

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

                                                                  В любом случае, это та самая ситуация, когда для воспроизведения бага нужно иметь одновременно 15 маленьких кусочков. Надо и чтобы буфер переполнялся (а это либо длинное имя машины, либо функция должна его весь трогать), и чтобы сразу за буфером были чувствительные данные, и чтобы обращение к ним было во время работы. Короче, и то, и это, и всё сразу. Убери один из кусочков - и всё начнёт работать нормально, хотя баг в коде всё ещё есть.
                                                                  Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                                                    Почти детективная история! (но с хэппи-эндом). Мораль - FastMM в режиме FullDebugMode - безоговорочный маст хэв!
                                                                    Ну а также, что не стоит мудрить с кучей там, где нет в этом необходимости: вместо GetMem отлично послужит s: string; SetLength(s, MAX_COMP_LEN); GetCompName(@s[1],..) - плюс, освобождать память не нужно :)
                                                                    Сообщение отредактировано: Fr0sT -
                                                                    Codero ergo sum
                                                                    // Программирую — значит, существую
                                                                      Только замена GetMem в общем случае (как RAW буфера) - это всё же S: RawByteString.

                                                                      Цитата leo @
                                                                      И кому интересно вообще пришла в голову выделять память динамически под строку длиной 15-16 байт вместо того, чтобы по простому обявить comp_name как локальный массив
                                                                      var comp_name: array[0..MAX_COMPUTERNAME_LENGTH ] of Char ?!


                                                                      Кстати, замечу, что "по-простому" там могло быть написано и AnsiChar, и передаваться "по-новичковски хитро" с отрубанием проверок компилятора, и размеры стоять неправильные...

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

                                                                      Поэтому лично я всегда предпочитаю делать AllocMem - тогда любые баги с памятью будут диагностированы отладочным фильтром менеджера памяти с очень хорошей вероятностью. Понятно, что в любом правиле бывают исключения и не всегда динамическое выделение подходит, но в общем и целом мне кажется предпочтительнее динамическое выделение для любых "буфероподобных" данных. Даже если я уверен в своём коде, я не уверен за человека, которому он достанется - мало ли, что он намутит с моим кодом. Так уж лучше пусть будет AllocMem с отладочным режимом, чем статический буфер и срыв стека.
                                                                      Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                                                        Цитата CodeMonkey @
                                                                        Только замена GetMem в общем случае (как RAW буфера) - это всё же S: RawByteString.

                                                                        Ну, я имел в виду, что если где-то возвращается строка - то не стоит пихать в нее буфер байт, и наоборот (а то от буфера в виде Pchar тоже хорошего мало).
                                                                        Цитата CodeMonkey @
                                                                        Что тут важно - выполз за границы буфера на стеке автоматически детектировать не удастся, в отличие от динамической памяти.

                                                                        Выполз за границу строки с включенной проверкой массивов тоже, емнип, засекается
                                                                        Codero ergo sum
                                                                        // Программирую — значит, существую
                                                                          Цитата Fr0sT @
                                                                          Выполз за границу строки с включенной проверкой массивов тоже, емнип, засекается


                                                                          Верно, но это только если обращаться к элементу по индексу. А если это буфер, то мы не обращаемся по индексу, мы берём указатели, делаем Move и кучу других вещей.

                                                                          И, кстати, даже проверка индексов нормально работает только в новых версиях Delphi. В старых (включая D7) там есть баги реализации.
                                                                          Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
                                                                          0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                          0 пользователей:


                                                                          Рейтинг@Mail.ru
                                                                          [ Script Execution time: 0,3257 ]   [ 17 queries used ]   [ Generated: 21.03.19, 06:16 GMT ]