Lots of work on removing malloc from driver
This commit is contained in:
@@ -64,6 +64,7 @@ LUA_OBJ_FILES=\
|
|||||||
|
|
||||||
CORE_OBJ_FILES=\
|
CORE_OBJ_FILES=\
|
||||||
obj/invocation.o\
|
obj/invocation.o\
|
||||||
|
obj/umm-malloc.o\
|
||||||
obj/spookyv2.o\
|
obj/spookyv2.o\
|
||||||
obj/debugcollector.o\
|
obj/debugcollector.o\
|
||||||
obj/drivenengine.o\
|
obj/drivenengine.o\
|
||||||
@@ -95,6 +96,7 @@ CORE_OBJ_FILES=\
|
|||||||
obj/drivertests.o\
|
obj/drivertests.o\
|
||||||
obj/printbuffer.o\
|
obj/printbuffer.o\
|
||||||
obj/main.o \
|
obj/main.o \
|
||||||
|
obj/driver-util.o\
|
||||||
obj/$(DRIVER).o\
|
obj/$(DRIVER).o\
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
#include "drivenengine.hpp"
|
#include "drivenengine.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
static std::vector<std::pair<std::string, DrivenEngineMaker>> makers;
|
||||||
|
|
||||||
|
void DrivenEngine::register_maker(const char *kind, DrivenEngineMaker maker) {
|
||||||
|
std::string skind(kind);
|
||||||
|
makers.push_back(std::make_pair(skind, maker));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrivenEngine::print_usage(std::ostream &strm, const char *progname) {
|
||||||
|
strm << "Usage: " << progname << " <mode>" << std::endl;
|
||||||
|
for (const auto &mpair : makers) {
|
||||||
|
strm << " Mode can be: " << mpair.first << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueDrivenEngine DrivenEngine::make(const char *kind) {
|
||||||
|
for (const auto &mpair : makers) {
|
||||||
|
if (strcmp(mpair.first.c_str(), kind) == 0) {
|
||||||
|
return mpair.second();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target, bool stop) {
|
Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target, bool stop) {
|
||||||
chid_ = chid;
|
chid_ = chid;
|
||||||
@@ -127,14 +154,14 @@ int DrivenEngine::find_unused_chid() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel *DrivenEngine::get_chid(int chid) {
|
Channel *DrivenEngine::get_chid(int chid) const {
|
||||||
assert(unsigned(chid) < MAX_CHAN);
|
assert(unsigned(chid) < MAX_CHAN);
|
||||||
assert(channels_[chid].get() != nullptr);
|
assert(channels_[chid].get() != nullptr);
|
||||||
return channels_[chid].get();
|
return channels_[chid].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::listen_port(int port) {
|
void DrivenEngine::listen_port(int port) {
|
||||||
listen_ports_.insert(port);
|
listen_ports_.push_back(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
double DrivenEngine::get_clock() {
|
double DrivenEngine::get_clock() {
|
||||||
@@ -143,7 +170,7 @@ double DrivenEngine::get_clock() {
|
|||||||
|
|
||||||
SharedChannel DrivenEngine::new_outgoing_channel(const std::string &target) {
|
SharedChannel DrivenEngine::new_outgoing_channel(const std::string &target) {
|
||||||
int chid = find_unused_chid();
|
int chid = find_unused_chid();
|
||||||
new_outgoing_.insert(chid);
|
new_outgoing_.push_back(chid);
|
||||||
SharedChannel result = std::make_shared<Channel>(this, chid, 0, target, stop_driver_);
|
SharedChannel result = std::make_shared<Channel>(this, chid, 0, target, stop_driver_);
|
||||||
channels_[chid] = result;
|
channels_[chid] = result;
|
||||||
return result;
|
return result;
|
||||||
@@ -180,30 +207,33 @@ void DrivenEngine::stop_driver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_get_listen_ports(std::set<int> &ports) {
|
const std::vector<int> &DrivenEngine::drv_get_listen_ports() const {
|
||||||
ports = listen_ports_;
|
return listen_ports_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_get_new_outgoing(std::set<int> &channels) {
|
const std::vector<int> &DrivenEngine::drv_get_new_outgoing() const {
|
||||||
channels = std::move(new_outgoing_);
|
return new_outgoing_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrivenEngine::drv_clear_new_outgoing() {
|
||||||
new_outgoing_.clear();
|
new_outgoing_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &DrivenEngine::drv_get_target(int chid) {
|
const std::string &DrivenEngine::drv_get_target(int chid) const {
|
||||||
return get_chid(chid)->target_;
|
return get_chid(chid)->target_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrivenEngine::drv_outgoing_empty(int chid) {
|
bool DrivenEngine::drv_outgoing_empty(int chid) const {
|
||||||
int nbytes; const char *bytes;
|
int nbytes; const char *bytes;
|
||||||
drv_peek_outgoing(chid, &nbytes, &bytes);
|
drv_peek_outgoing(chid, &nbytes, &bytes);
|
||||||
return (nbytes == 0);
|
return (nbytes == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrivenEngine::drv_get_channel_released(int chid) {
|
bool DrivenEngine::drv_get_channel_released(int chid) const {
|
||||||
return channels_[chid].use_count() == 1;
|
return channels_[chid].use_count() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
|
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) const {
|
||||||
return get_chid(chid)->peek_outgoing(nbytes, bytes);
|
return get_chid(chid)->peek_outgoing(nbytes, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +252,7 @@ void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_notify_close(int chid, const std::string &err) {
|
void DrivenEngine::drv_notify_close(int chid, std::string_view err) {
|
||||||
Channel *ch = get_chid(chid);
|
Channel *ch = get_chid(chid);
|
||||||
ch->closed_ = true;
|
ch->closed_ = true;
|
||||||
ch->error_ = err;
|
ch->error_ = err;
|
||||||
@@ -236,8 +266,20 @@ int DrivenEngine::drv_notify_accept(int port) {
|
|||||||
return chid;
|
return chid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_set_lua_source(util::LuaSourcePtr source) {
|
void DrivenEngine::drv_clear_lua_source() {
|
||||||
lua_source_ = std::move(source);
|
lua_source_.reset();
|
||||||
|
rescan_lua_source_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrivenEngine::drv_add_lua_source(const char *fn, const char *data) {
|
||||||
|
if (lua_source_ == nullptr) {
|
||||||
|
lua_source_.reset(new util::LuaSourceVec);
|
||||||
|
}
|
||||||
|
lua_source_->emplace_back(std::string(fn), std::string(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrivenEngine::drv_set_lua_source(util::LuaSourcePtr src) {
|
||||||
|
lua_source_ = std::move(src);
|
||||||
rescan_lua_source_ = false;
|
rescan_lua_source_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,11 +292,11 @@ void DrivenEngine::drv_invoke_event_update(double clock) {
|
|||||||
event_update();
|
event_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrivenEngine::drv_get_rescan_lua_source() {
|
bool DrivenEngine::drv_get_rescan_lua_source() const {
|
||||||
return rescan_lua_source_;
|
return rescan_lua_source_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrivenEngine::drv_get_stop_driver() {
|
bool DrivenEngine::drv_get_stop_driver() const {
|
||||||
return stop_driver_;
|
return stop_driver_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,11 +95,13 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <ostream>
|
||||||
#include "streambuffer.hpp"
|
#include "streambuffer.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
class DrivenEngine;
|
class DrivenEngine;
|
||||||
|
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
|
||||||
|
using DrivenEngineMaker = UniqueDrivenEngine (*)();
|
||||||
|
|
||||||
class Channel {
|
class Channel {
|
||||||
public:
|
public:
|
||||||
@@ -299,36 +301,41 @@ public:
|
|||||||
// Get a list of all the listening ports. The driver is expected
|
// Get a list of all the listening ports. The driver is expected
|
||||||
// to fetch this set shortly after the event_init callback is invoked.
|
// to fetch this set shortly after the event_init callback is invoked.
|
||||||
//
|
//
|
||||||
void drv_get_listen_ports(std::set<int> &ports);
|
const std::vector<int> &drv_get_listen_ports() const;
|
||||||
|
|
||||||
// Get a list of all recently-opened channels that were created using
|
// Get a list of all recently-opened channels that were created using
|
||||||
// drv_new_outgoing_channel. The driver should initiate outgoing
|
// drv_new_outgoing_channel. The driver should initiate outgoing
|
||||||
// connections for these channels.
|
// connections for these channels.
|
||||||
//
|
//
|
||||||
void drv_get_new_outgoing(std::set<int> &closed);
|
const std::vector<int> &drv_get_new_outgoing() const;
|
||||||
|
|
||||||
|
// Clear the list of recently-opened channels that were created using
|
||||||
|
// drv_new_outgoing_channel.
|
||||||
|
//
|
||||||
|
void drv_clear_new_outgoing();
|
||||||
|
|
||||||
// Get the target of a channel. A target is a string like
|
// Get the target of a channel. A target is a string like
|
||||||
// "www.whatever.com:80". It indicates the host and port that the channel
|
// "www.whatever.com:80". It indicates the host and port that the channel
|
||||||
// is supposed to be talking to. Non-socket channels and incoming channels
|
// is supposed to be talking to. Non-socket channels and incoming channels
|
||||||
// have empty targets.
|
// have empty targets.
|
||||||
//
|
//
|
||||||
const std::string &drv_get_target(int chid);
|
const std::string &drv_get_target(int chid) const;
|
||||||
|
|
||||||
// Return true if the outgoing buffer is empty.
|
// Return true if the outgoing buffer is empty.
|
||||||
//
|
//
|
||||||
bool drv_outgoing_empty(int chid);
|
bool drv_outgoing_empty(int chid) const;
|
||||||
|
|
||||||
// Return true if the user has released all references to this channel.
|
// Return true if the user has released all references to this channel.
|
||||||
// In this case, the driver should initiate shutdown of the channel,
|
// In this case, the driver should initiate shutdown of the channel,
|
||||||
// and the driver should eventually call drv_notify_close.
|
// and the driver should eventually call drv_notify_close.
|
||||||
//
|
//
|
||||||
bool drv_get_channel_released(int chid);
|
bool drv_get_channel_released(int chid) const;
|
||||||
|
|
||||||
// Get a pointer to the bytes in the outgoing buffer. The pointer returned
|
// Get a pointer to the bytes in the outgoing buffer. The pointer returned
|
||||||
// here is naturally only valid until the buffer is changed. This function
|
// here is naturally only valid until the buffer is changed. This function
|
||||||
// is used for all channels, including sockets and stdio.
|
// is used for all channels, including sockets and stdio.
|
||||||
//
|
//
|
||||||
void drv_peek_outgoing(int chid, int *nbytes, const char **bytes);
|
void drv_peek_outgoing(int chid, int *nbytes, const char **bytes) const;
|
||||||
|
|
||||||
// Notifies the channel that some bytes were transmitted. This causes those
|
// Notifies the channel that some bytes were transmitted. This causes those
|
||||||
// bytes to be removed from the outgoing buffer. This function is used for
|
// bytes to be removed from the outgoing buffer. This function is used for
|
||||||
@@ -349,7 +356,7 @@ public:
|
|||||||
// delete it. Closing a channel prevents it from showing up in
|
// delete it. Closing a channel prevents it from showing up in
|
||||||
// 'drv_list_channels'.
|
// 'drv_list_channels'.
|
||||||
//
|
//
|
||||||
void drv_notify_close(int chid, const std::string &err);
|
void drv_notify_close(int chid, std::string_view err);
|
||||||
|
|
||||||
// Notify the DrivenEngine that somebody connected to an incoming port.
|
// Notify the DrivenEngine that somebody connected to an incoming port.
|
||||||
// This will cause the DrivenEngine to allocate a new channel and put the
|
// This will cause the DrivenEngine to allocate a new channel and put the
|
||||||
@@ -359,9 +366,15 @@ public:
|
|||||||
//
|
//
|
||||||
int drv_notify_accept(int port);
|
int drv_notify_accept(int port);
|
||||||
|
|
||||||
|
|
||||||
|
// Clear the lua source code.
|
||||||
|
//
|
||||||
|
void drv_clear_lua_source();
|
||||||
|
|
||||||
// Set the lua source code. The driver is expected to read the lua source
|
// Set the lua source code. The driver is expected to read the lua source
|
||||||
// code and store it (using this function) once before invoking
|
// code and store it (using this function) once before invoking
|
||||||
//
|
//
|
||||||
|
void drv_add_lua_source(const char *fn, const char *data);
|
||||||
void drv_set_lua_source(util::LuaSourcePtr source);
|
void drv_set_lua_source(util::LuaSourcePtr source);
|
||||||
|
|
||||||
// Invoke the init or update event.
|
// Invoke the init or update event.
|
||||||
@@ -374,11 +387,11 @@ public:
|
|||||||
// When the driver sees this flag, it should rescan the source and call
|
// When the driver sees this flag, it should rescan the source and call
|
||||||
// drv_set_source.
|
// drv_set_source.
|
||||||
//
|
//
|
||||||
bool drv_get_rescan_lua_source();
|
bool drv_get_rescan_lua_source() const;
|
||||||
|
|
||||||
// If true, the engine is done. Stop the driver.
|
// If true, the engine is done. Stop the driver.
|
||||||
//
|
//
|
||||||
bool drv_get_stop_driver();
|
bool drv_get_stop_driver() const;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -409,29 +422,39 @@ public:
|
|||||||
static void set(DrivenEngine *de);
|
static void set(DrivenEngine *de);
|
||||||
static DrivenEngine *get();
|
static DrivenEngine *get();
|
||||||
|
|
||||||
|
public:
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// The Registry of DrivenEngineMakers.
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void register_maker(const char *kind, DrivenEngineMaker maker);
|
||||||
|
|
||||||
|
static void print_usage(std::ostream &strm, const char *progname);
|
||||||
|
|
||||||
|
static UniqueDrivenEngine make(const char *kind);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Find a currently-unused channel ID. Channel IDs
|
// Find a currently-unused channel ID. Channel IDs
|
||||||
// are small integers that are reused.
|
// are small integers that are reused.
|
||||||
int find_unused_chid();
|
int find_unused_chid();
|
||||||
|
|
||||||
// Get the channel associated with the specified channel ID.
|
// Get the channel associated with the specified channel ID.
|
||||||
Channel *get_chid(int chid);
|
Channel *get_chid(int chid) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedChannel channels_[MAX_CHAN];
|
SharedChannel channels_[MAX_CHAN];
|
||||||
int next_unused_chid_;
|
int next_unused_chid_;
|
||||||
SharedChannel stdio_channel_;
|
SharedChannel stdio_channel_;
|
||||||
std::vector<SharedChannel> accepted_channels_;
|
std::vector<SharedChannel> accepted_channels_;
|
||||||
std::set<int> new_outgoing_;
|
std::vector<int> new_outgoing_;
|
||||||
util::LuaSourcePtr lua_source_;
|
util::LuaSourcePtr lua_source_;
|
||||||
std::set<int> listen_ports_;
|
std::vector<int> listen_ports_;
|
||||||
bool rescan_lua_source_;
|
bool rescan_lua_source_;
|
||||||
double clock_;
|
double clock_;
|
||||||
bool stop_driver_;
|
bool stop_driver_;
|
||||||
friend class Channel;
|
friend class Channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
|
|
||||||
using DrivenEngineMaker = UniqueDrivenEngine (*)();
|
|
||||||
|
|
||||||
#endif // DRIVENENGINE_HPP
|
#endif // DRIVENENGINE_HPP
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#define OPENSSL_HEAP_SIZE (4*1024*1024)
|
||||||
|
#define CHBUF_SIZE (256*1024)
|
||||||
|
#define POLLVEC_SIZE (DrivenEngine::MAX_CHAN+1)
|
||||||
|
|
||||||
static MonoClock monoclock;
|
static MonoClock monoclock;
|
||||||
|
|
||||||
@@ -7,7 +10,7 @@ namespace util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void if_error_print_and_exit(const std::string &str) {
|
static void if_error_print_and_exit(const UmmString &str) {
|
||||||
if (!str.empty()) {
|
if (!str.empty()) {
|
||||||
std::cerr << std::endl << "error: " << str << std::endl;
|
std::cerr << std::endl << "error: " << str << std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -28,12 +31,12 @@ static SSL_CTX *new_ssl_context(bool server_cert, bool root_certs, const std::st
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string err_print_errors_str() {
|
static UmmString err_print_errors_str() {
|
||||||
BIO *bio = BIO_new(BIO_s_mem());
|
BIO *bio = BIO_new(BIO_s_mem());
|
||||||
ERR_print_errors(bio);
|
ERR_print_errors(bio);
|
||||||
char *buf;
|
char *buf;
|
||||||
size_t len = BIO_get_mem_data(bio, &buf);
|
size_t len = BIO_get_mem_data(bio, &buf);
|
||||||
std::string ret(buf, len);
|
UmmString ret(buf, len);
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -58,8 +61,12 @@ static int ssl_ctx_use_privatekey_str(SSL_CTX *ctx, const char *str) {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<char[]> chbuf;
|
||||||
|
static std::unique_ptr<struct pollfd[]> pollvec;
|
||||||
|
|
||||||
class Driver {
|
class Driver {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum ChanState {
|
enum ChanState {
|
||||||
CHAN_INACTIVE,
|
CHAN_INACTIVE,
|
||||||
CHAN_PLAINTEXT,
|
CHAN_PLAINTEXT,
|
||||||
@@ -87,19 +94,18 @@ public:
|
|||||||
DrivenEngine *driven_;
|
DrivenEngine *driven_;
|
||||||
std::vector<ChanInfo> chans_;
|
std::vector<ChanInfo> chans_;
|
||||||
std::map<int, SOCKET> listen_sockets_;
|
std::map<int, SOCKET> listen_sockets_;
|
||||||
std::unique_ptr<char[]> chbuf;
|
|
||||||
bool read_console_recently_;
|
bool read_console_recently_;
|
||||||
|
|
||||||
SSL_CTX *ssl_ctx_with_root_certs_;
|
SSL_CTX *ssl_ctx_with_root_certs_;
|
||||||
SSL_CTX *ssl_ctx_with_server_certs_;
|
SSL_CTX *ssl_ctx_with_server_certs_;
|
||||||
SSL_CTX *ssl_ctx_with_no_certs_;
|
SSL_CTX *ssl_ctx_with_no_certs_;
|
||||||
|
|
||||||
|
|
||||||
void handle_listen_ports() {
|
void handle_listen_ports() {
|
||||||
std::set<int> listenports;
|
const std::vector<int> &listenports = driven_->drv_get_listen_ports();
|
||||||
driven_->drv_get_listen_ports(listenports);
|
|
||||||
for (int port : listenports) {
|
for (int port : listenports) {
|
||||||
if (listen_sockets_.find(port) == listen_sockets_.end()) {
|
if (listen_sockets_.find(port) == listen_sockets_.end()) {
|
||||||
std::string err;
|
UmmString err;
|
||||||
SOCKET sock = listen_on_port(port, err);
|
SOCKET sock = listen_on_port(port, err);
|
||||||
if_error_print_and_exit(err);
|
if_error_print_and_exit(err);
|
||||||
assert(sock != INVALID_SOCKET);
|
assert(sock != INVALID_SOCKET);
|
||||||
@@ -114,7 +120,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_channel(ChanInfo &chan, const std::string &err) {
|
void close_channel(ChanInfo &chan, std::string_view err) {
|
||||||
// std::cerr << "Closing channel " << chan.chid << std::endl;
|
// std::cerr << "Closing channel " << chan.chid << std::endl;
|
||||||
assert(chan.state != CHAN_INACTIVE);
|
assert(chan.state != CHAN_INACTIVE);
|
||||||
// Close and release the SSL channel.
|
// Close and release the SSL channel.
|
||||||
@@ -196,10 +202,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handle_new_outgoing_sockets() {
|
void handle_new_outgoing_sockets() {
|
||||||
std::set<int> chans;
|
const std::vector<int> &chans = driven_->drv_get_new_outgoing();
|
||||||
driven_->drv_get_new_outgoing(chans);
|
|
||||||
for (int chid : chans) {
|
for (int chid : chans) {
|
||||||
std::string err;
|
UmmString err;
|
||||||
SOCKET sock = open_connection(driven_->drv_get_target(chid), err);
|
SOCKET sock = open_connection(driven_->drv_get_target(chid), err);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
driven_->drv_notify_close(chid, err);
|
driven_->drv_notify_close(chid, err);
|
||||||
@@ -208,10 +213,13 @@ public:
|
|||||||
make_channel(sock, chid, ssl_ctx_with_no_certs_, CHAN_SSL_CONNECTING);
|
make_channel(sock, chid, ssl_ctx_with_no_certs_, CHAN_SSL_CONNECTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!chans.empty()) {
|
||||||
|
driven_->drv_clear_new_outgoing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void accept_connection(int port, SOCKET sock) {
|
void accept_connection(int port, SOCKET sock) {
|
||||||
std::string err;
|
UmmString err;
|
||||||
SOCKET socket = accept_on_socket(sock, err);
|
SOCKET socket = accept_on_socket(sock, err);
|
||||||
if_error_print_and_exit(err);
|
if_error_print_and_exit(err);
|
||||||
if (socket != INVALID_SOCKET) {
|
if (socket != INVALID_SOCKET) {
|
||||||
@@ -222,7 +230,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void advance_plaintext(ChanInfo &chan) {
|
void advance_plaintext(ChanInfo &chan) {
|
||||||
std::string err;
|
UmmString err;
|
||||||
|
|
||||||
// If the channel has no outgoing bytes and has been released,
|
// If the channel has no outgoing bytes and has been released,
|
||||||
// just close it.
|
// just close it.
|
||||||
@@ -359,8 +367,8 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
void handle_socket_input_output() {
|
void handle_socket_input_output() {
|
||||||
std::string err;
|
UmmString err;
|
||||||
int mstimeout = 1000;
|
int mstimeout = read_console_recently_ ? 100 : 1000;
|
||||||
|
|
||||||
// Peek output buffers and determine channel release flags.
|
// Peek output buffers and determine channel release flags.
|
||||||
for (ChanInfo &chan : chans_) {
|
for (ChanInfo &chan : chans_) {
|
||||||
@@ -373,17 +381,15 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct the struct pollfd vector.
|
// Construct the struct pollfd vector.
|
||||||
int pollsize = listen_sockets_.size() + chans_.size() + 1;
|
int pollsize = 0;
|
||||||
PollVector pollvec(pollsize);
|
|
||||||
int index = 0;
|
|
||||||
for (const auto &p : listen_sockets_) {
|
for (const auto &p : listen_sockets_) {
|
||||||
struct pollfd &pfd = pollvec[index++];
|
struct pollfd &pfd = pollvec[pollsize++];
|
||||||
pfd.fd = p.second;
|
pfd.fd = p.second;
|
||||||
pfd.events = POLLIN;
|
pfd.events = POLLIN;
|
||||||
pfd.revents = 0;
|
pfd.revents = 0;
|
||||||
}
|
}
|
||||||
for (const ChanInfo &chan : chans_) {
|
for (const ChanInfo &chan : chans_) {
|
||||||
struct pollfd &pfd = pollvec[index++];
|
struct pollfd &pfd = pollvec[pollsize++];
|
||||||
assert(chan.socket != INVALID_SOCKET);
|
assert(chan.socket != INVALID_SOCKET);
|
||||||
pfd.fd = chan.socket;
|
pfd.fd = chan.socket;
|
||||||
pfd.events = 0;
|
pfd.events = 0;
|
||||||
@@ -395,14 +401,13 @@ public:
|
|||||||
if (chan.ready_on_outgoing && (chan.nbytes > 0)) pfd.events |= POLLOUT;
|
if (chan.ready_on_outgoing && (chan.nbytes > 0)) pfd.events |= POLLOUT;
|
||||||
// std::cerr << "evt=" << pfd.events << ".nb=" << chan.nbytes << " ";
|
// std::cerr << "evt=" << pfd.events << ".nb=" << chan.nbytes << " ";
|
||||||
}
|
}
|
||||||
fill_stdio_pollfd(pollvec, mstimeout, read_console_recently_);
|
|
||||||
|
|
||||||
// Do the poll.
|
// Do the poll.
|
||||||
socket_poll(pollvec, mstimeout, err);
|
socket_poll(pollvec.get(), pollsize, mstimeout, err);
|
||||||
if_error_print_and_exit(err);
|
if_error_print_and_exit(err);
|
||||||
|
|
||||||
// Check listening sockets.
|
// Check listening sockets.
|
||||||
index = 0;
|
int index = 0;
|
||||||
for (auto &p : listen_sockets_) {
|
for (auto &p : listen_sockets_) {
|
||||||
struct pollfd &pfd = pollvec[index++];
|
struct pollfd &pfd = pollvec[index++];
|
||||||
if (pfd.revents & (POLLIN | POLLERR)) {
|
if (pfd.revents & (POLLIN | POLLERR)) {
|
||||||
@@ -435,14 +440,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drive(DrivenEngine *de, int argc, char *argv[]) {
|
void drive(DrivenEngine *de, int argc, char *argv[]) {
|
||||||
disable_randomization(argc, argv);
|
|
||||||
socket_init();
|
|
||||||
SSL_load_error_strings();
|
|
||||||
ERR_load_crypto_strings();
|
|
||||||
enable_tty_raw();
|
|
||||||
driven_ = de;
|
driven_ = de;
|
||||||
read_console_recently_ = false;
|
read_console_recently_ = false;
|
||||||
chbuf.reset(new char[65536]);
|
|
||||||
|
|
||||||
ssl_ctx_with_root_certs_ = new_ssl_context(false, true, "");
|
ssl_ctx_with_root_certs_ = new_ssl_context(false, true, "");
|
||||||
ssl_ctx_with_server_certs_ = new_ssl_context(true, false, "");
|
ssl_ctx_with_server_certs_ = new_ssl_context(true, false, "");
|
||||||
@@ -480,13 +479,33 @@ public:
|
|||||||
SSL_CTX_free(ssl_ctx_with_root_certs_);
|
SSL_CTX_free(ssl_ctx_with_root_certs_);
|
||||||
SSL_CTX_free(ssl_ctx_with_server_certs_);
|
SSL_CTX_free(ssl_ctx_with_server_certs_);
|
||||||
DrivenEngine::set(nullptr);
|
DrivenEngine::set(nullptr);
|
||||||
socket_uninit();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void driver_drive(DrivenEngine *de, int argc, char *argv[]) {
|
void driver_drive(int argc, char *argv[]) {
|
||||||
|
// The only place in the driver where we're allowed to use malloc
|
||||||
|
// is here, before even looking at the arguments. That way, the
|
||||||
|
// impact on the malloc heap is always exactly the same, which
|
||||||
|
// doesn't break the determinism of the execution during replay.
|
||||||
|
|
||||||
|
umm_init_heap(malloc(OPENSSL_HEAP_SIZE), OPENSSL_HEAP_SIZE);
|
||||||
|
CRYPTO_set_mem_functions(umm_malloc_ssl, umm_realloc_ssl, umm_free_ssl);
|
||||||
|
chbuf.reset(new char[CHBUF_SIZE]);
|
||||||
|
pollvec.reset(new struct pollfd[POLLVEC_SIZE]);
|
||||||
|
ERR_load_crypto_strings();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
|
||||||
Driver driver;
|
Driver driver;
|
||||||
driver.drive(de, argc, argv);
|
if (argc < 2) {
|
||||||
|
DrivenEngine::print_usage(std::cerr, argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
UniqueDrivenEngine engine = DrivenEngine::make(argv[1]);
|
||||||
|
if (engine == nullptr) {
|
||||||
|
DrivenEngine::print_usage(std::cerr, argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
driver.drive(engine.get(), argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
#include "driver.hpp"
|
#include "driver.hpp"
|
||||||
|
#include "umm-malloc.hpp"
|
||||||
|
#include "driver-util.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "drivenengine.hpp"
|
#include "drivenengine.hpp"
|
||||||
#include "dummycert.hpp"
|
#include "dummycert.hpp"
|
||||||
@@ -31,11 +33,10 @@
|
|||||||
|
|
||||||
using SOCKET=int;
|
using SOCKET=int;
|
||||||
const int INVALID_SOCKET = -1;
|
const int INVALID_SOCKET = -1;
|
||||||
using PollVector = std::vector<struct pollfd>;
|
|
||||||
|
|
||||||
struct termios orig_termios;
|
struct termios orig_termios;
|
||||||
|
|
||||||
static std::string strerror_str(int err) {
|
static UmmString strerror_str(int err) {
|
||||||
char errbuf[256];
|
char errbuf[256];
|
||||||
return strerror_r(errno, errbuf, 256);
|
return strerror_r(errno, errbuf, 256);
|
||||||
}
|
}
|
||||||
@@ -65,12 +66,11 @@ static void enable_tty_raw() {
|
|||||||
assert(status >= 0);
|
assert(status >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOCKET open_connection(const std::string &target, std::string &err) {
|
static SOCKET open_connection(std::string_view target, UmmString &err) {
|
||||||
struct addrinfo *addrs = nullptr;
|
struct addrinfo *addrs = nullptr;
|
||||||
struct addrinfo *goodaddr = nullptr;
|
struct addrinfo *goodaddr = nullptr;
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
SOCKET sock = INVALID_SOCKET;
|
SOCKET sock = INVALID_SOCKET;
|
||||||
std::string host, port;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
memset(&hints, 0, sizeof(hints));
|
||||||
hints.ai_family = AF_INET;
|
hints.ai_family = AF_INET;
|
||||||
@@ -79,7 +79,8 @@ static SOCKET open_connection(const std::string &target, std::string &err) {
|
|||||||
hints.ai_flags = AI_NUMERICSERV;
|
hints.ai_flags = AI_NUMERICSERV;
|
||||||
|
|
||||||
err.clear();
|
err.clear();
|
||||||
util::split_host_port(target, host, port);
|
UmmString host, port;
|
||||||
|
drv::split_host_port(target, host, port);
|
||||||
int status = getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs);
|
int status = getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs);
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
err = gai_strerror(status);
|
err = gai_strerror(status);
|
||||||
@@ -111,7 +112,7 @@ error_general:
|
|||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOCKET listen_on_port(int port, std::string &err) {
|
static SOCKET listen_on_port(int port, UmmString &err) {
|
||||||
int status, enable;
|
int status, enable;
|
||||||
err.clear();
|
err.clear();
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ error_errno:
|
|||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
static SOCKET accept_on_socket(SOCKET listen_socket, UmmString &err) {
|
||||||
err.clear();
|
err.clear();
|
||||||
SOCKET chsock = accept(listen_socket, nullptr, nullptr);
|
SOCKET chsock = accept(listen_socket, nullptr, nullptr);
|
||||||
if (chsock >= 0) {
|
if (chsock >= 0) {
|
||||||
@@ -162,7 +163,7 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
|||||||
// zero: would block
|
// zero: would block
|
||||||
// negative: channel closed, possibly cleanly or possibly with error
|
// negative: channel closed, possibly cleanly or possibly with error
|
||||||
//
|
//
|
||||||
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
static int socket_send(SOCKET socket, const char *bytes, int nbytes, UmmString &err) {
|
||||||
err.clear();
|
err.clear();
|
||||||
int wbytes = send(socket, bytes, nbytes, 0);
|
int wbytes = send(socket, bytes, nbytes, 0);
|
||||||
if (wbytes < 0) {
|
if (wbytes < 0) {
|
||||||
@@ -177,7 +178,7 @@ static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
static int socket_recv(SOCKET socket, char *bytes, int nbytes, UmmString &err) {
|
||||||
err.clear();
|
err.clear();
|
||||||
int nrecv = recv(socket, bytes, nbytes, 0);
|
int nrecv = recv(socket, bytes, nbytes, 0);
|
||||||
if (nrecv < 0) {
|
if (nrecv < 0) {
|
||||||
@@ -198,8 +199,17 @@ static int socket_close(SOCKET socket) {
|
|||||||
return close(socket);
|
return close(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) {
|
static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, UmmString &err) {
|
||||||
int status = poll(&pollvec[0], pollvec.size(), mstimeout);
|
// socket_poll is implicitly expected to also poll stdin,
|
||||||
|
// if the OS allows that. Linux does, so we add stdin to the
|
||||||
|
// poll vector. The poll vector is required to have at
|
||||||
|
// least one free space in order to do this.
|
||||||
|
pollvec[pollcount].fd = 0;
|
||||||
|
pollvec[pollcount].events = POLLIN;
|
||||||
|
pollcount += 1;
|
||||||
|
|
||||||
|
// Do the poll.
|
||||||
|
int status = poll(pollvec, pollcount, mstimeout);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
err = strerror_str(errno);
|
err = strerror_str(errno);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -207,14 +217,6 @@ static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void socket_init() {
|
|
||||||
// Nothing needed on linux
|
|
||||||
}
|
|
||||||
|
|
||||||
static void socket_uninit() {
|
|
||||||
// Nothing needed on linux
|
|
||||||
}
|
|
||||||
|
|
||||||
static int console_write(const char *bytes, int nbytes) {
|
static int console_write(const char *bytes, int nbytes) {
|
||||||
return write(1, bytes, nbytes);
|
return write(1, bytes, nbytes);
|
||||||
}
|
}
|
||||||
@@ -223,15 +225,7 @@ static int console_read(char *bytes, int nbytes) {
|
|||||||
return read(0, bytes, nbytes);
|
return read(0, bytes, nbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The last element in the vector is supposed to be
|
|
||||||
// for polling stdio. But on windows, you can't poll
|
|
||||||
// stdio, so on windows, we remove the last element from
|
|
||||||
// the vector and we reduce mstimeout instead.
|
|
||||||
static void fill_stdio_pollfd(PollVector &pollvec, int &mstimeout, bool read_console_recently) {
|
|
||||||
struct pollfd &stdiopoll = pollvec.back();
|
|
||||||
stdiopoll.fd = 0;
|
|
||||||
stdiopoll.events = POLLIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disable_randomization(int argc, char *argv[]) {
|
static void disable_randomization(int argc, char *argv[]) {
|
||||||
const int old_personality = personality(ADDR_NO_RANDOMIZE);
|
const int old_personality = personality(ADDR_NO_RANDOMIZE);
|
||||||
@@ -243,6 +237,11 @@ static void disable_randomization(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void driver_sysinit(int argc, char *argv[]) {
|
||||||
|
disable_randomization(argc, argv);
|
||||||
|
enable_tty_raw();
|
||||||
|
}
|
||||||
|
|
||||||
class MonoClock {
|
class MonoClock {
|
||||||
private:
|
private:
|
||||||
struct timespec base_;
|
struct timespec base_;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#define _WIN32_WINNT 0x0600
|
#define _WIN32_WINNT 0x0600
|
||||||
|
|
||||||
#include "driver.hpp"
|
#include "driver.hpp"
|
||||||
|
#include "umm-malloc.hpp"
|
||||||
|
#include "driver-util.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "drivenengine.hpp"
|
#include "drivenengine.hpp"
|
||||||
#include "dummycert.hpp"
|
#include "dummycert.hpp"
|
||||||
@@ -23,18 +25,12 @@
|
|||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
|
|
||||||
using PollVector = std::vector<struct pollfd>;
|
|
||||||
|
|
||||||
static void set_nonblocking(SOCKET sock) {
|
static void set_nonblocking(SOCKET sock) {
|
||||||
u_long mode = 1; // 1 to enable non-blocking socket
|
u_long mode = 1; // 1 to enable non-blocking socket
|
||||||
int status = ioctlsocket(sock, FIONBIO, &mode);
|
int status = ioctlsocket(sock, FIONBIO, &mode);
|
||||||
assert(status == 0);
|
assert(status == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void enable_tty_raw() {
|
|
||||||
// Do nothing on windows.
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string winsock_error_string(int errcode) {
|
static std::string winsock_error_string(int errcode) {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "error " << errcode;
|
oss << "error " << errcode;
|
||||||
@@ -46,21 +42,21 @@ static PADDRINFOA find_good_addr(PADDRINFOA addrinfo) {
|
|||||||
if (addr->ai_family == AF_INET) {
|
if (addr->ai_family == AF_INET) {
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}std::string
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOCKET open_connection(const std::string &target, std::string &err) {
|
static SOCKET open_connection(std::string_view target, UmmString &err) {
|
||||||
PADDRINFOA addrs = nullptr;
|
PADDRINFOA addrs = nullptr;
|
||||||
PADDRINFOA goodaddr = nullptr;
|
PADDRINFOA goodaddr = nullptr;
|
||||||
SOCKET sock = INVALID_SOCKET;
|
SOCKET sock = INVALID_SOCKET;
|
||||||
std::string host, port;
|
std::string_view host, port;
|
||||||
|
|
||||||
err.clear();
|
err.clear();
|
||||||
util::split_host_port(target, host, port);
|
util::split_host_port(target, host, port);
|
||||||
int status = getaddrinfo(host.c_str(), port.c_str(), nullptr, &addrs);
|
int status = getaddrinfo(host.data(), port.data(), nullptr, &addrs);
|
||||||
while (status == WSATRY_AGAIN) {
|
while (status == WSATRY_AGAIN) {
|
||||||
status = getaddrinfo(host.c_str(), port.c_str(), nullptr, &addrs);
|
status = getaddrinfo(host.data(), port.data(), nullptr, &addrs);
|
||||||
}
|
}
|
||||||
if (status == WSAHOST_NOT_FOUND) {
|
if (status == WSAHOST_NOT_FOUND) {
|
||||||
err = "host not found";
|
err = "host not found";
|
||||||
@@ -98,7 +94,7 @@ error:
|
|||||||
return SOCKET_ERROR;
|
return SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
SOCKET listen_on_port(int port, std::string &err) {
|
SOCKET listen_on_port(int port, UmmString &err) {
|
||||||
int status;
|
int status;
|
||||||
err.clear();
|
err.clear();
|
||||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
@@ -131,11 +127,10 @@ error:
|
|||||||
return SOCKET_ERROR;
|
return SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
static SOCKET accept_on_socket(SOCKET listen_socket, UmmString &err) {
|
||||||
SOCKET chsock = accept(listen_socket, nullptr, nullptr);
|
SOCKET chsock = accept(listen_socket, nullptr, nullptr);
|
||||||
if (chsock != INVALID_SOCKET) {
|
if (chsock != INVALID_SOCKET) {
|
||||||
set_nonblocking(chsock);
|
set_nonblocking(chsock);
|
||||||
std::cerr << "accepted socket is " << chsock << std::endl;
|
|
||||||
return chsock;
|
return chsock;
|
||||||
} else {
|
} else {
|
||||||
int errcode = WSAGetLastError();
|
int errcode = WSAGetLastError();
|
||||||
@@ -148,7 +143,7 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
static int socket_send(SOCKET socket, const char *bytes, int nbytes, UmmString &err) {
|
||||||
err.clear();
|
err.clear();
|
||||||
int wbytes = send(socket, bytes, nbytes, 0);
|
int wbytes = send(socket, bytes, nbytes, 0);
|
||||||
if (wbytes == SOCKET_ERROR) {
|
if (wbytes == SOCKET_ERROR) {
|
||||||
@@ -165,7 +160,7 @@ static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
static int socket_recv(SOCKET socket, char *bytes, int nbytes, UmmString &err) {
|
||||||
err.clear();
|
err.clear();
|
||||||
int nrecv = recv(socket, bytes, nbytes, 0);
|
int nrecv = recv(socket, bytes, nbytes, 0);
|
||||||
if (nrecv < 0) {
|
if (nrecv < 0) {
|
||||||
@@ -187,8 +182,8 @@ static int socket_close(SOCKET socket) {
|
|||||||
return closesocket(socket);
|
return closesocket(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) {
|
static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, UmmString &err) {
|
||||||
int status = WSAPoll(&pollvec[0], pollvec.size(), mstimeout);
|
int status = WSAPoll(pollvec, pollcount, mstimeout);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
err = winsock_error_string(WSAGetLastError());
|
err = winsock_error_string(WSAGetLastError());
|
||||||
return -1;
|
return -1;
|
||||||
@@ -196,7 +191,7 @@ static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void socket_init() {
|
static void init_winsock() {
|
||||||
WSADATA data;
|
WSADATA data;
|
||||||
int errcode = WSAStartup(2, &data);
|
int errcode = WSAStartup(2, &data);
|
||||||
if (errcode != 0) {
|
if (errcode != 0) {
|
||||||
@@ -205,10 +200,6 @@ static void socket_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void socket_uninit() {
|
|
||||||
// Nothing needed.
|
|
||||||
}
|
|
||||||
|
|
||||||
static int console_write(const char *bytes, int nbytes) {
|
static int console_write(const char *bytes, int nbytes) {
|
||||||
if (nbytes == 0) return 0;
|
if (nbytes == 0) return 0;
|
||||||
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
@@ -243,17 +234,8 @@ static int console_read(char *bytes, int nbytes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The last element in the vector is supposed to be
|
void driver_sysinit(int argc, char *argv[]) {
|
||||||
// for polling stdio. But on windows, you can't poll
|
init_winsock();
|
||||||
// stdio, so on windows, we remove the last element from
|
|
||||||
// the vector and we reduce mstimeout instead.
|
|
||||||
static void fill_stdio_pollfd(PollVector &pollvec, int &mstimeout, bool read_console_recently) {
|
|
||||||
pollvec.pop_back();
|
|
||||||
if (mstimeout > 100) mstimeout = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disable_randomization(int argc, char *argv[]) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MonoClock {
|
class MonoClock {
|
||||||
|
|||||||
29
luprex/core/cpp/driver-util.cpp
Normal file
29
luprex/core/cpp/driver-util.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
#include "driver-util.hpp"
|
||||||
|
#include "luastack.hpp"
|
||||||
|
|
||||||
|
namespace drv {
|
||||||
|
|
||||||
|
void split_host_port(std::string_view target, UmmString &host, UmmString &port) {
|
||||||
|
size_t lastcolon = target.rfind(':');
|
||||||
|
if (lastcolon == std::string_view::npos) {
|
||||||
|
host = ""; port = ""; return;
|
||||||
|
}
|
||||||
|
host = target.substr(0, lastcolon);
|
||||||
|
port = target.substr(lastcolon + 1);
|
||||||
|
if ((host == "") || (port == "")) {
|
||||||
|
host = ""; port = ""; return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace drv
|
||||||
|
|
||||||
|
LuaDefine(unittests_driverutil, "", "some unit tests") {
|
||||||
|
// Test split_host_port
|
||||||
|
UmmString host, port;
|
||||||
|
drv::split_host_port("stanford.edu:80", host, port);
|
||||||
|
LuaAssertStrEq(L, host, "stanford.edu");
|
||||||
|
LuaAssertStrEq(L, port, "80");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
13
luprex/core/cpp/driver-util.hpp
Normal file
13
luprex/core/cpp/driver-util.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
#ifndef DRIVER_UTIL_HPP
|
||||||
|
#define DRIVER_UTIL_HPP
|
||||||
|
|
||||||
|
#include "umm-malloc.hpp"
|
||||||
|
|
||||||
|
namespace drv {
|
||||||
|
|
||||||
|
void split_host_port(std::string_view target, UmmString &host, UmmString &port);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DRIVER_UTIL_HPP
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
class DrivenEngine;
|
class DrivenEngine;
|
||||||
|
|
||||||
void driver_drive(DrivenEngine *de, int argc, char *argv[]);
|
void driver_sysinit(int argc, char *argv[]);
|
||||||
|
void driver_drive(int argc, char *argv[]);
|
||||||
|
|
||||||
#endif // DRIVER_HPP
|
#endif // DRIVER_HPP
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ extern "C" {
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
class LuaSlot {
|
class LuaSlot {
|
||||||
protected:
|
protected:
|
||||||
@@ -507,5 +508,5 @@ public:
|
|||||||
|
|
||||||
#define LuaStringify(x) #x
|
#define LuaStringify(x) #x
|
||||||
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
|
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
|
||||||
#define LuaAssertStrEq(L, x, y) { std::string _s1_ = (x); std::string _s2_ = (y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); }
|
#define LuaAssertStrEq(L, x, y) { std::string _s1_(x); std::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); }
|
||||||
#endif // LUASTACK_HPP
|
#endif // LUASTACK_HPP
|
||||||
|
|||||||
@@ -7,44 +7,21 @@
|
|||||||
#include "source.hpp"
|
#include "source.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
struct EngineMaker {
|
|
||||||
const char *name;
|
|
||||||
DrivenEngineMaker func;
|
|
||||||
};
|
|
||||||
|
|
||||||
static EngineMaker makers[] = {
|
|
||||||
{ "textgame", make_TextGame },
|
|
||||||
{ "lpxclient", make_LpxClient },
|
|
||||||
{ "lpxserver", make_LpxServer },
|
|
||||||
{ "driverlistentest", make_DriverListenTest },
|
|
||||||
{ "driverwebservertest", make_DriverWebServerTest },
|
|
||||||
{ "driverdnsfailtest", make_DriverDNSFailTest },
|
|
||||||
{ "driverprintclocktest", make_DriverPrintClockTest },
|
|
||||||
{ "unittest", make_RunUnitTests },
|
|
||||||
{ nullptr, nullptr },
|
|
||||||
};
|
|
||||||
|
|
||||||
static void usage() {
|
|
||||||
std::cerr << "Usage: main <mode>" << std::endl;
|
|
||||||
for (int i = 0; makers[i].name != nullptr; i++) {
|
|
||||||
std::cerr << " <mode> can be: " << makers[i].name << std::endl;
|
|
||||||
}
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
driver_sysinit(argc, argv);
|
||||||
SourceDB::register_lua_builtins();
|
SourceDB::register_lua_builtins();
|
||||||
UniqueDrivenEngine engine;
|
|
||||||
if (argc < 2) usage();
|
DrivenEngine::register_maker("textgame", make_TextGame);
|
||||||
std::string mode = argv[1];
|
DrivenEngine::register_maker("lpxclient", make_LpxClient);
|
||||||
for (int i = 0; makers[i].name != nullptr; i++) {
|
DrivenEngine::register_maker("lpxserver", make_LpxServer);
|
||||||
if (mode == makers[i].name) {
|
DrivenEngine::register_maker("driverlistentest", make_DriverListenTest);
|
||||||
engine = makers[i].func();
|
DrivenEngine::register_maker("driverwebservertest", make_DriverWebServerTest);
|
||||||
break;
|
DrivenEngine::register_maker("driverdnsfailtest", make_DriverDNSFailTest);
|
||||||
}
|
DrivenEngine::register_maker("driverprintclocktest", make_DriverPrintClockTest);
|
||||||
}
|
DrivenEngine::register_maker("unittest", make_RunUnitTests);
|
||||||
if (engine == nullptr) usage();
|
|
||||||
driver_drive(engine.get(), argc, argv);
|
driver_drive(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
929
luprex/core/cpp/umm-malloc.cpp
Normal file
929
luprex/core/cpp/umm-malloc.cpp
Normal file
@@ -0,0 +1,929 @@
|
|||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* umm_malloc.c - a memory allocator for embedded systems (microcontrollers)
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Ralph Hempel
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use, copy,
|
||||||
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Note: this version is significantly modified from the version
|
||||||
|
* distributed by Ralph Hempel. In particular, block numbers are
|
||||||
|
* 32 bits in this version.
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "umm-malloc.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* UMM_BEST_FIT (default)
|
||||||
|
*
|
||||||
|
* Set this if you want to use a best-fit algorithm for allocating new blocks.
|
||||||
|
* On by default, turned off by UMM_FIRST_FIT
|
||||||
|
*
|
||||||
|
* UMM_FIRST_FIT
|
||||||
|
*
|
||||||
|
* Set this if you want to use a first-fit algorithm for allocating new blocks.
|
||||||
|
* Faster than UMM_BEST_FIT but can result in higher fragmentation.
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UMM_BEST_FIT
|
||||||
|
#undef UMM_FIRST_FIT
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Three macros to make it easier to protect the memory allocator in a
|
||||||
|
* multitasking system. You should set these macros up to use whatever your
|
||||||
|
* system uses for this purpose. You can disable interrupts entirely, or just
|
||||||
|
* disable task switching - it's up to you
|
||||||
|
*
|
||||||
|
* NOTE WELL that these macros MUST be allowed to nest, because umm_free() is
|
||||||
|
* called from within umm_malloc()
|
||||||
|
*
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UMM_CRITICAL_DECL(tag)
|
||||||
|
#define UMM_CRITICAL_ENTRY(tag)
|
||||||
|
#define UMM_CRITICAL_EXIT(tag)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Debug Logging.
|
||||||
|
*
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #define DBGLOG(format, ...) { fprintf(stderr, format,##__VA_ARGS__); fflush(stderr); }
|
||||||
|
|
||||||
|
#define DBGLOG(format, ...)
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct umm_ptr_t {
|
||||||
|
uint32_t next;
|
||||||
|
uint32_t prev;
|
||||||
|
} umm_ptr;
|
||||||
|
|
||||||
|
typedef struct umm_block_t {
|
||||||
|
union {
|
||||||
|
umm_ptr used;
|
||||||
|
} header;
|
||||||
|
union {
|
||||||
|
umm_ptr free;
|
||||||
|
uint8_t data[8];
|
||||||
|
} body;
|
||||||
|
} umm_block;
|
||||||
|
|
||||||
|
#define UMM_FREELIST_MASK ((uint32_t)(0x80000000))
|
||||||
|
#define UMM_BLOCKNO_MASK ((uint32_t)(0x7FFFFFFF))
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
struct umm_heap_config {
|
||||||
|
umm_block *pheap;
|
||||||
|
size_t heap_size;
|
||||||
|
uint32_t numblocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct umm_heap_config umm_heap_current;
|
||||||
|
|
||||||
|
#define UMM_HEAP (umm_heap_current.pheap)
|
||||||
|
#define UMM_HEAPSIZE (umm_heap_current.heap_size)
|
||||||
|
#define UMM_NUMBLOCKS (umm_heap_current.numblocks)
|
||||||
|
|
||||||
|
#define UMM_BLOCKSIZE (sizeof(umm_block))
|
||||||
|
#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1)
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* These macros evaluate to the address of the block and data respectively
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UMM_BLOCK(b) (UMM_HEAP[b])
|
||||||
|
#define UMM_DATA(b) (UMM_BLOCK(b).body.data)
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* These macros evaluate to the index of the block - NOT the address!!!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next)
|
||||||
|
#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev)
|
||||||
|
#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next)
|
||||||
|
#define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev)
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* One of the coolest things about this little library is that it's VERY
|
||||||
|
* easy to get debug information about the memory heap by simply iterating
|
||||||
|
* through all of the memory blocks.
|
||||||
|
*
|
||||||
|
* As you go through all the blocks, you can check to see if it's a free
|
||||||
|
* block by looking at the high order bit of the next block index. You can
|
||||||
|
* also see how big the block is by subtracting the next block index from
|
||||||
|
* the current block number.
|
||||||
|
*
|
||||||
|
* The umm_info function does all of that and makes the results available
|
||||||
|
* in the ummHeapInfo structure.
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct UMM_HEAP_INFO_t {
|
||||||
|
unsigned int totalEntries;
|
||||||
|
unsigned int usedEntries;
|
||||||
|
unsigned int freeEntries;
|
||||||
|
|
||||||
|
unsigned int totalBlocks;
|
||||||
|
unsigned int usedBlocks;
|
||||||
|
unsigned int freeBlocks;
|
||||||
|
unsigned int freeBlocksSquared;
|
||||||
|
|
||||||
|
unsigned int maxFreeContiguousBlocks;
|
||||||
|
|
||||||
|
int usage_metric;
|
||||||
|
int fragmentation_metric;
|
||||||
|
}
|
||||||
|
UMM_HEAP_INFO;
|
||||||
|
|
||||||
|
static UMM_HEAP_INFO ummHeapInfo;
|
||||||
|
|
||||||
|
static void compute_usage_metric(void)
|
||||||
|
{
|
||||||
|
if (0 == ummHeapInfo.freeBlocks) {
|
||||||
|
ummHeapInfo.usage_metric = -1; // No free blocks!
|
||||||
|
} else {
|
||||||
|
ummHeapInfo.usage_metric = (int)((ummHeapInfo.usedBlocks * 100) / (ummHeapInfo.freeBlocks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void compute_fragmentation_metric(void)
|
||||||
|
{
|
||||||
|
if (0 == ummHeapInfo.freeBlocks) {
|
||||||
|
ummHeapInfo.fragmentation_metric = 0; // No free blocks ... so no fragmentation either!
|
||||||
|
} else {
|
||||||
|
ummHeapInfo.fragmentation_metric = 100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100) / (ummHeapInfo.freeBlocks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DBGLOG_32_BIT_PTR(x) ((uint32_t)(((uintptr_t)(x)) & 0xffffffff))
|
||||||
|
|
||||||
|
void umm_info() {
|
||||||
|
uint32_t blockNo = 0;
|
||||||
|
|
||||||
|
UMM_CRITICAL_DECL(id_info);
|
||||||
|
|
||||||
|
/* Protect the critical section... */
|
||||||
|
UMM_CRITICAL_ENTRY(id_info);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear out all of the entries in the ummHeapInfo structure before doing
|
||||||
|
* any calculations..
|
||||||
|
*/
|
||||||
|
memset(&ummHeapInfo, 0, sizeof(ummHeapInfo));
|
||||||
|
|
||||||
|
DBGLOG("\n");
|
||||||
|
DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n");
|
||||||
|
DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n",
|
||||||
|
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||||
|
blockNo,
|
||||||
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
||||||
|
UMM_PBLOCK(blockNo),
|
||||||
|
(UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo,
|
||||||
|
UMM_NFREE(blockNo),
|
||||||
|
UMM_PFREE(blockNo));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now loop through the block lists, and keep track of the number and size
|
||||||
|
* of used and free blocks. The terminating condition is an nb pointer with
|
||||||
|
* a value of zero...
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK;
|
||||||
|
|
||||||
|
while (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) {
|
||||||
|
size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo;
|
||||||
|
|
||||||
|
++ummHeapInfo.totalEntries;
|
||||||
|
ummHeapInfo.totalBlocks += curBlocks;
|
||||||
|
|
||||||
|
/* Is this a free block? */
|
||||||
|
|
||||||
|
if (UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) {
|
||||||
|
++ummHeapInfo.freeEntries;
|
||||||
|
ummHeapInfo.freeBlocks += curBlocks;
|
||||||
|
ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks);
|
||||||
|
|
||||||
|
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
||||||
|
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u|NF %5i|PF %5i|\n",
|
||||||
|
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||||
|
blockNo,
|
||||||
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
||||||
|
UMM_PBLOCK(blockNo),
|
||||||
|
(uint32_t)curBlocks,
|
||||||
|
UMM_NFREE(blockNo),
|
||||||
|
UMM_PFREE(blockNo));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
++ummHeapInfo.usedEntries;
|
||||||
|
ummHeapInfo.usedBlocks += curBlocks;
|
||||||
|
|
||||||
|
DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u| |\n",
|
||||||
|
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||||
|
blockNo,
|
||||||
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
||||||
|
UMM_PBLOCK(blockNo),
|
||||||
|
(uint32_t)curBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The very last block is used as a placeholder to indicate that
|
||||||
|
* there are no more blocks in the heap, so it cannot be used
|
||||||
|
* for anything - at the same time, the size of this block must
|
||||||
|
* ALWAYS be exactly 1 !
|
||||||
|
*/
|
||||||
|
|
||||||
|
DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n",
|
||||||
|
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||||
|
blockNo,
|
||||||
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
||||||
|
UMM_PBLOCK(blockNo),
|
||||||
|
UMM_NUMBLOCKS - blockNo,
|
||||||
|
UMM_NFREE(blockNo),
|
||||||
|
UMM_PFREE(blockNo));
|
||||||
|
|
||||||
|
DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n");
|
||||||
|
|
||||||
|
DBGLOG("Total Entries %5i Used Entries %5i Free Entries %5i\n",
|
||||||
|
ummHeapInfo.totalEntries,
|
||||||
|
ummHeapInfo.usedEntries,
|
||||||
|
ummHeapInfo.freeEntries);
|
||||||
|
|
||||||
|
DBGLOG("Total Blocks %5i Used Blocks %5i Free Blocks %5i\n",
|
||||||
|
ummHeapInfo.totalBlocks,
|
||||||
|
ummHeapInfo.usedBlocks,
|
||||||
|
ummHeapInfo.freeBlocks);
|
||||||
|
|
||||||
|
DBGLOG("+--------------------------------------------------------------+\n");
|
||||||
|
|
||||||
|
compute_usage_metric();
|
||||||
|
DBGLOG("Usage Metric: %5i\n", ummHeapInfo.usage_metric);
|
||||||
|
|
||||||
|
compute_fragmentation_metric();
|
||||||
|
DBGLOG("Fragmentation Metric: %5i\n", ummHeapInfo.fragmentation_metric);
|
||||||
|
|
||||||
|
DBGLOG("+--------------------------------------------------------------+\n");
|
||||||
|
|
||||||
|
/* Release the critical section... */
|
||||||
|
UMM_CRITICAL_EXIT(id_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static uint32_t umm_blocks(size_t size) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The calculation of the block size is not too difficult, but there are
|
||||||
|
* a few little things that we need to be mindful of.
|
||||||
|
*
|
||||||
|
* When a block removed from the free list, the space used by the free
|
||||||
|
* pointers is available for data. That's what the first calculation
|
||||||
|
* of size is doing.
|
||||||
|
*
|
||||||
|
* We don't check for the special case of (size == 0) here as this needs
|
||||||
|
* special handling in the caller depending on context. For example when we
|
||||||
|
* realloc() a block to size 0 it should simply be freed.
|
||||||
|
*
|
||||||
|
* We do NOT need to check for allocating more blocks than the heap can
|
||||||
|
* possibly hold - the allocator figures this out for us.
|
||||||
|
*
|
||||||
|
* There are only two cases left to consider:
|
||||||
|
*
|
||||||
|
* 1. (size <= body) Obviously this is just one block
|
||||||
|
* 2. (blocks > (2^15)) This should return ((2^15)) to force a
|
||||||
|
* failure when the allocator runs
|
||||||
|
*
|
||||||
|
* If the requested size is greater that 32677-2 blocks (max block index
|
||||||
|
* minus the overhead of the top and bottom bookkeeping blocks) then we
|
||||||
|
* will return an incorrectly truncated value when the result is cast to
|
||||||
|
* a uint32_t.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (size <= (sizeof(((umm_block *)0)->body))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If it's for more than that, then we need to figure out the number of
|
||||||
|
* additional whole blocks the size of an umm_block are required, so
|
||||||
|
* reduce the size request by the number of bytes in the body of the
|
||||||
|
* first block.
|
||||||
|
*/
|
||||||
|
|
||||||
|
size -= (sizeof(((umm_block *)0)->body));
|
||||||
|
|
||||||
|
/* NOTE WELL that we take advantage of the fact that INT16_MAX is the
|
||||||
|
* number of blocks that we can index in 15 bits :-)
|
||||||
|
*
|
||||||
|
* The below expression looks wierd, but it's right. Assuming body
|
||||||
|
* size of 4 bytes and a block size of 8 bytes:
|
||||||
|
*
|
||||||
|
* BYTES (BYTES-BODY) (BYTES-BODY-1)/BLOCKSIZE BLOCKS
|
||||||
|
* 1 n/a n/a 1
|
||||||
|
* 5 1 0 2
|
||||||
|
* 12 8 0 2
|
||||||
|
* 13 9 1 3
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t blocks = (2 + ((size-1) / (UMM_BLOCKSIZE)));
|
||||||
|
|
||||||
|
if (blocks > (INT16_MAX)) {
|
||||||
|
blocks = INT16_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint32_t)blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/*
|
||||||
|
* Split the block `c` into two blocks: `c` and `c + blocks`.
|
||||||
|
*
|
||||||
|
* - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK`
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* Note that free pointers are NOT modified by this function.
|
||||||
|
*/
|
||||||
|
static void umm_split_block(uint32_t c,
|
||||||
|
uint32_t blocks,
|
||||||
|
uint32_t new_freemask) {
|
||||||
|
|
||||||
|
UMM_NBLOCK(c + blocks) = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) | new_freemask;
|
||||||
|
UMM_PBLOCK(c + blocks) = c;
|
||||||
|
|
||||||
|
UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c + blocks);
|
||||||
|
UMM_NBLOCK(c) = (c + blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void umm_disconnect_from_free_list(uint32_t c) {
|
||||||
|
/* Disconnect this block from the FREE list */
|
||||||
|
|
||||||
|
UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c);
|
||||||
|
UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c);
|
||||||
|
|
||||||
|
/* And clear the free block indicator */
|
||||||
|
|
||||||
|
UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
* The umm_assimilate_up() function does not assume that UMM_NBLOCK(c)
|
||||||
|
* has the UMM_FREELIST_MASK bit set. It only assimilates up if the
|
||||||
|
* next block is free.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void umm_assimilate_up(uint32_t c) {
|
||||||
|
|
||||||
|
if (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The next block is a free block, so assimilate up and remove it from
|
||||||
|
* the free list
|
||||||
|
*/
|
||||||
|
|
||||||
|
DBGLOG("Assimilate up to next block, which is FREE\n");
|
||||||
|
|
||||||
|
/* Disconnect the next block from the FREE list */
|
||||||
|
|
||||||
|
umm_disconnect_from_free_list(UMM_NBLOCK(c));
|
||||||
|
|
||||||
|
/* Assimilate the next block with this one */
|
||||||
|
|
||||||
|
UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c;
|
||||||
|
UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
* The umm_assimilate_down() function assumes that UMM_NBLOCK(c) does NOT
|
||||||
|
* have the UMM_FREELIST_MASK bit set. In other words, try to assimilate
|
||||||
|
* up before assimilating down.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static uint32_t umm_assimilate_down(uint32_t c, uint32_t freemask) {
|
||||||
|
|
||||||
|
// We are going to assimilate down to the previous block because
|
||||||
|
// it was free, so remove it from the fragmentation metric
|
||||||
|
|
||||||
|
UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask;
|
||||||
|
UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c);
|
||||||
|
|
||||||
|
return UMM_PBLOCK(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void umm_init_heap(void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
/* init heap pointer and size, and memset it to 0 */
|
||||||
|
UMM_HEAP = (umm_block *)ptr;
|
||||||
|
UMM_HEAPSIZE = size;
|
||||||
|
UMM_NUMBLOCKS = (UMM_HEAPSIZE / UMM_BLOCKSIZE);
|
||||||
|
memset(UMM_HEAP, 0x00, UMM_HEAPSIZE);
|
||||||
|
|
||||||
|
/* Set up umm_block[0], which just points to umm_block[1] */
|
||||||
|
UMM_NBLOCK(0) = 1;
|
||||||
|
UMM_NFREE(0) = 1;
|
||||||
|
UMM_PFREE(0) = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now, we need to set the whole heap space as a huge free block. We should
|
||||||
|
* not touch umm_block[0], since it's special: umm_block[0] is the head of
|
||||||
|
* the free block list. It's a part of the heap invariant.
|
||||||
|
*
|
||||||
|
* See the detailed explanation at the beginning of the file.
|
||||||
|
*
|
||||||
|
* umm_block[1] has pointers:
|
||||||
|
*
|
||||||
|
* - next `umm_block`: the last one umm_block[n]
|
||||||
|
* - prev `umm_block`: umm_block[0]
|
||||||
|
*
|
||||||
|
* Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK`
|
||||||
|
*
|
||||||
|
* And it's the last free block, so the next free block is 0 which marks
|
||||||
|
* the end of the list. The previous block and free block pointer are 0
|
||||||
|
* too, there is no need to initialize these values due to the init code
|
||||||
|
* that memsets the entire umm_ space to 0.
|
||||||
|
*/
|
||||||
|
UMM_NBLOCK(1) = UMM_BLOCK_LAST | UMM_FREELIST_MASK;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Last umm_block[n] has the next block index at 0, meaning it's
|
||||||
|
* the end of the list, and the previous block is umm_block[1].
|
||||||
|
*
|
||||||
|
* The last block is a special block and can never be part of the
|
||||||
|
* free list, so its pointers are left at 0 too.
|
||||||
|
*/
|
||||||
|
|
||||||
|
UMM_PBLOCK(UMM_BLOCK_LAST) = 1;
|
||||||
|
|
||||||
|
// DBGLOG(true, "nblock(0) %04x pblock(0) %04x nfree(0) %04x pfree(0) %04x\n", UMM_NBLOCK(0) & UMM_BLOCKNO_MASK, UMM_PBLOCK(0), UMM_NFREE(0), UMM_PFREE(0));
|
||||||
|
// DBGLOG(true, "nblock(1) %04x pblock(1) %04x nfree(1) %04x pfree(1) %04x\n", UMM_NBLOCK(1) & UMM_BLOCKNO_MASK, UMM_PBLOCK(1), UMM_NFREE(1), UMM_PFREE(1));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
* Must be called only from within critical sections guarded by
|
||||||
|
* UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id).
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void umm_free_core(void *ptr) {
|
||||||
|
|
||||||
|
uint32_t c;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: At some point it might be a good idea to add a check to make sure
|
||||||
|
* that the pointer we're being asked to free up is actually within
|
||||||
|
* the umm_heap!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Figure out which block we're in. Note the use of truncated division... */
|
||||||
|
|
||||||
|
c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE;
|
||||||
|
|
||||||
|
DBGLOG("Freeing block %6i\n", c);
|
||||||
|
|
||||||
|
/* Now let's assimilate this block with the next one if possible. */
|
||||||
|
|
||||||
|
umm_assimilate_up(c);
|
||||||
|
|
||||||
|
/* Then assimilate with the previous block if possible */
|
||||||
|
|
||||||
|
if (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) {
|
||||||
|
|
||||||
|
DBGLOG("Assimilate down to previous block, which is FREE\n");
|
||||||
|
|
||||||
|
c = umm_assimilate_down(c, UMM_FREELIST_MASK);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* The previous block is not a free block, so add this one to the head
|
||||||
|
* of the free list
|
||||||
|
*/
|
||||||
|
DBGLOG("Just add to head of free list\n");
|
||||||
|
|
||||||
|
UMM_PFREE(UMM_NFREE(0)) = c;
|
||||||
|
UMM_NFREE(c) = UMM_NFREE(0);
|
||||||
|
UMM_PFREE(c) = 0;
|
||||||
|
UMM_NFREE(0) = c;
|
||||||
|
|
||||||
|
UMM_NBLOCK(c) |= UMM_FREELIST_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
void umm_free(void *ptr) {
|
||||||
|
UMM_CRITICAL_DECL(id_free);
|
||||||
|
|
||||||
|
/* If we're being asked to free a NULL pointer, well that's just silly! */
|
||||||
|
|
||||||
|
if ((void *)0 == ptr) {
|
||||||
|
DBGLOG("free a null pointer -> do nothing\n");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the memory withing a protected critical section */
|
||||||
|
|
||||||
|
UMM_CRITICAL_ENTRY(id_free);
|
||||||
|
|
||||||
|
umm_free_core(ptr);
|
||||||
|
|
||||||
|
UMM_CRITICAL_EXIT(id_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
* Must be called only from within critical sections guarded by
|
||||||
|
* UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id).
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void *umm_malloc_core(size_t size) {
|
||||||
|
uint32_t blocks;
|
||||||
|
uint32_t blockSize = 0;
|
||||||
|
|
||||||
|
uint32_t bestSize;
|
||||||
|
uint32_t bestBlock;
|
||||||
|
|
||||||
|
uint32_t cf;
|
||||||
|
|
||||||
|
blocks = umm_blocks(size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we can scan through the free list until we find a space that's big
|
||||||
|
* enough to hold the number of blocks we need.
|
||||||
|
*
|
||||||
|
* This part may be customized to be a best-fit, worst-fit, or first-fit
|
||||||
|
* algorithm
|
||||||
|
*/
|
||||||
|
|
||||||
|
cf = UMM_NFREE(0);
|
||||||
|
|
||||||
|
bestBlock = UMM_NFREE(0);
|
||||||
|
bestSize = 0x7FFFFFFF;
|
||||||
|
|
||||||
|
while (cf) {
|
||||||
|
blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf;
|
||||||
|
|
||||||
|
DBGLOG("Looking at block %6i size %6i\n", cf, blockSize);
|
||||||
|
|
||||||
|
#if defined UMM_BEST_FIT
|
||||||
|
if ((blockSize >= blocks) && (blockSize < bestSize)) {
|
||||||
|
bestBlock = cf;
|
||||||
|
bestSize = blockSize;
|
||||||
|
}
|
||||||
|
#elif defined UMM_FIRST_FIT
|
||||||
|
/* This is the first block that fits! */
|
||||||
|
if ((blockSize >= blocks)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "No UMM_*_FIT is defined - check umm_malloc_cfg.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cf = UMM_NFREE(cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0x7FFFFFFF != bestSize) {
|
||||||
|
cf = bestBlock;
|
||||||
|
blockSize = bestSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK && blockSize >= blocks) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an existing block in the memory heap, we just need to split off
|
||||||
|
* what we need, unlink it from the free list and mark it as in use, and
|
||||||
|
* link the rest of the block back into the freelist as if it was a new
|
||||||
|
* block on the free list...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (blockSize == blocks) {
|
||||||
|
/* It's an exact fit and we don't neet to split off a block. */
|
||||||
|
DBGLOG("Allocating %6i blocks starting at %6i - exact\n", blocks, cf);
|
||||||
|
|
||||||
|
/* Disconnect this block from the FREE list */
|
||||||
|
|
||||||
|
umm_disconnect_from_free_list(cf);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/* It's not an exact fit and we need to split off a block. */
|
||||||
|
DBGLOG("Allocating %6i blocks starting at %6i - existing\n", blocks, cf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* split current free block `cf` into two blocks. The first one will be
|
||||||
|
* returned to user, so it's not free, and the second one will be free.
|
||||||
|
*/
|
||||||
|
umm_split_block(cf, blocks, UMM_FREELIST_MASK /*new block is free*/);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `umm_split_block()` does not update the free pointers (it affects
|
||||||
|
* only free flags), but effectively we've just moved beginning of the
|
||||||
|
* free block from `cf` to `cf + blocks`. So we have to adjust pointers
|
||||||
|
* to and from adjacent free blocks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* previous free block */
|
||||||
|
UMM_NFREE(UMM_PFREE(cf)) = cf + blocks;
|
||||||
|
UMM_PFREE(cf + blocks) = UMM_PFREE(cf);
|
||||||
|
|
||||||
|
/* next free block */
|
||||||
|
UMM_PFREE(UMM_NFREE(cf)) = cf + blocks;
|
||||||
|
UMM_NFREE(cf + blocks) = UMM_NFREE(cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Out of memory */
|
||||||
|
|
||||||
|
DBGLOG("Can't allocate %5i blocks\n", blocks);
|
||||||
|
|
||||||
|
return (void *)NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (void *)&UMM_DATA(cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
void *umm_malloc(size_t size) {
|
||||||
|
UMM_CRITICAL_DECL(id_malloc);
|
||||||
|
|
||||||
|
void *ptr = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the very first thing we do is figure out if we're being asked to allocate
|
||||||
|
* a size of 0 - and if we are we'll simply return a null pointer. if not
|
||||||
|
* then reduce the size by 1 byte so that the subsequent calculations on
|
||||||
|
* the number of blocks to allocate are easier...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (0 == size) {
|
||||||
|
DBGLOG("malloc a block of 0 bytes -> do nothing\n");
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate the memory withing a protected critical section */
|
||||||
|
|
||||||
|
UMM_CRITICAL_ENTRY(id_malloc);
|
||||||
|
|
||||||
|
ptr = umm_malloc_core(size);
|
||||||
|
|
||||||
|
UMM_CRITICAL_EXIT(id_malloc);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
void *umm_realloc(void *ptr, size_t size) {
|
||||||
|
UMM_CRITICAL_DECL(id_realloc);
|
||||||
|
|
||||||
|
uint32_t blocks;
|
||||||
|
uint32_t blockSize;
|
||||||
|
uint32_t prevBlockSize = 0;
|
||||||
|
uint32_t nextBlockSize = 0;
|
||||||
|
|
||||||
|
uint32_t c;
|
||||||
|
|
||||||
|
size_t curSize;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This code looks after the case of a NULL value for ptr. The ANSI C
|
||||||
|
* standard says that if ptr is NULL and size is non-zero, then we've
|
||||||
|
* got to work the same a malloc(). If size is also 0, then our version
|
||||||
|
* of malloc() returns a NULL pointer, which is OK as far as the ANSI C
|
||||||
|
* standard is concerned.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (((void *)NULL == ptr)) {
|
||||||
|
DBGLOG("realloc the NULL pointer - call malloc()\n");
|
||||||
|
|
||||||
|
return umm_malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we're sure that we have a non_NULL ptr, but we're not sure what
|
||||||
|
* we should do with it. If the size is 0, then the ANSI C standard says that
|
||||||
|
* we should operate the same as free.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (0 == size) {
|
||||||
|
DBGLOG("realloc to 0 size, just free the block\n");
|
||||||
|
|
||||||
|
umm_free(ptr);
|
||||||
|
|
||||||
|
return (void *)NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Otherwise we need to actually do a reallocation. A naiive approach
|
||||||
|
* would be to malloc() a new block of the correct size, copy the old data
|
||||||
|
* to the new block, and then free the old block.
|
||||||
|
*
|
||||||
|
* While this will work, we end up doing a lot of possibly unnecessary
|
||||||
|
* copying. So first, let's figure out how many blocks we'll need.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blocks = umm_blocks(size);
|
||||||
|
|
||||||
|
/* Figure out which block we're in. Note the use of truncated division... */
|
||||||
|
|
||||||
|
c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE;
|
||||||
|
|
||||||
|
/* Figure out how big this block is ... the free bit is not set :-) */
|
||||||
|
|
||||||
|
blockSize = (UMM_NBLOCK(c) - c);
|
||||||
|
|
||||||
|
/* Figure out how many bytes are in this block */
|
||||||
|
|
||||||
|
curSize = (blockSize * UMM_BLOCKSIZE) - (sizeof(((umm_block *)0)->header));
|
||||||
|
|
||||||
|
/* Protect the critical section... */
|
||||||
|
UMM_CRITICAL_ENTRY(id_realloc);
|
||||||
|
|
||||||
|
/* Now figure out if the previous and/or next blocks are free as well as
|
||||||
|
* their sizes - this will help us to minimize special code later when we
|
||||||
|
* decide if it's possible to use the adjacent blocks.
|
||||||
|
*
|
||||||
|
* We set prevBlockSize and nextBlockSize to non-zero values ONLY if they
|
||||||
|
* are free!
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK)) {
|
||||||
|
nextBlockSize = (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) - UMM_NBLOCK(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK)) {
|
||||||
|
prevBlockSize = (c - UMM_PBLOCK(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
DBGLOG("realloc blocks %i blockSize %i nextBlockSize %i prevBlockSize %i\n", blocks, blockSize, nextBlockSize, prevBlockSize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ok, now that we're here we know how many blocks we want and the current
|
||||||
|
* blockSize. The prevBlockSize and nextBlockSize are set and we can figure
|
||||||
|
* out the best strategy for the new allocation as follows:
|
||||||
|
*
|
||||||
|
* 1. If the new block is the same size or smaller than the current block do
|
||||||
|
* nothing.
|
||||||
|
* 2. If the next block is free and adding it to the current block gives us
|
||||||
|
* EXACTLY enough memory, assimilate the next block. This avoids unwanted
|
||||||
|
* fragmentation of free memory.
|
||||||
|
*
|
||||||
|
* The following cases may be better handled with memory copies to reduce
|
||||||
|
* fragmentation
|
||||||
|
*
|
||||||
|
* 3. If the previous block is NOT free and the next block is free and
|
||||||
|
* adding it to the current block gives us enough memory, assimilate
|
||||||
|
* the next block. This may introduce a bit of fragmentation.
|
||||||
|
* 4. If the prev block is free and adding it to the current block gives us
|
||||||
|
* enough memory, remove the previous block from the free list, assimilate
|
||||||
|
* it, copy to the new block.
|
||||||
|
* 5. If the prev and next blocks are free and adding them to the current
|
||||||
|
* block gives us enough memory, assimilate the next block, remove the
|
||||||
|
* previous block from the free list, assimilate it, copy to the new block.
|
||||||
|
* 6. Otherwise try to allocate an entirely new block of memory. If the
|
||||||
|
* allocation works free the old block and return the new pointer. If
|
||||||
|
* the allocation fails, return NULL and leave the old block intact.
|
||||||
|
*
|
||||||
|
* TODO: Add some conditional code to optimise for less fragmentation
|
||||||
|
* by simply allocating new memory if we need to copy anyways.
|
||||||
|
*
|
||||||
|
* All that's left to do is decide if the fit was exact or not. If the fit
|
||||||
|
* was not exact, then split the memory block so that we use only the requested
|
||||||
|
* number of blocks and add what's left to the free list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Case 1 - block is same size or smaller
|
||||||
|
if (blockSize >= blocks) {
|
||||||
|
DBGLOG("realloc the same or smaller size block - %i, do nothing\n", blocks);
|
||||||
|
/* This space intentionally left blank */
|
||||||
|
|
||||||
|
// Case 2 - block + next block fits EXACTLY
|
||||||
|
} else if ((blockSize + nextBlockSize) == blocks) {
|
||||||
|
DBGLOG("exact realloc using next block - %i\n", blocks);
|
||||||
|
umm_assimilate_up(c);
|
||||||
|
blockSize += nextBlockSize;
|
||||||
|
|
||||||
|
// Case 3 - prev block NOT free and block + next block fits
|
||||||
|
} else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) {
|
||||||
|
DBGLOG("realloc using next block - %i\n", blocks);
|
||||||
|
umm_assimilate_up(c);
|
||||||
|
blockSize += nextBlockSize;
|
||||||
|
|
||||||
|
// Case 4 - prev block + block fits
|
||||||
|
} else if ((prevBlockSize + blockSize) >= blocks) {
|
||||||
|
DBGLOG("realloc using prev block - %i\n", blocks);
|
||||||
|
umm_disconnect_from_free_list(UMM_PBLOCK(c));
|
||||||
|
c = umm_assimilate_down(c, 0);
|
||||||
|
memmove((void *)&UMM_DATA(c), ptr, curSize);
|
||||||
|
ptr = (void *)&UMM_DATA(c);
|
||||||
|
blockSize += prevBlockSize;
|
||||||
|
|
||||||
|
// Case 5 - prev block + block + next block fits
|
||||||
|
} else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) {
|
||||||
|
DBGLOG("realloc using prev and next block - %i\n", blocks);
|
||||||
|
umm_assimilate_up(c);
|
||||||
|
umm_disconnect_from_free_list(UMM_PBLOCK(c));
|
||||||
|
c = umm_assimilate_down(c, 0);
|
||||||
|
memmove((void *)&UMM_DATA(c), ptr, curSize);
|
||||||
|
ptr = (void *)&UMM_DATA(c);
|
||||||
|
blockSize += (prevBlockSize + nextBlockSize);
|
||||||
|
|
||||||
|
// Case 6 - default is we need to realloc a new block
|
||||||
|
} else {
|
||||||
|
DBGLOG("realloc a completely new block %i\n", blocks);
|
||||||
|
void *oldptr = ptr;
|
||||||
|
if ((ptr = umm_malloc_core(size))) {
|
||||||
|
DBGLOG("realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks);
|
||||||
|
memcpy(ptr, oldptr, curSize);
|
||||||
|
umm_free_core(oldptr);
|
||||||
|
} else {
|
||||||
|
DBGLOG("realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks);
|
||||||
|
/* This space intentionally left blnk */
|
||||||
|
}
|
||||||
|
blockSize = blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now all we need to do is figure out if the block fit exactly or if we
|
||||||
|
* need to split and free ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (blockSize > blocks) {
|
||||||
|
DBGLOG("split and free %i blocks from %i\n", blocks, blockSize);
|
||||||
|
umm_split_block(c, blocks, 0);
|
||||||
|
umm_free_core((void *)&UMM_DATA(c + blocks));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the critical section... */
|
||||||
|
UMM_CRITICAL_EXIT(id_realloc);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The openssl library expects a version of malloc that takes
|
||||||
|
// extra parameters 'file' and 'line'. We provide these for openssl.
|
||||||
|
// The extra parameters are ignored.
|
||||||
|
|
||||||
|
void *umm_malloc_ssl(size_t size, const char *file, int line) {
|
||||||
|
return umm_malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *umm_realloc_ssl(void *ptr, size_t size, const char *file, int line) {
|
||||||
|
return umm_realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void umm_free_ssl(void *ptr, const char *file, int line) {
|
||||||
|
umm_free(ptr);
|
||||||
|
}
|
||||||
108
luprex/core/cpp/umm-malloc.hpp
Normal file
108
luprex/core/cpp/umm-malloc.hpp
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// To achieve determinism of replay logs, the driver is not allowed
|
||||||
|
// to touch the malloc heap. We've given the driver its own
|
||||||
|
// separate heap. The driver's heap is accessed using umm_malloc,
|
||||||
|
// umm_free, and umm_realloc.
|
||||||
|
//
|
||||||
|
// To initialize the umm heap, you allocate a single large
|
||||||
|
// block of RAM from the OS, perhaps 4M. You pass that block
|
||||||
|
// to umm_heap_init. The umm_malloc routine will allocate out of
|
||||||
|
// that block. The umm heap cannot grow, if the block fills up, umm_malloc
|
||||||
|
// will fail. It is fine to get the initial heap block from the
|
||||||
|
// regular system malloc! That doesn't compromise determinism,
|
||||||
|
// since you're always allocating the same 4M block.
|
||||||
|
//
|
||||||
|
// The UMM malloc library is distributed under the MIT license
|
||||||
|
// by Ralph Hempel. This is a slightly-modified version, you can
|
||||||
|
// find the original online. Mr. Hempel considers this to be a malloc
|
||||||
|
// for microcontrollers, but it works just fine on workstations.
|
||||||
|
// Of all the mallocs to choose from, I picked this one because it
|
||||||
|
// satisfies three main criteria: it works out of a fixed block
|
||||||
|
// of RAM, it's not terribly inefficient, and it's not too complicated
|
||||||
|
// to modify.
|
||||||
|
//
|
||||||
|
// Warning! Don't forget that using the C++ STL tends to use malloc
|
||||||
|
// all over the place. For example, if you create a std::string,
|
||||||
|
// you're using malloc! Don't use normal STL classes in the driver!
|
||||||
|
// Fortunately, most STL classes allow you to specify a custom
|
||||||
|
// allocator class. The following classes use the UMM allocator:
|
||||||
|
//
|
||||||
|
// UmmString - same as std::string.
|
||||||
|
// UmmVector<T> - same as std::vector<T>.
|
||||||
|
// UmmMap<K,V> - same as std::map<K,V>
|
||||||
|
// UmmSet<T> - same as std::set<T>
|
||||||
|
//
|
||||||
|
// Sadly, routines that accept std::string, std::vector, std::map,
|
||||||
|
// or std::set do not accept their Umm equivalents. It is possible
|
||||||
|
// to write a routine that accepts both std::string and UmmString
|
||||||
|
// by accepting a std::string_view parameter. Other than that,
|
||||||
|
// these types are separate.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef UMM_MALLOC_HPP
|
||||||
|
#define UMM_MALLOC_HPP
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
void umm_init_heap(void *ptr, size_t size);
|
||||||
|
void *umm_malloc(size_t size);
|
||||||
|
void *umm_realloc(void *ptr, size_t size);
|
||||||
|
void umm_free(void *ptr);
|
||||||
|
void umm_info();
|
||||||
|
void *umm_malloc_ssl(size_t size, const char *file, int line);
|
||||||
|
void *umm_realloc_ssl(void *ptr, size_t size, const char *file, int line);
|
||||||
|
void umm_free_ssl(void *ptr, const char *file, int line);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class UmmAllocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
UmmAllocator() noexcept {}
|
||||||
|
template <class U> UmmAllocator(UmmAllocator<U> const&) noexcept {}
|
||||||
|
|
||||||
|
value_type* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
return static_cast<value_type*>(umm_malloc(n*sizeof(value_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(value_type* p, std::size_t) noexcept
|
||||||
|
{
|
||||||
|
umm_free(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator==(UmmAllocator<T> const&, UmmAllocator<U> const&) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator!=(UmmAllocator<T> const&, UmmAllocator<U> const&) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using UmmString = std::basic_string<char, std::char_traits<char>, UmmAllocator<char>>;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using UmmVector = std::vector<T, UmmAllocator<T>>;
|
||||||
|
|
||||||
|
template <class K, class T, class C=std::less<K>>
|
||||||
|
using UmmMap = std::map<K, T, C, UmmAllocator<std::pair<const K, T>>>;
|
||||||
|
|
||||||
|
template <class K, class C=std::less<K>>
|
||||||
|
using UmmSet = std::set<K, C, UmmAllocator<K>>;
|
||||||
|
|
||||||
|
#endif // UMM_MALLOC_HPP
|
||||||
@@ -269,18 +269,6 @@ double strtodouble(const std::string &value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void split_host_port(const std::string &target, std::string &host, std::string &port) {
|
|
||||||
size_t lastcolon = target.rfind(':');
|
|
||||||
if (lastcolon == std::string::npos) {
|
|
||||||
host = ""; port = ""; return;
|
|
||||||
}
|
|
||||||
host = target.substr(0, lastcolon);
|
|
||||||
port = target.substr(lastcolon + 1);
|
|
||||||
if ((host == "") || (port == "")) {
|
|
||||||
host = ""; port = ""; return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ltrim(std::string s) {
|
std::string ltrim(std::string s) {
|
||||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
|
||||||
std::not1(std::ptr_fun<int, int>(std::isspace))));
|
std::not1(std::ptr_fun<int, int>(std::isspace))));
|
||||||
@@ -436,12 +424,6 @@ LuaDefine(unittests_util, "", "some unit tests") {
|
|||||||
LuaAssert(L, std::isnan(util::strtodouble("12ab")));
|
LuaAssert(L, std::isnan(util::strtodouble("12ab")));
|
||||||
LuaAssert(L, std::isnan(util::strtodouble("")));
|
LuaAssert(L, std::isnan(util::strtodouble("")));
|
||||||
|
|
||||||
// Test split_host_port
|
|
||||||
std::string host, port;
|
|
||||||
util::split_host_port("stanford.edu:80", host, port);
|
|
||||||
LuaAssertStrEq(L, host, "stanford.edu");
|
|
||||||
LuaAssertStrEq(L, port, "80");
|
|
||||||
|
|
||||||
// Test trim, ltrim, rtrim
|
// Test trim, ltrim, rtrim
|
||||||
LuaAssert(L, util::ltrim(" foo ") == "foo ");
|
LuaAssert(L, util::ltrim(" foo ") == "foo ");
|
||||||
LuaAssert(L, util::rtrim(" foo ") == " foo");
|
LuaAssert(L, util::rtrim(" foo ") == " foo");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define UTIL_HPP
|
#define UTIL_HPP
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -99,10 +100,6 @@ int64_t strtoint(const std::string &value, int64_t errval);
|
|||||||
// String to double. Returns NAN if the number is not parseable.
|
// String to double. Returns NAN if the number is not parseable.
|
||||||
double strtodouble(const std::string &value);
|
double strtodouble(const std::string &value);
|
||||||
|
|
||||||
// Split a string of the form "hostname:port" into a hostname and a port.
|
|
||||||
// On failure, returns empty strings.
|
|
||||||
void split_host_port(const std::string &target, std::string &host, std::string &port);
|
|
||||||
|
|
||||||
// Trim strings: left end, right end, both ends.
|
// Trim strings: left end, right end, both ends.
|
||||||
std::string ltrim(std::string s);
|
std::string ltrim(std::string s);
|
||||||
std::string rtrim(std::string s);
|
std::string rtrim(std::string s);
|
||||||
@@ -120,6 +117,9 @@ LuaSourcePtr make_lua_source(const std::string &code);
|
|||||||
// Return true if the line of code is a lua comment.
|
// Return true if the line of code is a lua comment.
|
||||||
bool is_lua_comment(const std::string &line);
|
bool is_lua_comment(const std::string &line);
|
||||||
|
|
||||||
|
// This has to go away.
|
||||||
|
LuaSourcePtr read_lua_source(const std::string &dir);
|
||||||
|
|
||||||
// Remove nullptrs from a vector of unique pointers.
|
// Remove nullptrs from a vector of unique pointers.
|
||||||
template<class T>
|
template<class T>
|
||||||
void remove_nullptrs(std::vector<std::unique_ptr<T>> &vec) {
|
void remove_nullptrs(std::vector<std::unique_ptr<T>> &vec) {
|
||||||
@@ -128,9 +128,6 @@ void remove_nullptrs(std::vector<std::unique_ptr<T>> &vec) {
|
|||||||
vec.erase(iter, vec.end());
|
vec.erase(iter, vec.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the lua source code from the specified directory.
|
|
||||||
LuaSourcePtr read_lua_source(const std::string &directory);
|
|
||||||
|
|
||||||
// An XYZ coordinate, general purpose.
|
// An XYZ coordinate, general purpose.
|
||||||
struct XYZ {
|
struct XYZ {
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
makeclass("unittests")
|
makeclass("unittests")
|
||||||
|
|
||||||
function unittests.globaldb()
|
function unittests.globaldb()
|
||||||
local g1a = global("unittest-g1")
|
local g1a = global.table("unittest-g1")
|
||||||
local g2a = global("unittest-g2")
|
local g2a = global.table("unittest-g2")
|
||||||
local g1b = global("unittest-g1")
|
local g1b = global.table("unittest-g1")
|
||||||
local g2b = global("unittest-g2")
|
local g2b = global.table("unittest-g2")
|
||||||
assert(g1a == g1b)
|
assert(g1a == g1b)
|
||||||
assert(g2a == g2b)
|
assert(g2a == g2b)
|
||||||
assert(g1a.__global == "unittest-g1")
|
assert(g1a.__global == "unittest-g1")
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ function unittests.diffcompare()
|
|||||||
assert(tdc({}, {a=_G}, {}, {}) == "a=globals;")
|
assert(tdc({}, {a=_G}, {}, {}) == "a=globals;")
|
||||||
|
|
||||||
-- GlobalDB tables should be forced to NIL.
|
-- GlobalDB tables should be forced to NIL.
|
||||||
assert(tdc({}, {a=global("foo")}, {}, {a=global("foo")}) == "a=nil;");
|
assert(tdc({}, {a=global.table("foo")}, {}, {a=global.table("foo")}) == "a=nil;");
|
||||||
assert(tdc({}, {}, {}, {a=global("foo")}) == "a=nil;");
|
assert(tdc({}, {}, {}, {a=global.table("foo")}) == "a=nil;");
|
||||||
assert(tdc({}, {a=global("foo")}, {}, {}) == "");
|
assert(tdc({}, {a=global.table("foo")}, {}, {}) == "");
|
||||||
|
|
||||||
-- Set up some numbered tables for tests involving such.
|
-- Set up some numbered tables for tests involving such.
|
||||||
local mtab10 = {}
|
local mtab10 = {}
|
||||||
@@ -118,7 +118,7 @@ function unittests.diffapply()
|
|||||||
|
|
||||||
-- GlobalDB tables should be forced to NIL.
|
-- GlobalDB tables should be forced to NIL.
|
||||||
rtab={a=3}
|
rtab={a=3}
|
||||||
assert(not tda({}, {a=global("foo")}, rtab))
|
assert(not tda({}, {a=global.table("foo")}, rtab))
|
||||||
assert(rtab.a == nil)
|
assert(rtab.a == nil)
|
||||||
|
|
||||||
-- Unnumbered tables should be forced to NIL.
|
-- Unnumbered tables should be forced to NIL.
|
||||||
|
|||||||
Reference in New Issue
Block a user