четверг, 29 декабря 2016 г.

#1330. Цитата. Win64

"GetMem лучше заменить на VirtualAlloc MEM_RESERVE, тогда это можно и в релиз, т.к. "бесплатно".

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

Это чтобы тестировать указатели с большими адресами.

среда, 28 декабря 2016 г.

#1329. Багель про локальные callback'и

#1328. Win64. Небольшой хинт

Есть небольшой хинт. Весь ассемблер для начала тупо изменяем на assert. Под ifdef CPU64. И запускаем тесты. И разбираемся уже по факту. По мне - сильно время экономит.

#1327. Ссылка. Прислали. Debug Engine

https://github.com/MahdiSafsafi/DebugEngine

"Случайно вот наткнулся - библиотечка у чувака со всякими низкоуровневыми штуками на дельфях (x32 + x64), может быть тебе что-нибудь оттуда где-нибудь пригодится.'

Посмотрел. Там есть что почерпнуть.

Цитата оттуда:

DebugEngine

What is DebugEngine ?

DebugEngine is a collection of utils related to debug stuff (stack trace, CPU registers snaphot, debug info,...). Basically, I started to write a commercial error log plugin for Delphi, then I noticed that my internal framework got bigger and bigger. So I decided to share it with the community in hope it will be useful.

Features:

DebugEngine has a lot of functions and utilities allowing to you to do for example:

Getting started:

Please refer to the Wiki page and see Demo included with the library. Note that all public functions are documented (XML doc). However if you don't understand something, please feel free to contact me.

И:

Getting address of symbol:

Use GetSymbolAddress function to get address of symbol.
function GetSymbolAddress(ModuleHandle: THandle; const UnitName, SymbolName: string): Pointer;
  • ModuleHandle = Module handle where the symbol is located. If you pass zero (0), the function will use the current module handle.
  • UnitName = Optional, unit name where the symbol was declared. This is useful when many units declare the same symbol.
  • SymbolName = symbol name.
  • Return value = If the function succeeds, the return value is the address of the symbol. Otherwise it returns nil.
Example:
var
  P: Pointer;
begin
  { Private variable System.MemoryManager }
  P := GetSymbolAddress(0, 'System', 'MemoryManager');
  { Private method System.SetExceptionHandler }
  P := GetSymbolAddress(0, '', 'SetExceptionHandler'); 
  { Protected method TCustomForm.CloseModal } 
  P := GetSymbolAddress(0, '', 'TCustomForm.CloseModal');
  { Windows api }
  P := GetSymbolAddress(GetModuleHandle(user32), '', 'MessageBoxA');
end;

Using DebugEngine stack trace when error occurs:

All what you need to do is to include DebugEngine.HookException unit into your project. And each time an error occurs, you will be able to get the stack trace from the point where the error occurred.
uses 
  DebugEngine.HookException;

{...}

procedure Foo;
begin
  try
    DoSomething;
  except
    on E: Exception do
      ShowMessage(E.StackTrace);
  end;
end;

Disasm and comment function:

If you plan to use this feature, you need first to update UnivDisasm.Config.inc file and tell UnivDisasm that you need display feature (Define NEED_DISPLAY). By default, I turned this option off just for optimization. So if you are going to use Disasm and comment feature, you should enable it again.
function DisasmAndCommentFunction(FunctionStartAddress: Pointer; var FunctionEndAddress: Pointer; CallBackFunction: TDisasmCallBack; UserData: Pointer)
  : Boolean;
  • FunctionStartAddress = Function address that you want to disasm.
  • FunctionEndAddress = End address where the disasm will stop. If not specified, UnivDisasm will break on the first retinstruction.
  • CallBackFunction = A pointer to TDisasmCallBack function. This function will be called by DisasmAndCommentFunction each time it decodes an instruction.
  • UserData = Optional data to pass to CallBackFunction function.
Example:

procedure DisasmCallBack(var Info: TDisasmInfo; UserData: Pointer);
var
  S: String;
begin
  with TMemo(UserData).Lines, Info do
  begin
    S := Format('[$%p]:    %s', [Address, InstStr]);
    if not comment.IsEmpty then
      S := S + '    ; ' + comment;
    Add(S);
  end;
end;

var
  P: Pointer;
begin
  P := nil;
  {LogMem = TMemo}
  LogMem.Clear;
  LogMem.Lines.BeginUpdate;
  try
    DisasmAndCommentFunction(@TMain.BtnLegRegSnapClick, P, DisasmCallBack, LogMem);
  finally
    LogMem.Lines.EndUpdate;
  end;

#1326. Портирование под Win64

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

понедельник, 26 декабря 2016 г.

#1325. Коротко. Прогресс портирования под Win64. ImageEn

Собрал с 64-хбитным ImageEn. Пришлось найти 64-битную dll на просторах интернета.

Исправил несколько ошибок. В частности - Seek за конец потока при определении формата картинки в потоке.

Собрать без dll - с испольлование родных 64-хбитных libpng, zlib из C-шного кода - пока не получилось.

Вообще хочу отказаться от ImageEn - в пользу "нативных" средств Delphi и/или GDI+. Т.к. ImageEn в основном используется для чтения/записи различных графических форматов.

среда, 14 декабря 2016 г.

#1324. Коротко. Прогресс портирования под Win64

Портирую наши проекты под Win64.

1. Собрал и запустил консольные тесты. Все прошли.
2. Собрал и запустил скриптованные консольные тесты. Все прошли.
3. Собрал и запустил скриптованные тесты под GUITestRunner. Все прошли.
4. Собрал и запустил небольшой набор GUI-тестов. Все прошли.
5. Сейчас работаю над собираемостью всех имеющихся в наличии GUI-тестов.
6. Собрал читатели/писатели формата EVD, а также его фильтры. Тесты пока не запускал.
7. Портировал под Win 64 наши доработки DUnit.
8. Портировал кодогенерацию.

Попутно разобрался с соглашениями о вызовах под Win 64.

Научился делать заглушки (stubs) для вызовов локальных функций из итераторов без использования анонимных функций.

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

Порадовал тот факт, что компилятор не даёт приводить Integer и Cardinal к Pointer. Это позволяет отсечь много ошибок уже на этапе компиляции.

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

Для портирования использовал Delphi XE4, т.к. у нас на неё куплены лицензии.

Из стороннего пока не собраны - miniLZO и ImageEn. Там линкуются объектные файлы 64-хбитную версию которых надо где-то найти.

понедельник, 12 декабря 2016 г.

#1323. Коротко. О соглашениях о вызовах под Win64

Ссылка на MSDN - https://msdn.microsoft.com/en-us/library/windows/hardware/ff561499(v=vs.85).aspx

По мотивам - http://programmingmindstream.blogspot.ru/2016/12/1321.html

Под Win64 модель передачи параметров только одна.

Т.е. stdcall, pascal, cdecl, register - эквивалентны.

Параметры передаются через регистры в порядке - rcx, rdx, r8, r9. Остальные через стек.

Если значение не помещается в регистр, то оно кладется на стек, а в регистре передается указатель на это значение.

Возвращаемое значение помещается в rax. Если оно целочисленное (Integer) или указатель (Pointer).

Вещественные числа возвращаются через регистр xmm0.

Регистры rax, rcx, rdx, r8-r11 - могут изменяться внутри вызываемой функции.

Регистры rbx, rbp, rdi, rsi, r12-r15 - должны сохранять своё значение при работе вызываемой функции.

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

(Оригинальная цитата - "The caller reserves space on the stack for arguments passed in registers. The called function can use this space to spill the contents of registers to the stack.")

Вызываемая процедура может использовать это место по своему усмотрению.

Пример ручного резервирования:

procedure TMethodHandlerInstance.Handler(Params: Pointer);
asm
        .NOFRAME
        SUB     RSP, 28H // - выделяем место в стеке
        CALL    InternalHandler // InternalHandler - может использовать это место адресуясь через [RSP+Offset]
        MOV     [RSP], RAX // - в этих двух строках ещё попользовали отведённое место
        MOVSD   XMM0, [RSP] // в качестве собственной переменной
        ADD     RSP, 28H // - возвращаем место в стеке
end;

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

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

От этой базы адресуются локальные переменные охватывающей процедуры.

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

На основании этой информации можно строить заглушки для вызовов локальных функций вместо анонимных.

Ссылки:
http://18delphi.blogspot.ru/2013/03/blog-post_5929.html
http://18delphi.blogspot.ru/2013/07/embarcadero.html

Подсмотреть направление исследований можно в коде Embarcadero. В методе MakeObjectInstance.

Позже я выложу примеры кода.




пятница, 9 декабря 2016 г.

#1322. Ссылка. Китайский ORM генератор

http://grandruru.blogspot.tw/2016/02/delphi-orm-generator.html

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

Написан под Windows 10.

Как его запустить(и вообще возможно ли) без Windows Store, я пока не знаю. 

пятница, 2 декабря 2016 г.

#1321. Только код. Заготовочка для заглушек для вызова локальных функций под Win64

program Lambda;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

function GetRBP: Pointer; assembler;
asm
   .NOFRAME
   mov rax, rbp // - тут получаем текущую базу локальных переменных
//   ret
//   mov r8, rdx
//   mov rdx, rcx
//   mov rbp, [RIP+0]
//   jmp [RIP+0]
//   mov rcx, $ffffffffffffffff
end;

function l3LocalStubPrim(aRBP: Pointer; anAction: Pointer): Pointer;
{$WriteableConst On}
const
 StubCode : array [0 .. 29 + 8] of Byte = (
  $49, $89, $D0, // mov r8, edx // - у глобальной процедуры параметры передаются через ecx, edx
  $48, $89, $CA, // mov edx, ecx // а у локальной через edx, r8
  $48, $B9, // mov rcx, 0000000000000000 // - в rcx - база
  $00, $00, $00, $00, $00, $00, $00, $00,
  $FF, $25, $00, $00, $00, $00, // jmp [RIP+0] // - тут переход на anAction
  $00, $00, $00, $00, $00, $00, $00, $00

  , $00, $00, $00, $00, $00, $00, $00, $00
 ); // StubCode
begin
 Move(aRBP, StubCode[8], SizeOf(aRBP)); // - конкретное значение rbp
 Move(anAction, StubCode[22], SizeOf(anAction)); // - конкретное значение anAction
 Result := @StubCode;
end;

function l3LocalStub(anAction: Pointer): Pointer; assembler;
asm
   .NOFRAME
   call GetRBP
   mov rdx, rcx
   mov rcx, rax
   jmp l3LocalStubPrim
   //call l3LocalStubPrim
end;

type
 TProc1 = procedure (A: Integer);
 TProc2 = procedure (A: Integer; B: Integer);

procedure Call1(aProc: TProc1);
begin
 aProc(12345);
end;

procedure Call2(aProc: TProc2);
begin
 aProc(12345, 789);
end;

procedure Test;

var
 l_S : String;

 procedure Local1(aStr: Integer);
 var
  l_S1 : String;
  l_S2 : String;
 begin
  l_S1 := 'YYY';
  l_S2 := l_S + l_S1;
  l_S2 := l_S2 + IntToStr(aStr);
  WriteLn(l_S2);
 end;

 procedure Local2(A: Integer; B: Integer);
 begin
  WriteLn(A);
  WriteLn(B);
 end;

var
 l_RBP : NativeUInt;
 l_P : Pointer;
begin
 l_RBP := NativeUInt(GetRBP);
 l_S := 'XXX';
 Local1(10);
 Local2(10, 0);
 Call1(l3LocalStubPrim(GetRBP, @Local1));
 Call1(l3LocalStub(@Local1));
 Call2(l3LocalStubPrim(GetRBP, @Local2));
 Call2(l3LocalStub(@Local2));
 ReadLn;
end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.