Jump to content
aehimself

CreateProcess causing some strange Access Violations

Recommended Posts

After solving some mysteries in DDE I decided to extend my Delphi instance helper with a new method: to launch a new Delphi instance of a selected version. The theory is simple: launch bds.exe and return the TDelphiInstance object only when the IDE is available (this was removed to further simplify the calls happening).

While the new process starts, I'm getting Access violation at address 00000000 in module 'Project3.exe'. Execution of address 00000000 upon:

- Calling ShowMessage (WinApi.CommCtl : 13107, TaskDialogIndirect)

        Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
          pfVerificationFlagChecked)

- Application.ProcessMessages loop (Vcl.Forms : 11460, TApplication.ProcessMessage)

            DispatchMessageW(Msg)

- Closing the instance which was created by CreateProcess (error raises in ProcessMessages, just like above)

 

From the third appearance I believe I'm not using CreateProcess correctly (in theory, after closing all handles my application should have nothing to do with the instance created) but if I move the code out of TBorlandDelphiVersion to the project itself, it just works.

 

Launching the exe is performed as follows:

Function TBorlandDelphiVersion.NewDelphiInstance: TDelphiInstance;
Var
  StartInfo  : TStartupInfo;
  ProcInfo   : TProcessInformation;
  ec: Cardinal;
Begin
  Result := nil;

  FillChar(StartInfo, SizeOf(TStartupInfo), #0);
  StartInfo.cb := SizeOf(TStartupInfo);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);

  If Not CreateProcess(PChar(_bdspath), nil, nil, nil, False, CREATE_NEW_PROCESS_GROUP, nil, nil, StartInfo, ProcInfo) Then
    RaiseLastOSError;

  Try
//    If Not GetExitCodeProcess(procinfo.hProcess, ec) Then
//      RaiseLastOSError;
//
//    WaitForInputIdle(procInfo.hProcess, INFINITE);

    Repeat
      If Not Assigned(Result) Then
      Begin
        Self.RefreshInstances;
        Result := Self.InstanceByPID(procinfo.dwProcessId);
      End;
    Until Assigned(Result);// And ((Result.IDEHWND <> 0) Or Result.FindIdeWindow) And Not Result.IsIDEBusy; // Normally here, removed for simplicity
  Finally
    CloseHandle(ProcInfo.hThread);
    CloseHandle(ProcInfo.hProcess);
  End;
End;

I attached the full source of said class, you can reproduce the issue by...

Uses AE.DelphiVersions;

procedure TForm3.Button1Click(Sender: TObject);
begin
 ShowMessage(ni.ToString);
End;

procedure TForm3.Button2Click(Sender: TObject);
Var
 dv: TDelphiVersions;
 c: Cardinal;
begin
 dv := TDelphiVersions.Create(nil);
 Try
  c := dv.LatestVersion.NewDelphiInstance.PID;
//  Self.Caption := c.ToString; // This works, no exception is raised
  ShowMessage(c.ToString);
 Finally
   FreeAndNil(dv);
 End;
end;

function TForm3.ni: cardinal;
Var
 dv: TDelphiVersions;
begin
 dv := TDelphiVersions.Create(nil);
 Try
  Result := dv.LatestVersion.NewDelphiInstance.PID;
 Finally
//   FreeAndNil(dv); // If you do not free this up, exception is raised in ShowMessage.
 End;
end;

MadExcept reports no buffer under- or overruns, no memory, handle or any other leaks are reported by DeLeaker.
 

I never really used CreateProcess, and never actually managed to produce an execute at address AV up until this point. Can anyone point me to the right direction?

 

P.s.: Using Delphi 11.2, compiled app is 32 bit, cannot check if the issue will appear anywhere else.

AE.DelphiVersions.zip

Edited by aehimself

Share this post


Link to post
18 minutes ago, David Heffernan said:

First argument of CreateProcess must be writeable? Is it? 

I was unaware of this, and even MSDN specifies it as IN only. Do you maybe have some references I can look into?

 

Changing it to

  pc: PChar;
Begin
  [...]

  GetMem(pc, Length(_bdspath) + 1);
  Try
    StrPCopy(pc, _bdspath);

    If Not CreateProcess(pc, nil, nil, nil, False, CREATE_NEW_PROCESS_GROUP, nil, nil, StartInfo, ProcInfo) Then
      RaiseLastOSError;
  Finally
    FreeMem(pc);
  End;

still throws the AV.

 

I am really baffled by the nature of this issue. Popping up at window messages, and being "execution at address" I'd say it's like a memory corruption. I have doubts though as commenting out the two CloseHandle lines will delay / get rid of these. That clearly points to improper use of CreateProcess...

Share this post


Link to post

The documentation says the first arg is LPCWSTR, pointer to const array. If you want to make a string writeable use UniqueString. 

Share this post


Link to post

Calling UniqueString(_bdspath) before the CreateProcess call still produces an error. Maybe it's even unnecessary...?

 

https://www.oreilly.com/library/view/delphi-in-a/1565926595/re354.html

"If you cast a string to PChar, Delphi also ensures that the PChar points to a unique instance of the string. If you modify the string through the PChar pointer, the other references to the original string are safe."

 

I don't know, I am in completely uncharted territory here. Never had to think more of PChar castings than if a method needed it instead of a String.

Share this post


Link to post
8 minutes ago, aehimself said:

https://www.oreilly.com/library/view/delphi-in-a/1565926595/re354.html

"If you cast a string to PChar, Delphi also ensures that the PChar points to a unique instance of the string. If you modify the string through the PChar pointer, the other references to the original string are safe."

I don't think that's true. You should use UniqueString. But it sounds like this isn't the cause of your problem. 

  • Like 1

Share this post


Link to post
1 hour ago, David Heffernan said:

First argument of CreateProcess must be writeable? Is it? 

Incorrect, it is the 2nd argument that must be writable. And only in the Unicode version.

Share this post


Link to post
2 hours ago, aehimself said:

While the new process starts, I'm getting Access violation at address 00000000 in module 'Project3.exe'. Execution of address 00000000 upon:

That means you are trying to call a function through a nil function pointer.

2 hours ago, aehimself said:

- Calling ShowMessage (WinApi.CommCtl : 13107, TaskDialogIndirect)


        Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
          pfVerificationFlagChecked)

Did you check to see whether _TaskDialogIndirect is nil or not?

  • Like 1

Share this post


Link to post
13 minutes ago, Remy Lebeau said:

Incorrect, it is the 2nd argument that must be writable. And only in the Unicode version.

Oh. Sorry. My mistake. 

 

Only in the Unicode version is an implementation detail though. I wouldn't rely on it. 

Edited by David Heffernan

Share this post


Link to post
25 minutes ago, Remy Lebeau said:

That means you are trying to call a function through a nil function pointer.

Did you check to see whether _TaskDialogIndirect is nil or not?

I only called ShowMessage, which requires nothing but a string to display. I checked all parameters and confirmed that pszContent indeed contains the PID I'm about to display, but never thought of _TaskDialogIndirect being nil... it's in Delphi VCL after all.

I just checked and yes it seems, it does point to a valid memory address:

 

image.png.5e2bf6b9793173c8ad0c3df1d4a48236.png

Share this post


Link to post
2 hours ago, David Heffernan said:

Only in the Unicode version is an implementation detail though. I wouldn't rely on it. 

It is not an implementation detail, it is documented behavior:

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw

Quote

The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

 

Share this post


Link to post
2 hours ago, aehimself said:

I just checked and yes it seems, it does point to a valid memory address:

 

image.png.5e2bf6b9793173c8ad0c3df1d4a48236.png

Using the Addr() intrinsic in that way will give you the memory address of the _TaskDialogIndirect variable itself, not the memory address that the variable holds.  Try inspecting just _TaskDialogIndirect by itself, or at least cast it to a Pointer.

  • Like 1

Share this post


Link to post
5 hours ago, Remy Lebeau said:

Using the Addr() intrinsic in that way will give you the memory address of the _TaskDialogIndirect variable itself, not the memory address that the variable holds.  Try inspecting just _TaskDialogIndirect by itself, or at least cast it to a Pointer.

Unfortunately (?) yeah, it has a valid address:

image.png.5a6ccec20765700841896a941ea62c48.png

 

There is also a check around this block in CommCtrl, if the method is not found we wouldn't even come to this block:

    if ComCtl32DLL <> 0 then
    begin
      @_TaskDialogIndirect := GetProcAddress(ComCtl32DLL, 'TaskDialogIndirect');
      if Assigned(_TaskDialogIndirect) then
        Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
          pfVerificationFlagChecked) // Exception is raised here
    end;

 

Share this post


Link to post
6 hours ago, Remy Lebeau said:

Oh dear, I'm not having a good day. I think that obce upon a time this wasn't documented as such, and I've not kept up. But I could be wrong about that too! 

Share this post


Link to post

As it turns out, the AV isn't caused by CreateProcess or anything related to it. The reason I wasn't able to reproduce it in a small bench application is I have a TDelphiDDEManager class created, which is however unused in the sample, but it calls DdeInitializeW. In the moment I comment that line out (or won't create the DDEManager instance) the AV disappears, PID is shown correctly, everything works like a charm.

 

Question is, is it because of improper parameterization (like in UnpackDDElParam and FreeDDElParam) or I'm calling that wrong...

 

Anyway, getting closer 🙂

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×