What you ask is doable, but not very simple.
One way to do this is to compensate for the movement of code in its instructions. You need to find all the instructions that use relative RIP addressing (they have ModRM bytes from 05h, 0dh, 15h, 1dh, 25h, 2dh, 35h or 3dh) and adjust their disp32 field to the amount (therefore the movement is limited to +/- 2 GB in the virtual address space, which may not be guaranteed if the 64-bit address space is more than 4 GB).
You can also replace these instructions with your equivalents, most likely replacing each original instruction with more than one, for example:
; These replace the original instruction and occupy exactly as many bytes as the original instruction: JMP Equivalent1 NOP NOP Equivalent1End: ; This is the code equivalent to the original instruction: Equivalent1: Equivalent subinstruction 1 Equivalent subinstruction 2 ... JMP Equivalent1End
Both methods will require at least some rudimentary x86 disassembly procedures.
The first may require the use of VirtualAlloc() on Windows (or some equivalent on Linux) to ensure that the memory containing the corrected copy of the source code is within +/- 2 GB of this source code. And distribution to specific addresses may still fail.
The latter will require not just a primitive disassembly, but also full decoding and command generation.
There may be other quirks.
The boundaries of the instruction can also be found by setting the TF flag in the RFLAGS to force the CPU to generate a single-step debugging interrupt at the end of each command. The debug exception handler will need to catch them and write the RIP value of the next command. I believe this can be done with Structured Exception Handling (SEH) on Windows (never tried with debug interrupts), not sure about Linux. To do this, you will need to execute all the code, each instruction.
Btw, there is absolute addressing in 64-bit mode, see, for example, the MOV to / from instructions of the battery with opcodes from 0A0h to 0A3h.