Категория: Delphi

Solved: Delphi XE3 64-bit debugger fails to run

Symptoms:

Delphi XE3 sometimes fails to run 64-bit applications under a debugger. Code would compile, but the part where Delphi switches to debug layout never happens, Delphi just pops a message saying "Cannot run the debugger".

32-bit debugging continues to work normally, and so does "Run without debugging".

The funny part is that this happens irregularly. Sometimes the first attempt would succeed, and then the debugger would run all the time in all instances of Delphi. But if it fails the first time then it would always fail even if you restart Delphi.

I also noticed that the earlier I launch Delphi + debugger, the higher is the chance it would run (and then continue working). It seemed like there was something I was doing or the computer was doing sometime after boot that broke the debugger if I hadn't launched it yet.

Solution:

Stop the "Internet connection sharing" service and restart Delphi.

What might have contributed:

– Uninstalling older versions of Delphi on the same PC.
– Disabling Windows Firewall
– Disabling Windows Defender

(Diagnostics process)

Diagnostics process:

Looking at the successful and failed debugger launches with Process Monitor, in both cases Delphi runs a remote debugger. But on the successful run it's dbkw64_17_0.exe (64 bit) while failed runs spawn rmtdbg170.exe (32 bit). Both are Delphi debuggers, but I suspected that the second one is only supposed to be used for 32 bit debugging.

Further investigation showed that in both cases dbkw64_17_0.exe launches initially, but in the second case it terminates shortly afterwards. Delphi then tries to connect to it through TCP, unable to do so, and restarts it automatically. But the code that does the restart probably wasn't updated to 64 bit and launches 32-bit rmtdbg170.exe instead.

Anyway, the problem lies in the initial instance of dbkw64_17_0.exe terminating. Comparing Process Monitor logs, both successful and failed runs load the libraries and then work with winsock. Stack in the final calls indicates ws2_32.dll's socket() is running – the debugger is probably trying to open it's command socket for listening – after which failed instance abruptly terminates (Thread Exit, Process Exit). I figured socket() probably returns with an error.

Using rohitab's Api Monitor I tried to find out the error code, but this didn't work out. Api Monitor successfully traced all the calls until roughly WSAStartup(), but no further – the last bunch of calls just before the termination always got lost, perhaps the injected driver wasn't being able to send it back to the main app in time before the application terminated.

Then I opened dbkw64_17_0.exe for debugging in Visual Studio. I set a breakpoint to {,,ws2_32.dll}socket, caught the execution there and studied what happens step by step. Turns out, socket() was successful. It was followed by setsockopt call, also successful (to know which functions we were stepping into, I used VS's standard ability to load Windows DLL symbols from Microsoft servers). Then dbkw64_17_0.exe called bind() which failed.

My initial guess was that someone else occupied the port it needed. Checking bind() parameters at MSDN, I looked into RDX, RCX, R8, R9 registers which host parameters in x64 calls, namely the memory referenced by RCX, which kept the requested family and port number. It turned out to be 0xC0F3 but it was unoccupied.

I then traced the call to bind() and from the internal call to WSPBind() got the error code: 0x1D27, that is 10013 (WSAEACCES: Permission denied. An attempt was made to access a socket in a way forbidden by its access permissions).

This code has no single specific reason for it. From the internet it looks like it appears when some driver or network-related service misbehaves. I tried stopping network related services one by one, until finally bind() succeeded. The infringing service was "Internet connection sharing (ICS)". As long as I stop this service, the debugger launches normally, and so long as ICS is running, the debugger would not start.

The reason why sometimes the debugger would run and then run always, is probably that ICS hadn't yet been started or did not yet harm the network stack at the time. If the debugger run at that point, it would bind the socket, and for whatever reason binding at that port would then continue working later. But if the debugger was initially launched after the harm has been done, it wouldn't be able to bind to the port neither once nor at all.

HOWTO: Assign checkable TAction to TSpeedButton

To make TSpeedButton work with TAction.Checked when it's a singular option (either On or Off), make sure that at design-time:

SpeedButton.Action = Action
SpeedButton.GroupIndex = 0
SpeedButton.AllowAllUp = true
Action.GroupIndex = 0
Action.AutoCheck = true //only if you need AutoCheck

Then add this to FormCreate:

SpeedButton.GroupIndex := 17; //any non-used group index

SpeedButtons are linked to Actions through TSpeedButtonActionLink. It only updates their Down property if AllowAllUp is set and SpeedButton.GroupIndex property is NOT 0.

But when Action is linked, SpeedButton.GroupIndex gets rewritten by Action.GroupIndex on load.

And if Action.GroupIndex is 0 because it's a singular option, then no matter what you put into SpeedButton.GroupIndex at design-time, it's going to be rewritten with 0 at load, so TSpeedButtonActionLink does not update Down property.

The simplest solution is to set SpeedButton.GroupIndex to something in FormCreate.

Multiobject try..finally

Just a simple Delphi pattern. We all have encountered nested try..finally blocks like this:

CChar := TTextTableCursor.Create(TChar);
try
 CCharProp := TTextTableCursor.Create(TCharProp);
 try
   Builder := TCharPropBuilder.Create(Result);
   try
     //Do some work with all three objects
     //Since all three are needed, we can't destroy any before this point
   finally
     FreeAndNil(Builder);
   end;
 finally
   FreeAndNil(CCharProp);
 end;

finally

 FreeAndNil(CChar);

end;But there's a nicer way of doing the same while still being exception safe (and avoiding the overhead of three try..finally exception frames):

CChar := nil;
CCharProp := nil;
Builder := nil;
try
 CChar := TTextTableCursor.Create(TChar);
 CCharProp := TTextTableCursor.Create(TCharProp);
 Builder := TCharPropBuilder.Create(Result);
//Do some work with all three objects

finally

 FreeAndNil(Builder);
 FreeAndNil(CCharProp);
 FreeAndNil(CChar);

end;

Installing Delphi VersionInsight Plus

Since Delphi XE, Delphi has SVN support integrated into file history display. SVN revisions are displayed in addition to local backups, all properly sorted by date. Very nice.

Mercurial and Git support wasn't added into the default distribution, but there's a newer version of VersionInsight plugin with fully functioning support for those, written by Delphi developers. Meet RAD Studio Version Insight Plus.

To use this you need to compile it. It's simple, but mind these fine points:

  • There are several branches in the repo, you need the /plus one. Not the trunk.
  • Delphi less than XE will not compile those, no simple solution.
  • You need to compile five packages: svn, svnui, svnide (already grouped into DelphiSVN) + hgide and gitide.
  • Delphi already includes pre-compiled svn, svnui and svnide. You need to remove those from "Component> Install packages" list. (And restart)
  • The ones from SVN are marked ver_150, and the ones with Delphi ver_170, but the ones from SVN are newer (I think).
  • When compiling the packages, Delphi might try to trip you up and use existing packages it cached somewhere instead of the sources right in front of it.
    To be on a safe side, do dir c:\svn*.bpl /s, dir c:\svn*.dcp /s, dir c:\svn*.dcu /s, and remove everything related to VersionInsight plus. (Some matches are going to be in the cached Delphi install distributions, these are fine).
    Particularly, svn*.dcp in Program Files\Embarcadero\Delphi\DelphiVersion\lib\Win32\debug or \release are known to silently cause problems such as svnui.bpl complaining that TSvnBlameOptions is not defined even though it's defined right there in SvnClient.pas.

Otherwise packages compile just fine, have no dependencies and produce almost no warnings.

After compiling the packages, install the last three (svnide, hgide and gitide). Restart the Delphi.

The SVN support will start working straight away (it should have been working before too). For Git and Mercurial you need to go to Tools> Options> Version Control, and set paths to git.exe and hg.exe executables in the respective sections.

Про паскаль

Если считать, что паскаль вымирает, то вымирать он умудряется с огоньком. У нас есть Delphi, который при всех проблемах управления постоянно вводит крутые возможности в язык. Он компилируется под win-x32, win-x64 и маки. Есть FreePascal, которые поддерживает большую часть вводимых Дельфи фич и компилируется подо всё на свете, в том числе под linux-x32, linux-x64 и native-android (с помощью этой штуки компилятор FPC можно встроить в интерфейс дельфи, если не нравится Lazarus). И есть Oxygene, который компилируется под .NET, Android и iPhone, и тоже вводит в язык много крутого. (И в нём вместо VCL используются нативные формы каждой платформы, типа Windows Forms).

То есть, в общем-то, покрыта вся мыслимая разработка для локальных компилируемых языков. Кроме интерпретируемых языков, единственный язык с таким покрытием – это C/C++. И кстати, если пользоваться только паскалём уровня Delphi 7, стандартной библиотекой вместо винапи, и ограничиться консольным или сервисным приложением, то программу можно будет скомпилить на любом из этих компиляторов. Интересно, пробовал ли кто-нибудь?

Tell me what I’m going to use it for

For those who didn’t know, there’s a new pascal-based compiler on a market for a while, and a pretty cool one at that. Enter RemObjects Oxygene.

It’s Visual Studio-based, compiles to .NET, Android Java and iPhone Cocoa, resembles Pascal and implements the majority of its cool features like generics. Parts of language are redesigned, some for better, some for worse.

Cool feature. Even the main unit now has the interface/implementation sections.

namespace Application1;
interface
implementation
begin
  Console.WriteLine("The magic happens here.");
end.

Uncool feature. initialization/finalization sections are no more. I guess you can kinda replace them with class constructors, but they were so much better.

Anyway.

The language is indeed pretty fresh, with support even for WinRT while Delphi has yet to convince Microsoft to let everyone else have a part of the cake. Turns out, the only way to do native WinRT applications is through using Microsoft Visual C++ Runtime. Ha-ha, funny joke Microsoft, you.

So I thought about playing with it for a change.
No, I’m not betraying Delphi just yet. It’s still pretty cool, compiling to 64 bit and not being afraid of anything.

But sitting before the empty Oxygene project, I have found myself at loss at what to do.
Okay, it runs. It compiles Hello World, alright.
What next?

Turns out, when you encounter a new language, you have to have a few use cases for it. And since you usually don’t know what this langage can do, it’s better if someone suggests those for you.

Inc(i)

Все знают, что когда перебираешь null-terminated строки, то нужно останавливаться по нулю:

while pc^<>#00 do Inc(pc); //ищем конец строки

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

while (i<Length(s)) and (s[i]=' ') do Inc(i); //пропускаем пробелы

Все знают, что у дельфийских строк в конце всё равно ноль.

 74 00 65 00 73 00 74 00 00 00

Но не всем и не сразу приходит в голову, что длину дельфийской строки тоже часто можно не проверять!
Второй пример можно записать так:

while s[i]=' ' do Inc(i); //пропускаем пробелы

В конце строки ноль, а ноль – это не пробел, поэтому цикл сам собой прервётся.

Где надо быть осторожным – так это при промотке строки назад. В начале строки нуля нет:

while (i>0) and (s[i]=' ') do Dec(i); //пропускаем пробелы в обратную сторону

type и class

В Delphi можно написать:

type HexString = string;

Так мы отметим специальный тип строк, который хранит в себе hex. Но для компилятора они ничем не отличаются от обычных. Вот это скомпилируется нормально:

var a: HexString;
  b: string;
begin
  a := b;
end;

Что, если мы не хотим разрешать такое копирование? (А обычно мы должны не хотеть! Разные по смыслу вещи нельзя присваивать, это опасно). Компилятор можно попросить создать “независимый тип”:

type HexString = type string;

Теперь строку типа HexString нельзя присвоить строке типа string, и наоборот.

Похожий приём работает с классами, только чуть иначе.

type
  HexStringList = TStringList; //можно присваивать HexStringList -> StringList и обратно!
  HexStringList = class(TStringList); //можно присваивать только HexStringList -> StringList, но не обратно!

Классы, в отличие от простых типов, поддерживают наследование. Более “частный” класс можно положить в переменную более общего, но не наоборот. Если мы объявляем тип без “class“, то мы просто создаём для него другое имя: оба типа на самом деле одно и то же. А с помощью “class(TStringList)” мы говорим компилятору “HexStringList – это частный случай StringList, он от него наследует”.

Но что, если мы напишем так?

type HexStringList = type TStringList;

Или так?

type HexStringList = type class(TStringList);

Ответы на это в следующий раз!

Консты нереально круты

Да, я понимаю, что тут никто не пишет на дельфи, но раз уж я иногда что-то пишу о ней, то позвольте мне.

В дельфи есть элемент языка, которым все пренебрегают. Очень крутой. Это атрибут входного параметра const.
Вместо:
function IsStringAbrakadabra(s:string): boolean
Получится:
function IsStringAbrakadabra(const s:string): boolean

Зачем?
Строки в Дельфи ведут учёт ссылок. Каждое присваивание увеличивает счётчик на 1. Каждое зануление – уменьшает его. Поэтому любая функция, которая получает строки, преобразуется компилятором в следующую:

UStrLAsg(s); //увеличить счётчик ссылок
try
  //сама функция
finally
  UStrLClr(s); //уменьшить счётчик ссылок
end;

Два лишних вызова! И фрейм try..finally (это очень дорогая конструкция). Эта обёртка легко может тратить больше времени, чем сама ваша функция! Скомпилите и посмотрите в ассемблер – инлайнить такую дуру тоже пропадает всякая выгода.

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

Но это ещё не всё.
Мало добавлять const к строковым параметрам. Строки могут передаваться неявно. Функция, которая получает структуру со строкой внутри, тоже требует учёта ссылок и фрейма try..finally. Даже хуже: вместо прямолинейного UStrLAsg будет вызван AddRefRecord, который с помощью некоего подобия рефлекшна изучает вашу структуру и решает, каким полям нужен какой учёт ссылок. И так в каждой функции!
Дельфи не глупая, и если структуре совсем не нужен учёт ссылок, она поймёт это при компиляции, и фрейм не вставит. Но когда хоть одно поле требует учёта, вы получите пенальти в размере полного разбора всей структуры дважды.

Поэтому ставьте const везде, где можно. Ставьте const всему, что передаёте на копирование во всевозможные “SetSomething” или “InitSomething”. В крайнем случае он будет просто подсказкой читающему код.

Ещё очень важная информация: отключайте “String format checking” в настройках компиляции. Всегда. Сразу же. Эту опцию следовало бы назвать “замедлить в три раза все операции со строками, для того, чтобы спрятать от вас чудовищные баги в вашем коде”.

Это просто праздник какой-то

if AnsiCompareStr(uppercase(value),uppercase(s))<0 then r:=c else
if AnsiCompareStr(uppercase(value),uppercase(s))>0 then l:=c+1 else
if AnsiCompareStr(uppercase(value),uppercase(s))=0 then r:=c;

Мало того, что “<=” разбито на “<” и “=” с одинаковым исходом, так тут вообще достаточно одной проверки:

if AnsiCompareStr(uppercase(value),uppercase(s))<=0 then r:=c else l:=c+1;

UPD. Я в этот пост буду складывать все такие примеры!

if (doall) then
begin
  if not doall then
  begin