Jump to content
sp0987

Changes in System.sysutils.pas were not reflecting in other unit in Delphi 11

Recommended Posts

Posted (edited)
46 minutes ago, sp0987 said:

GetLocalTime is just reference into external Windows DLL in Windows unit I think.

Yes.

46 minutes ago, sp0987 said:

I'll need to create a wrapper with same name around it with additional logic somewhere within my project which can be reached by the original GetLocalTime in sysUtils?

You would write a replacement function in your code, and then detour the original DLL function, and then all calls to the DLL function will go through your replacement function instead.  A detour doesn't care whether the original function resides in a DLL or not.  It is all in the same address space of the calling process.

46 minutes ago, sp0987 said:

Correction: it doesn't need to have same name. I could do something like:

 

// excuse wrong code as I do not have D11 in front of me

createNewDetour(@Window.GetLocalTime, @myNewLocalTime, ...);

 

yes?

Yes.

Edited by Remy Lebeau

Share this post


Link to post

One thing I find odd is that you feel the need to change the behaviour of basic windows API functions. Why are you doing this? 

  • Like 2

Share this post


Link to post
4 hours ago, David Heffernan said:

One thing I find odd is that you feel the need to change the behaviour of basic windows API functions. Why are you doing this? 

Because our software needs to operate/tested through time from past to the future in cycles and we can't change the OS time as we did in past.

Share this post


Link to post

before I start, how do I ensure that calling the original Windows.GetLocalTime from within my detoured function does not get also detoured causing circular reference errors? Or that will no longer possible? Example:

 

procedure myNewLocalTime(var st: TSystemTime);

begin

  st := Windows.GetLocalTime(st);

  // now further modify st

end;

 

createNewDetour(@Window.GetLocalTime, @myNewLocalTime, ...);

Share this post


Link to post

When you create a detour you should store the original function address to be able to return it. we don't know what createNewDetour is doing (it is not part of DDetours that was suggested).

 

 

 

Share this post


Link to post
24 minutes ago, Lajos Juhász said:

When you create a detour you should store the original function address to be able to return it. we don't know what createNewDetour is doing (it is not part of DDetours that was suggested).

Great idea, thank you. I will save the original reference before the detour and use that from within.

FYI, I'm going to rewrite the existing D7 implementation using the mentioned DDetour library and use same or built in D11 as well.

Share this post


Link to post
6 hours ago, David Heffernan said:

One thing I find odd is that you feel the need to change the behaviour of basic windows API functions. Why are you doing this? 

He explained this earlier. He needs to be able to test operation of code with different dates (past, future). Seems legitimate to me; I've had to do this kind of thing but using RunAsDate has always been sufficient. 

Share this post


Link to post
Posted (edited)
1 hour ago, sp0987 said:

Great idea, thank you. I will save the original reference before the detour and use that from within.

That is not how a detour works. If you just save the original function address then you would call back into yourself, as you fear. The original function is still called everywhere, the detour simply modifies the first few bytes of the function to jump to your new function. So creating a detour requires saving the original bytes being modified so the new function can still execute them before jumping back into the original function past the modified bytes to continue the work normally. This is known as a "trampoline".

 

However, in the case of a DLL function, it is simpler to just modify the PE imports table at runtime to redirect the entry of the target function to simply point at a different function, thus bypassing the original function completely. All calls go directly to the new function. In which case, yes, you would need to save the address of the original function so you can call it normally when needed.

 

Since we don't know what your createNewDetour() is actually doing, it's important to know which technique is actually being used.

Edited by Remy Lebeau

Share this post


Link to post
20 minutes ago, Remy Lebeau said:

Since we don't know what your createNewDetour() is actually doing, it's important to know which technique is actually being used.

I was planning to use the suggested DDetours library and since I didn't have it nor D11 in front of me I called it createNewDetour(). This is the actual function:

 

function InterceptCreate(const TargetProc, InterceptProc: Pointer; const Param: Pointer = nil; const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;

 

25 minutes ago, Remy Lebeau said:

That is not how a detour works. If you just save the original function address then you would call back into yourself, as you fear.

Calling reference of the original function will not work then. Thank you for the explanation of the internal DDetours implementation. If there is no other way than I'll look into modifying the PE imports table. That is something I never had to do. Hopefully it is easy.

Share this post


Link to post
Posted (edited)
2 hours ago, sp0987 said:

This is the actual function:

 

function InterceptCreate(const TargetProc, InterceptProc: Pointer; const Param: Pointer = nil; const Options: TInterceptOptions = DefaultInterceptOptions): Pointer; overload;

That function returns a pointer to a trampoline.  You must use that pointer when your hook function wants to call the original function.

 

 

https://github.com/MahdiSafsafi/DDetours/wiki#interceptcreate

Quote

Return: If the function succeeds, the return value is a pointer to a TrampoLine function that can be used either to call the original function or to call the next hook (if exists). If the function fails, the return value is nil.

For example:

var
  TrampolineGetLocalTime: procedure(var st: TSystemTime); stdcall;

procedure myNewLocalTime(var st: TSystemTime); stdcall;
begin
  ...
  TrampolineGetLocalTime(st);
  ...
end;

TrampolineGetLocalTime := InterceptCreate(@Windows.GetLocalTime, @myNewLocalTime, ...);

This is demonstrated in the DDetours documentation:

https://github.com/MahdiSafsafi/DDetours/wiki#hooking-winapi-function

Edited by Remy Lebeau
  • Thanks 1

Share this post


Link to post
26 minutes ago, Remy Lebeau said:

That function returns a pointer to a trampoline.  You must use that pointer when your hook function wants to call the original function

I didn't fully go through their WiKi and examples before, but the "Hooking WinAPI function" example is exactly what I need. I've got the idea how it works now. Thank you ALL for helping out pointing me the right direction!

Share this post


Link to post

Does DDetours offer support for Delphi 11? I'm having trouble using the library right now. DDetours was utilized in place of editing the sysutils.pas file, as advised. However, it functions properly with Delphi 7 but returns a "Inavalid Date to EncodeDate" issue with Delphi 11. This code sample, which functions well in D7 but not in D11, was supplied.

 

implementation

{$R *.dfm}

var
  windows_GetLocalTime: procedure(var st: TSystemTime); stdcall;

 

procedure getTbtTime(var st: TSystemTime);
var
  dt: TDateTime;
begin
  dt := strtodatetime('12/06/2024');
  DecodeDateTime(dt, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute,
    st.wSecond, st.wMilliseconds);
end;

 

procedure TForm1.FormCreate(Sender: TObject);
begin
  windows_GetLocalTime := Windows.GetLocalTime;
end;

procedure TForm1.Button1Click(Sender: TObject);
var

  dt, value: TDateTime;
  s: string;
begin
  dt := Now;
  showmessage('before hook:- ' + datetimetostr(dt));
  windows_GetLocalTime := InterceptCreate(@Windows.GetLocalTime, @getTbtTime);
  dt := Now; // Returns error "Invalid date to Encodedate"
  s := FormatDateTime('mm/dd/yyyy : hh:nn:ss.zzz - ', dt);
  // showmessage('after hook:- ' + datetimetostr(dt));
end;

Share this post


Link to post
Posted (edited)

Are you setting every field in st? Looks like you didn't set DayOfWeek. It's a Wednesday, the 12th June. Doesn't seem worthwhile calling StrToDateTime and the DecodeDateTime when you can just set the fields directly. 

Edited by David Heffernan

Share this post


Link to post

In the DecodeDateTime, we will not use that param.

Share this post


Link to post
24 minutes ago, sp0987 said:

  dt := Now; // Returns error "Invalid date to Encodedate"

Are you sure DDetours is not working?

Have you tried to trace into Now() to debug and see what GetLocalTime(SystemTime) is returning in SystemTime?

And to see actually where the exception is raised?

Share this post


Link to post

AM sure, it's not working. When the call the Now() after "Interceptcreate", then as we hooked the "gettbttime" it will go to GetTBTtime proc and then decode the date which was given in the code. And then it goes to function Now() in sysutils.pas. In that function , the SystemTime var will be filled with either '0''s or weird numbers. 

 

The same code when in debugged using D7, the SystemTime var in Now() of sysutils.pas was filled with the date given in "gettbttime"

Share this post


Link to post
44 minutes ago, sp0987 said:

the SystemTime var will be filled with either '0''s or weird numbers. 

Not sure about this, but the following works fine

var
  windows_GetLocalTime: procedure(var st: TSystemTime); stdcall;

procedure getTbtTime(var st: TSystemTime); stdcall;
var
  dt: TDateTime;
begin
  dt := strtodatetime('12/06/2025');
  DateTimeToSystemTime(dt, st);
  //DecodeDateTime(dt, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute,
  //  st.wSecond, st.wMilliseconds);
end;

procedure TForm10.Button1Click(Sender: TObject);
var
  dt, value: TDateTime;
  s: string;
begin
  dt := Now;
  Memo1.Lines.Add('before hook:- ' + datetimetostr(dt));

  windows_GetLocalTime := InterceptCreate(@GetLocalTime, @getTbtTime);   // if it is only for local (here and now) then we need to clean it up locally
  try
    dt := Now; // Returns error "Invalid date to Encodedate"

    s := FormatDateTime('mm/dd/yyyy : hh:nn:ss.zzz - ', dt);
    Memo1.Lines.Add(s);
  finally
    InterceptRemove(@windows_GetLocalTime);                              // cleanup the hook and restore
  end;
end;

procedure TForm10.FormCreate(Sender: TObject);
begin
  windows_GetLocalTime := GetLocalTime;
end;

See that stdcall modifier for getTbtTime, it is essential here when intercepting Windows API, without it all the parameters are wrong.

Share this post


Link to post

Much grateful for you response. It worked. 

1 hour ago, Kas Ob. said:

procedure getTbtTime(var st: TSystemTime); stdcall;

The problem is with this LOC. Without stdcall it worked in D7.

 

 

Share this post


Link to post
Posted (edited)

Strange how it worked in D7. Maybe the garbage on the stack was useful.

With stdcall TSystemTime will be passed as value directly on the stack.

Without stdcall TSystemTime will be passed as reference.

Do I'm missing something?

 

https://docwiki.embarcadero.com/RADStudio/Sydney/en/Program_Control_(Delphi)

Quote
  • Sets, records, and static arrays of 1, 2, or 4 bytes are passed as 8-bit, 16-bit, and 32bit values. Larger sets, records, and static arrays are passed as 32-bit pointers to the value. An exception to this rule is that records are always passed directly on the stack under the cdecl, stdcall, and safecall conventions; the size of a record passed this way is rounded upward to the nearest double-word boundary.

 

Edited by Cristian Peța

Share this post


Link to post
2 hours ago, Cristian Peța said:

With stdcall TSystemTime will be passed as value directly on the stack.

That would be true only if the record were being passed by value, but in this case it is being passed by explicit 'var' reference instead, so the size and location of the record doesn't matter. The GetLocalTime() API takes a pointer, which the 'var' satisfies. 

Share this post


Link to post

Now I see that stdcall will force to put on stack the reference to TSystemTime.

Without stdcall the reference will be passed in EAX registry.

Maybe D7 is not using EAX and will put the reference on stack also without stdcall.

Share this post


Link to post
8 minutes ago, Cristian Peța said:

Now I see that stdcall will force to put on stack the reference to TSystemTime.

Without stdcall the reference will be passed in EAX registry.

Maybe D7 is not using EAX and will put the reference on stack also without stdcall.

This is wrong on D7 too. Don't believe everything you read. 

  • Thanks 1

Share this post


Link to post
1 hour ago, Cristian Peța said:

Now I see that stdcall will force to put on stack the reference to TSystemTime.

Without stdcall the reference will be passed in EAX registry.

Maybe D7 is not using EAX and will put the reference on stack also without stdcall.

Well, this is sort of bugs are the worst because in some context they stay hidden for long time even for ever, and then one change in the code (a correct, innocent and valid) in different place might trigger this one to show its face wasting hours of bug hunting in the wrong places.

 

Take an example of this code

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TDoSomethingProc = procedure(var Value: TDateTime); stdcall;

procedure DoSomething(var Value: TDateTime); stdcall;
begin
  Value := 1;
end;

procedure DoSomething2(var Value: TDateTime);
begin
  Value := 2;
end;

procedure Test;
var
  dt: TDateTime;
  Proc1,Proc2:TDoSomethingProc;
begin
  Proc1 := @DoSomething;
  Proc2 := @DoSomething2;
  Proc1(dt);
  Proc2(dt);
end;

begin
  Test;
  Readln;
end.

As you said it is wrong and should not work, yet this code on my Delphi XE8 does work fine with no problem.

The stack is clearly broken in Test() right after Proc2(dt) and in theory returning to main form Test() should be broken, yet it does return like nothing happen !

 

The secret is Stack Framing and this is one of the power of not depending on the stack in the first place but on a pointer to it with minimum or no push/pop at all ( unless it is needed), to get the big picture we need to look at the assembly for this code and the stack been fixed right in place.

image.thumb.png.8bba1de823b3591fbba62766227f38a5.png

So in this case no harm detected and that because proc2 does need the parameter in eax and in fact it is there, because the compiler used eax on push.

 

When this can go wrong and break ? we i can think of at least two (other of course do exist)

1) If Test() has more local variable or just more code, and the compiler did use different register other than eax to load the pointer to the variable dt and push it on the stack.

2) Any code after Proc2() will have the stack (the real stack on esp) broken, so calling functions after it and their func/proc called from there might raise unexplained exceptions or show undefined behavior or logic.

 

ps: this use of ebp as stack storage is not exclusive to Delphi neither its invention, but in Delphi we don't have the extreme optimization setting like in Visual Studio when remove this protection to gain speed, hence exposing such bugs right away.

Share this post


Link to post
6 minutes ago, Kas Ob. said:

s you said it is wrong and should not work, yet this code on my Delphi XE8 does work fine with no problem.

A change in the test procedure will cause an access violation in D12.1 if compiled as win32, win64 will not cause error:

 

procedure Test;
var
  dt: TDateTime;
  Proc1,Proc2:TDoSomethingProc;
begin
  Proc1 := @DoSomething;
  Proc2 := @DoSomething2;
  Proc1(dt);
  WriteLn(DateTimeToStr(dt));
  Proc2(dt);
  WriteLn(DateTimeToStr(dt));
end;

 

Share this post


Link to post
1 minute ago, Lajos Juhász said:

A change in the test procedure will cause an access violation in D12.1 if compiled as win32, win64 will not cause error:

Right as (2)

9 minutes ago, Kas Ob. said:

2) Any code after Proc2() will have the stack (the real stack on esp) broken, so calling functions after it and their func/proc called from there might raise unexplained exceptions or show undefined behavior or logic.

 

2 minutes ago, Lajos Juhász said:

win64 will not cause error

Because on Win64 the default calling convention is the same for Windows OS API and Delphi 64bit, stdcall does nothing, the stack is not used for the first 4 parameters.

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

×