-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
adds traps for memory and division operations #840
adds traps for memory and division operations #840
Conversation
Division by zero as well as faulty memory operations may terminate the Primus Machine. Previously we were just terminating it with a specific exception. However, this exceptions should actually be represented by an CPU/ABI-specific interrupt or trap. The proposed implementation provides a mechanism that allows: - trap the exception and translate it to a machine specific interrupt; - ignore or fix it; - catch it on a low-level. The mechanism is based on the same idea as in the Linker that used the primus_unresolved_handler to trap unresolved names. For each trap we provide a corresponding observation, that could be used to install the trap. The trap itself, is a special name, that could be linked (either on a permanent basis, or from the observation). If the trap hanlder is assigned, then it is invoked. Concrete behavior depends on a particular trap, e.g., - for linker trap - the hanlder is invoked instead of the missing code; - for the division by zero - if the handler returns then the result is undefined; - for memory fault - the trap should fix the problem or terminate. We also introduce the Pagefault exception, to represent memory traps. We keep segfault as a non-maskable (non-preventable) exception. In addition, we have provided several new operations in the Linker interface: - unlink: for code unlinking, useful for removing traps - lookup: useful for restoring other's traps As a showcase, we also reimplemented some parts of the promiscuous mode. Now we use the pagefault trap to prevent segmentation faults. Also the fixing is more efficient as instead of mapping one byte pages, we are mapping pages with size of up to 4k. Besides others, this commit will also provide a fix for BinaryAnalysisPlatform#839.
b83233c
to
d6b29e1
Compare
| _ -> None) | ||
|
||
|
||
let division_by_zero_handler = "__primus_division_by_zero_handler" | ||
let pagefault_handler = "__primus_pagefault_hanlder" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in the string, should be: "__primus_pagefault_handler"
Primus.Interpreter.storing >>> make_writable; | ||
ignore_division_by_zero; | ||
ignore_unresolved_names; | ||
Primus.Interpreter.pagefault >>> pagefault; | ||
Primus.Interpreter.leave_pos >>> step; | ||
Primus.Interpreter.leave_blk >>> mark_visited; | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand what's going on. Let me summarize and correct me if I got this wrong:
A page fault condition occurs when trying to access a page of memory that hasn't been mapped yet. This is handled by allocating a new page if the requested range is writable. This is what the trap
function does. If these addresses are not writable, we don't call trap
. We check whether the pagefault was trapped and if it wasn't, we raise a Segmentation_fault
exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm testing these changes on our application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything works great. 💯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A page fault condition occurs when trying to access a page of memory that hasn't been mapped yet. This is handled by allocating a new page if the requested range is writable. This is what the trap function does. If these addresses are not writable, we don't call trap. We check whether the pagefault was trapped and if it wasn't, we raise a Segmentation_fault exception.
Roughly correct, a pagefault occurs when the memory subsystem can't satisfy the request, either because of insufficient permissions or because of the missing page. Since this is a promiscuous mode, we're going to fix both cases and prevent the segmentation fault from occurring.
In the pagefault
handler
let pagefault x =
Mem.is_mapped x >>= function
| false -> map_page Mem.is_mapped x >>= trap
| true ->
Mem.is_writable x >>= function
| false -> map_page Mem.is_writable x >>= trap
| true -> Machine.return ()
we first check whether the page is mapped, if it is not, we map up to 4K of memory, trying not to override an existing mapping, i.e., if the new page is overlapping with an existent one, then we half the size of the new page and try again until, at the worst case, we end up with the 1 byte page mapping (this scenario could occur when 1 byte is requested just before the already mapped page, quite a common case, so a better algorithm could be devised). After the mapping we trap the pagefault by registering a handler. The handler is invoked by the memory subsystem. Since there is nothing else to do, it just unlinks itself, so that it doesn't trap the consequent pagefaults. If the page is mapped, but we still hit the pagefault, then we try to remap this region with writable permissions. (Note, the map_page
function is promiscuous, it maps everything with writable permissions). Finally, if a page is present, and is writable, but pagefault is still signaled by the memory subsystem for unknown for us reason that we don't know how to handle, then we bail out without installing the trap, letting other components to deal with it.
Division by zero as well as faulty memory operations may terminate the
Primus Machine. Previously we were just terminating it with a specific
exception. However, this exceptions should actually be represented by
an CPU/ABI-specific interrupt or trap. The proposed implementation
provides a mechanism that allows:
trap the exception and translate it to a machine specific
interrupt;
ignore or fix it;
catch it on a low-level.
The mechanism is based on the same idea as in the Linker that used the
primus_unresolved_handler to trap unresolved names. For each trap we
provide a corresponding observation, that could be used to install the
trap. The trap itself, is a special name, that could be linked (either
on a permanent basis, or from the observation). If the trap hanlder is
assigned, then it is invoked. Concrete behavior depends on a
particular trap, e.g.,
then the result is undefined;
We also introduce the Pagefault exception, to represent memory
traps. We keep segfault as a non-maskable (non-preventable)
exception.
In addition, we have provided several new operations in the Linker
interface:
As a showcase, we also reimplemented some parts of the promiscuous
mode. Now we use the pagefault trap to prevent segmentation
faults. Also the fixing is more efficient as instead of mapping one
byte pages, we are mapping pages with size of up to 4k.
Besides others, this commit will also provide a fix for #839.