In your virtual memory code you use inline assembly and a lot of it is incorrect. There is a OR of 0x80000000 but it doesn't have a $ sign before it so it is a memory operand. I see many of the inline assembly instruction have the source and destination reversed including the loading of CR3. In at&t syntax source is first instruction operand and destination is the second operand (Intel is reverse as it is dest, source).
If inline assembly modifies a register it has to be done with extended inline assembly statement where at a minimum it needs to be listed at a clobber (or better through an output or output/input&output constraint). Beyond that I'm unsure if pym_malloc works right or there is some other bug. I noticed the value of CR3 ended up being 0x100000 which is the memory where the bootloader starts? That ended up being used for the pageDirectoryPointer which seems very wrong.
Regarding using inline assembly, the general advice is if you are new to it - don't use it. Unlike Microsoft whose inline assembly is far more forgiving - GCC is not, but GCC inline assembly is more powerful. GCC's inline assembly has some interesting nuances that can cause code to work in one case and not another if it isn't done correctly. It is often a reason why code that works with optimizations off and fails with them on. There is an article about reasons not to use inline assembly:
https://gcc.gnu.org/wiki/DontUseInlineAsm . The alternative to inline assembly is writing small functions in an assembly module (.S files). There you only have to really worry (in most use cases) about saving and restoring the non-volatile registers EBX, EDI, ESI, EBP if you modify them in the function.