User talk:ConradPino/Christian Wimmer/Dynamic Function Invocation Update

From Project JEDI Wiki
Jump to navigationJump to search

Completed or tabled discussion items are archived here.

_

Design Offer JediAPILib.inc Counter Proposal 2

Christian counter proposed with:

{$IFDEF DELPHI2010_UP}
  {$IFDEF DYNAMIC_LINK}
    {$DEFINE DELAYED_LINKING}
    {$UNDEF DYNAMIC_LINK}
  {$ENDIF DYNAMIC_LINK}
{$ELSE}
  {$IFDEF DELAYED_LINKING}
    {$UNDEF DELAYED_LINKING}
    {$DEFINE DYNAMIC_LINK}
  {$ENDIF DELAYED_LINKING}
{$ENDIF}
DELPHI2010_UP DELAYED_LINKING DYNAMIC_LINK Link Method
False False False Static
False False True Dynamic
False True, Undefined False, Defined Dynamic
False True, Undefined True, Defined Dynamic
True False False Static
True False, Defined True, Undefined Delayed
True True False Delayed
True True, Defined True, Undefined Delayed

Add Comments Here

Conrad T. Pino 22:04, 2 February 2010 (UTC):

Not my favorite but I can live with it as I don't have Delphi 2010.
How shall a project member with just Delphi 2010 support dynamic linking?

Design Offer JediAPILib.inc Counter Proposal

Christian, I've counter proposed with:

{$IFDEF DELPHI2010_UP}
  {$IFDEF DELAYED_LINKING}
    {$UNDEF DYNAMIC_LINK}
  {$ENDIF DELAYED_LINKING}
{$ELSE}
  {$IFDEF DELAYED_LINKING}
    {$UNDEF DELAYED_LINKING}
    {$DEFINE DYNAMIC_LINK}
  {$ENDIF DELAYED_LINKING}
{$ENDIF}
DELPHI2010_UP DELAYED_LINKING DYNAMIC_LINK Link Method
False False False Static
False False True Dynamic
False True, Undefined False, Defined Dynamic
False True, Undefined True, Defined Dynamic
True False False Static
True False True Dynamic
True True False, Undefined Delayed
True True True, Undefined Delayed

Add Comments Here

You've neither commented nor deleted your original proposal. – Conrad T. Pino 19:43, 1 February 2010 (UTC)

ChristianWimmer 20:01, 1 February 2010 (UTC) No this is the only proposal but, I mean, I haven't written out.
Conrad T. Pino 22:12, 1 February 2010 (UTC):
I'm not sure what you mean but let's not belabor the point.
I shall use my implementation until you build a better case for yours; in the meantime I shall archive your proposal on this talk page.
Thank you!
Conrad T. Pino 22:47, 1 February 2010 (UTC) – I believe I understand; I shall add a use case table.

ChristianWimmer 18:50, 2 February 2010 (UTC):

I meant, that I haven't brought my thoughts to an end. It was just an unfinished idea.

Conrad T. Pino 19:23, 2 February 2010 (UTC):

  • My proposal is complete now.
  • This task is stalled until you finish this part.
  • I shall move onto something else pending your notice.

ChristianWimmer 19:49, 2 February 2010 (UTC):

Delphi 2010 and newer won't use the DYNAMIC_LINKING part (that is using asm). So in the end there are alway only two ways how to load a function : static and dynamic.

Conrad T. Pino 21:54, 2 February 2010 (UTC):

I shall assume static and dynamic above really means static and delayed'.
I shall move and second proposal into a new discussion topic (see above).

Invocation Analysis Case Current

Current invocation example with instructions generated included as comments:

var
  _TestFunctionRealOld: Pointer;

function TestFunctionRealOld( const Index: Integer; const Value: PChar ): Integer; stdcall;
begin // push stack frame here
  //  push  ebp
  //  mov   ebp,esp
  //  push  ebx

  GetProcedureAddress( _TestFunctionRealOld, LibReal, 'TestFunctionRealOld' );
  //  push  eax,@_TestFunctionRealOld
  //  push  ecx,@LibReal
  //  push  edx,@'TestFunctionRealOld'
  //  call  GetProcedureAddress

  asm
    MOV   ESP,EBP       // undo: ESP change of push ebx; EBX not restored!!!
    //  mov   esp,ebp

    POP   EBP           // undo: push ebp; mov ebp,esp
    //  pop   ebp

    JMP   [_TestFunctionRealOld]
    //  jmp   dword ptr [_TestFunctionRealOld]
    //  mov   eax,ebx
  end;
end; // pop stack frame here
//  pop   ebx
//  pop   ebp
//  ret   8

Defect EBX not restored

While MOV ESP,EBP restores ESP, EBX is NOT restored. I speculate GetProcedureAddress is preserving EBX.

Recommendation:

   asm
-    MOV   ESP,EBP
+    POP   EBX
     POP   EBP
     JMP   [_TestFunctionRealOld]
   end;

Defect Speed

Procedure GetProcedureAddress is called N times when once is sufficient. This is a simple improvement and easy to implement:

-  GetProcedureAddress( _TestFunctionRealOld, LibReal, 'TestFunctionRealOld' );
+  if not Assigned( _TestFunctionRealOld ) then GetProcedureAddress( _TestFunctionRealOld, LibReal, 'TestFunctionRealOld' );
+  //  cmp   dword ptr [_TestFunctionRealOld],$0
+  //  jnz   +$14
   //  push  eax,@_TestFunctionRealOld
   //  push  ecx,@LibReal
   //  push  edx,@'TestFunctionRealOld'
   //  call  GetProcedureAddress

Add Comments Here

Conrad T. Pino 12:47, 2 February 2010 (UTC):

Christian, if we do nothing else, we should replace "MOV ESP,EBP" with "POP EBX" in ALL units.

ChristianWimmer 18:47, 2 February 2010 (UTC)

Even if the current approach has a bug it luckily seems not be to bad.
Fixing such a bug that affects a lot of units (in this case: all) is really a risky venture.
We should get more comments from other people imho. We should ask in different forums and newsgroups.

Conrad T. Pino 19:16, 2 February 2010 (UTC):

Even if ... luckily ... This is a CERTAINTY proven by the Delphi compiler's pop stack frame instruction sequence. Should surrounding context change we shall the unlucky owners of a difficult to understand bug. The time to act is soon while the problem is still well understood.
... a lot of units ... risky venture. No harder and no riskier than removing extra white space and tabs. This is a JEDIedit job.
... get more comments ... I'm certain but I respect your uncertainty. I leave such solicitation to you.
I developed a test harness to prove "POP EBX" and other optimizations work in real code. The test harness is two (2) Delphi projects:
  1. Delphi console application uses dynamic linking
  2. Delphi Dynamic Load Library (DLL) exports stdcall functions

ChristianWimmer 20:00, 2 February 2010 (UTC)

I have asked an expert on this subject and he disagrees to the EBX problem.

Hi Conrad,

Chris was so nice to give me a simple sample binary with the current implementation of the JEDI wrapper for dynamically linked functions. In this case it's MessageBoxW and I loaded it into a disassembler.

The call to the wrapper looks like this:

                     push    0
                     push    offset aOliver  ; "Oliver"
                     push    offset aHello   ; "hello"
                     push    0
                     call    Wrap_MessageBoxW

And here goes the wrapper itself:

     Wrap_MessageBoxW proc near
                 push    ebp
                 mov     ebp, esp
                 push    ebx
                 mov     eax, offset pfnMessageBoxW
                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
                 mov     edx, offset szUser32dll ; "user32.dll"
                 call    GetProcedureAddress
                 mov     esp, ebp
                 pop     ebp
                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp
                 mov     eax, ebx
                 pop     ebx
                 pop     ebp
                 retn    10h

We can probably agree that everything after ENDP is just left-over from the compiler-generated stack frame. We'll ignore it, so it boils down to:

     Wrap_MessageBoxW proc near
                 push    ebp
                 mov     ebp, esp
                 push    ebx
                 mov     eax, offset pfnMessageBoxW
                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
                 mov     edx, offset szUser32dll ; "user32.dll"
                 call    GetProcedureAddress
                 mov     esp, ebp
                 pop     ebp
                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

With this we observe that GetProcedureAddress() is using the (Delphi) register calling convention. Since this calling convention only requires the registers EDX, ECX and EAX to be preserved (the rest will end up on the stack), EBX is of no concern. But let's get to your claim concerning the restoration of EBX.

If it would matter for the sake of the register calling convention (which it doesn't) to restore EBX, does your solution provide the key?

  • Actually nope. *Neither does the JEDI one (obviously).
  • Yours would restore EBX but end up with the stack unbalanced,
  • the JEDI one ends up with the stack balanced *and* EBX is preserved even if there wasn't a PUSH EBX in the prolog.

If at all, you'd have to POP EBX *before* the MOV ESP, EBP! Because by then EBX will be in the right place on the stack to POP it.

Why is that? Well, let's have a closer look at the stack pointer:

     Wrap_MessageBoxW proc near
 000                 push    ebp
 004                 mov     ebp, esp
 004                 push    ebx
 008                 mov     eax, offset pfnMessageBoxW
 008                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
 008                 mov     edx, offset szUser32dll ; "user32.dll"
 008                 call    GetProcedureAddress
 008                 mov     esp, ebp
 004                 pop     ebp
 000                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

It turns out the stack is fully balanced at the JMP (as it should, current state). Now let's look at yours:

     Wrap_MessageBoxW proc near
 000                 push    ebp
 004                 mov     ebp, esp
 004                 push    ebx
 008                 mov     eax, offset pfnMessageBoxW
 008                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
 008                 mov     edx, offset szUser32dll ; "user32.dll"
 008                 call    GetProcedureAddress
 008                 mov     esp, ebp
 004                 pop     ebx ; <-- inserted as per your suggestion
 000                 pop     ebp
-004                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

Woohoo, we just ate up more stack than allowed and probably ended up popping the return address or some argument upon which ds:pfnMessageBoxW would rely.

Let's break it down though. The register calling convention requires only the values of the three general purpose registers EDX, ECX and EAX to be preserved.

Are we fine so far? Okay, well then it means that GetProcedureAddress() is guaranteed not to meddle with EBX anyway. Right?


So why restore it?


We simply balance out the stack and we're done.


Remember: the PUSH EBX is actually compiler-generated, while all after the call to GetProcedureAddress() up to the ENDP is manual "trickery" (or "hackery" ;)).


Oliver (assarbad.net)


Hi Conrad,

I had a little glitch in my wording in one paragraph. Since it was conditional clause and we assumed for that moment that EBX matters, of course yours and the JEDI implementation fail to restore EBX properly, but the JEDI implementation would still leave the stack balanced, while you eat up four bytes too much.

Conrad T. Pino 22:58, 2 February 2010 (UTC):

Christian, Oliver misunderstood my proposal and reached the wrong conclusion regarding stack balance:
     Wrap_MessageBoxW proc near
 000                 push    ebp
 004                 mov     ebp, esp
 004                 push    ebx
 008                 mov     eax, offset pfnMessageBoxW
 008                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
 008                 mov     edx, offset szUser32dll ; "user32.dll"
 008                 call    GetProcedureAddress
 008                 // mov     esp, ebp <- removed per my suggestion
 008                 pop     ebx ; <-- inserted as per your suggestion
 004                 pop     ebp
 000                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp
According to Delph 6 Help File, The asm statement page, Register Use section, which I quote with added emphasis:
In general, the rules of register use in an asm statement are the same as those of an external procedure or function. An asm statement must preserve the EDI, ESI, ESP, EBP, and EBX registers, but can freely modify the EAX, ECX, and EDX registers. On entry to an asm statement, BP points to the current stack frame, SP points to the top of the stack, SS contains the segment address of the stack segment, and DS contains the segment address of the data segment. Except for ESP and EBP, an asm statement can assume nothing about register contents on entry to the statement.
I should clarify, "POP EBX" is NOT ALWAYS the correct choice; I apply this choice to Project JEDI Dynamic Linking where:
  • Delphi requires EBX preservation
  • calling convention stdcall
  • arguments are used (triggers stack frame push)
  • procedure var section is NOT USED (omits ESP decrement)
  • procedure GetProcedureAddress is called (triggers PUSH EBX)
should any of the above change then the correct solution changes.
However it seems all JWAPI cases meet the above criteria but I shall verify nevertheless should we proceed.
Would a calling sequence use case enumeration be helpful to you and Oliver?
Reply #1 to Conrad (--Oliver 01:33, 3 February 2010 (UTC))

I just created a Wiki account as it might make communication easier. Also, I'm creating this as a subsection so structuring and editing will become a bit easier. In fact I suggest you do something similar (even for past instances) to allow for navigation from the TOC to separate items in the discussion and to be able to edit sections separately instead of always editing a huge chunk which may lead to edit conflicts.


  • Sorry about the misunderstanding concerning the removal of MOV ESP, EBP.
  • But first off: tell me, what is the effective difference between the two variants (yours and the current) in terms of register state? (Hint: no difference whatsoever!)
  • The quote of the Delphi help file does not the least contradict the observations and statements I made. The help merely states the obvious: i.e. that no one must tamper with the registers or restore those it did tamper with. Well obviously:
    • from the context we can prove that neither we (only using PUSH EBX) nor the called function (GetProcedureAddress), if it abides the rules of the register calling convention, tampers with EBX.
    • in fact the called function has the very same obligation that you quoted from the help file, except it may tamper with EDX, ECX and EAX due to the use of the register calling convention. Of course the "effective tampering" is limited to EDX and ECX because EAX is used as the return value.
    • if neither we nor our callees tamper with it, who or what does? This is the key to uphold your argument, by the way.
    • hence it is irrelevant whether we restore EBX - it is only relevant to balance out the stack. q.e.d.


Let's look at the help file quote:

In general, the rules of register use in an asm statement are the same as those of an external procedure or function.

Which tells us that we have to preserve the state. Are we on the same page about that? It also tells us what other functions such as GetProcedureAddress have to do. Still on the same page?


An asm statement must preserve the EDI, ESI, ESP, EBP, and EBX registers, but can freely modify the EAX, ECX, and EDX registers.

How does the current code violate this requirement? The current code just cuts a corner by using a JMP to "call" the target function and thus not even requiring the epilogue. So if the epilogue goes unused that leaves us with the restoration of the registers and with the reliance on the fact that the quote above the last one holds true for any callees.


On entry to an asm statement, BP points to the current stack frame, SP points to the top of the stack, SS contains the segment address of the stack segment, and DS contains the segment address of the data segment.

Well, too bad. The assembler code in this example makes much more than just the assumption about EBP. In fact it makes certain assumptions about what happened in the prologue. So if "Borland" chose to change that behavior for whatever reason, users of this method would have to change those units in order to make it compile with a newer compiler. That's one side of the coin. The other one is that x64 puts an end to calling conventions as we know them from x86 and therefore all of that code (besides the change of register names) would become invalid in an x64 context. So I'd vouch for a pure Delphi implementation in any case. And your proposal is still using assembler, so if it doesn't change anything about the register state what exactly does it improve?

Oh and to look at it from another angle: let's say your change would be applied and wrote:

   asm
     POP   EBX
     POP   EBP
     JMP   [_TestFunctionRealOld]
   end;

instead of:

   asm
     MOV   ESP,EBP
     POP   EBP
     JMP   [_TestFunctionRealOld]
   end;

how exactly is that in agreement with the rule you quoted? In short, neither one is. Neither yours nor the current one. So why change it in the first place?


Except for ESP and EBP, an asm statement can assume nothing about register contents on entry to the statement.

Actually it can and does in this case. The sole reason for this being the assumption that only one function has been called beforehand and that this function did nothing arcane and thus only modified EDX, ECX and EAX (at most). Things would change slightly if it would return a 64bit value, but they wouldn't (for example) change if the function had three more parameters. They might change if a loop construct was in front of the asm block, if there were local stack variables or if the Borland compilers were truly optimizing. And by the way all these assumptions and all your assembler code (not only the current one, but also yours) would break down if Delphi (or any other compiler for that matter) would optimize functions in which assembler blocks were used. It doesn't; and that for a reason ...


Now for the remaining comments:

Delphi requires EBX preservation

Please show me the place that modifies the EBX register (except your proposed POP EBX) and doesn't restore them in any of the above code snippets?! I.e. any reason to chose your solution over the current one.


calling convention stdcall

Why is that even relevant? Other calling conventions will also use the frame pointer in order to use the stack frame. The only exception probably (I haven't and can't verify it) being a pure assembler function using the register calling convention:

function ReturnZero: Integer; register;
asm
  xor eax, eax
  ret
end;


arguments are used (triggers stack frame push)

That's the beauty of it. Whenever a frame pointer (that's what EBP is called in this case) is used, you don't need to do any fancy stack pointer arithmetics (based on ESP), instead offsets from EBP are used. The only times it changes are:

  • when arguments get pushed to the stack for passing to a called function
  • when a function is called
  • (optionally) when the stack is balanced after a call to a cdecl function


procedure var section is NOT USED (omits ESP decrement)

Huh? You rely on compiler-internals and build on assumptions based on these internals (which could theoretically change with every single Delphi version). So how is that different from the hackery that is currently in use?


procedure GetProcedureAddress is called (triggers PUSH EBX)

Well, no one showed me the implementation of this one, but I got the binary. So let me give you the gist of that function with respect to the state of the EBX register

Calling Sequence Use Case 0:

    GetProcedureAddress proc near
; Omitting the stack frame layout
000                 push    ebp
004                 mov     ebp, esp
004                 add     esp, 0FFFFFFE4h
020                 push    ebx ; <--- SAVE EBX
024                 push    esi
028                 push    edi
; Omitting tons of implementation details
02C                 pop     edi
028                 pop     esi
024                 pop     ebx ; <--- RESTORE EBX
020                 mov     esp, ebp
004                 pop     ebp
000                 retn
    GetProcedureAddress endp

Need I say more?


should any of the above change then the correct solution changes.

Nope, sorry but it doesn't and I think by now you can also see that the solution doesn't change at all. It may, but there is no reason why it would have to - or why the current solution would have to be considered defective in any way.


However it seems all JWAPI cases meet the above criteria but I shall verify nevertheless should we proceed.

Well, I don't know whether they meet your criteria, but what does that help anyway if the criteria aren't valid? ;-)


Would a calling sequence use case enumeration be helpful to you and Oliver?

Sorry, have been a reverse engineer for about a decade and a developer quite a bit longer. What is a "calling sequence use case enumeration"? I've honestly never heard of it.


What difference does it make?

What is the difference between these three versions in terms of register state?

Calling Sequence Use Case 1:

     Wrap_MessageBoxW proc near
                 push    ebp
                 mov     ebp, esp
                 push    ebx ; <--- SAVE EBX on stack
                 mov     eax, offset pfnMessageBoxW
                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
                 mov     edx, offset szUser32dll ; "user32.dll"
                 call    GetProcedureAddress
                 mov     esp, ebp
                 pop     ebp
                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

... and this one:

Calling Sequence Use Case 2:

     Wrap_MessageBoxW proc near
                 push    ebp
                 mov     ebp, esp
                 mov     eax, offset pfnMessageBoxW
                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
                 mov     edx, offset szUser32dll ; "user32.dll"
                 call    GetProcedureAddress
                 mov     esp, ebp
                 pop     ebp
                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

... and this one:

Calling Sequence Use Case 3:

     Wrap_MessageBoxW proc near
                 push    ebp
                 mov     ebp, esp
                 push    ebx ; <--- SAVE EBX on stack
                 mov     eax, offset pfnMessageBoxW
                 mov     ecx, offset szMessageBoxW ; "MessageBoxW"
                 mov     edx, offset szUser32dll ; "user32.dll"
                 call    GetProcedureAddress
                 pop     ebx ; <--- RESTORE EBX, even though it has the same value as during the push above
                 pop     ebp
                 jmp     ds:pfnMessageBoxW
     Wrap_MessageBoxW endp

If you can see that there is no difference between those, you'll notice that your argument has been flawed all along. So please, let's all return to something productive instead of arguing about something that isn't worth arguing. Especially in the light that all of these wrappers may have to be replaced in a future version for a "Borland" x64 compiler and that we all are probably quite busy people ;o)

Let me quote you from even before (the part that I misunderstood):

While MOV ESP,EBP restores ESP, EBX is NOT restored. I speculate GetProcedureAddress is preserving EBX.

You don't have to speculate. In fact you can postulate, because otherwise GetProcedureAddress as the callee wouldn't abide by the rules of the register calling convention and would therefore have to be considered defective (not its caller).

Conclusion

The only thing one could argue about is whether the MOV instruction is less efficient than the POP instruction. But given modern processors this is such a moot argument that I won't even enter it ...

Reply #2 to Oliver – Conrad T. Pino 02:37, 3 February 2010 (UTC)

What difference does it make? ... I speculate GetProcedureAddress is preserving EBX. — Answer: None BUT ONLY WHEN GetProcedureAddress preserves EBX.

You've said nothing that prevents some future inept developer from using asm poorly within GetProcedureAddress procedure. I shall elaborate:

  1. Calling Sequence Use Cases 1 & 2 (above) shall propogate GetProcedureAddress EBX corruption back to the Wrap_MessageBoxW caller.
  2. Calling Sequence Use Cases 0 & 3 (above) shall contain GetProcedureAddress EBX corruption by preserving Wrap_MessageBoxW caller's EBX value.
  3. The difference is good versus better.
  4. Probability of GetProcedureAddress EBX corruption is low but not zero.
  5. Remember even proton decay is debated! :)
  6. Why not implement an EBX fire break when it's cheap enough using automated editing?

I assert Project JEDI code inserted before JMP instruction is most reliable when it mimics the containing procedure's epilogue code which always mirrors the containing procedure's prologue code (assuming the compiler is bug free of course). :)

Christian asked we postpone this task; I've agreed. I enjoy the lively discussion but please feel free to balance your time accordingly.

Reply #3 to Conrad (--Oliver 11:11, 3 February 2010 (UTC))

What difference does it make? ... I speculate GetProcedureAddress is preserving EBX. — Answer: None BUT ONLY WHEN GetProcedureAddress preserves EBX.

Which, by the very definition of the register calling convention is always the case. Even if it wasn't always the case, the defect would be in GetProcedureAddress, not in each of the wrappers - or, to make it more obvious, each of its callers. If you are so serious about this, you should also doubt the validity of the contracts entered by declaring and then calling functions as cdecl or stdcall.

NB: the speculation was yours. I quoted you. Please keep that in mind. (added --Oliver 11:15, 3 February 2010 (UTC))


You've said nothing that prevents some future inept developer from using asm poorly within GetProcedureAddress procedure. I shall elaborate:

Which is an absolute borderline case and wasn't part of your original argumentation either. But it's a valid point. However, this is easy to control, because GetProcedureAddress is part of this project, so the inept developer would have to have VCS access, which may cause much greater havoc in many other places without any proper safeguards in place.


Calling Sequence Use Cases 1 & 2 (above) shall propogate GetProcedureAddress EBX corruption back to the Wrap_MessageBoxW caller.

You still haven't explained what "calling sequence use cases" are, though.

Another question is why a corruption of EBX would happen in GetProcedureAddress and, presuming that it happens/happened, why you think the right place to fix the behavior is in the wrapper rather than the faulty implementation of GetProcedureAddress itself. After all I could assert that callers other than the wrappers could also make use of GetProcedureAddress and would thus rely on the behavior defined by the register calling convention and fall prey to the same corruption. In the worst case the caller wouldn't even be able to explicitly control the register state, e.g. because they aren't implemented using assembler blocks.


Calling Sequence Use Cases 0 & 3 (above) shall contain GetProcedureAddress EBX corruption by preserving Wrap_MessageBoxW caller's EBX value.

Which is still only relevant if you can't rely on the register calling convention in which case you have a much bigger problem.


The difference is good versus better.

There is no functional difference whatsoever between the cases I gave, if we may rely on the register calling convention.


Probability of GetProcedureAddress EBX corruption is low but not zero.

Sure, but that also holds with your change.


Remember even proton decay is debated! :)

So the whole proposal was a joke then? Because, if we discuss on that level, nothing in programming should ever be considered a safe assumption which negates the idea of a deterministic behavior of computers.


Why not implement an EBX fire break when it's cheap enough using automated editing?

Because there is wisdom in the words: never change a running system. But as far as I'm concerned, I see no apparent reason why it shouldn't be changed either. The change is sound and it is functionally identical.

NB: Only, by these words I quoted, the question is why should it be changed, not the inverse.


I assert Project JEDI code inserted before JMP instruction is most reliable when it mimics the containing procedure's epilogue code which always mirrors the containing procedure's prologue code (assuming the compiler is bug free of course). :)

Well, shouldn't the first queestion be whether the assertion is valid?


Christian asked we postpone this task; I've agreed. I enjoy the lively discussion but please feel free to balance your time accordingly.

I typed this during breakfast, so to speak. During the days and in the evenings it will be a bit harder to find the time ...

Reply #4 to Oliver Conrad T. Pino 17:46, 3 February 2010 (UTC)

...you should also doubt the validity of the contracts entered by declaring and then calling functions as cdecl or stdcall. I do and have consider calling convention deviations.

Your defense for the inept developer is single point; I accept that one and the EBX correction; I prefer defense in depth.

WikiPedia describes uses cases well.

... why you think the right place to fix the behavior is in the wrapper rather than the faulty implementation ... Strictly speaking not "the right place" but definately a good place for another point in a defense in depth strategy.

... nothing in programming should ever be considered a safe assumption which negates the idea of a deterministic behavior of computers. All too true; computing systems are NOT 100% deterministic. Similar to proton decay. – All this means is I'm considering further down the improbable events list.

Because there is wisdom in the words: never change a running system. But as far as I'm concerned, I see no apparent reason why it shouldn't be changed either. The change is sound and it is functionally identical. NB: Only, by these words I quoted, the question is why should it be changed, not the inverse. Another defense in depth error containment point.

Well, shouldn't the first queestion be whether the assertion is valid? The compiler's prologue and epilogue are the reference standard because the compiler is the context. Even if the compiler writers errored, we must process any error as correct since we can't change the compiler.

I am now certain we understand the technical issues. We agree there is no functional difference.

What remains are best practice opinions:

  • there is insufficient benefit to offset the change risk,
  • the risk reduction produced by the change is the sufficient benefit.

Should we consider reporting something back to Christian?

ChristianWimmer 18:38, 3 February 2010 (UTC)

Well, I do not see the necessity to change anything that has worked for at least 10 years. I suggest to think about more urgent subjects. IMO the future implementation is a combination of the old style (ASM) and the delayed directive. In this way we kill three birds with one stone :
1. Speed improvment (delayed is very fast)
2. Emb. has to bother about changes in their implementation
3. Emb. must make it 64bit compatible
Reply #5 to Conrad (--Oliver 18:55, 3 February 2010 (UTC))

I do and have consider calling convention deviations.

Then were does your doubt end - or, to put it differently, where do you start to trust your caller and your callees in your code?


Your defense for the inept developer is single point; I accept that one and the EBX correction; I prefer defense in depth.

Defensive programming doesn't mean that one has to doubt every proposition. It means that checks should be done in all sensible places, e.g. after parameters got passed to you as a callee. If on the other hand you can't trust your callees then, I'm afraid, the programming language you use is the wrong one. Plain Delphi doesn't even allow you to check the assumptions it makes when calling a function using a particular calling convention. And it does make a whole lot of assumptions, just like the current code and your proposal.

Besides, the whole EBX issue is similar to when I give you a DLL and a declaration of a function and the declaration tells you that the function will treat the contents of a passed zero-terminated string as constant. Then I go and modify the contents in the DLL to which you don't have the source. Whose fault is it, mine (callee, GetProcedureAddress) or yours (caller, Wrap_MessageBoxW)?


WikiPedia describes uses cases well.

Sorry, probably another misunderstanding. I know what use cases are, but I still don't know what's meant by "Calling Sequence Use Cases". And yes, I understand the term "call sequence".


All too true; computing systems are NOT 100% deterministic. Similar to proton decay. – All this means is I'm considering further down the improbable events list.

That's like me using a conditional statement to rule out an error and you'll still tell me that cosmic rays might switch the register state just between my check and the use of the variable (which we assume to not be atomic) and thus cause a disaster. You have to draw the line somewhere. And this has nothing to do with defensive programming anymore.


Another defense in depth error containment point.

I'd like to coin the term "overly paranoid programming" instead of "defensive programming" for this. If all your doubts were founded, not a single program in the world could safely use more than one bit as variable at any given time and conditions depending on an access would have to be coupled to make this an atomic operation. And even then someone could claim the proton decay or cosmic rays or the will of some deity can change it just in the split-nanosecond between check and access.


The compiler's prologue and epilogue are the reference standard because the compiler is the context.

They are a standard for each (as in "each individual") version of the compiler. No one guarantees you the standardization across version. It's more of a convention than a standard in that sense.


Even if the compiler writers errored, we must process any error as correct since we can't change the compiler.

How does that concern the issue at hand?


I am now certain we understand the technical issues. We agree there is no functional difference.

We do.


  • there is insufficient benefit to offset the change risk,
  • the risk reduction produced by the change is the sufficient benefit.

Exactly.


Should we consider reporting something back to Christian?

Sure, feel free to.

Current Action Plan

  • Christian shall solicit additional opinions.
  • Conrad shall send Christian test harness. – Completed 2010-Feb-2 11:45 PST.