Recent

Author Topic: in-memory machine code generation in fpc  (Read 1007 times)

beria2

  • New Member
  • *
  • Posts: 16
in-memory machine code generation in fpc
« on: April 16, 2025, 02:59:59 pm »
We all have the fpc source files, and I thought there might be a lot of delicious and really (well, for me, of course) useful things there. For example, methods for generating not only assembly code for functions, but also for immediate generation of processor machine code. And this code can already be executed inside the program - in 20 minutes I wrote a procedure that does this for win x64, and checked its functionality (it probably works for Linux, but I just didn't check).. There are specialists here who understand the internal structure of the compiler and how to extract the necessary procedures from there, which are located inside \fpcsrc\compiler\x86_64. I've even figured out roughly what's what and how it works....

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. program ExecuteDoubleDouble;
  3.  
  4. uses
  5.   {$ifdef windows}
  6.   Windows;
  7.   {$else}
  8.   baseunix, sysmman;
  9.   {$endif}
  10.  
  11. type
  12.   TFunc = function(a, b: Double): Double; cdecl;
  13.  
  14. function Execute(const a, b: Double; const code: array of Byte): Double;
  15. var
  16.   mem: Pointer;
  17.   size: PtrUInt;
  18. begin
  19.   size := Length(code);
  20.   if size = 0 then Exit(0 / 0);
  21.  
  22.   {$ifdef windows}
  23.   mem := VirtualAlloc(nil, size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  24.   {$else}
  25.   mem := mmap(nil, size, PROT_READ or PROT_WRITE or PROT_EXEC, MAP_PRIVATE or MAP_ANONYMOUS, -1, 0);
  26.   if PtrUInt(mem) = PtrUInt(-1) then mem := nil;
  27.   {$endif}
  28.  
  29.   if mem = nil then Exit(0 / 0);
  30.  
  31.   Move(code[0], mem^, size);
  32.   Execute := TFunc(mem)(a, b);
  33.  
  34.   {$ifdef windows}
  35.   VirtualFree(mem, 0, MEM_RELEASE);
  36.   {$else}
  37.   munmap(mem, size);
  38.   {$endif}
  39. end;
  40.  
  41. var
  42.   a, b, r: Double;
  43. begin
  44.   a := 3.0;
  45.   b := 4.0;
  46.  
  47.   // mulsd xmm0, xmm1; ret
  48.   r := Execute(a, b, [$F2, $0F, $59, $C1, $C3]);
  49.  
  50.   Writeln('Результат: ', r:0:1);
  51.   Readln;
  52. end.
  53.  

ALLIGATOR

  • Full Member
  • ***
  • Posts: 152
Re: in-memory machine code generation in fpc
« Reply #1 on: April 16, 2025, 04:07:07 pm »
Need a libFPC project - compilable scripting

Thaddy

  • Hero Member
  • *****
  • Posts: 16937
  • Ceterum censeo Trump esse delendam
Re: in-memory machine code generation in fpc
« Reply #2 on: April 16, 2025, 04:21:52 pm »
Vladimir Kladov wrote something similar in the early 2000's for KOL.
(And that also works with fpc)
And way more complete, given the available intel/amd processors at the time.
« Last Edit: April 16, 2025, 04:23:54 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

beria2

  • New Member
  • *
  • Posts: 16
Re: in-memory machine code generation in fpc
« Reply #3 on: April 16, 2025, 07:15:36 pm »
Need a libFPC project - compilable scripting
There's no need to do anything here, and you don't even need to program. Just pull everything out of the finished code base and arrange it as a library.  Even two - one generates an assembler based on the source code of the function, and the second, in my opinion, is more important, it makes processor opcodes from it.  Who knows exactly how the compiler is programmed is a very simple and useful job for everyone..  And I'm sure there are many of them here. For me, it takes a long time and hard to figure it out, although I already understand it in general terms. Moreover, this topic will very steeply raise the possible functionality towards professional use.

Thaddy

  • Hero Member
  • *****
  • Posts: 16937
  • Ceterum censeo Trump esse delendam
Re: in-memory machine code generation in fpc
« Reply #4 on: April 16, 2025, 07:17:37 pm »
I interpreted it that you want to execute at runtime?
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

ALLIGATOR

  • Full Member
  • ***
  • Posts: 152
Re: in-memory machine code generation in fpc
« Reply #5 on: April 16, 2025, 07:44:47 pm »
Need a libFPC project - compilable scripting
There's no need to do anything here, and you don't even need to program. Just pull everything out of the finished code base and arrange it as a library.

👉 Actually, that's what I said 👈
👉 We need the libfpc project

beria2

  • New Member
  • *
  • Posts: 16
Re: in-memory machine code generation in fpc
« Reply #6 on: April 16, 2025, 10:05:53 pm »
I interpreted it that you want to execute at runtime?
Well yeah.. We ALREADY have a ready-made and highly optimized JIT compiler, from within the language, which translates the programming language into the next phase of development, and it's rather surprising to me that no one has ever done this.  Pascal Script is not that at all, and not for that... I don't even know what it's for, because there are so many different interpreters, and it loses to almost everyone :-)
PS I even gave an example above that binary code can be executed perfectly from fpc.

TRon

  • Hero Member
  • *****
  • Posts: 4353
Re: in-memory machine code generation in fpc
« Reply #7 on: April 16, 2025, 10:22:31 pm »
PS I even gave an example above that binary code can be executed perfectly from fpc.
Crashes on my PPC, m68k, arm, risc-v and a dozen of other by FPC supported CPU's (if it compiles et all).

So many compilers and programming languages out there and none exists that implement that idea.... should make one perhaps wonder why ?
« Last Edit: April 16, 2025, 10:45:12 pm by TRon »
Today is tomorrow's yesterday.

beria2

  • New Member
  • *
  • Posts: 16
Re: in-memory machine code generation in fpc
« Reply #8 on: April 17, 2025, 10:30:16 pm »
PS I even gave an example above that binary code can be executed perfectly from fpc.
Crashes on my PPC, m68k, arm, risc-v and a dozen of other by FPC supported CPU's (if it compiles et all).

So many compilers and programming languages out there and none exists that implement that idea.... should make one perhaps wonder why ?
You probably didn't read well.   I repeat. I have a program for x64 and Win, and it works.  Just because I'm using this system now and I don't have any other compiler versions, but I think it will work for Linux (I haven't checked).  That is, it is a processor- and system-dependent function. I also want to remind you that fpc includes the generation of machine code for all processors and operating systems that it supports, although for me personally it is enough that it will generate specific opcodes from the assembler text.

TRon

  • Hero Member
  • *****
  • Posts: 4353
Re: in-memory machine code generation in fpc
« Reply #9 on: April 17, 2025, 11:18:31 pm »
You probably didn't read well.
Nah, I did. You did not seem to have picked up on the underlying message of my response.

Quote
I repeat. I have a program for x64 and Win, and it works.
Yes, I got that and which was exactly the point.

Quote
Just because I'm using this system now and I don't have any other compiler versions,
Has nothing to do with other compiler versions. FPC (cross) compiles for every supported platform (as long as you have set it up properly).

Quote
I also want to remind you that fpc includes the generation of machine code for all processors and operating systems that it supports, although for me personally it is enough that it will generate specific opcodes from the assembler text.
Hurray... no more binutils required. Let me remove them immediately ... oh wait  ;D

A standalone compiler library, as suggested, is nice but would most probably be realized by the time pigs fly  :)
Today is tomorrow's yesterday.

440bx

  • Hero Member
  • *****
  • Posts: 5268
Re: in-memory machine code generation in fpc
« Reply #10 on: April 18, 2025, 12:01:38 am »
@Beria2,

Just in case you might be interested, Zydis offers functions to generate binary code from a _description_ of the machine instruction.  That's a way to generate as much code as needed at runtime and then execute it.

If interested, you can find the Zydis dlls and examples of how to use it at:
https://forum.lazarus.freepascal.org/index.php/topic,67665.msg521439.html#msg521439
The examples that apply to your case are named "Encode..." and "Rewrite..."

HTH.

PS: it only assembles for the 32/64 bit AMD/Intel architectures  (no ARM, PPC, etc)
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v4.0rc3) on Windows 7 SP1 64bit.

beria2

  • New Member
  • *
  • Posts: 16
Re: in-memory machine code generation in fpc
« Reply #11 on: April 18, 2025, 02:03:06 am »

A standalone compiler library, as suggested, is nice but would most probably be realized by the time pigs fly  :)


I'm not arguing here.   It's just not interesting or necessary for anyone, although the entire code base is assembled on fpc itself, and the entire assembler is located inside cgcpu.pas. And then there are people on programming language forums, where this has been around for a long time, mocking an outdated programming language without advanced libraries.

beria2

  • New Member
  • *
  • Posts: 16
Re: in-memory machine code generation in fpc
« Reply #12 on: April 18, 2025, 02:19:05 am »
@Beria2,

Just in case you might be interested, Zydis offers functions to generate binary code from a _description_ of the machine instruction.  That's a way to generate as much code as needed at runtime and then execute it.

If interested, you can find the Zydis dlls and examples of how to use it at:
https://forum.lazarus.freepascal.org/index.php/topic,67665.msg521439.html#msg521439
The examples that apply to your case are named "Encode..." and "Rewrite..."

HTH.

PS: it only assembles for the 32/64 bit AMD/Intel architectures  (no ARM, PPC, etc)

I'm very stupid and don't know how to understand someone else's code well... I realized that this was an assembler/disassembler, as I understood it, ported from C, but I still did not find any products in the ZydisB catalog, which, according to the line of the assembler procedure, gives me a sequence of x64 opcodes.

440bx

  • Hero Member
  • *****
  • Posts: 5268
Re: in-memory machine code generation in fpc
« Reply #13 on: April 18, 2025, 02:47:19 am »
I realized that this was an assembler/disassembler, as I understood it, ported from C, but I still did not find any products in the ZydisB catalog, which, according to the line of the assembler procedure, gives me a sequence of x64 opcodes.
I'll comment one of the examples for you and contrast it with the code in your original post.

The following is the listing for the EncodeMovA example:
Code: Pascal  [Select][+][-]
  1. { --------------------------------------------------------------------------- }
  2. { @file                                                                       }
  3. { Demonstrates encoding a single instruction using the encoder.               }
  4.  
  5.  
  6. {$APPTYPE       CONSOLE}
  7.  
  8. {$TYPEDADDRESS       ON}
  9. {$LONGSTRINGS       OFF}
  10.  
  11. {$WRITEABLECONST    OFF}
  12.  
  13.  
  14. { --------------------------------------------------------------------------- }
  15.  
  16. program EncodeMovA;
  17.  
  18. uses
  19.   ZyDis,
  20.  
  21.   winapi
  22.   ;
  23.  
  24.  
  25. {$include Z900_zydis_inlines.inc}
  26.  
  27. { --------------------------------------------------------------------------- }
  28.  
  29. var
  30.   req                    : ZydisEncoderRequest;
  31.  
  32.   encoded_instruction    : array[ZYDIS_MAX_INSTRUCTION_RANGE] of ZyanU8;
  33.  
  34.   encoded_length         : ZyanUSize  = sizeof(encoded_instruction);
  35.  
  36.   i                      : integer; { can't initialize "for" loop variables!! }
  37.  
  38.   sprintf_buffer         : packed array[0..511] of char = #0;
  39.  
  40. begin
  41.   RtlZeroMemory(@req,                 sizeof(req));
  42.   RtlZeroMemory(@encoded_instruction, sizeof(encoded_instruction));
  43.  
  44.   with req do
  45.   begin
  46.     mnemonic              := ZYDIS_MNEMONIC_MOV;
  47.     machine_mode          := ZYDIS_MACHINE_MODE_LONG_64;
  48.     operand_count         := 2;
  49.     operands[0].&type     := ZYDIS_OPERAND_TYPE_REGISTER;
  50.     operands[0].reg.value := ZYDIS_REGISTER_RAX;
  51.     operands[1].&type     := ZYDIS_OPERAND_TYPE_IMMEDIATE;
  52.     operands[1].imm.u     := $1337;
  53.   end;
  54.  
  55.   repeat                  { scope, not a loop                                 }
  56.  
  57.     if ZYAN_FAILED
  58.          (
  59.           ZydisEncoderEncodeInstruction
  60.             (
  61.              @req, encoded_instruction, @encoded_length
  62.             )
  63.          ) then
  64.      begin
  65.        writeln('Failed to encode instruction');
  66.        break;
  67.      end;
  68.  
  69.     for i := 0 to encoded_length - 1 do
  70.     begin
  71.       { output the bytes that make up the instruction                         }
  72.  
  73.       sprintf(sprintf_buffer, '%02X ', encoded_instruction[i]);
  74.       write(sprintf_buffer);
  75.     end;
  76.     writeln;
  77.  
  78.   until TRUE;             { end of scope                                      }
  79.  
  80.  
  81.   writeln;
  82.   writeln;
  83.   writeln('press ENTER/RETURN to end this program');
  84.  
  85.   readln;
  86. end.
  87.  
In your original post, the execute function uses a list of bytes (basically binary instructions) to be executed.

Zydis is higher level than that.  You give Zydis a _description_ of the instruction you wish to obtain the encoding for and Zydis will return the bytes that are the binary representation of that instruction.  In the example above, the instruction that is being encoded is "mov rax, $1337"

That's why the mnemonic is "...MOV"
the machine mode "...LONG_64"
the operand count is 2 because there is a source operand and a destination operand.
the first operand is a register (rax) "...TYPE_REGISTER"
the register is rax "...REGISTER_RAX"
the second operand is an immediate value "...TYPE_IMMEDIATE"
the value is "$1337"

Using Zydis it is possible to write an assembler.  It certainly is more convenient than manually figuring out the binary representation of every instruction.

The EncodeFromScratchA example is an example of assembling more than 1 instruction _and_ then executing the resulting stream of bytes. 

Essentailly, as long as you know assembly, it's easy to describe the instruction to Zydis, which will assemble it for you and once that is done, it's ready for execution.   You could even have the user type assembler, assemble it on the fly then execute it, all without leaving the program that accepts assembly instructions (a bit like a Turbo Pascal IDE but, for assembler...)  IOW, you can generate executable code on the fly and execute immediately after it has been generated (which I believe is what you want to do.)
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v4.0rc3) on Windows 7 SP1 64bit.

paule32

  • Sr. Member
  • ****
  • Posts: 441
Re: in-memory machine code generation in fpc
« Reply #14 on: April 19, 2025, 11:02:10 am »
MS-IIS - Internet Information Server, Apache, PHP/HTML/CSS, MinGW-32/64 MSys2 GNU C/C++ 13 (-stdc++20), FPC 3.2.2
A Friend in need, is a Friend indeed.

 

TinyPortal © 2005-2018