PDA

View Full Version : Converting ASM to Pascal



Cybermonkey
18-03-2013, 01:46 PM
Since we've got the converting C to Pas thread, I thought some of you are familiar with inline ASM. The reason is that it is obviously 32 bit Assembler. (It is originally written for Delphi but compiles with FPC, too). I want to port the program to 64 bit, so porting to Pascal would be an advantage. Speed issues are no problem. Of course it is also possible to convert the ASM part to 64 bit assembler ... Now here's the piece of code:

asm push esi // esi, edi, ebp and ebx must be kept
push edi // (we use only esi and edi here).
sub esp, StkUsage // make room on stack
mov edi, esp // set destination of mem copy, it is the stack
mov esi, StkAdr // set source of mem copy, it is Addr(ExtStk)
mov ecx, StkUsage // prepare ecx to copy StkUsage bytes
shr ecx, 2 // divide by 4 to perform DWORD-copy (is faster)


//add edi, StkUsage;
//sub edi,4
//add esi, StkUsage;
//sub esi,4
cld // choose copy direction
rep movsd // do DWORD-copy


cmp RegCall, true
jnz @@EXEC
mov EAX, _EAX
mov EDX, _EDX
mov ECX, _ECX


@@EXEC:
call Adr // execute the external function
// esp is restored by the external function
// (except for cdecl-convention)
pop edi // restore edi...
pop esi // ...and esi
mov _EAX,eax
end; // asm

Colin
18-03-2013, 04:53 PM
This just looks like an inefficient string copy routine, it copies string onto stack then calls some other function, i'm guessing it is stdcall, not sure what Adr is or doing in the called function. Note that is aligns memory on 4 bytes for speed copy of dword's but i don't see it writing any left over bytes. Also what is _EAX, _EDX, _ECX ? you need to post all variables used so i can convert it.

Is RegCall a param? can you post the function declaration?



procedure YourProc(StkAdr: PAnsiChar; StkUsage: LongWord);
var
stkDest: array of AnsiChar;
nLen: LongWord;
begin
SetLength(stkDest, stkUsage);
StrLCopy(@stkDest[0], stkAdr, StkUsage);

if RegCall = True then begin
//_EAX, _EDX etc ???

Adr( _EAX, _EDX, _ECX);
end else
Adr( .... // this does not make sense what your function is doing, no params? if so this needs to be cdecl and this function needs to correct the stack.
end;


Also if RegCall is false, then your stack is going to become corrupt. Since stdcall requires you pass the params that it expects, you could get away with this with cdecl but not stdcall.

Cybermonkey
18-03-2013, 05:08 PM
Okay, this is going to be quite long ...

procedure CallFunction(var n:valrec; fi:integer; needResult:boolean); var arity:integer;
var descriptors:string;
var args:TArgs;
var flag:integer;
var needBraces:boolean;
var i:integer;
var missing:boolean;


var stack:array [0..30 { TUNE: }] of integer;
var sp:integer;
var _eax,_edx,_ecx:integer;
var regcount:integer;
var ltr:boolean;



var stkusage:integer;
var stkadr:integer;
var regcall:boolean;
var adr:pointer;
var resadr:pointer;
var resvalue:integer;
var needsExtraParam:boolean;
var isMethod:boolean;
var obj:TObject;

asm
push esi // esi, edi, ebp and ebx must be kept
push edi // (we use only esi and edi here).
sub esp, StkUsage // make room on stack
mov edi, esp // set destination of mem copy, it is the stack
mov esi, StkAdr // set source of mem copy, it is Addr(ExtStk)
mov ecx, StkUsage // prepare ecx to copy StkUsage bytes
shr ecx, 2 // divide by 4 to perform DWORD-copy (is faster)


//add edi, StkUsage;
//sub edi,4
//add esi, StkUsage;
//sub esi,4
cld // choose copy direction
rep movsd // do DWORD-copy


cmp RegCall, true
jnz @@EXEC
mov EAX, _EAX
mov EDX, _EDX
mov ECX, _ECX


@@EXEC:
call Adr // execute the external function
// esp is restored by the external function
// (except for cdecl-convention)
pop edi // restore edi...
pop esi // ...and esi
mov _EAX,eax
end; // asm




// TODO: convert references back


I hope this is the correct code since there are a lot of nested procedures and functions ... ???

Colin
18-03-2013, 05:21 PM
OK, so from you routines i see you are needing to deal with low level access, so converting to high level pascal would not be a good idea. What you will need to do is make your integers be nativeint, ie. _EAX, _EDX etc... also change them to _RAX, _RCX etc

replace your registers for mov to RAX, RCX etc e.g. mov rax, _RAX, the stack on 64-bit is also 16 byte aligned so you can push int64 directly on the stack for stdcall (however i don't recommend it as if the called function is not 64-bit and they do not clean the stack correctly, since they will expect stack on 4 byte alignment this could be a disaster). else you can still use the pascal functions StrLCopy etc for the memory copy to stack it will probably be more efficient.

some other things to note, for Adr if the called function is fastcall, it will not alter esp for passed params since it does not use the stack, in this case you would need to correct the stack yourself.

Cybermonkey
18-03-2013, 05:29 PM
Ok, thanks, but I have no clue what you are talking about. ;D The complete code is not by me but by someone else, I just wanted the program to compile with 64 bit Freepascal ...

Colin
18-03-2013, 05:49 PM
if the functions that are called by "CallFunction" are 32-bit libraries functions etc, then you should not have a problem to compile this on 64-bit pascal, but if you re-compile the libs it calls into 64-bit then you will have a-lot of work ahead.

What i would suggest is to rename this function to CallFunctionX86(... and make sure all pointers and variables used are 32-bit standard, e.g. pointer should be 32-bit pointer (i don't use freepascal dunno which you will need to use)

then you can create a new function CallFunctionX64(

This would be similar but using 64-bit variables and registers.

Cybermonkey
18-03-2013, 05:55 PM
Ok, but I learned that "mov EAX" etc. isn't available on 64 bit processors, is it?

Colin
18-03-2013, 06:02 PM
it is, eax is 32-bit part of RAX, maybe TASM or pascal assembler does not support mov with 32-bit regs (eax, ecx etc) in 64-bit mode (if so that is flawed)

so if mov eax, _EAX does not work try either:

movd eax, _EAX

or

mov rax, dword _EAX

Cybermonkey
18-03-2013, 07:42 PM
It is even worse, "push esi" isn't known either. I added the compiler directive {$asmmode intel}. Now the problems are:

Chip.pas(2041,13) Error: Asm: [push reg32] invalid combination of opcode and operands
Chip.pas(2042,13) Error: Asm: [push reg32] invalid combination of opcode and operands
Chip.pas(2066,12) Error: Asm: [pop reg32] invalid combination of opcode and operands
Chip.pas(2067,12) Error: Asm: [pop reg32] invalid combination of opcode and operands

Another problem causes this piece of code (it's obviously Pascal, but i don't know to solve it either):


args.outVal.n.sbuf:=basicstring(resvalue);
basicstring(resvalue):=''


Error:

Chip.pas(2076,27) Error: Illegal type conversion: "LongInt" to "AnsiString"
Chip.pas(2077,7) Error: Illegal type conversion: "LongInt" to "AnsiString"

With a 32 bit FPC it compiles ... BTW, the complete source of Chip.pas can be found on: http://xmojmr.ohmygod.cz/software/Chip/Chip.pas.htm

Colin
18-03-2013, 07:55 PM
I guess the assembler does some checking for stack alignment to prevent problems with other 64-bit libs, this is an issue with the assembler unless it has an override option, maybe try



[32BIT]
push edi
push .. etc


as for the other code, does basicstring return a pchar/pointer? if so try



basicstring(resvalue)^ := ''


better still:



var
p: PAnsiChar;




p := basicstring(resvalue);
args.outVal.n.sbuf := p;
p^ := ''

phibermon
18-03-2013, 08:04 PM
I'm glad you're here, I got as far as "ahh, it's pushing params onto the stack for the routine that's called", I'd worked out we'd need to see more of the code to understand the context, the function that's called etc meanwhile you've already sorted it ;)

Colin
18-03-2013, 08:14 PM
1 other thing to try is the pop's, try:



pop dword edi

etc

if all else fails you can always manually solve this by replacing the pop's with something like



mov rdi, dword ptr [rsp+4]
mov rsi, dword ptr [rsp]
add rsp, 8


EDIT

but actually hehe, i'm tired, anyways you just need to keep edi etc so in this case we can solve it easily..

your code:


asm
push esi
push edi
sub esp, StkUsage // make room on stack
mov edi, esp // set destination of mem copy, it is the stack
mov esi, StkAdr // set source of mem copy, it is Addr(ExtStk)
mov ecx, StkUsage // prepare ecx to copy StkUsage bytes
shr ecx, 2 // divide by 4 to perform DWORD-copy (is faster)


//add edi, StkUsage;
//sub edi,4
//add esi, StkUsage;
//sub esi,4
cld // choose copy direction
rep movsd // do DWORD-copy


cmp RegCall, true
jnz @@EXEC
mov EAX, _EAX
mov EDX, _EDX
mov ECX, _ECX


@@EXEC:
call Adr // execute the external function
// esp is restored by the external function
// (except for cdecl-convention)
pop edi // restore edi...
pop esi // ...and esi
mov _EAX,eax
end; // asm


so try this:



asm
push rsi
push rdi
sub rsp, StkUsage // make room on stack
mov rdi, rsp // set destination of mem copy, it is the stack
mov rsi, StkAdr // set source of mem copy, it is Addr(ExtStk)
mov rcx, StkUsage // prepare ecx to copy StkUsage bytes
shr rcx, 4 // divide by 8 to perform QWORD-copy (is faster)

cld // choose copy direction
rep movsq // do QWORD-copy


cmp RegCall, true
jnz @@EXEC
mov RAX, dword _EAX
mov RDX, dword _EDX
mov RCX, dword _ECX


@@EXEC:
call Adr // execute the external function
// esp is restored by the external function
// (except for cdecl-convention)
pop rdi // restore edi...
pop rsi // ...and esi
mov _EAX,eax
end; // asm


for 64-bit you want to change _EAX etc to int64 and then remove dword casts. Note that this is only valid for fastcall, stdcall would be a big trouble for stack alignment across 32/64 bit

Cybermonkey
18-03-2013, 08:42 PM
Okay, I changed the code as proposed. This is the [32BIT] part:

Chip.pas(2042,8) Error: Unrecognized opcode
Chip.pas(2042,8) Error: Assembler syntax error
Chip.pas(2042,9) Error: Error converting binary 32


Now the error from this code:

p := basicstring(resvalue);
args.outVal.n.sbuf := p;
p^ := ''



Chip.pas(2078,9) Error: Illegal type conversion: "LongInt" to "AnsiString"
Chip.pas(2080,5) Error: Illegal qualifier
(1st and 3rd line)

Colin
18-03-2013, 08:50 PM
for assembly, see above as for the error i've just checked that source you posted (the link)

seems basicstring is a record, so i think you need to check the function "basicstring2string"

Cybermonkey
18-03-2013, 09:04 PM
Hm, the funny thing is there is no function basicstring, it's a type ...

TYPE basicstring = AnsiString;

Colin
18-03-2013, 09:08 PM
on your chip.pas you posted i see this:



126: basicstring = packed record
127: size:integer;
128: data:pointer
129: end;


anyways as i remember this is the standard format for ansistring, does it doesn't really matter, but in that case what is resvalue?

try this.



args.outVal.n.sbuf := pansistring(resvalue)^;
pansistring(resvalue)^ := ''


i go to sleep for couple hour now, good night and good luck.

Cybermonkey
18-03-2013, 09:47 PM
Oops, I didn't know the versions differ so much ... I am not using the latest source of that page although the ASM part is the same ... because on newer versions there are even more problems.

E. g.
Flags := Flags or tfCRLF;
results in error


Chip.pas(4170,15) Error: Identifier not found "Flags"
Chip.pas(4170,24) Error: Identifier not found "Flags"
Chip.pas(4170,33) Error: Identifier not found "tfCRLF"

Anyway, the older version now compiles with your ASM code, for the basicstring I did this:

var p:integer;
p := resvalue;
args.outVal.n.sbuf := ^p;
p := 0;

But of course if I am trying to execute an external function I got a segmentation fault. >:(
This is the console app which uses Chip.pas, all internal functions are working so far but as said e.g. Test (1,2,3,4,5) or hi results in a segmentation fault.

program cmd;

{$APPTYPE CONSOLE}
uses SysUtils,Chip;


function _Open(const fileName:string):integer;stdcall;
begin
Result:=FileOpen(fileName,fmOpenRead)
end;


function _Read(handle:integer; count:integer):string;stdcall;
var buf:string;
begin
SetLength(buf,count);
count:=FileRead(handle,buf[1],count);
if count<0 then
Result:=''
else begin
SetLength(buf,count);
Result:=buf
end
end;


procedure _Close(handle:integer);stdcall;
begin
FileClose(handle)
end;


procedure _Test(a,b,c,d,e:integer);
begin
WriteLn(Format('a=%d b=%d c=%d d=%d e=%d',[a,b,c,d,e]))
end;


type
TMBasic=class(TBasic)
private
counter:integer;
protected
function Hallo:integer;
end;


function TMBasic.Hallo;
begin
inc(counter);
Result:=counter
end;


var scr,scr2:TMBasic;
var m:TMethod;
type tf=procedure of object;
type tp=function:integer of object;
var p:tp;
var pf:tf;
var p1:pointer;
begin
_Test(1,2,3,4,5);
scr:=TMBasic.Create;
scr2:=TMBasic.Create;
try

scr.Map('System.Open',vkHandle or vkStdCall,chr(vkConst or vkString),@_Open);
scr.Map('System.Read',vkString or vkStdCall,chr(vkHandle)+chr(vkInteger),@_Read);
scr.Map('System.Close',vkNone or vkStdCall,chr(vkHandle),@_Close);
scr.Map('Test',vkNone,chr(vkInteger)+chr(vkInteger )+chr(vkInteger)+chr(vkInteger)+chr(vkInteger),@_T est);
scr.MapMethod('Hi',vkInteger,'',scr,@TMBasic.Hallo );
scr.MapMethod('Hi2',vkInteger,'',scr2,@TMBasic.Hal lo);


// scr.MapMethod('TObject.Free',vkNone,[vkHandle],TObject,@TObject.Free);


// pf:=tf(TMethod(@scr.Hallo));


{ p:=tp(@scr.Hallo);
p1:=@scr.Hallo;
m:=TMethod(tp(@scr.Hallo));
scr.MapMethod('Hi',vkInteger,[vkNone],TMethod(@scr.Hallo));}


// scr.Map('Hi',vkInteger or vkSelfMethod,[vkNone],@TMBasic.Hallo);


scr.Run('')
finally
scr.Free;
scr2.Free
end
end.


Good night, I go to sleep, too ...

Colin
19-03-2013, 12:15 AM
don't forget that class functions require "this" to be passed, you can try make it a class static function. I've not took a deep look into chip app yet but if you're wanting to use also stack etc then you need to convert everything to 64-bit, this includes the variables also.

Cybermonkey
19-03-2013, 10:34 AM
Thanks a lot for your help, but I think it isn't worth the effort. I will keep it 32 bit only, it might execute on a 64 bit Linux, too.

EDIT: Ok, i compiled it on a 32 bit Linux and it works fine on 64 bit. I think I will go that way now. Nevertheless, thank you very much for your help. Maybe one day ...