Files
revng-revng/support.c
T
Alessandro Di Federico 61cfbdfc56 Introduce support for dynamic binaries
This commit introduces support for dynamic programs. The current
implementation translate the main binary and uses native libraries. This
works only if the target architecture is the same as the source
one. Currently we only handle x86-64.

* The `ExternalJumpsHandler` class has been introduced. It basically
  takes care of extending the dispatcher handling the case in which the
  program counter is an address outside the range of executable
  addresses of the input program. In this case, a `setjmp` is perfomed,
  the CPU state is serialized to physical registers and jump to the
  value of the program counter is performed.

  Once the target code will try to return to the translated program, a
  segmentation fault will be triggered, a `longjmp` is performed and the
  CPU state is deserialized so that the execution can resume (from the
  dispatcher).

* `early-linked.c` has been introduced. Its purposes is to provide
  declarations of variables and functions defined in `support.c`. In the
  past, we had to manually create these definitions, a cumbersome and
  error prone we now avoid by letting `clang` compile `early-linked.c`
  and then linking it in.

* The old `support.h` is now known as `commonconstants.h`. `support.h`
  now contains declarations that have to be consumed by
  `early-linked.c`.

* Each architecture now provides additional information:

  1. Which registers are part of the ABI and have to be preserved. If
     necessary the QEMU name can be provided. For each register it's
     also possible to provide their position within the `mcontext_t`
     structure, provided by the signal handler.
  2. Three assembly snippets, one to write a register, one to read it
     and one perform an indirect jump.

  Some of this information is also exposed in the output module as
  metadata.

* `support.c` now installs a SIGSEGV signal handler. Since pages that
  were originally executable are no longer executable, jumping there
  (typically, from a library) will trigger a SIGSEGV that we will
  handle. This allows us to properly deserialize the CPU state and
  resume execution of the translate code.

* Now also a dynamic version of each test program is translated and
  tested.

* The `merge-dynamic.py` script has been introduced: it takes case of
  rewriting the translated binary so to tell the linker to performe both
  the relocations of the translate program and the relocations of the
  original program. It does so by rewriting a large portion of the
  sections employed by the dynamic linker such as `.dynamic`, `.dynsym`
  and so on.

* The `compile-time-constants.py` script has been introduced: it a
  user-specified compiler on a source file producing an object
  file. This object file is inspected and the value of global read-only
  variables is produced in a CSV.
2018-05-29 15:10:51 +02:00

491 lines
14 KiB
C

/*
* This file is distributed under the MIT License. See LICENSE.md for details.
*/
// Standard includes
#include <assert.h>
#include <elf.h>
#include <endian.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ucontext.h>
#include <unistd.h>
#include <unwind.h>
// Local includes
#include "commonconstants.h"
#include "support.h"
// Save the program arguments for meaningful error reporting
static int saved_argc;
static char **saved_argv;
// Macros to ensure that when we downcast from a 64-bit pointer to a 32-bit
// integer for the target architecture we're not losing information
#define MAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | \
(0xFULL << ((sizeof(t) * 8ULL) - 4ULL)))
#define SAFE_CAST(ptr) do { \
assert((uintptr_t) (ptr) <= MAX_OF(target_reg)); \
} while(0)
noreturn void root(target_reg stack);
static const unsigned align = sizeof(target_reg);
// Define some variables declared in support.h
jmp_buf jmp_buffer;
target_reg *saved_registers;
// Default SIGSEGV handler
static struct sigaction default_handler;
static void *prepare_stack(void *stack, int argc, char **argv) {
target_reg tmp;
target_reg platform_address;
target_reg random_address;
void *arg_area;
char **argp;
char **arge;
// Define some helper macros for building the stack
#define MOVE(ptr, size) do { \
(ptr) -= ((size) + align - 1) & ~(align - 1); \
} while (0);
#define PUSH(ptr, size, data) do { \
MOVE(ptr, size); \
memcpy((void *) (ptr), (data), size); \
} while(0)
#define PUSH_STR(ptr, data) do { \
tmp = strlen(data) + 1; \
PUSH(ptr, (tmp), (void *) (data)); \
} while(0)
#define PUSH_REG(ptr, data) do { \
tmp = SWAP(data); \
PUSH(ptr, align, (void *) &tmp); \
} while (0);
#define PUSH_AUX(ptr, key, value) do { \
PUSH_REG(ptr, (target_reg) (value)); \
PUSH_REG(ptr, key); \
} while(0)
// Reserve space for arguments and environment variables
arg_area = stack;
argp = argv;
while (*++argp != NULL) {
}
while (*++argp != NULL) {
}
arge = argp;
// Push the environment variables
unsigned env_count = 0;
while (*--argp != NULL) {
MOVE(stack, strlen(*argp) + 1);
env_count++;
}
// Push the arguments
while (--argp != (argv - 1)) {
MOVE(stack, strlen(*argp) + 1);
}
PUSH_STR(stack, "revamb");
platform_address = (target_reg) stack;
PUSH_STR(stack, "4 I used a dice");
random_address = (target_reg) stack;
// Compute the value of stack pointer once we'll be done
// WARNING: keep this number in sync with the number of auxiliary entries
const unsigned aux_count = 17;
unsigned entries_count = aux_count * 2 + 1 + env_count + 1 + argc + 1;
uintptr_t final_stack = ((uintptr_t) stack
- entries_count * sizeof(target_reg));
// Force 256 bits alignment of the final stack value
const unsigned alignment = 256 / 8 - 1;
uintptr_t alignment_offset = (final_stack
- (final_stack & ~((uintptr_t) alignment)));
stack -= alignment_offset;
final_stack -= alignment_offset;
assert((final_stack & alignment) == 0);
// Push the auxiliary vector
// WARNING: if you add something here, update aux_count
uintptr_t aux_start = (uintptr_t) stack;
PUSH_AUX(stack, AT_NULL, 0);
PUSH_AUX(stack, AT_PHDR, phdr_address);
PUSH_AUX(stack, AT_PHENT, e_phentsize);
PUSH_AUX(stack, AT_PHNUM, e_phnum);
PUSH_AUX(stack, AT_PAGESZ, 4096);
PUSH_AUX(stack, AT_BASE, 0);
PUSH_AUX(stack, AT_FLAGS, 0);
PUSH_AUX(stack, AT_ENTRY, &root);
PUSH_AUX(stack, AT_UID, getuid());
PUSH_AUX(stack, AT_EUID, geteuid());
PUSH_AUX(stack, AT_GID, getgid());
PUSH_AUX(stack, AT_EGID, getegid());
PUSH_AUX(stack, AT_HWCAP, 0);
PUSH_AUX(stack, AT_HWCAP2, 0);
PUSH_AUX(stack, AT_CLKTCK, sysconf(_SC_CLK_TCK));
PUSH_AUX(stack, AT_RANDOM, random_address);
PUSH_AUX(stack, AT_PLATFORM, platform_address);
// WARNING: if you add something here, update aux_count
assert(aux_start - aux_count * 2 * sizeof(target_reg) == (uintptr_t) stack);
// Push a separator
PUSH_REG(stack, 0);
// Copy arguments and environment variables, and store their address
argp = arge;
// First push environment variables
while (*--argp != NULL) {
PUSH_STR(arg_area, *argp);
SAFE_CAST(arg_area);
PUSH_REG(stack, (target_reg) arg_area);
}
// Push the separator
PUSH_REG(stack, 0);
// Push the arguments
while (--argp != (argv - 1)) {
PUSH_STR(arg_area, *argp);
SAFE_CAST(arg_area);
PUSH_REG(stack, (target_reg) arg_area);
}
PUSH_REG(stack, argc);
assert((((uintptr_t) stack) & alignment) == 0);
assert((uintptr_t) stack == final_stack);
#undef PUSH_AUX
#undef PUSH_REG
#undef PUSH
#undef PUSH_STR
#undef MOVE
return stack;
}
// Helper functions we need
void target_set_brk(target_reg new_brk);
void syscall_init(void);
// Variables and functions required by helpers
uintptr_t qemu_real_host_page_size = 1 << 12;
uintptr_t qemu_real_host_page_mask = ~((1 << 12) - 1);
uintptr_t qemu_host_page_size = 1 << 12;
uintptr_t qemu_host_page_mask = ~((1 << 12) - 1);
void page_set_flags(target_reg start, target_reg end, int flags) {
}
void tb_invalidate_phys_range(target_reg start, target_reg end) {
}
const char *path(const char *name) {
return name;
}
void *g_malloc0_n(size_t n, size_t size) {
return calloc(n, size);
}
void *g_malloc(size_t n_bytes) {
if(n_bytes == 0)
return NULL;
else
return malloc(n_bytes);
}
void g_free(void *memory) {
if(memory == NULL)
return;
else
return free(memory);
}
void unknownPC() {
int arg;
const char *error = "Unknown PC\n";
write(2, error, strlen(error));
for (arg = 0; arg < saved_argc; arg++) {
write(2, saved_argv[arg], strlen(saved_argv[arg]));
write(2, " ", 1);
}
write(2, "\n", 1);
abort();
}
#ifdef TRACE
// Execution tracing support
static int trace_fd = -1;
static size_t trace_buffer_size = 1024 * 1024;
static size_t trace_buffer_index = 0;
static uint64_t *trace_buffer;
static void flush_trace_buffer(void);
void flush_trace_buffer(void);
void flush_trace_buffer_signal_handler(int signal);
void init_tracing(void) {
// If REVAMB_TRACE_PATH contains a path, enable tracing
char *trace_path = getenv("REVAMB_TRACE_PATH");
if (trace_path != NULL && strlen(trace_path) > 0) {
trace_fd = open(trace_path,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
assert(trace_fd != -1);
// Set REVAMB_TRACE_BUFFER_SIZE to customimze buffer size, default is 1024
// * 1024 instructions
char *trace_buffer_size_string = getenv("REVAMB_TRACE_BUFFER_SIZE");
if (trace_buffer_size_string != NULL
&& strlen(trace_buffer_size_string) > 0) {
char **first_invalid = NULL;
trace_buffer_size = strtoll(trace_buffer_size_string, first_invalid, 0);
assert(**first_invalid == '\0');
}
// Allocate buffer to hold program counters
trace_buffer = malloc(trace_buffer_size * sizeof(uint64_t));
assert(trace_buffer != NULL);
// In case of a crash, flush the buffer
static const int signals[] = { SIGINT, SIGABRT, SIGTERM, SIGSEGV };
for (unsigned c = 0; c < sizeof(signals) / sizeof(int); c++) {
struct sigaction new_handler;
struct sigaction old_handler;
new_handler.sa_handler = flush_trace_buffer_signal_handler;
int result = sigaction(signals[c], &new_handler, &old_handler);
assert(result == 0);
assert(old_handler.sa_handler == SIG_IGN
|| old_handler.sa_handler == SIG_DFL);
}
// Upon exit, flush the buffer too
int result = atexit(flush_trace_buffer);
assert(result == 0);
}
}
static void flush_trace_buffer(void) {
if (trace_fd == -1 || trace_buffer_index == 0)
return;
// Write the all buffer out and reset the counter
write(trace_fd, trace_buffer, sizeof(uint64_t) * trace_buffer_index);
trace_buffer_index = 0;
}
void flush_trace_buffer_signal_handler(int signal) {
flush_trace_buffer();
}
// This function is called by the syscall helpers in case of exit/exit_group
void on_exit_syscall(void) {
flush_trace_buffer();
}
void newpc(uint64_t pc,
uint64_t instruction_size,
uint32_t is_first,
uint8_t *vars, ...) {
// Check if tracing is enabled
if (trace_fd == -1)
return;
// Record the program counter
trace_buffer[trace_buffer_index++] = pc;
// If the buffer is full, flush it out
if (trace_buffer_index >= trace_buffer_size)
flush_trace_buffer();
}
#else
void init_tracing(void) {
}
void on_exit_syscall(void) {
}
void newpc(uint64_t pc,
uint64_t instruction_size,
uint32_t is_first,
uint8_t *vars, ...) {
}
#endif
// Check if the target address is inside an executable segment,
// if so serialize and jump
bool is_executable(uint64_t pc) {
assert(segments_count != 0);
// Check if the pc is inside one of the executable segments
for (int i = 0; i < segments_count; i++)
if (pc >= segment_boundaries[2 * i] && pc < segment_boundaries[2 * i + 1])
return true;
return false;
}
void handle_sigsegv(int signo, siginfo_t *info, void *opaque_context) {
// If we are catching a SIGSEGV not thrown by the kill command
if (signo == SIGSEGV
&& info->si_code != SI_USER
&& is_executable((uint64_t) info->si_addr)) {
ucontext_t *context = opaque_context;
saved_registers = (target_reg *) &context->uc_mcontext.gregs;
longjmp(jmp_buffer, 0);
}
// If the address is not executable, this is not a jump into our code
default_handler.sa_sigaction(SIGSEGV, info, opaque_context);
}
// Implant our custom SIGSEGV handler
void install_sigsegv_handler(void) {
struct sigaction segv_handler;
segv_handler.sa_sigaction = &handle_sigsegv;
sigemptyset(&segv_handler.sa_mask);
segv_handler.sa_flags = SA_SIGINFO | SA_NODEFER;
int result = 0;
result = sigaction(SIGSEGV, &segv_handler, &default_handler);
assert(result == 0);
}
int main(int argc, char *argv[]) {
// Save the program arguments for error reporting purposes
saved_argc = argc;
saved_argv = argv;
// Initialize the tracing system
init_tracing();
// Allocate and initialize the stack
void *stack = mmap((void *) NULL,
16 * 0x100000,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE,
-1,
0) + 16 * 0x100000 - 0x1000;
assert(stack != NULL);
stack = prepare_stack(stack, argc, argv);
// Allocate the brk page
void *brk = mmap((void *) NULL,
0x1000,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE,
-1,
0);
assert(brk != NULL);
brk += 0x1000;
SAFE_CAST(brk);
target_set_brk((target_reg) brk);
// Initialize the syscall system
syscall_init();
// Implant custom SIGSEGV handler
install_sigsegv_handler();
// Run the translated program
SAFE_CAST(stack);
root((target_reg) stack);
}
// Helper function used to raise an exception
void raise_exception_helper() {
// Declare the exception object
struct _Unwind_Exception exc;
// Raise the exception using the function provided by the unwind library
_Unwind_RaiseException(&exc);
}
// Personality function
int exception_personality(int version,
_Unwind_Action actions,
uint64_t exceptionClass,
struct _Unwind_Exception *unwind_exception,
struct _Unwind_Context *context) {
// Check the action parameter and match the correct expected return code, the
// other paramters are not used in our implementation
if (actions == _UA_SEARCH_PHASE) {
return _URC_HANDLER_FOUND;
} else if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)){
return _URC_INSTALL_CONTEXT;
}
return _URC_NO_REASON;
}
// Helper function to debug informations when an exception is about to be
// raised
void exception_warning(Reason Code,
target_reg Source,
target_reg Target,
target_reg ExpectedDestination) {
switch(Code) {
case StandardTranslatedBlock:
fprintf(stderr,
"Unexpected control-flow in isolated function: 0x%"
TARGET_REG_FORMAT " -> 0x%" TARGET_REG_FORMAT "\n",
Source,
Target);
break;
case StandardNonTranslatedBlock:
fprintf(stderr,
"Unexpected control-flow in isolated function after unexpectedpc "
"or anypc block: 0x%" TARGET_REG_FORMAT "\n",
Target);
break;
case BadReturnAddress:
fprintf(stderr,
"Expected and actual fallthrough after ret not corresponding: 0x%"
TARGET_REG_FORMAT " / 0x%" TARGET_REG_FORMAT "\n",
Target,
ExpectedDestination);
break;
case FunctionDispatcherFallBack:
fprintf(stderr,
"Erroneous call to function dispatcher: "
"0x%" TARGET_REG_FORMAT "\n",
Target);
break;
default:
assert(0 && "Reason code not supported");
}
}