1

Тема: Поддержка исключений и RtlUnwind

Имеется в KolbiriOS какая-либо поддержка исключений для прикладных программ? Есть ли аналог RtlUnwind из Windows, раскручивающая стек? Описана ли функция в заголовках для Delphi?

2 (изменено: Freeman, 14.06.2020 в 17:16)

Re: Поддержка исключений и RtlUnwind

Freeman пишет:

поддержка исключений для прикладных программ

В документации по системным функциям есть такие функции

======================================================================
==== Функция 68, подфункция 24 - установить обработчик исключений. ===
======================================================================
Параметры:
  * eax = 68 - номер функции
  * ebx = 24 - номер подфункции
  * ecx = адрес нового обработчика исключений
  * edx = маска обрабатываемых исключений
Возвращаемое значение:
  * eax = адрес старого обработчика исключений (0, если не установлен)
  * ebx = маска старого обработчика исключений
Замечания:
  * Номер бита в маске исключений соответствует номеру исключения по
    спецификации на процессор (Intel-PC). Так, например, исключения
    FPU имеют номер 16 (#MF), а SSE - 19 (#XF).
  * В данной реализации игнорируется запрос на перехват исключения 7
    - система обрабатывает #NM самостоятельно.
  * Пользовательский обработчик получает номер исключения параметром
    в стеке. Поэтому правильный выход из обработчика: RET 4. Возврат
    при этом производится на команду, вызвавшую исключение.
  * При передаче управления обработчику исключений сбрасывается
    соответствующий бит в маске исключений. Возникновение этого же
    исключения впоследствии приведёт к умолчальной обработке такового.
    А именно: к завершению работы приложения в отсутствии отладчика,
    приостановка с уведомлением отлаживающего приложения иначе.
  * После завершения критических действий в обработчике пользователя
    восстановление бита маски данного исключения можно сделать
    подфункцией 25. Сброс флагов исключений в модулях FPU и XMM также
    возлагается на обработчик пользователя.
======================================================================
= Функция 68, подфункция 25 - изменить состояние активности сигнала. =
======================================================================
Параметры:
  * eax = 68 - номер функции
  * ebx = 25 - номер подфункции
  * ecx = номер сигнала
  * edx = значение устанавливаемой активности (0/1)
Возвращаемое значение:
  * eax = -1 - задан неверный номер сигнала
  * иначе eax = старое значение активности сигнала (0/1)
Замечания:
  * В текущей реализации изменяется только маска пользовательского
    обработчика исключений, установленного подфункцией 24. При этом
    номер сигнала соответствует номеру исключения.

Номер бита в маске исключений соответствует номеру исключения по
    спецификации на процессор (Intel-PC).

Вот из мануала AMD
misc.php?action=pun_attachment&item=15

Freeman пишет:

Описана ли функция в заголовках для Delphi?

В файле KolibriOS.pas есть вот это

{68.24}   Function  SetExceptionHandler(Handler: Pointer; Mask: Dword; Var OldMask: Dword): Pointer; StdCall; External 'KolibriOS';
{68.25}   Function  SetExceptionActivity(Signal, Activity: Dword): Integer; StdCall; External 'KolibriOS';

но примеров использования пока нет

Post's attachments

Иконка вложений exceptions.PNG 70.07 Кб, 72 скачиваний с 2020-05-03 

3

Re: Поддержка исключений и RtlUnwind

На самом деле я спрашивал про RtlUnwind. Установка обработчика исключений в KolibriOS вроде с самого начала. Он разве не нужен для работы отладчика?

4 (изменено: Freeman, 05.12.2020 в 18:36)

Re: Поддержка исключений и RtlUnwind

Freeman пишет:

Установка обработчика исключений в KolibriOS вроде с самого начала.

нет, не с самого

Сначала были функции:

  • Функция 68, подфункция 15 - установить обработчик исключений FPU.

  • Функция 68, подфункция 18 - установить обработчик исключений SSE.

Потом вот тут websvn.kolibrios.org/diff.php?repname=K … p;rev=1074 (Редакция 1018 → 1074) была изменена функциональность:

  • Функция 68, подфункция 15 - установить обработчик исключений

  • Функция 68, подфункция 18 - изменение состояния активности сигнала

А потом их и вовсе websvn.kolibrios.org/diff.php?repname=K … p;rev=1077 (Редакция 1074 → 1077) заменили на другие, запись в журнале:

1) f68:15,18 moved to f68:24,25
2) f68:15,18 set to obsolete

Вот как-то так

Freeman пишет:

Он разве не нужен для работы отладчика?

нет, не нужен

Вот функции, касающиеся отладки:

{69.0}    Procedure SetDebugBuffer(Const Buffer: TDebugBuffer); StdCall; External 'KolibriOS';
{69.1}    Procedure GetThreadContext(ID: Dword; Var Context: TThreadContext); StdCall; External 'KolibriOS';
{69.2}    Procedure SetThreadContext(ID: Dword; Const Context: TThreadContext); StdCall; External 'KolibriOS';
{69.3}    Procedure DetachThread(ID: Dword); StdCall; External 'KolibriOS';
{69.4}    Procedure SuspendThread(ID: Dword); StdCall; External 'KolibriOS';
{69.5}    Procedure ResumeThread(ID: Dword); StdCall; External 'KolibriOS';
{69.6}    Function  ReadProcessMemory(ID, Count: Dword; Const Src; Var Dst): Integer; StdCall; External 'KolibriOS';
{69.7}    Function  WriteProcessMemory(ID, Count: Dword; Const Src; Var Dst): Integer; StdCall; External 'KolibriOS';
{69.8}    Procedure DebugTerminate(ID: Dword); StdCall; External 'KolibriOS';
{69.9}    Function  SetBreakPoint(ID: Dword; Index, Flags: Byte; Address: Pointer): Integer; StdCall; External 'KolibriOS';
{69.9}    Function  ResetBreakPoint(ID: Dword; Index, Flags: Byte; Address: Pointer): Integer; StdCall; External 'KolibriOS';
{70.7}    Function  DebugFile(Path, CmdLine: PChar): Integer; StdCall; External 'KolibriOS';

Для отладки нужно установить буффер, в который будут приходить отладочные сообщения:

  TDebugMessage = Packed Record
    Code: Dword;
    ID:   Dword;
    Data: Dword;
  End;

  TDebugBuffer = Packed Record
    TotalSize:   Dword;
    CurrentSize: Dword;
    Buffer:      Packed Array[0..0] Of TDebugMessage;
  End;

Обрабатывать события отладки: 

  DEBUG_EVENT          = 9;

Установить соответствующую маску событий:

  EM_DEBUG             = $100;
Freeman пишет:

RtlUnwind

Среди системных функций такого нет.

Добавлено 2020-06-25 в 22:39

{68.24}   Function  SetExceptionHandler(Handler: Pointer; Mask: Dword; Var OldMask: Dword): Pointer; StdCall; External 'KolibriOS';

C этой функцией тоже есть проблемы

для того, чтобы установить обработчик исключений нужно

type
  TExceptionKind = (ekDivide, ekDebug, ekNonMaskInt, ekBreakPoint, ekOverflow, 
                    ekBoundRange, ekInvalidOpcode, ekDevNotAvailable, ekDoubleFault, 
                    ekCoprSegmOver, ekInvalidTSS, ekSegmentNotPresent, ekStack, 
                    ekGeneralProtection, ekPageFault, ekReserved, ekFPU, 
                    ekAlignmentCheck, ekMachineCheck, ekSSE);
..........................................................
// в случае возникновения исключения в соответствии с установленной маской, будет вызвана эта процедура, на стеке будет номер исключения, поэтому процедура stdcall
procedure ExceptionHandler(Kind: TExceptionKind); stdcall;
begin  
..........................................................
end;
..........................................................
var
  OldExceptionMask: LongWord;  
..........................................................
// здесь устанавливаем обработчик, передаём адрес процедуры ExceptionHandler, желаемую маску, в данном случае $FFFFF
SetExceptionHandler(@ExceptionHandler, $FFFFF, OldExceptionMask);

значения номеров(констант) исключений брал отсюда

misc.php?action=pun_attachment&item=15

пример целиком
program TestExceptionHandler;

uses
  CRT, KolibriOS;

type
  TExceptionKind = (ekDivide, ekDebug, ekNonMaskInt, ekBreakPoint, ekOverflow, 
                    ekBoundRange, ekInvalidOpcode, ekDevNotAvailable, ekDoubleFault, 
                    ekCoprSegmOver, ekInvalidTSS, ekSegmentNotPresent, ekStack, 
                    ekGeneralProtection, ekPageFault, ekReserved, ekFPU, 
                    ekAlignmentCheck, ekMachineCheck, ekSSE);

procedure ExceptionHandler(Kind: TExceptionKind); stdcall;
begin  
  Write('Exception occured: ');
  case Kind of
    ekDivide:
      WriteLn('Divide-By-Zero-Error');
    ekDebug:
      WriteLn('Debug');
    ekNonMaskInt:
      WriteLn('Non-Maskable-Interrupt');    
    ekBreakPoint:
      WriteLn('Breakpoint');    
    ekOverflow:
      WriteLn('Overflow');
    ekBoundRange:
      WriteLn('Bound-Range');
    ekInvalidOpcode:
      WriteLn('Invalid-Opcode');
    ekDevNotAvailable:
      WriteLn('Device-Not-Available');
    ekDoubleFault:
      WriteLn('Double-Fault');
    ekCoprSegmOver:
      WriteLn('Coprocessor-Segment-Overrun');
    ekInvalidTSS:
      WriteLn('Invalid-TSS');
    ekSegmentNotPresent:
      WriteLn('Segment-Not-Present');
    ekStack:
      WriteLn('Stack');
    ekGeneralProtection:
      WriteLn('General-Protection');
    ekPageFault:
      WriteLn('Page-Fault');
    ekReserved:
      WriteLn('Reserved');
    ekFPU:
      WriteLn('FPU');
    ekAlignmentCheck:
      WriteLn('Alignment-Check');
    ekMachineCheck:
      WriteLn('Machine-Check');
    ekSSE:
      WriteLn('SSE');
  end;
end;

procedure test;
var
  X: LongInt;
  Y: LongInt;  
begin
    WriteLn('test begin');
    asm into end;
    asm int 3 end;
    asm mov dword ptr [-1], 0 end;
    Y:= 0; X := 1000 div Y;  
    WriteLn('test end');
end;  

var
  OldExceptionMask: LongWord;
  
begin
  SetExceptionHandler(@ExceptionHandler, $FFFFF, OldExceptionMask);  
  InitConsole('TestExceptionHandler', True);    
  Test;  
  ReadKey;
end.  

Сперва тестировал в VirtualBox.
Если просто запустить этот пример из программы RUN или файлового менеджера,
то в консоли будет выведено только "General-Protection"
а если же запустить этот пример из-под отладчика(программа "mtdbg")
в некоторый момент по какой-то причине запускается второе окно консоли,
в диспетчере задач(программа "CPU") становится 3 потока консоли.
но если это новое окно активировать мышью, то этот третий поток умирает
на доске отладки(программа "BOARD", вкладка "kernel") появляется
сообщение о том, что поток умер

Потом я всё же решил загрузиться на реальном железе
в принципе всё то же самое
ещё могу добавить, что после изменения настроек скорости указателя мыши
при запуске kiv(Kolibri Image Viewer) настройки снова сбиваются,
но при запуске других программ проблем не было, только kiv почему-то

В общем, больше похоже, что проблема где-то в ядре
иначе как вообще такое возможно?
так что пока исключениями в Delphi заниматься, думаю, рано ещё

Добавлено 2020-06-26 в 18:38

Второе окно консоли могло запуститься, если снова вызвать InitConsole.
Каким-то образом передалось туда управление
Надо ещё разбираться

Добавлено 2020-06-26 в 22:00

мда... под отладчиком и без него — результаты разнятся
но это пока в VirtualBox, а там была проблема при отладке http://board.kolibrios.org/viewtopic.ph … 904#p68065

Вот этот пример вроде работает примерно как задумано
program TestExceptionHandler;

uses
  KolibriOS;

  type
  TExceptionKind = (ekDivide, ekDebug, ekNonMaskInt, ekBreakPoint, ekOverflow, 
                    ekBoundRange, ekInvalidOpcode, ekDevNotAvailable, ekDoubleFault, 
                    ekCoprSegmOver, ekInvalidTSS, ekSegmentNotPresent, ekStack, 
                    ekGeneralProtection, ekPageFault, ekReserved, ekFPU, 
                    ekAlignmentCheck, ekMachineCheck, ekSSE);

procedure ExceptionHandler(Kind: TExceptionKind); stdcall;
begin  
asm 
  mov edx, $11111111
  mov cl, Kind
end;
  SetExceptionActivity(longword(Kind), 1);
end;

procedure test(i:longword);
begin
    asm
      fldz
      fldz
      fdiv
    end;
    asm into end;
    asm int 3 end;
    asm xor edx, edx; div edx end;
    asm mov dword ptr [-1], 0 end;
end;  

var
  OldExceptionMask: LongWord;
  
begin
  SetExceptionHandler(@ExceptionHandler, $FFFFF, OldExceptionMask); 
  test($77777777);  
end.

При возникновении исключения в обработчике с помощью SetExceptionActivity снова устанавливается сброшенный после исключения флаг.
Потом управление снова передаётся на тот же адрес, снова исключение...
Программа ожидаемо зацикливается.
Надо по идее прыгать из обработчика на другой адрес.
Это чтобы можно было сделать try...finally.
После try компилятор сохраняет адрес, куда нужно перейти в случае исключения.

там вот эти типы используются
type
  JmpInstruction =
  packed record
    opCode:   Byte;
    distance: Longint;
  end;
  TExcDescEntry =
  record
    vTable:  Pointer;
    handler: Pointer;
  end;
  PExcDesc = ^TExcDesc;
  TExcDesc =
  packed record
    jmp: JmpInstruction;
    case Integer of
    0:      (instructions: array [0..0] of Byte);
    1{...}: (cnt: Integer; excTab: array [0..0{cnt-1}] of TExcDescEntry);
  end;

  PExcFrame = ^TExcFrame;
  TExcFrame = record
    next: PExcFrame;
    desc: PExcDesc;
    hEBP: Pointer;
    case Integer of
    0:  ( );
    1:  ( ConstructedObject: Pointer );
    2:  ( SelfOfMethod: Pointer );
  end;

  PExceptionRecord = ^TExceptionRecord;
  TExceptionRecord =
  record
    ExceptionCode        : LongWord;
    ExceptionFlags       : LongWord;
    OuterException       : PExceptionRecord;
    ExceptionAddress     : Pointer;
    NumberParameters     : Longint;
    case {IsOsException:} Boolean of
    True:  (ExceptionInformation : array [0..14] of Longint);
    False: (ExceptAddr: Pointer; ExceptObject: Pointer);
  end;

Добавлено 2020-06-27 в 01:25

попробовал ещё пример с выводом на доску отладки(приложение BOARD, это приложение также при запуске сохраняет информацию на tmp-диске в файл boardlog.txt)

исходный код
program Board_TestExceptionHandler;

uses
  KolibriOS;

type
  TExceptionKind = (ekDivide, ekDebug, ekNonMaskInt, ekBreakPoint, ekOverflow, 
                    ekBoundRange, ekInvalidOpcode, ekDevNotAvailable, ekDoubleFault, 
                    ekCoprSegmOver, ekInvalidTSS, ekSegmentNotPresent, ekStack, 
                    ekGeneralProtection, ekPageFault, ekReserved, ekFPU, 
                    ekAlignmentCheck, ekMachineCheck, ekSSE);

procedure DebugWriteString(Str: ShortString);
var
  i: Byte;
begin
  i := 1;
  while i <= Byte(Str[0]) do
  begin
    DebugWrite(Byte(Str[i]));
    Inc(i);
  end;
end;

procedure WriteLn(Str: ShortString);
begin
  DebugWriteString(Str);
  DebugWrite(10);
end;
                    
procedure ExceptionHandler(Kind: TExceptionKind); stdcall;
begin  
asm 
  mov edx, $11111111
  mov cl, Kind
end;
  //SetExceptionActivity(longword(Kind), 1);
  case Kind of
    ekDivide:
      WriteLn('Divide-By-Zero-Error');
    ekDebug:
      WriteLn('Debug');
    ekNonMaskInt:
      WriteLn('Non-Maskable-Interrupt');    
    ekBreakPoint:
      WriteLn('Breakpoint');    
    ekOverflow:
      WriteLn('Overflow');
    ekBoundRange:
      WriteLn('Bound-Range');
    ekInvalidOpcode:
      WriteLn('Invalid-Opcode');
    ekDevNotAvailable:
      WriteLn('Device-Not-Available');
    ekDoubleFault:
      WriteLn('Double-Fault');
    ekCoprSegmOver:
      WriteLn('Coprocessor-Segment-Overrun');
    ekInvalidTSS:
      WriteLn('Invalid-TSS');
    ekSegmentNotPresent:
      WriteLn('Segment-Not-Present');
    ekStack:
      WriteLn('Stack');
    ekGeneralProtection:
      WriteLn('General-Protection');
    ekPageFault:
      WriteLn('Page-Fault');
    ekReserved:
      WriteLn('Reserved');
    ekFPU:
      WriteLn('FPU');
    ekAlignmentCheck:
      WriteLn('Alignment-Check');
    ekMachineCheck:
      WriteLn('Machine-Check');
    ekSSE:
      WriteLn('SSE');
  end;
end;

procedure test(i:longword);
begin
    WriteLn('fdiv');
    asm
      fldz
      fldz
      fdiv
    end;    
    WriteLn('into');
    asm into end;
    WriteLn('int3');
    asm int 3 end;
    WriteLn('div 0');
    asm xor edx, edx; div edx end;
    WriteLn('mov dword ptr [-1], 0');
    asm mov dword ptr [-1], 0 end;
end;  

var
  OldExceptionMask: LongWord;
  
begin
  SetExceptionHandler(@ExceptionHandler, $FFFFF, OldExceptionMask); 
  test($77777777);  
end.
Вывод при запуске без отладчика

fdiv
into
int3
General-Protection

Вывод при запуске под отладчиком

General-Protection
Debug
fdiv
into
int3
div 0
Divide-By-Zero-Error

это тестировалось в VirtualBox

5

Re: Поддержка исключений и RtlUnwind

0CodErr пишет:

В общем, больше похоже, что проблема где-то в ядре
иначе как вообще такое возможно?
так что пока исключениями в Delphi заниматься, думаю, рано ещё

Что и требовалось доказать. Реальной поддержкой исключений из ЯВУ пока никто не озадачивался, вот в ядре она и нерабочая до сих пор.

6

Re: Поддержка исключений и RtlUnwind

Да, с этим пока не ясно.
Вот есть тема Как сделать полноценный SEH
Там, кстати, упоминается ещё TLS(Thread Local Storage), TLS в Колибри, который понадобится для приложений с несколькими потоками, использующими обработчик исключений.

7

Re: Поддержка исключений и RtlUnwind

bw написал на форуме KolibriOS, что в FreePascal у него есть исключения.

8 (изменено: Freeman, 09.12.2020 в 23:55)

Re: Поддержка исключений и RtlUnwind

в FreePascal у него есть исключения

Вряд ли это работает
Судя по выводу на BOARD до блока "finally" управление не дошло
misc.php?action=pun_attachment&amp;item=77&amp;download=0а на вкладке "kernel" приложения BOARD было сообщение о вылете программы

Post's attachments

Иконка вложений fpc224_try_finally_test.png 43.5 Кб, 60 скачиваний с 2020-12-08 

9

Re: Поддержка исключений и RtlUnwind

Возникла мысль, что поддержка исключений может быть темой Google Summer of Code 2021. Но я не смогу быть ментором. И занят, и внутренности не так хорошо знаю. 0CodErr, не возьмешься?

Поддержка исключений должна быть полной, чтобы аппаратные исключения тоже перехватывались и транслировались RTL целевого языка. Насколько это реально?

10

Re: Поддержка исключений и RtlUnwind

Тут ещё похоже, что сама системная функция

{68.24}   Function  SetExceptionHandler(Handler: Pointer; Mask: Dword; Var OldMask: Dword): Pointer; StdCall; External 'KolibriOS';

работает не совсем так, как ожидается(судя по документации).
И плюс нужно TLS ещё.
Да и какой из меня ментор smile