Reputation: 199
While converting old Turbo Pascal units to modern Object Pascal, I ran into the following:
function Less (var a, b; Relation : POINTER) : boolean;
inline($5B/$59/$0E/$E8/$00/$00/$58/$05/$08/$00/$50/$51/$53/$CB);
The code is supposed to call an external function {$F+} function VariableLess(var a, b : Index) : boolean; {$F-}
, collect the result and pass it to the calling function. The function is used in a unit that provides binary trees for untyped data
procedure InsVarBBTree(var B: BBTree; var E; S: word; A: pointer; var ok: boolean);
{ puts variable E of size S into tree B. The order relation address is A. }
Therefore, the unit itself cannot provide a comparison function, that is the job of the unit that defines the payload.
Using an online disassembler I found out that this corresponds to:
{$ASMMODE intel}
function Less (var a, b; Relation : POINTER) : boolean; assembler;
asm
pop bx
pop cx
push cs
call 6
pop ax
add ax, 8
push ax
push cx
push bx
retf
end;
However, the compiler doesn't like the push
statement. What should I do to get this to work on a modern 64-bit machine? I realise the code is 16-bit.
Upvotes: 4
Views: 666
Reputation: 1
on 64 bit there are some command-size-names else. and there are asm-procedures which must be called, perhaps not for this "Less". further the variable-size-names are rAX,...
Upvotes: 0
Reputation: 199
The solution is as follows: Inside the unit handling dynamic types define
type
TMyCompare = function(var a, b) : boolean;
function Less (var a, b; Relation : TMyCompare) : boolean;
begin
Result := Relation(a, b);
end;
procedure InsVarBBTree(var B: BBTree; var E; S: word; A: TMyCompare;
var ok: boolean);
{ puts variable E of size S into tree B. The order relation address is A. }
This is called from outside
{$F+} function VariableLess(var a, b : Index) : boolean; {$F-}
begin
...
end;
InsVarBBTree(Baum, TempStr, SizeOf(TempStr), TMyCompare(@VariableLess), OK)
Thanks to all who helped with that
Engelbert
Upvotes: 2
Reputation: 18503
I just compiled some inline
function on Turbo Pascal 5 for MS-DOS to check how Turbo Pascal generates code:
For non-inline
function calls, Turbo Pascal pushes all function arguments to the stack. The first one is pushed first (so SS:SP
points to the last function argument). Then a (far
) call
is executed. The function returns using retf n
, which means that the function called removes all parameters from the stack.
In an inline
function, the raw bytes given simply replace the call
instruction. This means that SS:SP
points to the arguments, not to the return address. The inline machine language code must pop
the arguments from the stack. And it must not return using ret
but simply continue code execution at the instruction after the inline
code.
With this knowledge the assembly code can be analyzed:
Using the assembly code given, you can call any function
or procedure
with any parameters (in your case: VariableLess
) indirectly by writing a helper function (in your case: Less
) that has the same arguments as the function to be called plus an additional argument that points to the actual function.
The code is equal to the following Delphi or FreePascal code:
type
TMyCompare = function(var a, b) : boolean;
function Less (var a, b; Relation : TMyCompare) : boolean;
begin
Less := Relation(a, b);
end;
If your compiler supports function pointers (type TMyCompare = function ...
), you could do it like this.
Or you could even replace all occurrences of Less(x,y,z)
in your program by z(x,y)
. This would even be more efficient.
Of course, the pointer to the function (VariableLess
) should not have the type pointer
but the type TMyCompare
if you do it like this.
If your compiler does not support function pointers (as Turbo Pascal obviously did not), you might need assembly.
But in that case, different compilers will need different assembly code!
So not knowing internals of your compiler, it is not possible to translate the assembly code.
EDIT
I'm not sure how exactly your compiler works. However, maybe the following code works if my original code does not work:
function Less (var a, b; Relation : Pointer) : boolean;
type
TMyCompare = function(var a, b) : boolean;
var
Relation2 : TMyCompare;
begin
Relation2 := TMyCompare(Relation);
Less := Relation2(a, b);
end;
Upvotes: 8
Reputation: 26356
Afaik it will work for Free Pascal 16-bits targets, but you are probably not interested in that. For non 16-bit targets, 16-bit code will need rewrite, the memory model and ABI is different. Your disassembly is symbolic and the constants don't really reflect the meaning of the source like an original assembler source (.asm) would.
This procedure is particularly hairy since it seems to make assumptions on the ABI, so it is inherently unportable.
Moreover, even if you would succeed, then the result would be suboptimal, since the object pascal compilers (Delphi, Free Pascal) are way more optimizing than TP ever was. Using external assembler for short procedures stunts the ability of the compiler to inline.
I think Peter Cordes is right, and this is a kind of thunk to the real functionality after the shown procedure. Nope, I think Martin is closer. It is a bit unlogical but because the compiler can't really inline (only dump an assembler block) the parameter calling remains the same, and must be undone without access to a stack frame/local variables. TP doesn't keep values in registers, so this is relatively safe.
You could try to disassemble that too, but the best is probably to simply try to formulate an pascal substitute for it from documentation, and forget about all the micro-optimizations.
Upvotes: 1