Skip to content
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

Merged

Conversation

ivg
Copy link
Member

@ivg ivg commented Jun 1, 2018

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 #839.

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.
| _ -> None)


let division_by_zero_handler = "__primus_division_by_zero_handler"
let pagefault_handler = "__primus_pagefault_hanlder"
Copy link
Contributor

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;
]
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything works great. 💯

Copy link
Member Author

@ivg ivg Jun 8, 2018

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.

@ivg ivg merged commit 91db2ae into BinaryAnalysisPlatform:master Jun 8, 2018
@ivg ivg deleted the traps-segfaults-and-divisions branch September 13, 2018 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants