forked from irungentoo/toxcore
-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "run_oomer" tool to simulate various out-of-memory situations.
Check run_oomer for flags. Flags are passed though environment variables. This commit also fixes a few bugs found by the tool.
- Loading branch information
Showing
9 changed files
with
269 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* SPDX-License-Identifier: GPL-3.0-or-later | ||
* Copyright © 2020 Tox project. | ||
*/ | ||
#include <fcntl.h> | ||
#include <stdbool.h> | ||
#include <stddef.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <time.h> | ||
#include <unistd.h> | ||
|
||
// The actual alloc functions. This only works on glibc. | ||
void *__libc_malloc(size_t size); | ||
void *__libc_calloc(size_t nmemb, size_t size); | ||
void *__libc_realloc(void *ptr, size_t size); | ||
void __libc_free(void *ptr); | ||
|
||
typedef struct Oomer_Config { | ||
/** | ||
* OOMER_MAX_ALLOCS: the number of allocations to pass through until oomer | ||
* starts running one of its strategies. | ||
*/ | ||
long max_allocs; | ||
/** | ||
* OOMER_DONE_FILE: the file to write when all allocations have been allowed. | ||
* This means any further tests are pointless because oomer will always allow | ||
* all of them. | ||
*/ | ||
const char *done_file; | ||
/** | ||
* OOMER_FLAKY: after max_allocs, start rejecting random allocations with the | ||
* specified rate (between 0 and 1). | ||
*/ | ||
double flaky; | ||
/** | ||
* Random seed for oomer's RNG. Not configurable through env vars for now. | ||
*/ | ||
uint32_t seed; | ||
/** | ||
* OOMER_ONE_SHOT: reject only a single allocation after max_allocs | ||
* allocations. All allocations after the one rejected one are passed through. | ||
*/ | ||
bool one_shot; | ||
/** | ||
* OOMER_TRAP_ON_FAIL: cause a SIGTRAP when rejecting an allocation. Useful | ||
* when running in a debugger to have a breakpoint at the rejected allocation. | ||
*/ | ||
bool trap_on_fail; | ||
} Oomer_Config; | ||
|
||
static Oomer_Config config = { -1, 0, 0, 123456789, false, false }; | ||
|
||
uint32_t rand_u32(void) { | ||
config.seed = (1664525 * config.seed + 1013904223); | ||
return config.seed; | ||
} | ||
|
||
static void init_oomer(void) { | ||
const char *env_max_allocs = getenv("OOMER_MAX_ALLOCS"); | ||
if (env_max_allocs != NULL) { | ||
char *end = NULL; | ||
config.max_allocs = strtol(env_max_allocs, &end, 10); | ||
if (end == NULL || *end != '\0') { | ||
fprintf(stderr, "invalid value for OOMER_MAX_ALLOCS: %s\n", env_max_allocs); | ||
abort(); | ||
} | ||
} | ||
|
||
const char *env_flaky = getenv("OOMER_FLAKY"); | ||
if (env_flaky != NULL) { | ||
char *end = NULL; | ||
config.flaky = strtod(env_flaky, &end); | ||
if (end == NULL || *end != '\0' || config.flaky < 0 || config.flaky > 1) { | ||
fprintf(stderr, "invalid value for OOMER_FLAKY: %s\n", env_flaky); | ||
abort(); | ||
} | ||
} | ||
|
||
const char *env_one_shot = getenv("OOMER_ONE_SHOT"); | ||
config.one_shot = env_one_shot != NULL && *env_one_shot == '1'; | ||
|
||
const char *env_trap_on_fail = getenv("OOMER_TRAP_ON_FAIL"); | ||
config.trap_on_fail = env_trap_on_fail != NULL && *env_trap_on_fail == '1'; | ||
|
||
config.done_file = getenv("OOMER_DONE_FILE"); | ||
|
||
fprintf(stderr, "oomer: done_file = %s\n", | ||
config.done_file != NULL ? config.done_file : "<unset>"); | ||
fprintf(stderr, "oomer: flaky = %f\n", config.flaky); | ||
fprintf(stderr, "oomer: seed = %u\n", config.seed); | ||
fprintf(stderr, "oomer: one_shot = %s\n", config.one_shot ? "true" : "false"); | ||
fprintf(stderr, "oomer: trap_on_fail = %s\n", config.trap_on_fail ? "true" : "false"); | ||
|
||
if (!config.trap_on_fail) { | ||
alarm(5); | ||
} | ||
} | ||
|
||
static __attribute__((__destructor__)) void deinit_oomer(void) { | ||
fprintf(stderr, "deinit_oomer: max_allocs = %ld\n", config.max_allocs); | ||
if (config.done_file != NULL && config.max_allocs > 0) { | ||
// Touch the done_file to signal to run_oomer that we've rejected at least | ||
// one malloc call. | ||
close(creat(config.done_file, 0644)); | ||
} | ||
} | ||
|
||
static bool can_alloc(void) { | ||
if (config.max_allocs == -1) { | ||
init_oomer(); | ||
} | ||
|
||
if (config.max_allocs == 0) { | ||
if (config.one_shot) { | ||
// Allow all mallocs except this one. | ||
config.max_allocs = -2; | ||
} | ||
if (config.trap_on_fail) { | ||
__asm__("int $3"); | ||
} | ||
if (config.flaky > 0) { | ||
return rand_u32() > config.flaky * UINT32_MAX; | ||
} | ||
return false; | ||
} | ||
|
||
--config.max_allocs; | ||
return true; | ||
} | ||
|
||
void *malloc(size_t size) { | ||
if (can_alloc()) { | ||
return __libc_malloc(size); | ||
} | ||
return NULL; | ||
} | ||
|
||
void *calloc(size_t nmemb, size_t size) { | ||
if (can_alloc()) { | ||
return __libc_calloc(nmemb, size); | ||
} | ||
return NULL; | ||
} | ||
|
||
void *realloc(void *ptr, size_t size) { | ||
if (can_alloc()) { | ||
return __libc_realloc(ptr, size); | ||
} | ||
return NULL; | ||
} | ||
|
||
void free(void *ptr) { | ||
__libc_free(ptr); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
#!/usr/bin/env python3 | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
# Copyright © 2020 Tox project. | ||
"""Run an oomer-enabled program with an increasing number of max_allocs.""" | ||
|
||
import os | ||
import subprocess | ||
import sys | ||
|
||
from typing import Dict, List | ||
|
||
|
||
def run(args: List[str], env: Dict[str, str]) -> None: | ||
"""Run a program with environment and print the env and args to stdout.""" | ||
print("-" * 60, "\x1b[0;32m") | ||
for k in sorted(env.keys()): | ||
print(f"{k}={env[k]} \\") | ||
print(" ".join(args)) | ||
print("\x1b[0m", "-" * 60) | ||
subprocess.run(args, env=env) | ||
|
||
|
||
def run_oomer(exe: str, max_allocs: int) -> bool: | ||
"""Run an oomer-enabled program with the provided max_allocs.""" | ||
print(f"\x1b[0;33mmax_allocs = {max_allocs}\x1b[0m") | ||
done_file = f"oomer-{max_allocs}" | ||
env = { | ||
"OOMER_MAX_ALLOCS": str(max_allocs), | ||
"OOMER_ONE_SHOT": "1", | ||
"OOMER_FLAKY": "0", | ||
"OOMER_DONE_FILE": done_file, | ||
} | ||
proc = subprocess.run([exe], env=env) | ||
|
||
# The done_file is created by oomer.c when it considers the test complete. | ||
if os.path.exists(done_file): | ||
os.unlink(done_file) | ||
return True | ||
|
||
if proc.returncode >= 0: | ||
# Process exited cleanly (success or failure). | ||
pass | ||
elif proc.returncode == -6: | ||
# Assertion failed. | ||
pass | ||
elif proc.returncode == -14: | ||
print(f"\x1b[1;31mProcess timed out at " | ||
f"max_allocs = {max_allocs}\x1b[0m") | ||
del env["OOMER_DONE_FILE"] | ||
env["OOMER_TRAP_ON_FAIL"] = "1" | ||
run(["gdb", "--eval-command=r", exe], env=env) | ||
return True | ||
else: | ||
print(f"Process exited with signal {-proc.returncode} at " | ||
f"max_allocs = {max_allocs}") | ||
del env["OOMER_DONE_FILE"] | ||
run(["gdb", "--eval-command=r", exe], env=env) | ||
return True | ||
|
||
return False | ||
|
||
|
||
def main(args: List[str]) -> None: | ||
"""Run an oomer-enabled program with an increasing number of max_allocs.""" | ||
if len(args) != 2: | ||
print("Usage: oomer <exe>") | ||
sys.exit(1) | ||
exe = args[1] | ||
|
||
for max_allocs in range(0, 1000): | ||
if run_oomer(exe, max_allocs): | ||
break | ||
|
||
|
||
if __name__ == "__main__": | ||
main(sys.argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters