Гость
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Stack Overflow / 19 сообщений из 19, страница 1 из 1
06.09.2021, 14:25
    #40095483
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Почему при Subj программа просто слетает, вместо того чтобы выдать сообщение и работать дальше?
...
Рейтинг: 0 / 0
06.09.2021, 14:28
    #40095485
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Потому что невозможно работать без свободного места в стэке, это слишком важная
часть памяти.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
06.09.2021, 14:35
    #40095488
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Но исключение предусмотрено и оно генерируется. Если бы было невозможно - поставили бы Halt.
Почему не происходит откат по стеку с его освобождением?
...
Рейтинг: 0 / 0
06.09.2021, 14:49
    #40095495
Barmaley57
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Maxim Rusov
Почему не происходит откат по стеку с его освобождением?
А как его откатывать?
...
Рейтинг: 0 / 0
06.09.2021, 15:07
    #40095500
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Как обычно. Если я на N-й глубине вложенности сгенерирую исключение - все корректно откатывается. А если на N+1 возникает Stack Overflow - краш. Почему?
...
Рейтинг: 0 / 0
06.09.2021, 15:38
    #40095512
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Maxim RusovА если на N+1 возникает Stack Overflow - краш.
Какой именно краш?
...
Рейтинг: 0 / 0
06.09.2021, 15:40
    #40095513
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Процесс слетает
...
Рейтинг: 0 / 0
06.09.2021, 16:07
    #40095523
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Молча? Так не бывает. Хоть что-то да должно остаться в Windows Events.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
06.09.2021, 16:16
    #40095528
Barmaley57
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Maxim Rusov
Как обычно. Если я на N-й глубине вложенности сгенерирую исключение - все корректно откатывается. А если на N+1 возникает Stack Overflow - краш. Почему?
Фрейм исключения пушится в стек. А стек закончился. При нормальном раскладе ОСь раскручивает цепочку EXCEPTION_RECORD's с помощью RtlUnwind. Но этой функции нужно за что-то зацепиться. А это что-то не влезло в стек...
...
Рейтинг: 0 / 0
06.09.2021, 16:23
    #40095531
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Ограниченно понял. Оказывается, когда попадаем в корневой Except стек еще не освободился, и мы получаем повторную ошибку при обработке исключения. Стек освобождается при завершении Except блока в DoneExcept (x86)

Т.е. ошибку можно обойти как то так:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
  procedure LocRecursion(aLevel :Integer);
  begin
//  Trace('%d', [aLevel]);
    if aLevel > 0 then
      LocRecursion(aLevel - 1);
  end;


  procedure TAboutBox1.Button1Click(Sender: TObject);
  var
    vStackOverflow :Boolean;
  begin
    vStackOverflow := False;
    try
      Trace('Run...');
      LocRecursion(100000);
      Trace('  done');
    except
      on E :EStackOverflow do
        vStackOverflow := True;
      on E :Exception do
        raise;
    end;

    if vStackOverflow then
      raise EStackOverflow.CreateRes(@SStackOverflow);
  end;



Но при повторном вызове - все плохо, Stack Overflow уже не возникает, возникает AV и программа все-таки слетает. Странно все это...
...
Рейтинг: 0 / 0
06.09.2021, 16:46
    #40095542
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Нашел такое:

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=msvc-160

Интересно, это может помочь? И как это можно из Delphi вызвать?...
...
Рейтинг: 0 / 0
06.09.2021, 18:36
    #40095573
GunSmoker
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Тут надо понять принцип возбуждения Stack Overflow. У стека потока в конце ставится специальная страница, которая имеет атрибут доступа GUARD. Когда поток исчерпает свободное место в стеке - он попытается записать в эту страницу. В результате страница будет выделена обычным образом, а атрибут GUARD будет перенесён на следующую страницу. Но если эта страница была последней - то атрибут GUARD переносить некуда, ибо там будет зарезервированное место с NO_ACCESS. И когда такое случается - возбуждается исключение Stack Overflow.

При этом картина следующая: стек выделен полностью и ограничен с двух сторон страницами с NO_ACCESS. Страницы с GUARD нет. А управление передаётся на обработчик Stack Overflow, который начинает работать в только что выделенной странице.

Предполагается, что приложение продолжит выполнение и обработает Stack Overflow. В типичных приложениях Delphi это приведёт к ShowMessage('Stack Overflow'). Однако, если для обработки исключения приложению потребуется больше памяти на стеке, чем там осталось (одна страница, 4 кб) - то в итоге оно попробует записать в область памяти с NO_ACCESS. В ответ на это система в конечном итоге закроет приложение извне.

Упражнение: что не так с Delphi-вым GetModuleName с его статичным буфером в 520 байт для имени?

Аналогичное справедливо, если исключение Stack Overflow было обработано, но приложение продолжило выполняться дальше и снова попало в ситуацию, когда должно было бы возбуждаться второе Stack Overflow. Но поскольку GUARD-страницы больше нет, то и возбуждать нечего. В итоге приложение снова упадёт с фатальным Access Violation при попытке записи в NO_ACCESS.

Аналог _resetstkoflw на Delphi выглядит примерно так:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
function _resetstkoflw: Boolean;
const
  MIN_STACK_REQ_WINNT = 2; // in pages

var
  pStack, pStackBase, pMaxGuard, pMinGuard: PAddress;
  mbi: TMemoryBasicInformation;
  si: TSystemInfo;
  PageSize: DWORD;
  RegionSize: DWORD;
  flOldProtect: DWORD;
  StackSizeInBytes: ULONG;
begin
  Result := False;
  pStack := GetStackPointer;

  // Find the base of the stack.
  if VirtualQuery(pStack, mbi, SizeOf(mbi)) = 0 then
    Exit;

  pStackBase := PAddress(mbi.AllocationBase);

  GetSystemInfo(si);
  PageSize := si.dwPageSize;
  RegionSize := 0;
  StackSizeInBytes := 0;               // Indicate just querying
  if Pointer(@GSetThreadStackGuarantee) = nil then
  begin
    GSetThreadStackGuarantee := GetProcAddress(LibKernel32, 'SetThreadStackGuarantee');
    if not Assigned(@GSetThreadStackGuarantee) then
      GSetThreadStackGuarantee := Pointer(1);
  end;
  if AssignedEx(Pointer(@GSetThreadStackGuarantee)) then
  begin
    if GSetThreadStackGuarantee(StackSizeInBytes) then
      RegionSize := StackSizeInBytes;
  end;

  {$R-}
  {$Q-}
  // Silence prefast about overflow/underflow
  RegionSize := PtrUInt(RegionSize + PageSize - 1) and (not PtrUInt(PageSize - 1));
  {$IFDEF OVERFLOWCHECKS_ON}{$Q+}{$ENDIF}
  {$IFDEF RANGECHECKS_ON}{$R+}{$ENDIF}

  //
  // If there is a stack guarantee (RegionSize nonzero), then increase
  // our guard page size by 1 so that even a subsequent fault that occurs
  // midway (instead of at the beginning) through the first guard page
  // will have the extra page to preserve the guarantee.
  //

  if RegionSize <> 0 then
    RegionSize := RegionSize + PageSize;

  if RegionSize < MIN_STACK_REQ_WINNT * PageSize then
    RegionSize := MIN_STACK_REQ_WINNT * PageSize;

  //
  // Find the page(s) just below where the stack pointer currently points.
  // This is the highest potential guard page.
  //

  {$R-}
  {$Q-}
  pMaxGuard := Pointer((PtrUInt(pStack) and (not (PtrUInt(PageSize) - 1))) - RegionSize);
  {$IFDEF OVERFLOWCHECKS_ON}{$Q+}{$ENDIF}
  {$IFDEF RANGECHECKS_ON}{$R+}{$ENDIF}

  //
  // If the potential guard page is too close to the start of the stack
  // region, abandon the reset effort for lack of space.  Win9x has a
  // larger reserved stack requirement.
  //

  pMinGuard := pStackBase + PageSize;

  if pMaxGuard < pMinGuard then
    Exit;

  // Set the new guard page just below the current stack page.

  if (VirtualAlloc(pMaxGuard, RegionSize, MEM_COMMIT, PAGE_READWRITE) = nil) or
     (not VirtualProtect(pMaxGuard, RegionSize, PAGE_READWRITE or PAGE_GUARD, flOldProtect)) then
    Exit;

  Result := True;
end;


Она может помочь во втором случае, но не может помочь в первом, потому что она, по сути, просто восстанавливает GUARD-страницу, но только при условии, что для этого есть место .

Дело в том, что _resetstkoflw можно использовать только следующим образом:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
NeedReset := False;
try
  Recursive(Recurse);
except
  on EStackOverflow do
  begin
    // Здесь нужно использовать минимум стека,
	// потому что у нас есть всего одна страница (4 кб).
    // Тут _resetstkoflw вызывать нельзя, 
	// потому что стек ещё не раскручен.
	// В настоящее время мы выполняемся в том месте,
	// куда нужно поместить GUARD-страницу, 
	// т.е. сейчас это сделать невозможно.
    // Поэтому вместо этого просто отметимся.
    NeedReset := True;
  end;
end;

// А здесь стек уже отмотался назад 
// и у нас есть гораздо больше места.
// Последняя страница теперь свободна 
// и её можно конвертировать в GUARD.
if NeedReset and (ac >= 2) then
  Result := _resetstkoflw;

// Если _resetstkoflw не смог создать GUARD-страницу,
// то дальнейшее выполнение небезопасно.
// Нужно максимально быстро корректно выйти из приложения.
if not Result then
begin
  Halt(3);
  Exit;
end;
...
Рейтинг: 0 / 0
06.09.2021, 18:49
    #40095575
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
GunSmoker,

Да, спасибо. Уже сам спортировал _resetstkoflw, а нужно было просто подождать :)

Такой код теперь работает стабильно:

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
  procedure LocRecursion(aLevel :Integer);
  begin
//   Trace('%d', [aLevel]);
    if aLevel > 0 then
      LocRecursion(aLevel - 1);
  end;


  procedure TAboutBox1.Button1Click(Sender: TObject);
  var
    vStackOverflow :Boolean;
  begin
    vStackOverflow := False;
    try
      Trace('Run...');
      LocRecursion(100000);
      Trace('  done');
    except
      on E :EStackOverflow do
        vStackOverflow := True;
      on E :Exception do
        raise;
    end;

    if vStackOverflow then begin
      ResetStkOflw;
      raise EStackOverflow.CreateRes(@SStackOverflow);
    end;
  end;
...
Рейтинг: 0 / 0
06.09.2021, 18:57
    #40095577
O_O_P
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
А почему при старте приложения не вызывать SetThreadStackGuarantee со значением, например, 16K?
MSDN прямо в первом предложении к описанию функции как бы намекает, что при Stack Overflow столько будет доступно как раз на вызов исключения.
Или нет?
...
Рейтинг: 0 / 0
06.09.2021, 19:08
    #40095578
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
O_O_P,

Но ResetStkOflw все равно делать нужно, там дальше написано
...
Рейтинг: 0 / 0
07.09.2021, 14:07
    #40095740
Maxim Rusov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Кстати, до кучи

GunSmoker
Дело в том, что _resetstkoflw можно использовать только следующим образом:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
try
  Recursive(Recurse);
except
  on EStackOverflow do
  begin
    // Здесь нужно использовать минимум стека,
	// потому что у нас есть всего одна страница (4 кб).
    // Тут _resetstkoflw вызывать нельзя, 
	// потому что стек ещё не раскручен.
	// В настоящее время мы выполняемся в том месте,
	// куда нужно поместить GUARD-страницу, 
	// т.е. сейчас это сделать невозможно.
  end;
end;


Отквоченное относится только к x86. В x64 стек освобождается и _resetstkoflw в except вызывать можно.
...
Рейтинг: 0 / 0
07.09.2021, 16:31
    #40095783
GunSmoker
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Да, к счастью в x64 - табличные исключения, указатели на обработчики исключкений хранятся в отдельной секции файла, не в стеке, поэтому там нет никаких проблем размотать стек сразу. Но в x86 указатели на обработчики исключений находятся в стеке, поэтому стек отмотать нельзя, пока обработка исключения не будет завершена. В противном случае (если стек размотать сразу) обработчик исключения начнёт выполняться и затрёт указатели на следующие обработчики.
...
Рейтинг: 0 / 0
07.09.2021, 17:48
    #40095806
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
А вроде бы везде пишется, что и там и там Delphi применяет SEH, а не
какой-нибудь SJLJ.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
07.09.2021, 18:13
    #40095810
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Stack Overflow
Dimitry Sibiryakov,

он и есть, просто для 32-битного режима стандарта нет и все делают как хотят
борланд исторически стек использовал для хранения цепочек try

в 64-битном формате мелкософт эту "недоработку" устранил, впрочем, как уже заведено "через одно место"
...
Рейтинг: 0 / 0
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Stack Overflow / 19 сообщений из 19, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]