The specific assembler line that keeps being reported time and time again, and which seems to be part of "SYSTEM_$$_CONCAT_TWO_BLOCKS$PMEMCHUNK_VAR$PMEMCHUNK_VAR" (whatever that is) is :
It is part of the heap manager (memory alloc/dealloc).
FPC gets big chunks of memory from the OS (the OS may only hand out big blocks). And it sub-allocates them to your code. When you free them they must be returned to the internal pool (may be deferred to a later point, maybe a later getmem).
For that each block has a few bytes of internal info. (i.e. in front of the address returned by GetMem, are a few bytes that fpc uses).
And that is bad news.
The error indicates that those internal bytes got overwritten by your code.
You somewhere wrote to outside the memory that your code owned.
I.e. SomeArray[lowbound-1] :=
or SomeFreedObject_NowPointingToFpcInternalStructs.field :=
And the really bad news: there is no easy way of telling where....
You do have range checks enabled?
You could try different -gt with 1..4 "t" -gtttt in case its an uninitialized var.
If on linux, you could try valgrind --tool=memcheck (compile with -gv / for valgrind).
I have never tried a dll in valgrind. No idea how good the results will be. But valgrind is the best tool to find that kind of error.
Otherwise you can use heaptrc. With "keepreleased". set the environment HEAPTRC="keepreleased"
But that also relies on the same internal structs, so its a coin toss, if it will help or not....
It may work, if it is a dangling pointer (a freed object).
In that case it tells you where the object was first allocated. You then have to figure out: Where was it freed, and where was it accessed after being freed. (breakpoints in dedicated destroy // and watchpoints may help....)
As a really last resort (I would not go down that route, until I tried all else multiple times)
If you have a reproducible case, and the address which is accessed by " mov %rdx,0x20(%rax)" is always the same, then you can set a watchpoint to it. ^byte($0123456)^ (or similar, you may need to play around a bit, its a long time since....
That will trigger whenever that addressed is accessed.
Try to set the watchpoint as late as possible. Memory is re-used, and so that will trigger hundreds of times before it gets real. (you can use keepreleased to minimize this)).
Set the watchpoint to take a snapshot, and to NOT break. Then after the crash go to the debug-history window....
Mind that this sounds hard enough in theory.... It will take countless attempts in practice to get it right....
What is quite odd is that when it gets the final 'end;' of the main function, rather than jumping to the next function (which the 3rd party tool calls next) the debugger seems to go back to the "begin", and the next step that I step into causes the page protection 216 error, but no obvious code is executed. It just goes "begin - corrupt". I don't know it is returning to the begin though.
That is "normal".
The "begin" and "end" line contain compiler generated code, such as stack management, and dealing with managed types, and local vars that need initialization.
FPC often writes debug info, so that some code that actually is in ending the procedure, appears to be in the "begin" line. So that is why you do jump like this.